@typeonce/effect-machine 0.20.0 → 0.22.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 +115 -5
- package/dist/Machine.d.ts +188 -52
- package/dist/Machine.d.ts.map +1 -1
- package/dist/Machine.js +6 -5
- package/dist/Machine.js.map +1 -1
- package/dist/internal/machine/executionPlan.d.ts.map +1 -1
- package/dist/internal/machine/executionPlan.js +50 -6
- package/dist/internal/machine/executionPlan.js.map +1 -1
- package/dist/internal/machine/initialization.d.ts.map +1 -1
- package/dist/internal/machine/initialization.js +7 -2
- package/dist/internal/machine/initialization.js.map +1 -1
- package/dist/internal/machine/machine.d.ts.map +1 -1
- package/dist/internal/machine/machine.js +132 -22
- package/dist/internal/machine/machine.js.map +1 -1
- package/dist/internal/machine/planner.d.ts +16 -2
- package/dist/internal/machine/planner.d.ts.map +1 -1
- package/dist/internal/machine/planner.js +67 -13
- package/dist/internal/machine/planner.js.map +1 -1
- package/dist/internal/machine/topology.d.ts +19 -2
- package/dist/internal/machine/topology.d.ts.map +1 -1
- package/dist/internal/machine/topology.js +17 -2
- package/dist/internal/machine/topology.js.map +1 -1
- package/dist/internal/testing/machine/finiteModel.d.ts.map +1 -1
- package/dist/internal/testing/machine/finiteModel.js +3 -3
- package/dist/internal/testing/machine/finiteModel.js.map +1 -1
- package/dist/internal/testing/machine/verification.d.ts.map +1 -1
- package/dist/internal/testing/machine/verification.js +17 -3
- package/dist/internal/testing/machine/verification.js.map +1 -1
- package/dist/testing/MachineTest.d.ts +5 -3
- package/dist/testing/MachineTest.d.ts.map +1 -1
- package/dist/testing/MachineTest.js +3 -3
- package/dist/testing/MachineTest.js.map +1 -1
- package/dist/unstable/reactivity/AtomMachine.d.ts +1 -1
- package/dist/unstable/reactivity/AtomMachine.js +1 -1
- package/docs/agent-guide.md +51 -3
- package/package.json +1 -1
- package/src/Machine.ts +532 -107
- package/src/internal/machine/executionPlan.ts +62 -6
- package/src/internal/machine/initialization.ts +10 -2
- package/src/internal/machine/machine.ts +188 -24
- package/src/internal/machine/planner.ts +86 -15
- package/src/internal/machine/topology.ts +40 -3
- package/src/internal/testing/machine/finiteModel.ts +7 -3
- package/src/internal/testing/machine/verification.ts +24 -3
- package/src/testing/MachineTest.ts +5 -3
- package/src/unstable/reactivity/AtomMachine.ts +1 -1
|
@@ -47,11 +47,13 @@ import {
|
|
|
47
47
|
getNode,
|
|
48
48
|
type InitialTarget as InitialTargetInstruction,
|
|
49
49
|
isChoiceTarget,
|
|
50
|
+
isCombinedTarget,
|
|
50
51
|
isDeclined,
|
|
51
52
|
isHistoryTarget,
|
|
52
53
|
isInitialTarget,
|
|
53
54
|
isNoTarget,
|
|
54
55
|
isSnapshot,
|
|
56
|
+
isStateUpdate,
|
|
55
57
|
isTarget,
|
|
56
58
|
makeChoiceTarget,
|
|
57
59
|
makeTarget,
|
|
@@ -69,6 +71,7 @@ export type MicrostepPlan<State, Event, E, R> = {
|
|
|
69
71
|
readonly branchKey: string | undefined
|
|
70
72
|
readonly target: string | undefined
|
|
71
73
|
readonly resolvedTarget: string | undefined
|
|
74
|
+
readonly updates: ReadonlyArray<string>
|
|
72
75
|
}>
|
|
73
76
|
readonly commands: ReadonlyArray<RuntimeCommand>
|
|
74
77
|
readonly raisedEvents: ReadonlyArray<Event>
|
|
@@ -78,6 +81,11 @@ export type MicrostepPlan<State, Event, E, R> = {
|
|
|
78
81
|
readonly changed: boolean
|
|
79
82
|
}
|
|
80
83
|
|
|
84
|
+
type SettlingMicrostep<State, Event, E, R> = MicrostepPlan<State, Event, E, R> & {
|
|
85
|
+
/** Internal signal that eventless stabilization must run again. */
|
|
86
|
+
readonly stabilize: boolean
|
|
87
|
+
}
|
|
88
|
+
|
|
81
89
|
export type MacrostepPlan<State, Event, E, R, Output> =
|
|
82
90
|
& {
|
|
83
91
|
readonly next: State
|
|
@@ -580,10 +588,15 @@ export type EvaluatedTransition<States extends Machine.StateSchemas, Event, E, R
|
|
|
580
588
|
| Machine.Snapshot<States>
|
|
581
589
|
| Machine.Target<States, Machine.StateIdentifier<States>>
|
|
582
590
|
| undefined
|
|
591
|
+
readonly update: {
|
|
592
|
+
readonly path: string
|
|
593
|
+
readonly value: unknown
|
|
594
|
+
} | undefined
|
|
583
595
|
readonly commands: ReadonlyArray<RuntimeCommand>
|
|
584
596
|
readonly raisedEvents: ReadonlyArray<Event>
|
|
585
597
|
readonly emittedEvents: ReadonlyArray<unknown>
|
|
586
598
|
readonly changed: boolean
|
|
599
|
+
readonly stabilize: boolean
|
|
587
600
|
readonly exitPaths: ReadonlyArray<string>
|
|
588
601
|
readonly entryPaths: ReadonlyArray<string>
|
|
589
602
|
readonly choiceTransitions: ReadonlyArray<{
|
|
@@ -594,6 +607,7 @@ export type EvaluatedTransition<States extends Machine.StateSchemas, Event, E, R
|
|
|
594
607
|
readonly branchKey: string | undefined
|
|
595
608
|
readonly target: string
|
|
596
609
|
readonly resolvedTarget: string
|
|
610
|
+
readonly updates: ReadonlyArray<string>
|
|
597
611
|
}>
|
|
598
612
|
}
|
|
599
613
|
|
|
@@ -1143,7 +1157,9 @@ export const removeConflictingTransitions = <
|
|
|
1143
1157
|
let preempted = false
|
|
1144
1158
|
const transitionsToRemove = new Set<EvaluatedTransition<States, Event, E, R, Context>>()
|
|
1145
1159
|
for (const selected of filtered) {
|
|
1146
|
-
|
|
1160
|
+
const writesSameState = transition.update !== undefined && selected.update !== undefined &&
|
|
1161
|
+
transition.update.path === selected.update.path
|
|
1162
|
+
if (hasPathIntersection(transition.exitPaths, selected.exitPaths) || writesSameState) {
|
|
1147
1163
|
if (isDescendantOf(transition.selection.sourcePath, selected.selection.sourcePath)) {
|
|
1148
1164
|
transitionsToRemove.add(selected)
|
|
1149
1165
|
} else {
|
|
@@ -1221,6 +1237,7 @@ interface ResolvedChoiceTransition {
|
|
|
1221
1237
|
readonly branchKey: string | undefined
|
|
1222
1238
|
readonly target: string
|
|
1223
1239
|
readonly resolvedTarget: string
|
|
1240
|
+
readonly updates: ReadonlyArray<string>
|
|
1224
1241
|
}
|
|
1225
1242
|
|
|
1226
1243
|
function resolveChoiceTarget(
|
|
@@ -1299,7 +1316,8 @@ function resolveChoiceTarget(
|
|
|
1299
1316
|
branchIndex: collected.branchIndex,
|
|
1300
1317
|
branchKey: collected.branchKey,
|
|
1301
1318
|
target: returnedPath,
|
|
1302
|
-
resolvedTarget: nested?.target.path ?? returnedPath
|
|
1319
|
+
resolvedTarget: nested?.target.path ?? returnedPath,
|
|
1320
|
+
updates: []
|
|
1303
1321
|
})
|
|
1304
1322
|
commands.push(...collected.commands)
|
|
1305
1323
|
raisedEvents.push(...collected.raisedEvents)
|
|
@@ -1343,9 +1361,12 @@ const collectEvaluatedTransition = <
|
|
|
1343
1361
|
if (transitionResult.declined) {
|
|
1344
1362
|
throw new Error("Machine transition returned decline without declaring declinable: true")
|
|
1345
1363
|
}
|
|
1346
|
-
const
|
|
1364
|
+
const combined = isCombinedTarget(transitionResult.state) ? transitionResult.state : undefined
|
|
1365
|
+
const transitionState = combined?.target ?? transitionResult.state
|
|
1366
|
+
const unresolvedTarget = transitionState === undefined
|
|
1367
|
+
|| isStateUpdate(transitionState)
|
|
1347
1368
|
? undefined
|
|
1348
|
-
:
|
|
1369
|
+
: transitionState as
|
|
1349
1370
|
| Machine.Snapshot<States>
|
|
1350
1371
|
| Machine.Target<States, Machine.StateIdentifier<States>>
|
|
1351
1372
|
| Machine.HistoryTarget<States, Machine.HistoryIdentifier<States>>
|
|
@@ -1356,6 +1377,22 @@ const collectEvaluatedTransition = <
|
|
|
1356
1377
|
selection.transition.targets,
|
|
1357
1378
|
unresolvedTarget
|
|
1358
1379
|
)
|
|
1380
|
+
const stateUpdate = combined?.update ?? (isStateUpdate(transitionState) ? transitionState : undefined)
|
|
1381
|
+
const update = stateUpdate !== undefined
|
|
1382
|
+
? (() => {
|
|
1383
|
+
const node = getNode(machine, stateUpdate.path)
|
|
1384
|
+
if (
|
|
1385
|
+
!state.active.has(node.path) || node.schema === undefined ||
|
|
1386
|
+
(node.type !== "compound" && node.type !== "parallel")
|
|
1387
|
+
) {
|
|
1388
|
+
throw new Error(`Machine state update owner "${node.path}" must be an active valued compound or parallel state`)
|
|
1389
|
+
}
|
|
1390
|
+
return {
|
|
1391
|
+
path: node.path,
|
|
1392
|
+
value: decodeStateValueSync(machine, node, stateUpdate.value)
|
|
1393
|
+
}
|
|
1394
|
+
})()
|
|
1395
|
+
: undefined
|
|
1359
1396
|
const choiceResolution = unresolvedTarget === undefined
|
|
1360
1397
|
? undefined
|
|
1361
1398
|
: resolveChoiceTarget(
|
|
@@ -1456,7 +1493,10 @@ const collectEvaluatedTransition = <
|
|
|
1456
1493
|
? getTargetNodePath(target)
|
|
1457
1494
|
: getTargetNodePath(unresolvedTarget)
|
|
1458
1495
|
let stateAfterTransition = target === undefined
|
|
1459
|
-
? state
|
|
1496
|
+
? update === undefined ? state : {
|
|
1497
|
+
...state,
|
|
1498
|
+
values: new Map(state.values).set(update.path, update.value)
|
|
1499
|
+
}
|
|
1460
1500
|
: normalizeTargetConfigurationSync<States>(machine, state, target)
|
|
1461
1501
|
for (const additionalTarget of additionalChoiceTargets) {
|
|
1462
1502
|
stateAfterTransition = normalizeTargetConfigurationSync<States>(
|
|
@@ -1465,7 +1505,17 @@ const collectEvaluatedTransition = <
|
|
|
1465
1505
|
additionalTarget
|
|
1466
1506
|
)
|
|
1467
1507
|
}
|
|
1508
|
+
if (combined !== undefined && update !== undefined && !stateAfterTransition.active.has(update.path)) {
|
|
1509
|
+
throw new Error(`Machine combined target exited its updating owner "${update.path}"`)
|
|
1510
|
+
}
|
|
1511
|
+
if (update !== undefined) {
|
|
1512
|
+
stateAfterTransition = {
|
|
1513
|
+
...stateAfterTransition,
|
|
1514
|
+
values: new Map(stateAfterTransition.values).set(update.path, update.value)
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1468
1517
|
const changed = selection.transition.reenter || !hasSameActivePaths(state, stateAfterTransition)
|
|
1518
|
+
const stabilize = changed || update !== undefined
|
|
1469
1519
|
|
|
1470
1520
|
if (!changed) {
|
|
1471
1521
|
return {
|
|
@@ -1474,6 +1524,7 @@ const collectEvaluatedTransition = <
|
|
|
1474
1524
|
branchKey: transitionResult.branchKey,
|
|
1475
1525
|
unresolvedTarget,
|
|
1476
1526
|
target,
|
|
1527
|
+
update,
|
|
1477
1528
|
commands: [
|
|
1478
1529
|
...transitionResult.commands,
|
|
1479
1530
|
...(choiceResolution?.commands ?? []),
|
|
@@ -1496,6 +1547,7 @@ const collectEvaluatedTransition = <
|
|
|
1496
1547
|
...additionalTargetEmittedEvents
|
|
1497
1548
|
],
|
|
1498
1549
|
changed,
|
|
1550
|
+
stabilize,
|
|
1499
1551
|
exitPaths: [],
|
|
1500
1552
|
entryPaths: [],
|
|
1501
1553
|
choiceTransitions: [
|
|
@@ -1521,6 +1573,7 @@ const collectEvaluatedTransition = <
|
|
|
1521
1573
|
branchKey: transitionResult.branchKey,
|
|
1522
1574
|
unresolvedTarget,
|
|
1523
1575
|
target,
|
|
1576
|
+
update,
|
|
1524
1577
|
commands: [
|
|
1525
1578
|
...transitionResult.commands,
|
|
1526
1579
|
...(choiceResolution?.commands ?? []),
|
|
@@ -1543,6 +1596,7 @@ const collectEvaluatedTransition = <
|
|
|
1543
1596
|
...additionalTargetEmittedEvents
|
|
1544
1597
|
],
|
|
1545
1598
|
changed,
|
|
1599
|
+
stabilize,
|
|
1546
1600
|
exitPaths: reenteredHistoryTarget !== undefined
|
|
1547
1601
|
? sortExitPaths(
|
|
1548
1602
|
machine,
|
|
@@ -1744,7 +1798,8 @@ export const planInitialSync = <
|
|
|
1744
1798
|
emittedEvents: [...choiceResolution.emittedEvents, ...initialHistoryEmittedEvents],
|
|
1745
1799
|
exitPaths: [],
|
|
1746
1800
|
entryPaths: [],
|
|
1747
|
-
changed: false
|
|
1801
|
+
changed: false,
|
|
1802
|
+
stabilize: false
|
|
1748
1803
|
}]
|
|
1749
1804
|
)
|
|
1750
1805
|
|
|
@@ -1850,7 +1905,8 @@ const microstep = <
|
|
|
1850
1905
|
emittedEvents: [],
|
|
1851
1906
|
exitPaths: [],
|
|
1852
1907
|
entryPaths: [],
|
|
1853
|
-
changed: false
|
|
1908
|
+
changed: false,
|
|
1909
|
+
stabilize: false
|
|
1854
1910
|
}
|
|
1855
1911
|
}
|
|
1856
1912
|
|
|
@@ -1876,11 +1932,23 @@ const microstep = <
|
|
|
1876
1932
|
branchIndex: transition.branchIndex,
|
|
1877
1933
|
branchKey: transition.branchKey,
|
|
1878
1934
|
target: transition.unresolvedTarget === undefined ? undefined : getTargetNodePath(transition.unresolvedTarget),
|
|
1879
|
-
resolvedTarget: transition.target === undefined ? undefined : getTargetNodePath(transition.target)
|
|
1935
|
+
resolvedTarget: transition.target === undefined ? undefined : getTargetNodePath(transition.target),
|
|
1936
|
+
updates: transition.update === undefined ? [] : [transition.update.path]
|
|
1880
1937
|
},
|
|
1881
1938
|
...transition.choiceTransitions
|
|
1882
1939
|
])
|
|
1883
1940
|
let stateAfterTransition = state
|
|
1941
|
+
// Updates never reactivate topology. Apply every retained value write to the
|
|
1942
|
+
// original active configuration before any control target becomes
|
|
1943
|
+
// authoritative.
|
|
1944
|
+
for (const transition of sortedTransitions) {
|
|
1945
|
+
if (transition.update !== undefined) {
|
|
1946
|
+
stateAfterTransition = {
|
|
1947
|
+
...stateAfterTransition,
|
|
1948
|
+
values: new Map(stateAfterTransition.values).set(transition.update.path, transition.update.value)
|
|
1949
|
+
}
|
|
1950
|
+
}
|
|
1951
|
+
}
|
|
1884
1952
|
// Value-only targets are evaluated against the original configuration. If
|
|
1885
1953
|
// one is applied after a control-changing transition, it can resurrect a
|
|
1886
1954
|
// branch that the changing transition exited. Apply value-only updates
|
|
@@ -1901,6 +1969,7 @@ const microstep = <
|
|
|
1901
1969
|
}
|
|
1902
1970
|
|
|
1903
1971
|
const changed = transitions.some((transition) => transition.changed)
|
|
1972
|
+
const stabilize = transitions.some((transition) => transition.stabilize)
|
|
1904
1973
|
const transitionActions = sortedTransitions
|
|
1905
1974
|
.flatMap((transition) => transition.commands)
|
|
1906
1975
|
const transitionRaisedEvents = sortedTransitions
|
|
@@ -1918,7 +1987,8 @@ const microstep = <
|
|
|
1918
1987
|
emittedEvents: transitionEmittedEvents,
|
|
1919
1988
|
exitPaths: [],
|
|
1920
1989
|
entryPaths: [],
|
|
1921
|
-
changed: false
|
|
1990
|
+
changed: false,
|
|
1991
|
+
stabilize
|
|
1922
1992
|
}
|
|
1923
1993
|
}
|
|
1924
1994
|
|
|
@@ -1951,7 +2021,8 @@ const microstep = <
|
|
|
1951
2021
|
emittedEvents: [...exit.emittedEvents, ...transitionEmittedEvents, ...entry.emittedEvents],
|
|
1952
2022
|
exitPaths,
|
|
1953
2023
|
entryPaths,
|
|
1954
|
-
changed: true
|
|
2024
|
+
changed: true,
|
|
2025
|
+
stabilize
|
|
1955
2026
|
}
|
|
1956
2027
|
}
|
|
1957
2028
|
|
|
@@ -1974,7 +2045,7 @@ const settle = <
|
|
|
1974
2045
|
commands: Array<RuntimeCommand>,
|
|
1975
2046
|
raisedEvents: Array<Machine.EventOf<Events>>,
|
|
1976
2047
|
emittedEvents: Array<unknown>,
|
|
1977
|
-
microsteps: Array<
|
|
2048
|
+
microsteps: Array<SettlingMicrostep<ActiveConfiguration, Machine.EventOf<Events>, E, R>>
|
|
1978
2049
|
) => {
|
|
1979
2050
|
let currentState = state
|
|
1980
2051
|
let currentEvent = event
|
|
@@ -2010,7 +2081,7 @@ const settle = <
|
|
|
2010
2081
|
pendingCompletions.length === 0 ? [] : [pendingCompletions.shift()!]
|
|
2011
2082
|
)
|
|
2012
2083
|
if (done.length > 0) {
|
|
2013
|
-
const doneStep:
|
|
2084
|
+
const doneStep: SettlingMicrostep<ActiveConfiguration, Machine.EventOf<Events>, E, R> = microstep(
|
|
2014
2085
|
machine,
|
|
2015
2086
|
currentState,
|
|
2016
2087
|
currentEvent,
|
|
@@ -2021,7 +2092,7 @@ const settle = <
|
|
|
2021
2092
|
emittedEvents.push(...doneStep.emittedEvents)
|
|
2022
2093
|
microsteps.push(doneStep)
|
|
2023
2094
|
currentState = doneStep.next
|
|
2024
|
-
shouldRunAlways = doneStep.
|
|
2095
|
+
shouldRunAlways = doneStep.stabilize
|
|
2025
2096
|
continue
|
|
2026
2097
|
}
|
|
2027
2098
|
if (isActiveFinalConfiguration(machine, currentState)) {
|
|
@@ -2038,7 +2109,7 @@ const settle = <
|
|
|
2038
2109
|
? selectAlwaysTransitions<States, Events, Emits, E, R>(machine, currentState, currentEvent)
|
|
2039
2110
|
: []
|
|
2040
2111
|
if (always.length > 0) {
|
|
2041
|
-
const alwaysStep:
|
|
2112
|
+
const alwaysStep: SettlingMicrostep<ActiveConfiguration, Machine.EventOf<Events>, E, R> = microstep(
|
|
2042
2113
|
machine,
|
|
2043
2114
|
currentState,
|
|
2044
2115
|
currentEvent,
|
|
@@ -2049,7 +2120,7 @@ const settle = <
|
|
|
2049
2120
|
emittedEvents.push(...alwaysStep.emittedEvents)
|
|
2050
2121
|
microsteps.push(alwaysStep)
|
|
2051
2122
|
currentState = alwaysStep.next
|
|
2052
|
-
shouldRunAlways = alwaysStep.
|
|
2123
|
+
shouldRunAlways = alwaysStep.stabilize
|
|
2053
2124
|
continue
|
|
2054
2125
|
}
|
|
2055
2126
|
|
|
@@ -29,6 +29,10 @@ export const DeclinedTypeId: unique symbol = Symbol("effect/Machine/Declined")
|
|
|
29
29
|
|
|
30
30
|
export const TargetSelectionTypeId: unique symbol = Symbol("effect/Machine/TargetSelection")
|
|
31
31
|
|
|
32
|
+
export const StateUpdateTypeId: unique symbol = Symbol("effect/Machine/StateUpdate")
|
|
33
|
+
|
|
34
|
+
export const CombinedTargetTypeId: unique symbol = Symbol("effect/Machine/CombinedTarget")
|
|
35
|
+
|
|
32
36
|
export const SelectedBranchTypeId: unique symbol = Symbol("effect/Machine/SelectedBranch")
|
|
33
37
|
|
|
34
38
|
interface StateInput {
|
|
@@ -73,7 +77,7 @@ export interface Declined {
|
|
|
73
77
|
readonly [DeclinedTypeId]: typeof DeclinedTypeId
|
|
74
78
|
}
|
|
75
79
|
|
|
76
|
-
export type TargetSelectionKind = "state" | "initial" | "history" | "choice" | "none"
|
|
80
|
+
export type TargetSelectionKind = "state" | "initial" | "history" | "choice" | "update" | "none"
|
|
77
81
|
|
|
78
82
|
export type TargetSelectionScope = "local" | "branch" | "full" | "initial"
|
|
79
83
|
|
|
@@ -83,6 +87,7 @@ export interface TargetSelection {
|
|
|
83
87
|
readonly kind: TargetSelectionKind
|
|
84
88
|
readonly scope: TargetSelectionScope | undefined
|
|
85
89
|
readonly path: string | undefined
|
|
90
|
+
readonly updatePath?: string
|
|
86
91
|
}
|
|
87
92
|
|
|
88
93
|
/** One branch selection returned by a compiled branching transition. */
|
|
@@ -97,13 +102,15 @@ export interface SelectedBranch {
|
|
|
97
102
|
export const makeTargetSelection = (
|
|
98
103
|
kind: TargetSelectionKind,
|
|
99
104
|
path?: string,
|
|
100
|
-
scope?: TargetSelectionScope
|
|
105
|
+
scope?: TargetSelectionScope,
|
|
106
|
+
updatePath?: string
|
|
101
107
|
): TargetSelection =>
|
|
102
108
|
Object.freeze({
|
|
103
109
|
[TargetSelectionTypeId]: TargetSelectionTypeId as typeof TargetSelectionTypeId,
|
|
104
110
|
kind,
|
|
105
111
|
scope,
|
|
106
|
-
path
|
|
112
|
+
path,
|
|
113
|
+
...(updatePath === undefined ? {} : { updatePath })
|
|
107
114
|
})
|
|
108
115
|
|
|
109
116
|
/** Source-independent definition-time selection for an explicitly targetless transition. */
|
|
@@ -111,6 +118,36 @@ export const noneTargetSelection: TargetSelection = makeTargetSelection("none",
|
|
|
111
118
|
|
|
112
119
|
export const isTargetSelection = (u: unknown): u is TargetSelection => hasProperty(u, TargetSelectionTypeId)
|
|
113
120
|
|
|
121
|
+
export interface StateUpdate {
|
|
122
|
+
readonly [StateUpdateTypeId]: typeof StateUpdateTypeId
|
|
123
|
+
readonly path: string
|
|
124
|
+
readonly value: unknown
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export const makeStateUpdate = (path: string, value: unknown): StateUpdate =>
|
|
128
|
+
Object.freeze({
|
|
129
|
+
[StateUpdateTypeId]: StateUpdateTypeId,
|
|
130
|
+
path,
|
|
131
|
+
value
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
export const isStateUpdate = (u: unknown): u is StateUpdate => hasProperty(u, StateUpdateTypeId)
|
|
135
|
+
|
|
136
|
+
export interface CombinedTarget {
|
|
137
|
+
readonly [CombinedTargetTypeId]: typeof CombinedTargetTypeId
|
|
138
|
+
readonly target: unknown
|
|
139
|
+
readonly update: StateUpdate
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export const makeCombinedTarget = (target: unknown, update: StateUpdate): CombinedTarget =>
|
|
143
|
+
Object.freeze({
|
|
144
|
+
[CombinedTargetTypeId]: CombinedTargetTypeId,
|
|
145
|
+
target,
|
|
146
|
+
update
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
export const isCombinedTarget = (u: unknown): u is CombinedTarget => hasProperty(u, CombinedTargetTypeId)
|
|
150
|
+
|
|
114
151
|
export const makeSelectedBranch = (
|
|
115
152
|
owner: object,
|
|
116
153
|
branchIndex: number,
|
|
@@ -1234,7 +1234,10 @@ const selectSnapshot = (
|
|
|
1234
1234
|
if (state.node._tag === "Choice") {
|
|
1235
1235
|
return (builder[state.node.key] as () => unknown)()
|
|
1236
1236
|
}
|
|
1237
|
-
const method = builder[state.node.key] as (
|
|
1237
|
+
const method = builder[state.node.key].decoded as (
|
|
1238
|
+
value: unknown,
|
|
1239
|
+
selector?: (builder: any) => unknown
|
|
1240
|
+
) => unknown
|
|
1238
1241
|
const value = stateValue(state, path === requestedParts?.join(".") ? requestedValue : undefined)
|
|
1239
1242
|
if (state.node._tag === "Atomic" || state.node._tag === "Final") return method(value)
|
|
1240
1243
|
|
|
@@ -1473,14 +1476,15 @@ const makeHandlers = (
|
|
|
1473
1476
|
...(Object.keys(history).length === 0 ? {} : { history }),
|
|
1474
1477
|
...(node._tag === "Compound"
|
|
1475
1478
|
? {
|
|
1476
|
-
initialize: ({ builder }: any) => builder(stateValue(byPath.get(`${path}.${node.initial}`)!))
|
|
1479
|
+
initialize: ({ builder }: any) => builder.decoded(stateValue(byPath.get(`${path}.${node.initial}`)!))
|
|
1477
1480
|
}
|
|
1478
1481
|
: {
|
|
1479
1482
|
initialize: ({ builder }: any) =>
|
|
1480
1483
|
node.states
|
|
1481
1484
|
.filter((child) => child._tag !== "History" && child._tag !== "Choice")
|
|
1482
1485
|
.reduce(
|
|
1483
|
-
(current: any, child) =>
|
|
1486
|
+
(current: any, child) =>
|
|
1487
|
+
current[child.key].decoded(stateValue(byPath.get(`${path}.${child.key}`)!)),
|
|
1484
1488
|
builder
|
|
1485
1489
|
)
|
|
1486
1490
|
}),
|
|
@@ -475,7 +475,7 @@ const targetWithinSelection = (
|
|
|
475
475
|
nodeByPath: ReadonlyMap<string, Machine.Machine.StateNode>
|
|
476
476
|
): boolean => {
|
|
477
477
|
const selection = branch.selection
|
|
478
|
-
if (selection.kind === "none") return target === undefined
|
|
478
|
+
if (selection.kind === "none" || selection.kind === "update") return target === undefined
|
|
479
479
|
if (target === undefined || selection.path === undefined) return false
|
|
480
480
|
if (target === selection.path) return true
|
|
481
481
|
const selectedNode = nodeByPath.get(selection.path)
|
|
@@ -577,6 +577,7 @@ export const coverage = <M extends AnyMachine>(
|
|
|
577
577
|
let microsteps = 0
|
|
578
578
|
let changedMicrosteps = 0
|
|
579
579
|
let targetlessTransitions = 0
|
|
580
|
+
let stateUpdates = 0
|
|
580
581
|
let raisedEvents = 0
|
|
581
582
|
let emittedEvents = 0
|
|
582
583
|
let eventTriggered = 0
|
|
@@ -630,7 +631,8 @@ export const coverage = <M extends AnyMachine>(
|
|
|
630
631
|
hitPaths(microstep.exitPaths, exitHits)
|
|
631
632
|
observeSnapshot(microstep.next)
|
|
632
633
|
for (const retained of microstep.transitions) {
|
|
633
|
-
|
|
634
|
+
stateUpdates += retained.updates.length
|
|
635
|
+
if (retained.target === undefined && retained.updates.length === 0) targetlessTransitions += 1
|
|
634
636
|
if (retained.trigger.type === "event") eventTriggered += 1
|
|
635
637
|
else if (retained.trigger.type === "always") alwaysTriggered += 1
|
|
636
638
|
else if (retained.trigger.type === "done") doneTriggered += 1
|
|
@@ -718,6 +720,7 @@ export const coverage = <M extends AnyMachine>(
|
|
|
718
720
|
total: microsteps,
|
|
719
721
|
changed: changedMicrosteps,
|
|
720
722
|
targetless: targetlessTransitions,
|
|
723
|
+
updates: stateUpdates,
|
|
721
724
|
raised: raisedEvents,
|
|
722
725
|
emitted: emittedEvents,
|
|
723
726
|
eventTriggered,
|
|
@@ -1540,6 +1543,18 @@ export const verify = <M extends AnyMachine>(
|
|
|
1540
1543
|
transition.target === undefined ? transition.source : String(transition.target)
|
|
1541
1544
|
)
|
|
1542
1545
|
}
|
|
1546
|
+
if (
|
|
1547
|
+
transition.updates.length !== branch.updates.length ||
|
|
1548
|
+
transition.updates.some((path, index) => path !== branch.updates[index])
|
|
1549
|
+
) {
|
|
1550
|
+
add(
|
|
1551
|
+
"definitions.selection",
|
|
1552
|
+
location,
|
|
1553
|
+
`transition branch ${transition.branchIndex} from "${transition.source}" reported retained owner updates ` +
|
|
1554
|
+
`${JSON.stringify(transition.updates)} instead of ${JSON.stringify(branch.updates)}`,
|
|
1555
|
+
transition.source
|
|
1556
|
+
)
|
|
1557
|
+
}
|
|
1543
1558
|
return branch
|
|
1544
1559
|
}
|
|
1545
1560
|
|
|
@@ -1598,7 +1613,13 @@ export const verify = <M extends AnyMachine>(
|
|
|
1598
1613
|
const resolvedTarget = transition.resolvedTarget === undefined ? undefined : String(transition.resolvedTarget)
|
|
1599
1614
|
const targetNode = target === undefined ? undefined : byPath.get(target)
|
|
1600
1615
|
let expected = target
|
|
1601
|
-
let explanation =
|
|
1616
|
+
let explanation = transition.updates.length > 0
|
|
1617
|
+
? target === undefined
|
|
1618
|
+
? `update owner "${transition.updates.join("\", \"")}"`
|
|
1619
|
+
: `target "${target}" and update owner "${transition.updates.join("\", \"")}"`
|
|
1620
|
+
: target === undefined
|
|
1621
|
+
? "an unresolved targetless transition"
|
|
1622
|
+
: `target "${target}"`
|
|
1602
1623
|
|
|
1603
1624
|
if (transition.trigger.type === "choice") {
|
|
1604
1625
|
explanation = target === undefined ? "a targetless choice edge" : `choice edge target "${target}"`
|
|
@@ -1109,7 +1109,7 @@ export const Invariant: {
|
|
|
1109
1109
|
* events: Machine.events(),
|
|
1110
1110
|
* initial: {
|
|
1111
1111
|
* target: (to) => to.Count(),
|
|
1112
|
-
* resolve: ({ target }) => target(new Count({ value: 0 }))
|
|
1112
|
+
* resolve: ({ target }) => target.decoded(new Count({ value: 0 }))
|
|
1113
1113
|
* }
|
|
1114
1114
|
* }).handle({ Count: {} })
|
|
1115
1115
|
*
|
|
@@ -1444,14 +1444,14 @@ export type ExploreOptions<M extends AnyMachine, Key extends ExplorationKey = Ex
|
|
|
1444
1444
|
* events: Machine.events(Increment),
|
|
1445
1445
|
* initial: {
|
|
1446
1446
|
* target: (to) => to.Count(),
|
|
1447
|
-
* resolve: ({ target }) => target(new Count({ value: 0 }))
|
|
1447
|
+
* resolve: ({ target }) => target.decoded(new Count({ value: 0 }))
|
|
1448
1448
|
* }
|
|
1449
1449
|
* }).handle({
|
|
1450
1450
|
* Count: {
|
|
1451
1451
|
* on: {
|
|
1452
1452
|
* Increment: (to) =>
|
|
1453
1453
|
* to.full.Count().resolve(({ state, target }) =>
|
|
1454
|
-
* target(new Count({ value: state.value + 1 })))
|
|
1454
|
+
* target.decoded(new Count({ value: state.value + 1 })))
|
|
1455
1455
|
* }
|
|
1456
1456
|
* }
|
|
1457
1457
|
* })
|
|
@@ -1822,6 +1822,8 @@ export interface MicrostepCoverageEvidence {
|
|
|
1822
1822
|
readonly total: number
|
|
1823
1823
|
readonly changed: number
|
|
1824
1824
|
readonly targetless: number
|
|
1825
|
+
/** Retained state-value update operations. */
|
|
1826
|
+
readonly updates: number
|
|
1825
1827
|
readonly raised: number
|
|
1826
1828
|
readonly emitted: number
|
|
1827
1829
|
readonly eventTriggered: number
|
|
@@ -374,7 +374,7 @@ type ChildState<Child extends Machine.ChildMachine.Any> = RefState<Machine.Child
|
|
|
374
374
|
* events: Machine.events(),
|
|
375
375
|
* initial: {
|
|
376
376
|
* target: (to) => to.Count(),
|
|
377
|
-
* resolve: ({ target }) => target(new Count({ value: 0 }))
|
|
377
|
+
* resolve: ({ target }) => target.decoded(new Count({ value: 0 }))
|
|
378
378
|
* }
|
|
379
379
|
* }).handle({ Count: {} })
|
|
380
380
|
* const machineAtom = AtomMachine.make(machine)
|