@typeonce/effect-machine 0.1.0 → 0.3.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/NOTICE +0 -2
- package/README.md +464 -53
- package/dist/AtomMachine.d.ts +189 -10
- package/dist/AtomMachine.d.ts.map +1 -1
- package/dist/AtomMachine.js +145 -18
- package/dist/AtomMachine.js.map +1 -1
- package/dist/ClusterMachine.d.ts +17 -7
- package/dist/ClusterMachine.d.ts.map +1 -1
- package/dist/ClusterMachine.js +2 -2
- package/dist/ClusterMachine.js.map +1 -1
- package/dist/Machine.d.ts +949 -202
- package/dist/Machine.d.ts.map +1 -1
- package/dist/Machine.js +245 -82
- package/dist/Machine.js.map +1 -1
- package/dist/internal/machineErrors.d.ts +2 -2
- package/dist/internal/machineErrors.d.ts.map +1 -1
- package/dist/internal/machineModel.d.ts +52 -4
- package/dist/internal/machineModel.d.ts.map +1 -1
- package/dist/internal/machineModel.js +576 -29
- package/dist/internal/machineModel.js.map +1 -1
- package/dist/internal/machinePlanner.d.ts +15 -5
- package/dist/internal/machinePlanner.d.ts.map +1 -1
- package/dist/internal/machinePlanner.js +260 -57
- package/dist/internal/machinePlanner.js.map +1 -1
- package/dist/internal/machineProcess.d.ts +2 -2
- package/dist/internal/machineProcess.d.ts.map +1 -1
- package/dist/internal/machineProcess.js +22 -11
- package/dist/internal/machineProcess.js.map +1 -1
- package/dist/internal/machineRuntime.d.ts +11 -5
- package/dist/internal/machineRuntime.d.ts.map +1 -1
- package/dist/internal/machineRuntime.js +24 -13
- package/dist/internal/machineRuntime.js.map +1 -1
- package/docs/agent-guide.md +680 -0
- package/package.json +20 -22
|
@@ -10,7 +10,169 @@ import { hasProperty } from "effect/Predicate";
|
|
|
10
10
|
import * as Schema from "effect/Schema";
|
|
11
11
|
import { MachineSchemaDecodeError, MachineSchemaEncodeError } from "./machineErrors.js";
|
|
12
12
|
export const TargetTypeId = "~effect/Machine/Target";
|
|
13
|
+
export const TargetSnapshotTypeId = Symbol("effect/Machine/TargetSnapshot");
|
|
14
|
+
export const StateInputTypeId = Symbol("effect/Machine/StateInput");
|
|
15
|
+
export const StateConstructionTypeId = Symbol("effect/Machine/StateConstruction");
|
|
16
|
+
export const HistoryTargetTypeId = Symbol("effect/Machine/HistoryTarget");
|
|
17
|
+
const validateHistoryRecordControl = (machine, record) => {
|
|
18
|
+
const ancestry = new Set(getPathToRoot(machine, record.parent));
|
|
19
|
+
const visit = (path) => {
|
|
20
|
+
const node = getNode(machine, path);
|
|
21
|
+
if (node.type === "compound") {
|
|
22
|
+
const children = node.children.filter((child) => record.active.has(child));
|
|
23
|
+
if (children.length !== 1) {
|
|
24
|
+
throw new Error(`Machine history expected compound state "${path}" to retain one active child`);
|
|
25
|
+
}
|
|
26
|
+
if (record.mode === "deep")
|
|
27
|
+
visit(children[0]);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
if (node.type === "parallel") {
|
|
31
|
+
for (const child of node.children) {
|
|
32
|
+
if (!record.active.has(child)) {
|
|
33
|
+
throw new Error(`Machine history expected parallel state "${path}" to retain region "${child}"`);
|
|
34
|
+
}
|
|
35
|
+
if (record.mode === "deep")
|
|
36
|
+
visit(child);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
visit(record.parent);
|
|
41
|
+
for (const path of record.active) {
|
|
42
|
+
if (!ancestry.has(path) && !isPathInSubtree(path, record.parent)) {
|
|
43
|
+
throw new Error(`Machine history contains state "${path}" outside parent "${record.parent}"`);
|
|
44
|
+
}
|
|
45
|
+
if (record.mode === "shallow" && isDescendantOf(path, record.parent) &&
|
|
46
|
+
getNode(machine, path).parent !== record.parent) {
|
|
47
|
+
throw new Error(`Machine shallow history contains deep descendant "${path}"`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
const historyFromSnapshot = (machine, snapshot) => {
|
|
52
|
+
const history = new Map();
|
|
53
|
+
for (const [path, entry] of Object.entries(snapshot.history ?? {})) {
|
|
54
|
+
const historyNode = getNode(machine, path);
|
|
55
|
+
if (historyNode.type !== "history" || historyNode.parent === undefined || historyNode.history !== entry.mode) {
|
|
56
|
+
throw new Error(`Machine snapshot contains invalid history record "${path}"`);
|
|
57
|
+
}
|
|
58
|
+
const active = new Set();
|
|
59
|
+
const values = new Map();
|
|
60
|
+
for (const activePath of entry.active) {
|
|
61
|
+
if (active.has(activePath) || !Object.prototype.hasOwnProperty.call(entry.values, activePath)) {
|
|
62
|
+
throw new Error(`Machine snapshot contains invalid remembered state "${activePath}"`);
|
|
63
|
+
}
|
|
64
|
+
const node = getNode(machine, activePath);
|
|
65
|
+
if (node.type === "history" ||
|
|
66
|
+
!(isPathInSubtree(activePath, historyNode.parent) ||
|
|
67
|
+
getPathToRoot(machine, historyNode.parent).includes(activePath)) ||
|
|
68
|
+
!Schema.is(getStateNodeSchema(node))(entry.values[activePath])) {
|
|
69
|
+
throw new Error(`Machine snapshot contains invalid remembered value for "${activePath}"`);
|
|
70
|
+
}
|
|
71
|
+
active.add(activePath);
|
|
72
|
+
values.set(activePath, entry.values[activePath]);
|
|
73
|
+
}
|
|
74
|
+
if (!active.has(historyNode.parent) || Object.keys(entry.values).length !== active.size) {
|
|
75
|
+
throw new Error(`Machine snapshot contains incomplete history record "${path}"`);
|
|
76
|
+
}
|
|
77
|
+
history.set(path, {
|
|
78
|
+
mode: entry.mode,
|
|
79
|
+
parent: historyNode.parent,
|
|
80
|
+
active,
|
|
81
|
+
values
|
|
82
|
+
});
|
|
83
|
+
validateHistoryRecordControl(machine, history.get(path));
|
|
84
|
+
}
|
|
85
|
+
return history;
|
|
86
|
+
};
|
|
87
|
+
const historyFromSnapshotEffect = Effect.fnUntraced(function* (machine, snapshot) {
|
|
88
|
+
const history = new Map();
|
|
89
|
+
for (const [path, entry] of Object.entries(snapshot.history ?? {})) {
|
|
90
|
+
const historyNode = machine.stateNodes.byPath.get(path);
|
|
91
|
+
if (historyNode === undefined || historyNode.type !== "history" || historyNode.parent === undefined ||
|
|
92
|
+
historyNode.history !== entry.mode) {
|
|
93
|
+
return yield* Effect.fail(new MachineSchemaDecodeError({
|
|
94
|
+
machineId: machine.id,
|
|
95
|
+
boundary: "history",
|
|
96
|
+
state: path,
|
|
97
|
+
cause: Cause.die(new Error(`Machine snapshot contains invalid history record "${path}"`))
|
|
98
|
+
}));
|
|
99
|
+
}
|
|
100
|
+
const active = new Set();
|
|
101
|
+
const values = new Map();
|
|
102
|
+
for (const activePath of entry.active) {
|
|
103
|
+
const node = machine.stateNodes.byPath.get(activePath);
|
|
104
|
+
if (active.has(activePath) || node === undefined || node.type === "history" ||
|
|
105
|
+
!Object.prototype.hasOwnProperty.call(entry.values, activePath) ||
|
|
106
|
+
!(isPathInSubtree(activePath, historyNode.parent) ||
|
|
107
|
+
getPathToRoot(machine, historyNode.parent).includes(activePath))) {
|
|
108
|
+
return yield* Effect.fail(new MachineSchemaDecodeError({
|
|
109
|
+
machineId: machine.id,
|
|
110
|
+
boundary: "history",
|
|
111
|
+
state: activePath,
|
|
112
|
+
cause: Cause.die(new Error(`Machine snapshot contains invalid remembered state "${activePath}"`))
|
|
113
|
+
}));
|
|
114
|
+
}
|
|
115
|
+
active.add(activePath);
|
|
116
|
+
values.set(activePath, yield* decodeBoundary(machine, getStateNodeSchema(node), entry.values[activePath], {
|
|
117
|
+
boundary: "history",
|
|
118
|
+
state: activePath
|
|
119
|
+
}));
|
|
120
|
+
}
|
|
121
|
+
if (!active.has(historyNode.parent) || Object.keys(entry.values).length !== active.size) {
|
|
122
|
+
return yield* Effect.fail(new MachineSchemaDecodeError({
|
|
123
|
+
machineId: machine.id,
|
|
124
|
+
boundary: "history",
|
|
125
|
+
state: path,
|
|
126
|
+
cause: Cause.die(new Error(`Machine snapshot contains incomplete history record "${path}"`))
|
|
127
|
+
}));
|
|
128
|
+
}
|
|
129
|
+
history.set(path, {
|
|
130
|
+
mode: entry.mode,
|
|
131
|
+
parent: historyNode.parent,
|
|
132
|
+
active,
|
|
133
|
+
values
|
|
134
|
+
});
|
|
135
|
+
try {
|
|
136
|
+
validateHistoryRecordControl(machine, history.get(path));
|
|
137
|
+
}
|
|
138
|
+
catch (cause) {
|
|
139
|
+
return yield* Effect.fail(new MachineSchemaDecodeError({
|
|
140
|
+
machineId: machine.id,
|
|
141
|
+
boundary: "history",
|
|
142
|
+
state: path,
|
|
143
|
+
cause: Cause.die(cause)
|
|
144
|
+
}));
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return history;
|
|
148
|
+
});
|
|
149
|
+
const historyToSnapshot = (history) => {
|
|
150
|
+
const entries = {};
|
|
151
|
+
for (const [path, record] of history) {
|
|
152
|
+
entries[path] = {
|
|
153
|
+
mode: record.mode,
|
|
154
|
+
active: Array.from(record.active),
|
|
155
|
+
values: Object.fromEntries(record.values)
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
return entries;
|
|
159
|
+
};
|
|
160
|
+
export const makeHistoryTarget = (path, parent) => ({
|
|
161
|
+
[HistoryTargetTypeId]: HistoryTargetTypeId,
|
|
162
|
+
path,
|
|
163
|
+
parent
|
|
164
|
+
});
|
|
165
|
+
export const isHistoryTarget = (u) => hasProperty(u, HistoryTargetTypeId);
|
|
13
166
|
export const getStateNodeDefinition = (path, definition) => {
|
|
167
|
+
if (!Schema.isSchema(definition) && definition.type === "history") {
|
|
168
|
+
return {
|
|
169
|
+
schema: undefined,
|
|
170
|
+
output: undefined,
|
|
171
|
+
type: "history",
|
|
172
|
+
initial: undefined,
|
|
173
|
+
states: undefined
|
|
174
|
+
};
|
|
175
|
+
}
|
|
14
176
|
if (Schema.isSchema(definition)) {
|
|
15
177
|
return {
|
|
16
178
|
schema: definition,
|
|
@@ -78,11 +240,20 @@ export const compileStateNodes = (states) => {
|
|
|
78
240
|
parent,
|
|
79
241
|
children: [],
|
|
80
242
|
initial: definition.initial === undefined ? undefined : `${path}.${definition.initial}`,
|
|
243
|
+
history: definition.type === "history"
|
|
244
|
+
? (tree[key].history === "deep" ? "deep" : "shallow")
|
|
245
|
+
: undefined,
|
|
81
246
|
order
|
|
82
247
|
};
|
|
83
248
|
byPath.set(path, node);
|
|
84
|
-
paths.push(path);
|
|
85
249
|
order += 1;
|
|
250
|
+
if (definition.type === "history") {
|
|
251
|
+
if (parent === undefined) {
|
|
252
|
+
throw new Error(`Machine history state "${path}" must belong to a parent state`);
|
|
253
|
+
}
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
paths.push(path);
|
|
86
257
|
if (definition.states !== undefined) {
|
|
87
258
|
const children = compile(definition.states, path);
|
|
88
259
|
if (node.type === "compound" && (node.initial === undefined || !children.includes(node.initial))) {
|
|
@@ -101,32 +272,99 @@ export const compileStateNodes = (states) => {
|
|
|
101
272
|
};
|
|
102
273
|
export const makeTarget = (path, value, options) => ({
|
|
103
274
|
[TargetTypeId]: TargetTypeId,
|
|
275
|
+
[TargetSnapshotTypeId]: options?.snapshot,
|
|
104
276
|
path,
|
|
105
277
|
value,
|
|
106
278
|
values: options?.values
|
|
107
279
|
});
|
|
108
280
|
export const isTarget = (u) => hasProperty(u, TargetTypeId);
|
|
281
|
+
export const makeStateInput = (input) => ({
|
|
282
|
+
[StateInputTypeId]: StateInputTypeId,
|
|
283
|
+
input
|
|
284
|
+
});
|
|
285
|
+
const isStateInput = (u) => hasProperty(u, StateInputTypeId);
|
|
286
|
+
export const markStateConstruction = (value) => {
|
|
287
|
+
if ((typeof value === "object" && value !== null) || typeof value === "function") {
|
|
288
|
+
Object.defineProperty(value, StateConstructionTypeId, {
|
|
289
|
+
value: StateConstructionTypeId,
|
|
290
|
+
enumerable: false
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
return value;
|
|
294
|
+
};
|
|
295
|
+
export const isStateConstruction = (u) => hasProperty(u, StateConstructionTypeId);
|
|
109
296
|
export const isSnapshot = (u) => hasProperty(u, "path") && hasProperty(u, "value");
|
|
297
|
+
export const getSnapshotByPath = (snapshot, path, parents) => {
|
|
298
|
+
if (snapshot.path === path) {
|
|
299
|
+
return Option.some(snapshot);
|
|
300
|
+
}
|
|
301
|
+
if (!path.startsWith(`${snapshot.path}.`)) {
|
|
302
|
+
return Option.none();
|
|
303
|
+
}
|
|
304
|
+
if (parents !== undefined) {
|
|
305
|
+
parents[snapshot.path] = snapshot.value;
|
|
306
|
+
}
|
|
307
|
+
if (hasProperty(snapshot, "state") && isSnapshot(snapshot.state)) {
|
|
308
|
+
return getSnapshotByPath(snapshot.state, path, parents);
|
|
309
|
+
}
|
|
310
|
+
if (hasProperty(snapshot, "states") && typeof snapshot.states === "object" && snapshot.states !== null) {
|
|
311
|
+
for (const child of Object.values(snapshot.states)) {
|
|
312
|
+
if (isSnapshot(child)) {
|
|
313
|
+
const result = getSnapshotByPath(child, path, parents);
|
|
314
|
+
if (Option.isSome(result)) {
|
|
315
|
+
return result;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
return Option.none();
|
|
321
|
+
};
|
|
322
|
+
const MachineProtocolTypeId = Symbol.for("effect/Machine/protocol");
|
|
323
|
+
const getProtocolSchemas = (machine) => {
|
|
324
|
+
const protocol = machine[MachineProtocolTypeId];
|
|
325
|
+
if (protocol === undefined) {
|
|
326
|
+
throw new Error("Machine protocol is unavailable");
|
|
327
|
+
}
|
|
328
|
+
return protocol;
|
|
329
|
+
};
|
|
330
|
+
const setProtocolSchemas = (machine, protocol) => {
|
|
331
|
+
Object.defineProperty(machine, MachineProtocolTypeId, {
|
|
332
|
+
value: protocol,
|
|
333
|
+
enumerable: false
|
|
334
|
+
});
|
|
335
|
+
};
|
|
336
|
+
export const setProtocol = (machine) => {
|
|
337
|
+
setProtocolSchemas(machine, {
|
|
338
|
+
event: Schema.Union([...machine.events, ...machine.internalEvents]),
|
|
339
|
+
emit: Schema.Union(machine.emits)
|
|
340
|
+
});
|
|
341
|
+
};
|
|
342
|
+
export const copyProtocol = (source, target) => setProtocolSchemas(target, getProtocolSchemas(source));
|
|
110
343
|
export const getEventName = (event) => hasProperty(event, "_tag") ? String(event._tag) : undefined;
|
|
111
|
-
export const decodeBoundary =
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
})));
|
|
119
|
-
});
|
|
344
|
+
export const decodeBoundary = (machine, schema, value, options) => Schema.decodeUnknownEffect(Schema.toType(schema))(value).pipe(Effect.mapError((cause) => new MachineSchemaDecodeError({
|
|
345
|
+
machineId: machine.id,
|
|
346
|
+
boundary: options.boundary,
|
|
347
|
+
cause,
|
|
348
|
+
...(options.state === undefined ? {} : { state: options.state }),
|
|
349
|
+
...(options.event === undefined ? {} : { event: options.event })
|
|
350
|
+
})));
|
|
120
351
|
export const decodeInput = (machine, schema, value) => decodeBoundary(machine, schema, value, { boundary: "input" });
|
|
121
352
|
export const decodeEvent = (machine, event) => {
|
|
122
353
|
const eventName = getEventName(event);
|
|
123
|
-
return decodeBoundary(machine,
|
|
354
|
+
return decodeBoundary(machine, getProtocolSchemas(machine).event, event, eventName === undefined ? { boundary: "event" } : { boundary: "event", event: eventName });
|
|
124
355
|
};
|
|
125
356
|
export const decodeEmit = (machine, event) => {
|
|
126
357
|
const eventName = getEventName(event);
|
|
127
|
-
return decodeBoundary(machine,
|
|
358
|
+
return decodeBoundary(machine, getProtocolSchemas(machine).emit, event, eventName === undefined ? { boundary: "emit" } : { boundary: "emit", event: eventName });
|
|
128
359
|
};
|
|
129
|
-
export const decodeStateValue = (machine, node, value) =>
|
|
360
|
+
export const decodeStateValue = (machine, node, value) => isStateInput(value)
|
|
361
|
+
? getStateNodeSchema(node).makeEffect(value.input).pipe(Effect.mapError((cause) => new MachineSchemaDecodeError({
|
|
362
|
+
machineId: machine.id,
|
|
363
|
+
boundary: "state",
|
|
364
|
+
state: node.path,
|
|
365
|
+
cause
|
|
366
|
+
})))
|
|
367
|
+
: decodeBoundary(machine, getStateNodeSchema(node), value, { boundary: "state", state: node.path });
|
|
130
368
|
export const decodeOutputValue = (machine, node, value) => node.output === undefined
|
|
131
369
|
? Effect.succeed(value)
|
|
132
370
|
: decodeBoundary(machine, node.output, value, { boundary: "output", state: node.path });
|
|
@@ -137,6 +375,12 @@ export const getNode = (machine, path) => {
|
|
|
137
375
|
}
|
|
138
376
|
return node;
|
|
139
377
|
};
|
|
378
|
+
export const getStateNodeSchema = (node) => {
|
|
379
|
+
if (node.schema === undefined || node.type === "history") {
|
|
380
|
+
throw new Error(`Machine history state "${node.path}" has no active value schema`);
|
|
381
|
+
}
|
|
382
|
+
return node.schema;
|
|
383
|
+
};
|
|
140
384
|
export const hasOwn = (u, key) => Object.prototype.hasOwnProperty.call(u, key);
|
|
141
385
|
export const isDescendantOf = (path, ancestor) => path.startsWith(`${ancestor}.`);
|
|
142
386
|
export const isPathInSubtree = (path, ancestor) => path === ancestor || isDescendantOf(path, ancestor);
|
|
@@ -180,7 +424,7 @@ export const getRootPath = (machine, configuration) => {
|
|
|
180
424
|
};
|
|
181
425
|
export const getActiveValue = (configuration, path) => {
|
|
182
426
|
if (!configuration.values.has(path)) {
|
|
183
|
-
throw new Error(`Machine expected active state "${path}" to have a value`);
|
|
427
|
+
throw new Error(`Machine expected active state "${path}" to have a value (available: ${Array.from(configuration.values.keys()).join(", ")})`);
|
|
184
428
|
}
|
|
185
429
|
return configuration.values.get(path);
|
|
186
430
|
};
|
|
@@ -193,6 +437,10 @@ export const getParentValues = (machine, configuration, path) => {
|
|
|
193
437
|
}
|
|
194
438
|
return parents;
|
|
195
439
|
};
|
|
440
|
+
export const getParentValue = (machine, configuration, path) => {
|
|
441
|
+
const parent = getNode(machine, path).parent;
|
|
442
|
+
return parent === undefined ? undefined : getActiveValue(configuration, parent);
|
|
443
|
+
};
|
|
196
444
|
export const getInitialEntryPaths = (machine, configuration) => {
|
|
197
445
|
const visit = (path) => {
|
|
198
446
|
if (!configuration.active.has(path)) {
|
|
@@ -234,15 +482,25 @@ export const snapshotFromPath = (machine, configuration, path) => {
|
|
|
234
482
|
};
|
|
235
483
|
export const snapshotFromConfiguration = (machine, configuration) => {
|
|
236
484
|
const snapshot = snapshotFromPath(machine, configuration, getRootPath(machine, configuration));
|
|
237
|
-
const root = getRootPath(machine, configuration);
|
|
238
|
-
const retainPartialOutputs = !isActiveFinalNode(machine, configuration, root);
|
|
239
485
|
const completed = Array.from(configuration.outputs)
|
|
240
|
-
.filter(([path]) => retainPartialOutputs || hasCompletionHandler(machine, path))
|
|
241
486
|
.map(([path, output]) => ({ path, output }));
|
|
242
487
|
if (completed.length > 0) {
|
|
243
488
|
;
|
|
244
489
|
snapshot.completed = completed;
|
|
245
490
|
}
|
|
491
|
+
if (configuration.history.size > 0) {
|
|
492
|
+
Object.assign(snapshot, { history: historyToSnapshot(configuration.history) });
|
|
493
|
+
}
|
|
494
|
+
return snapshot;
|
|
495
|
+
};
|
|
496
|
+
/** Creates a targetable subtree snapshot while carrying machine-level history
|
|
497
|
+
* metadata on that subtree root. This is used when a nested history target
|
|
498
|
+
* must preserve active ancestors and unaffected parallel regions. */
|
|
499
|
+
export const snapshotFromConfigurationAtPath = (machine, configuration, path) => {
|
|
500
|
+
const snapshot = snapshotFromPath(machine, configuration, path);
|
|
501
|
+
if (configuration.history.size > 0) {
|
|
502
|
+
Object.assign(snapshot, { history: historyToSnapshot(configuration.history) });
|
|
503
|
+
}
|
|
246
504
|
return snapshot;
|
|
247
505
|
};
|
|
248
506
|
export const configurationFromSnapshot = (machine, snapshot) => {
|
|
@@ -251,7 +509,7 @@ export const configurationFromSnapshot = (machine, snapshot) => {
|
|
|
251
509
|
const snapshotOutputs = snapshot.completed;
|
|
252
510
|
const visit = (current) => {
|
|
253
511
|
const node = getNode(machine, String(current.path));
|
|
254
|
-
if (!Schema.is(node
|
|
512
|
+
if (!Schema.is(getStateNodeSchema(node))(current.value)) {
|
|
255
513
|
throw new Error(`Machine expected snapshot for "${node.path}" to match its schema`);
|
|
256
514
|
}
|
|
257
515
|
active.add(node.path);
|
|
@@ -294,7 +552,7 @@ export const configurationFromSnapshot = (machine, snapshot) => {
|
|
|
294
552
|
}
|
|
295
553
|
}
|
|
296
554
|
}
|
|
297
|
-
return { active, values, outputs };
|
|
555
|
+
return { active, values, outputs, history: historyFromSnapshot(machine, snapshot) };
|
|
298
556
|
};
|
|
299
557
|
export const normalizeConfiguration = (machine, state) => configurationFromSnapshot(machine, state);
|
|
300
558
|
export const configurationFromSnapshotEffect = Effect.fnUntraced(function* (machine, snapshot) {
|
|
@@ -344,7 +602,12 @@ export const configurationFromSnapshotEffect = Effect.fnUntraced(function* (mach
|
|
|
344
602
|
}
|
|
345
603
|
}
|
|
346
604
|
}
|
|
347
|
-
return {
|
|
605
|
+
return {
|
|
606
|
+
active,
|
|
607
|
+
values,
|
|
608
|
+
outputs,
|
|
609
|
+
history: yield* historyFromSnapshotEffect(machine, snapshot)
|
|
610
|
+
};
|
|
348
611
|
});
|
|
349
612
|
export const normalizeConfigurationEffect = (machine, state) => configurationFromSnapshotEffect(machine, state).pipe(Effect.catchCause((cause) => {
|
|
350
613
|
const error = Cause.findErrorOption(cause);
|
|
@@ -374,6 +637,85 @@ export const validateInitialConfiguration = (machine, configuration) => {
|
|
|
374
637
|
}
|
|
375
638
|
}
|
|
376
639
|
};
|
|
640
|
+
/** Capture every history register whose owning parent exits in this microstep.
|
|
641
|
+
* The control record is deliberately independent from effects/actions: it is
|
|
642
|
+
* part of the logical snapshot and is therefore preserved by pure planning. */
|
|
643
|
+
export const captureHistory = (machine, current, next, exitPaths) => {
|
|
644
|
+
if (exitPaths.length === 0) {
|
|
645
|
+
return next;
|
|
646
|
+
}
|
|
647
|
+
const exited = new Set(exitPaths);
|
|
648
|
+
const history = new Map(next.history);
|
|
649
|
+
for (const node of machine.stateNodes.byPath.values()) {
|
|
650
|
+
if (node.type !== "history" || node.parent === undefined || !exited.has(node.parent)) {
|
|
651
|
+
continue;
|
|
652
|
+
}
|
|
653
|
+
const mode = node.history === "deep" ? "deep" : "shallow";
|
|
654
|
+
const active = new Set();
|
|
655
|
+
for (const ancestor of getPathToRoot(machine, node.parent)) {
|
|
656
|
+
if (current.active.has(ancestor)) {
|
|
657
|
+
active.add(ancestor);
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
for (const path of current.active) {
|
|
661
|
+
if (path === node.parent ||
|
|
662
|
+
(mode === "deep" && isDescendantOf(path, node.parent)) ||
|
|
663
|
+
(mode === "shallow" && getNode(machine, path).parent === node.parent)) {
|
|
664
|
+
active.add(path);
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
const values = new Map();
|
|
668
|
+
for (const path of active) {
|
|
669
|
+
values.set(path, getActiveValue(current, path));
|
|
670
|
+
}
|
|
671
|
+
history.set(node.path, {
|
|
672
|
+
mode,
|
|
673
|
+
parent: node.parent,
|
|
674
|
+
active,
|
|
675
|
+
values
|
|
676
|
+
});
|
|
677
|
+
}
|
|
678
|
+
return {
|
|
679
|
+
active: next.active,
|
|
680
|
+
values: next.values,
|
|
681
|
+
outputs: next.outputs,
|
|
682
|
+
history
|
|
683
|
+
};
|
|
684
|
+
};
|
|
685
|
+
export const getHistoryRecord = (configuration, path) => configuration.history.get(path);
|
|
686
|
+
/** Builds the remembered portion of a configuration. Shallow records are
|
|
687
|
+
* intentionally incomplete below their direct child; the planner completes
|
|
688
|
+
* them by invoking only the required typed initializers. */
|
|
689
|
+
export const configurationFromHistoryRecord = (machine, current, record) => {
|
|
690
|
+
const active = new Set(record.active);
|
|
691
|
+
const values = new Map(record.values);
|
|
692
|
+
const outputs = new Map();
|
|
693
|
+
const ancestors = getPathToRoot(machine, record.parent);
|
|
694
|
+
const ancestorSet = new Set(ancestors);
|
|
695
|
+
// A history transition can occur while an ancestor parallel state remains
|
|
696
|
+
// active. Its unaffected regions retain their current configuration.
|
|
697
|
+
for (const ancestor of ancestors) {
|
|
698
|
+
const node = getNode(machine, ancestor);
|
|
699
|
+
if (node.type !== "parallel") {
|
|
700
|
+
continue;
|
|
701
|
+
}
|
|
702
|
+
for (const child of node.children) {
|
|
703
|
+
if (ancestorSet.has(child) || record.active.has(child) || !current.active.has(child)) {
|
|
704
|
+
continue;
|
|
705
|
+
}
|
|
706
|
+
for (const path of current.active) {
|
|
707
|
+
if (isPathInSubtree(path, child)) {
|
|
708
|
+
active.add(path);
|
|
709
|
+
if (current.values.has(path))
|
|
710
|
+
values.set(path, current.values.get(path));
|
|
711
|
+
if (current.outputs.has(path))
|
|
712
|
+
outputs.set(path, current.outputs.get(path));
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
return { active, values, outputs, history: current.history };
|
|
718
|
+
};
|
|
377
719
|
export const configurationFromTargetPathEffect = Effect.fnUntraced(function* (machine, current, path, value, providedValues) {
|
|
378
720
|
const node = getNode(machine, path);
|
|
379
721
|
const active = new Set();
|
|
@@ -421,20 +763,83 @@ export const configurationFromTargetPathEffect = Effect.fnUntraced(function* (ma
|
|
|
421
763
|
if (node.type === "compound" || node.type === "parallel") {
|
|
422
764
|
throw new Error(`Machine target "${node.path}" must include an active child state`);
|
|
423
765
|
}
|
|
424
|
-
return {
|
|
766
|
+
return {
|
|
767
|
+
active,
|
|
768
|
+
values,
|
|
769
|
+
outputs,
|
|
770
|
+
history: current.history
|
|
771
|
+
};
|
|
772
|
+
});
|
|
773
|
+
export const configurationFromTargetSnapshotEffect = Effect.fnUntraced(function* (machine, current, snapshot, providedValues) {
|
|
774
|
+
const subtree = yield* configurationFromSnapshotEffect(machine, snapshot);
|
|
775
|
+
const active = new Set(subtree.active);
|
|
776
|
+
const values = new Map(subtree.values);
|
|
777
|
+
const outputs = new Map(subtree.outputs);
|
|
778
|
+
const paths = getPathToRoot(machine, String(snapshot.path));
|
|
779
|
+
const pathSet = new Set(paths);
|
|
780
|
+
for (const ancestor of paths.slice(0, -1)) {
|
|
781
|
+
const node = getNode(machine, ancestor);
|
|
782
|
+
active.add(ancestor);
|
|
783
|
+
if (providedValues !== undefined && hasOwn(providedValues, ancestor)) {
|
|
784
|
+
values.set(ancestor, yield* decodeStateValue(machine, node, providedValues[ancestor]));
|
|
785
|
+
}
|
|
786
|
+
else if (current.values.has(ancestor)) {
|
|
787
|
+
values.set(ancestor, current.values.get(ancestor));
|
|
788
|
+
}
|
|
789
|
+
else {
|
|
790
|
+
throw new Error(`Machine target "${snapshot.path}" requires a value for ancestor state "${ancestor}"`);
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
for (const ancestor of paths.slice(0, -1)) {
|
|
794
|
+
const ancestorNode = getNode(machine, ancestor);
|
|
795
|
+
if (ancestorNode.type === "parallel") {
|
|
796
|
+
for (const child of ancestorNode.children) {
|
|
797
|
+
if (pathSet.has(child) || !current.active.has(child)) {
|
|
798
|
+
continue;
|
|
799
|
+
}
|
|
800
|
+
for (const activePath of current.active) {
|
|
801
|
+
if (isPathInSubtree(activePath, child)) {
|
|
802
|
+
active.add(activePath);
|
|
803
|
+
if (current.values.has(activePath)) {
|
|
804
|
+
values.set(activePath, current.values.get(activePath));
|
|
805
|
+
}
|
|
806
|
+
if (current.outputs.has(activePath)) {
|
|
807
|
+
outputs.set(activePath, current.outputs.get(activePath));
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
return {
|
|
815
|
+
active,
|
|
816
|
+
values,
|
|
817
|
+
outputs,
|
|
818
|
+
history: new Map([...current.history, ...subtree.history])
|
|
819
|
+
};
|
|
425
820
|
});
|
|
426
821
|
export const normalizeTargetConfigurationEffect = (machine, current, target) => {
|
|
427
822
|
if (isTarget(target)) {
|
|
823
|
+
const snapshot = target[TargetSnapshotTypeId];
|
|
824
|
+
if (snapshot !== undefined) {
|
|
825
|
+
if (String(snapshot.path) !== String(target.path)) {
|
|
826
|
+
throw new Error(`Machine expected target snapshot path to be "${target.path}"`);
|
|
827
|
+
}
|
|
828
|
+
return configurationFromTargetSnapshotEffect(machine, current, snapshot, target.values);
|
|
829
|
+
}
|
|
428
830
|
return configurationFromTargetPathEffect(machine, current, target.path, target.value, target.values);
|
|
429
831
|
}
|
|
430
832
|
if (isSnapshot(target)) {
|
|
431
|
-
return normalizeConfigurationEffect(machine, target)
|
|
833
|
+
return normalizeConfigurationEffect(machine, target).pipe(Effect.map((configuration) => ({
|
|
834
|
+
...configuration,
|
|
835
|
+
history: new Map([...current.history, ...configuration.history])
|
|
836
|
+
})));
|
|
432
837
|
}
|
|
433
838
|
throw new Error("Machine expected transition target to be a snapshot or target builder result");
|
|
434
839
|
};
|
|
435
840
|
export const getStateConfigByPath = (machine, path) => machine.handlers[path];
|
|
436
841
|
export const getActiveChildPath = (machine, configuration, path) => getNode(machine, path).children.find((child) => configuration.active.has(child));
|
|
437
|
-
export const isDirectFinalPath = (machine, path) => getNode(machine, path).type === "final"
|
|
842
|
+
export const isDirectFinalPath = (machine, path) => getNode(machine, path).type === "final";
|
|
438
843
|
export const hasCompletionHandler = (machine, path) => getStateConfigByPath(machine, path)?.onDone !== undefined;
|
|
439
844
|
export const isActiveFinalNode = (machine, configuration, path) => {
|
|
440
845
|
if (!configuration.active.has(path)) {
|
|
@@ -478,6 +883,7 @@ export const resolveFinalOutputEffect = Effect.fnUntraced(function* (machine, co
|
|
|
478
883
|
const node = getNode(machine, path);
|
|
479
884
|
const output = getStateConfigByPath(machine, path)?.output?.({
|
|
480
885
|
state: getActiveValue(configuration, path),
|
|
886
|
+
parent: getParentValue(machine, configuration, path),
|
|
481
887
|
parents: getParentValues(machine, configuration, path),
|
|
482
888
|
event,
|
|
483
889
|
outputs
|
|
@@ -488,6 +894,13 @@ export const completeActiveFinalNodeEffect = Effect.fnUntraced(function* (machin
|
|
|
488
894
|
if (!configuration.active.has(path)) {
|
|
489
895
|
return undefined;
|
|
490
896
|
}
|
|
897
|
+
if (outputs.has(path)) {
|
|
898
|
+
return {
|
|
899
|
+
path,
|
|
900
|
+
output: outputs.get(path),
|
|
901
|
+
isNew: false
|
|
902
|
+
};
|
|
903
|
+
}
|
|
491
904
|
const node = getNode(machine, path);
|
|
492
905
|
if (node.type === "compound") {
|
|
493
906
|
const child = getActiveChildPath(machine, configuration, path);
|
|
@@ -540,7 +953,8 @@ export const completeConfigurationEffect = Effect.fnUntraced(function* (machine,
|
|
|
540
953
|
const completed = {
|
|
541
954
|
active: configuration.active,
|
|
542
955
|
values: configuration.values,
|
|
543
|
-
outputs
|
|
956
|
+
outputs,
|
|
957
|
+
history: configuration.history
|
|
544
958
|
};
|
|
545
959
|
for (const path of Array.from(completed.active).sort((left, right) => {
|
|
546
960
|
const depth = pathDepth(machine, right) - pathDepth(machine, left);
|
|
@@ -559,6 +973,11 @@ const EncodedSnapshotSchema = Schema.Struct({
|
|
|
559
973
|
completed: Schema.optional(Schema.Array(Schema.Struct({
|
|
560
974
|
path: Schema.String,
|
|
561
975
|
output: Schema.optional(Schema.Unknown)
|
|
976
|
+
}))),
|
|
977
|
+
history: Schema.optional(Schema.Record(Schema.String, Schema.Struct({
|
|
978
|
+
mode: Schema.Literals(["shallow", "deep"]),
|
|
979
|
+
active: Schema.Array(Schema.String),
|
|
980
|
+
values: Schema.Record(Schema.String, Schema.Unknown)
|
|
562
981
|
})))
|
|
563
982
|
});
|
|
564
983
|
const encodeBoundary = (machine, schema, value, options) => Schema.encodeUnknownEffect(schema)(value).pipe(Effect.mapError((cause) => new MachineSchemaEncodeError({
|
|
@@ -616,7 +1035,7 @@ const failDecodeCause = (machine, cause) => {
|
|
|
616
1035
|
export const encodeSnapshot = (machine, snapshot) => Effect.gen(function* () {
|
|
617
1036
|
const configuration = yield* normalizeConfigurationEffect(machine, snapshot).pipe(Effect.mapError((error) => new MachineSchemaEncodeError({
|
|
618
1037
|
machineId: machine.id,
|
|
619
|
-
boundary: error.boundary === "state"
|
|
1038
|
+
boundary: error.boundary === "state" || error.boundary === "history" ? error.boundary : "configuration",
|
|
620
1039
|
...(error.state === undefined ? {} : { state: error.state }),
|
|
621
1040
|
cause: error.cause
|
|
622
1041
|
})));
|
|
@@ -635,7 +1054,7 @@ export const encodeSnapshot = (machine, snapshot) => Effect.gen(function* () {
|
|
|
635
1054
|
const node = getNode(machine, path);
|
|
636
1055
|
active.push({
|
|
637
1056
|
path,
|
|
638
|
-
value: yield* encodeBoundary(machine, node
|
|
1057
|
+
value: yield* encodeBoundary(machine, getStateNodeSchema(node), getActiveValue(configuration, path), {
|
|
639
1058
|
boundary: "state",
|
|
640
1059
|
state: path
|
|
641
1060
|
})
|
|
@@ -655,10 +1074,62 @@ export const encodeSnapshot = (machine, snapshot) => Effect.gen(function* () {
|
|
|
655
1074
|
...(encodedOutput === undefined ? {} : { output: encodedOutput })
|
|
656
1075
|
});
|
|
657
1076
|
}
|
|
1077
|
+
const history = {};
|
|
1078
|
+
for (const [historyPath, record] of Array.from(configuration.history).sort(([left], [right]) => left.localeCompare(right))) {
|
|
1079
|
+
const historyNode = machine.stateNodes.byPath.get(historyPath);
|
|
1080
|
+
if (historyNode === undefined || historyNode.type !== "history" || historyNode.parent !== record.parent ||
|
|
1081
|
+
historyNode.history !== record.mode) {
|
|
1082
|
+
return yield* Effect.fail(new MachineSchemaEncodeError({
|
|
1083
|
+
machineId: machine.id,
|
|
1084
|
+
boundary: "history",
|
|
1085
|
+
state: historyPath,
|
|
1086
|
+
cause: Cause.die(new Error(`Machine snapshot contains invalid history record "${historyPath}"`))
|
|
1087
|
+
}));
|
|
1088
|
+
}
|
|
1089
|
+
try {
|
|
1090
|
+
validateHistoryRecordControl(machine, record);
|
|
1091
|
+
}
|
|
1092
|
+
catch (cause) {
|
|
1093
|
+
return yield* Effect.fail(new MachineSchemaEncodeError({
|
|
1094
|
+
machineId: machine.id,
|
|
1095
|
+
boundary: "history",
|
|
1096
|
+
state: historyPath,
|
|
1097
|
+
cause: Cause.die(cause)
|
|
1098
|
+
}));
|
|
1099
|
+
}
|
|
1100
|
+
const encodedValues = {};
|
|
1101
|
+
for (const path of record.active) {
|
|
1102
|
+
const stateNode = machine.stateNodes.byPath.get(path);
|
|
1103
|
+
if (stateNode === undefined || stateNode.type === "history" || !record.values.has(path) ||
|
|
1104
|
+
!(isPathInSubtree(path, record.parent) || getPathToRoot(machine, record.parent).includes(path))) {
|
|
1105
|
+
return yield* Effect.fail(new MachineSchemaEncodeError({
|
|
1106
|
+
machineId: machine.id,
|
|
1107
|
+
boundary: "history",
|
|
1108
|
+
state: path,
|
|
1109
|
+
cause: Cause.die(new Error(`Machine snapshot contains invalid remembered state "${path}"`))
|
|
1110
|
+
}));
|
|
1111
|
+
}
|
|
1112
|
+
encodedValues[path] = yield* encodeBoundary(machine, getStateNodeSchema(stateNode), record.values.get(path), { boundary: "history", state: path });
|
|
1113
|
+
}
|
|
1114
|
+
if (record.values.size !== record.active.size) {
|
|
1115
|
+
return yield* Effect.fail(new MachineSchemaEncodeError({
|
|
1116
|
+
machineId: machine.id,
|
|
1117
|
+
boundary: "history",
|
|
1118
|
+
state: historyPath,
|
|
1119
|
+
cause: Cause.die(new Error(`Machine history record "${historyPath}" contains values outside its paths`))
|
|
1120
|
+
}));
|
|
1121
|
+
}
|
|
1122
|
+
history[historyPath] = {
|
|
1123
|
+
mode: record.mode,
|
|
1124
|
+
active: Array.from(record.active).sort((left, right) => compareDocumentOrder(machine, left, right)),
|
|
1125
|
+
values: encodedValues
|
|
1126
|
+
};
|
|
1127
|
+
}
|
|
658
1128
|
return {
|
|
659
1129
|
_tag: "MachineSnapshot",
|
|
660
1130
|
active,
|
|
661
|
-
...(completed.length === 0 ? {} : { completed })
|
|
1131
|
+
...(completed.length === 0 ? {} : { completed }),
|
|
1132
|
+
...(Object.keys(history).length === 0 ? {} : { history })
|
|
662
1133
|
};
|
|
663
1134
|
}).pipe(Effect.catchCause((cause) => failEncodeCause(machine, cause)));
|
|
664
1135
|
export const decodeSnapshot = (machine, encoded) => Effect.gen(function* () {
|
|
@@ -675,15 +1146,91 @@ export const decodeSnapshot = (machine, encoded) => Effect.gen(function* () {
|
|
|
675
1146
|
}
|
|
676
1147
|
const node = getNode(machine, entry.path);
|
|
677
1148
|
active.add(entry.path);
|
|
678
|
-
values.set(entry.path, yield* decodeEncodedBoundary(machine, node
|
|
1149
|
+
values.set(entry.path, yield* decodeEncodedBoundary(machine, getStateNodeSchema(node), entry.value, {
|
|
679
1150
|
boundary: "state",
|
|
680
1151
|
state: entry.path
|
|
681
1152
|
}));
|
|
682
1153
|
}
|
|
1154
|
+
const history = new Map();
|
|
1155
|
+
for (const [historyPath, encodedRecord] of Object.entries(decoded.history ?? {})) {
|
|
1156
|
+
const historyNode = machine.stateNodes.byPath.get(historyPath);
|
|
1157
|
+
if (historyNode === undefined || historyNode.type !== "history" || historyNode.parent === undefined ||
|
|
1158
|
+
historyNode.history !== encodedRecord.mode) {
|
|
1159
|
+
return yield* Effect.fail(new MachineSchemaDecodeError({
|
|
1160
|
+
machineId: machine.id,
|
|
1161
|
+
boundary: "history",
|
|
1162
|
+
state: historyPath,
|
|
1163
|
+
cause: Cause.die(new Error(`Machine encoded snapshot contains invalid history record "${historyPath}"`))
|
|
1164
|
+
}));
|
|
1165
|
+
}
|
|
1166
|
+
const rememberedActive = new Set();
|
|
1167
|
+
const rememberedValues = new Map();
|
|
1168
|
+
for (const path of encodedRecord.active) {
|
|
1169
|
+
if (rememberedActive.has(path)) {
|
|
1170
|
+
return yield* Effect.fail(new MachineSchemaDecodeError({
|
|
1171
|
+
machineId: machine.id,
|
|
1172
|
+
boundary: "history",
|
|
1173
|
+
state: path,
|
|
1174
|
+
cause: Cause.die(new Error(`Machine encoded history contains duplicate state "${path}"`))
|
|
1175
|
+
}));
|
|
1176
|
+
}
|
|
1177
|
+
const stateNode = machine.stateNodes.byPath.get(path);
|
|
1178
|
+
if (stateNode === undefined || stateNode.type === "history" ||
|
|
1179
|
+
!Object.prototype.hasOwnProperty.call(encodedRecord.values, path) ||
|
|
1180
|
+
!(isPathInSubtree(path, historyNode.parent) || getPathToRoot(machine, historyNode.parent).includes(path))) {
|
|
1181
|
+
return yield* Effect.fail(new MachineSchemaDecodeError({
|
|
1182
|
+
machineId: machine.id,
|
|
1183
|
+
boundary: "history",
|
|
1184
|
+
state: path,
|
|
1185
|
+
cause: Cause.die(new Error(`Machine encoded snapshot contains invalid remembered state "${path}"`))
|
|
1186
|
+
}));
|
|
1187
|
+
}
|
|
1188
|
+
rememberedActive.add(path);
|
|
1189
|
+
rememberedValues.set(path, yield* decodeEncodedBoundary(machine, getStateNodeSchema(stateNode), encodedRecord.values[path], {
|
|
1190
|
+
boundary: "history",
|
|
1191
|
+
state: path
|
|
1192
|
+
}));
|
|
1193
|
+
}
|
|
1194
|
+
if (Object.keys(encodedRecord.values).length !== rememberedActive.size) {
|
|
1195
|
+
return yield* Effect.fail(new MachineSchemaDecodeError({
|
|
1196
|
+
machineId: machine.id,
|
|
1197
|
+
boundary: "history",
|
|
1198
|
+
state: historyPath,
|
|
1199
|
+
cause: Cause.die(new Error(`Machine encoded history "${historyPath}" contains values outside its paths`))
|
|
1200
|
+
}));
|
|
1201
|
+
}
|
|
1202
|
+
if (!rememberedActive.has(historyNode.parent)) {
|
|
1203
|
+
return yield* Effect.fail(new MachineSchemaDecodeError({
|
|
1204
|
+
machineId: machine.id,
|
|
1205
|
+
boundary: "history",
|
|
1206
|
+
state: historyPath,
|
|
1207
|
+
cause: Cause.die(new Error(`Machine encoded history "${historyPath}" does not contain its parent state`))
|
|
1208
|
+
}));
|
|
1209
|
+
}
|
|
1210
|
+
const record = {
|
|
1211
|
+
mode: encodedRecord.mode,
|
|
1212
|
+
parent: historyNode.parent,
|
|
1213
|
+
active: rememberedActive,
|
|
1214
|
+
values: rememberedValues
|
|
1215
|
+
};
|
|
1216
|
+
try {
|
|
1217
|
+
validateHistoryRecordControl(machine, record);
|
|
1218
|
+
}
|
|
1219
|
+
catch (cause) {
|
|
1220
|
+
return yield* Effect.fail(new MachineSchemaDecodeError({
|
|
1221
|
+
machineId: machine.id,
|
|
1222
|
+
boundary: "history",
|
|
1223
|
+
state: historyPath,
|
|
1224
|
+
cause: Cause.die(cause)
|
|
1225
|
+
}));
|
|
1226
|
+
}
|
|
1227
|
+
history.set(historyPath, record);
|
|
1228
|
+
}
|
|
683
1229
|
const configuration = {
|
|
684
1230
|
active,
|
|
685
1231
|
values,
|
|
686
|
-
outputs: new Map()
|
|
1232
|
+
outputs: new Map(),
|
|
1233
|
+
history
|
|
687
1234
|
};
|
|
688
1235
|
const snapshot = validateEncodedConfiguration(machine, configuration);
|
|
689
1236
|
const completions = [];
|