xstate 3.2.1 → 3.3.3
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/.vscode/launch.json +15 -13
- package/README.md +37 -9
- package/dist/xstate.js +1 -1
- package/dist/xstate.utils.js +1 -1
- package/es/Machine.d.ts +2 -2
- package/es/Machine.js +2 -2
- package/es/State.d.ts +8 -7
- package/es/State.js +3 -2
- package/es/StateNode.d.ts +50 -13
- package/es/StateNode.js +617 -412
- package/es/graph.d.ts +9 -6
- package/es/graph.js +31 -24
- package/es/patterns.js +1 -1
- package/es/scxml.d.ts +2 -1
- package/es/scxml.js +33 -10
- package/es/types.d.ts +38 -7
- package/es/utils.d.ts +14 -1
- package/es/utils.js +33 -5
- package/lib/Machine.d.ts +2 -2
- package/lib/Machine.js +2 -2
- package/lib/State.d.ts +8 -7
- package/lib/State.js +3 -2
- package/lib/StateNode.d.ts +50 -13
- package/lib/StateNode.js +616 -411
- package/lib/graph.d.ts +9 -6
- package/lib/graph.js +30 -22
- package/lib/patterns.js +1 -1
- package/lib/scxml.d.ts +2 -1
- package/lib/scxml.js +33 -10
- package/lib/types.d.ts +38 -7
- package/lib/utils.d.ts +14 -1
- package/lib/utils.js +35 -5
- package/package.json +3 -3
- package/src/Machine.ts +5 -3
- package/src/State.ts +10 -2
- package/src/StateNode.ts +966 -590
- package/src/graph.ts +60 -31
- package/src/scxml.ts +80 -49
- package/src/types.ts +48 -7
- package/src/utils.ts +52 -7
- package/test/actions.test.ts +24 -1
- package/test/activities.test.ts +165 -0
- package/test/deep.test.ts +14 -16
- package/test/deterministic.test.ts +26 -5
- package/test/examples/6.17.test.ts +64 -0
- package/test/fixtures/id.ts +1 -1
- package/test/graph.test.ts +39 -16
- package/test/guards.test.ts +172 -15
- package/test/history.test.ts +193 -58
- package/test/invalid.test.ts +48 -0
- package/test/multiple.test.ts +12 -18
- package/test/parallel.test.ts +472 -1
- package/test/scxml.test.ts +13 -4
- package/test/stateIn.test.ts +1 -1
- package/test/transient.test.ts +183 -1
package/src/graph.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { StateNode } from './index';
|
|
2
|
-
import { toStateValue, getActionType } from './utils';
|
|
2
|
+
import { toStateValue, getActionType, flatMap } from './utils';
|
|
3
3
|
import {
|
|
4
4
|
StateValue,
|
|
5
5
|
Machine,
|
|
@@ -16,37 +16,51 @@ const EMPTY_MAP = {};
|
|
|
16
16
|
|
|
17
17
|
export function getNodes(node: StateNode): StateNode[] {
|
|
18
18
|
const { states } = node;
|
|
19
|
-
const nodes = Object.keys(
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const subNodes = getNodes(states[stateKey]);
|
|
19
|
+
const nodes = Object.keys(states).reduce(
|
|
20
|
+
(accNodes: StateNode[], stateKey) => {
|
|
21
|
+
const subState = states[stateKey];
|
|
22
|
+
const subNodes = getNodes(states[stateKey]);
|
|
24
23
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
accNodes.push(subState, ...subNodes);
|
|
25
|
+
return accNodes;
|
|
26
|
+
},
|
|
27
|
+
[]
|
|
28
|
+
);
|
|
28
29
|
|
|
29
30
|
return nodes;
|
|
30
31
|
}
|
|
31
32
|
|
|
32
|
-
function getEventEdges(node: StateNode, event: string): Edge[] {
|
|
33
|
+
export function getEventEdges(node: StateNode, event: string): Edge[] {
|
|
33
34
|
const transitions = node.on[event]!;
|
|
34
35
|
|
|
35
|
-
return
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
36
|
+
return flatMap(
|
|
37
|
+
transitions.map(transition => {
|
|
38
|
+
const targets = ([] as string[]).concat(transition.target);
|
|
39
|
+
return targets.map(target => {
|
|
40
|
+
const targetNode = node.getRelativeStateNodes(
|
|
41
|
+
target,
|
|
42
|
+
undefined,
|
|
43
|
+
false
|
|
44
|
+
)[0];
|
|
45
|
+
return {
|
|
46
|
+
source: node,
|
|
47
|
+
target: targetNode,
|
|
48
|
+
event,
|
|
49
|
+
actions: transition.actions
|
|
50
|
+
? transition.actions.map(getActionType)
|
|
51
|
+
: [],
|
|
52
|
+
cond: transition.cond
|
|
53
|
+
};
|
|
54
|
+
});
|
|
55
|
+
})
|
|
56
|
+
);
|
|
44
57
|
}
|
|
45
58
|
|
|
46
|
-
export function getEdges(node: StateNode): Edge[] {
|
|
59
|
+
export function getEdges(node: StateNode, options?: { deep: boolean }): Edge[] {
|
|
60
|
+
const { deep = true } = options || {};
|
|
47
61
|
const edges: Edge[] = [];
|
|
48
62
|
|
|
49
|
-
if (node.states) {
|
|
63
|
+
if (node.states && deep) {
|
|
50
64
|
Object.keys(node.states).forEach(stateKey => {
|
|
51
65
|
edges.push(...getEdges(node.states[stateKey]));
|
|
52
66
|
});
|
|
@@ -59,7 +73,10 @@ export function getEdges(node: StateNode): Edge[] {
|
|
|
59
73
|
return edges;
|
|
60
74
|
}
|
|
61
75
|
|
|
62
|
-
export function getAdjacencyMap(
|
|
76
|
+
export function getAdjacencyMap(
|
|
77
|
+
node: Machine,
|
|
78
|
+
extendedState?: any
|
|
79
|
+
): AdjacencyMap {
|
|
63
80
|
const adjacency: AdjacencyMap = {};
|
|
64
81
|
|
|
65
82
|
const events = node.events;
|
|
@@ -74,7 +91,7 @@ export function getAdjacencyMap(node: Machine): AdjacencyMap {
|
|
|
74
91
|
adjacency[stateKey] = {};
|
|
75
92
|
|
|
76
93
|
for (const event of events) {
|
|
77
|
-
const nextState = node.transition(stateValue, event);
|
|
94
|
+
const nextState = node.transition(stateValue, event, extendedState);
|
|
78
95
|
adjacency[stateKey][event] = { state: nextState.value };
|
|
79
96
|
|
|
80
97
|
findAdjacencies(nextState.value);
|
|
@@ -86,11 +103,14 @@ export function getAdjacencyMap(node: Machine): AdjacencyMap {
|
|
|
86
103
|
return adjacency;
|
|
87
104
|
}
|
|
88
105
|
|
|
89
|
-
export function getShortestPaths(
|
|
106
|
+
export function getShortestPaths(
|
|
107
|
+
machine: Machine,
|
|
108
|
+
extendedState?: any
|
|
109
|
+
): PathMap {
|
|
90
110
|
if (!machine.states) {
|
|
91
111
|
return EMPTY_MAP;
|
|
92
112
|
}
|
|
93
|
-
const adjacency = getAdjacencyMap(machine);
|
|
113
|
+
const adjacency = getAdjacencyMap(machine, extendedState);
|
|
94
114
|
const initialStateId = JSON.stringify(machine.initialState.value);
|
|
95
115
|
const pathMap: PathMap = {
|
|
96
116
|
[initialStateId]: []
|
|
@@ -148,20 +168,26 @@ export function getShortestPaths(machine: Machine): PathMap {
|
|
|
148
168
|
return pathMap;
|
|
149
169
|
}
|
|
150
170
|
|
|
151
|
-
export function getShortestPathsAsArray(
|
|
152
|
-
|
|
171
|
+
export function getShortestPathsAsArray(
|
|
172
|
+
machine: Machine,
|
|
173
|
+
extendedState?: any
|
|
174
|
+
): PathItem[] {
|
|
175
|
+
const result = getShortestPaths(machine, extendedState);
|
|
153
176
|
return Object.keys(result).map(key => ({
|
|
154
177
|
state: JSON.parse(key),
|
|
155
178
|
path: result[key]
|
|
156
179
|
}));
|
|
157
180
|
}
|
|
158
181
|
|
|
159
|
-
export function getSimplePaths(
|
|
182
|
+
export function getSimplePaths(
|
|
183
|
+
machine: Machine,
|
|
184
|
+
extendedState?: any
|
|
185
|
+
): PathsMap {
|
|
160
186
|
if (!machine.states) {
|
|
161
187
|
return EMPTY_MAP;
|
|
162
188
|
}
|
|
163
189
|
|
|
164
|
-
const adjacency = getAdjacencyMap(machine);
|
|
190
|
+
const adjacency = getAdjacencyMap(machine, extendedState);
|
|
165
191
|
const visited = new Set();
|
|
166
192
|
const path: Segment[] = [];
|
|
167
193
|
const paths: PathsMap = {};
|
|
@@ -202,8 +228,11 @@ export function getSimplePaths(machine: Machine): PathsMap {
|
|
|
202
228
|
return paths;
|
|
203
229
|
}
|
|
204
230
|
|
|
205
|
-
export function getSimplePathsAsArray(
|
|
206
|
-
|
|
231
|
+
export function getSimplePathsAsArray(
|
|
232
|
+
machine: Machine,
|
|
233
|
+
extendedState?: any
|
|
234
|
+
): PathsItem[] {
|
|
235
|
+
const result = getSimplePaths(machine, extendedState);
|
|
207
236
|
return Object.keys(result).map(key => ({
|
|
208
237
|
state: JSON.parse(key),
|
|
209
238
|
paths: result[key]
|
package/src/scxml.ts
CHANGED
|
@@ -62,25 +62,54 @@ function stateNodeToSCXML(stateNode: StateNode) {
|
|
|
62
62
|
return stateNodeToSCXML(subStateNode);
|
|
63
63
|
}),
|
|
64
64
|
...Object.keys(stateNode.on)
|
|
65
|
-
.map(
|
|
66
|
-
|
|
65
|
+
.map(
|
|
66
|
+
(event): XMLElement[] => {
|
|
67
|
+
const transition = stateNode.on![event];
|
|
67
68
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
if (!transition) {
|
|
70
|
+
return [];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (Array.isArray(transition)) {
|
|
74
|
+
return transition.map(targetTransition => {
|
|
75
|
+
return {
|
|
76
|
+
type: 'element',
|
|
77
|
+
name: 'transition',
|
|
78
|
+
attributes: {
|
|
79
|
+
...(event ? { event } : undefined),
|
|
80
|
+
target: stateNode.parent!.getRelativeStateNodes(
|
|
81
|
+
targetTransition.target
|
|
82
|
+
)[0]!.id, // TODO: fixme
|
|
83
|
+
...(targetTransition.cond
|
|
84
|
+
? { cond: targetTransition.cond.toString() }
|
|
85
|
+
: undefined)
|
|
86
|
+
},
|
|
87
|
+
elements: targetTransition.actions
|
|
88
|
+
? targetTransition.actions.map(action => ({
|
|
89
|
+
type: 'element',
|
|
90
|
+
name: 'send',
|
|
91
|
+
attributes: {
|
|
92
|
+
event: getActionType(action)
|
|
93
|
+
}
|
|
94
|
+
}))
|
|
95
|
+
: undefined
|
|
96
|
+
};
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return Object.keys(transition).map(target => {
|
|
101
|
+
const targetTransition = transition[target];
|
|
71
102
|
|
|
72
|
-
if (Array.isArray(transition)) {
|
|
73
|
-
return transition.map(targetTransition => {
|
|
74
103
|
return {
|
|
75
104
|
type: 'element',
|
|
76
105
|
name: 'transition',
|
|
77
106
|
attributes: {
|
|
78
|
-
...event ? { event } : undefined,
|
|
79
|
-
target: stateNode.parent!.
|
|
80
|
-
.id,
|
|
81
|
-
...targetTransition.cond
|
|
107
|
+
...(event ? { event } : undefined),
|
|
108
|
+
target: stateNode.parent!.getRelativeStateNodes(target)![0]
|
|
109
|
+
.id, // TODO: fixme
|
|
110
|
+
...(targetTransition.cond
|
|
82
111
|
? { cond: targetTransition.cond.toString() }
|
|
83
|
-
: undefined
|
|
112
|
+
: undefined)
|
|
84
113
|
},
|
|
85
114
|
elements: targetTransition.actions
|
|
86
115
|
? targetTransition.actions.map(action => ({
|
|
@@ -94,32 +123,7 @@ function stateNodeToSCXML(stateNode: StateNode) {
|
|
|
94
123
|
};
|
|
95
124
|
});
|
|
96
125
|
}
|
|
97
|
-
|
|
98
|
-
return Object.keys(transition).map(target => {
|
|
99
|
-
const targetTransition = transition[target];
|
|
100
|
-
|
|
101
|
-
return {
|
|
102
|
-
type: 'element',
|
|
103
|
-
name: 'transition',
|
|
104
|
-
attributes: {
|
|
105
|
-
...event ? { event } : undefined,
|
|
106
|
-
target: stateNode.parent!.getState(target)!.id,
|
|
107
|
-
...targetTransition.cond
|
|
108
|
-
? { cond: targetTransition.cond.toString() }
|
|
109
|
-
: undefined
|
|
110
|
-
},
|
|
111
|
-
elements: targetTransition.actions
|
|
112
|
-
? targetTransition.actions.map(action => ({
|
|
113
|
-
type: 'element',
|
|
114
|
-
name: 'send',
|
|
115
|
-
attributes: {
|
|
116
|
-
event: getActionType(action)
|
|
117
|
-
}
|
|
118
|
-
}))
|
|
119
|
-
: undefined
|
|
120
|
-
};
|
|
121
|
-
});
|
|
122
|
-
})
|
|
126
|
+
)
|
|
123
127
|
.reduce((a, b) => a.concat(b))
|
|
124
128
|
].filter(Boolean) as XMLElement[]
|
|
125
129
|
};
|
|
@@ -210,10 +214,37 @@ function toConfig(
|
|
|
210
214
|
let initial = parallel ? undefined : nodeJson.attributes!.initial;
|
|
211
215
|
let states: Record<string, any>;
|
|
212
216
|
let on: Record<string, any>;
|
|
217
|
+
const { elements } = nodeJson;
|
|
218
|
+
|
|
219
|
+
switch (nodeJson.name) {
|
|
220
|
+
case 'history': {
|
|
221
|
+
if (!elements) {
|
|
222
|
+
return {
|
|
223
|
+
id,
|
|
224
|
+
history: nodeJson.attributes!.type || 'shallow'
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const [transitionElement] = elements.filter(
|
|
229
|
+
element => element.name === 'transition'
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
return {
|
|
233
|
+
id,
|
|
234
|
+
history: nodeJson.attributes!.type || 'shallow',
|
|
235
|
+
target: `#${transitionElement.attributes!.target}`
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
default:
|
|
239
|
+
break;
|
|
240
|
+
}
|
|
213
241
|
|
|
214
242
|
if (nodeJson.elements) {
|
|
215
243
|
const stateElements = nodeJson.elements.filter(
|
|
216
|
-
element =>
|
|
244
|
+
element =>
|
|
245
|
+
element.name === 'state' ||
|
|
246
|
+
element.name === 'parallel' ||
|
|
247
|
+
element.name === 'history'
|
|
217
248
|
);
|
|
218
249
|
|
|
219
250
|
const transitionElements = nodeJson.elements.filter(
|
|
@@ -248,12 +279,12 @@ function toConfig(
|
|
|
248
279
|
(values: XMLElement[]) => {
|
|
249
280
|
return values.map(value => ({
|
|
250
281
|
target: `#${value.attributes!.target}`,
|
|
251
|
-
...value.elements ? executableContent(value.elements) : undefined,
|
|
252
|
-
...value.attributes!.cond
|
|
282
|
+
...(value.elements ? executableContent(value.elements) : undefined),
|
|
283
|
+
...(value.attributes!.cond
|
|
253
284
|
? {
|
|
254
285
|
cond: evalCond(value.attributes!.cond as string)
|
|
255
286
|
}
|
|
256
|
-
: undefined
|
|
287
|
+
: undefined)
|
|
257
288
|
}));
|
|
258
289
|
}
|
|
259
290
|
);
|
|
@@ -283,18 +314,18 @@ function toConfig(
|
|
|
283
314
|
return {
|
|
284
315
|
id,
|
|
285
316
|
delimiter: options.delimiter,
|
|
286
|
-
...initial ? { initial } : undefined,
|
|
287
|
-
...parallel ? { parallel } : undefined,
|
|
288
|
-
...stateElements.length
|
|
317
|
+
...(initial ? { initial } : undefined),
|
|
318
|
+
...(parallel ? { parallel } : undefined),
|
|
319
|
+
...(stateElements.length
|
|
289
320
|
? {
|
|
290
321
|
states: mapValues(states, (state, key) =>
|
|
291
322
|
toConfig(state, key, options)
|
|
292
323
|
)
|
|
293
324
|
}
|
|
294
|
-
: undefined,
|
|
295
|
-
...transitionElements.length ? { on } : undefined,
|
|
296
|
-
...onEntry ? { onEntry } : undefined,
|
|
297
|
-
...onExit ? { onExit } : undefined
|
|
325
|
+
: undefined),
|
|
326
|
+
...(transitionElements.length ? { on } : undefined),
|
|
327
|
+
...(onEntry ? { onEntry } : undefined),
|
|
328
|
+
...(onExit ? { onExit } : undefined)
|
|
298
329
|
};
|
|
299
330
|
}
|
|
300
331
|
|
package/src/types.ts
CHANGED
|
@@ -25,12 +25,24 @@ export interface StateValueMap {
|
|
|
25
25
|
|
|
26
26
|
export type StateValue = string | StateValueMap;
|
|
27
27
|
|
|
28
|
-
export
|
|
28
|
+
export interface HistoryValue {
|
|
29
|
+
states: Record<string, HistoryValue | undefined>;
|
|
30
|
+
current: StateValue | undefined;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export type ConditionPredicate = (
|
|
34
|
+
extendedState: any,
|
|
35
|
+
event: EventObject,
|
|
36
|
+
microstepState: StateValue
|
|
37
|
+
) => boolean;
|
|
38
|
+
|
|
39
|
+
export type Condition = string | ConditionPredicate;
|
|
29
40
|
|
|
30
41
|
export interface TransitionConfig {
|
|
31
|
-
cond?:
|
|
42
|
+
cond?: Condition;
|
|
32
43
|
actions?: Action[];
|
|
33
44
|
in?: StateValue;
|
|
45
|
+
internal?: boolean;
|
|
34
46
|
}
|
|
35
47
|
|
|
36
48
|
export interface TargetTransitionConfig extends TransitionConfig {
|
|
@@ -50,6 +62,12 @@ export interface StateNodeConfig {
|
|
|
50
62
|
key?: string;
|
|
51
63
|
initial?: string | undefined;
|
|
52
64
|
parallel?: boolean | undefined;
|
|
65
|
+
/**
|
|
66
|
+
* Indicates whether the state node is a history state node, and what
|
|
67
|
+
* type of history:
|
|
68
|
+
* shallow, deep, true (shallow), false (none), undefined (none)
|
|
69
|
+
*/
|
|
70
|
+
history?: 'shallow' | 'deep' | boolean | undefined;
|
|
53
71
|
states?: Record<string, SimpleOrCompoundStateNodeConfig> | undefined;
|
|
54
72
|
on?: Record<string, Transition | undefined>;
|
|
55
73
|
onEntry?: Action | Action[];
|
|
@@ -67,19 +85,29 @@ export interface SimpleStateNodeConfig extends StateNodeConfig {
|
|
|
67
85
|
states?: undefined;
|
|
68
86
|
}
|
|
69
87
|
|
|
88
|
+
export interface HistoryStateNodeConfig extends SimpleStateNodeConfig {
|
|
89
|
+
history: 'shallow' | 'deep' | true;
|
|
90
|
+
target: StateValue | undefined;
|
|
91
|
+
}
|
|
92
|
+
|
|
70
93
|
export interface CompoundStateNodeConfig extends StateNodeConfig {
|
|
71
94
|
initial?: string;
|
|
72
95
|
parallel?: boolean;
|
|
73
96
|
states: Record<string, SimpleOrCompoundStateNodeConfig>;
|
|
97
|
+
history?: false | undefined;
|
|
74
98
|
}
|
|
75
99
|
|
|
76
100
|
export type SimpleOrCompoundStateNodeConfig =
|
|
77
101
|
| CompoundStateNodeConfig
|
|
78
102
|
| SimpleStateNodeConfig;
|
|
79
103
|
|
|
104
|
+
export interface MachineOptions {
|
|
105
|
+
guards: Record<string, ConditionPredicate>;
|
|
106
|
+
}
|
|
80
107
|
export interface MachineConfig extends CompoundStateNodeConfig {
|
|
81
108
|
key?: string;
|
|
82
109
|
strict?: boolean;
|
|
110
|
+
history?: false | undefined;
|
|
83
111
|
}
|
|
84
112
|
export interface StandardMachineConfig extends MachineConfig {
|
|
85
113
|
initial: string;
|
|
@@ -89,7 +117,6 @@ export interface StandardMachineConfig extends MachineConfig {
|
|
|
89
117
|
export interface ParallelMachineConfig extends MachineConfig {
|
|
90
118
|
initial?: undefined;
|
|
91
119
|
parallel: true;
|
|
92
|
-
states: Record<string, CompoundStateNodeConfig>;
|
|
93
120
|
}
|
|
94
121
|
|
|
95
122
|
export interface EntryExitEffectMap {
|
|
@@ -102,6 +129,8 @@ export interface StateNode {
|
|
|
102
129
|
id: string;
|
|
103
130
|
initial: string | undefined;
|
|
104
131
|
parallel: boolean;
|
|
132
|
+
transient: boolean;
|
|
133
|
+
history: false | 'shallow' | 'deep';
|
|
105
134
|
states: Record<string, StateNode>;
|
|
106
135
|
on?: Record<string, Transition>;
|
|
107
136
|
onEntry?: Action | Action[];
|
|
@@ -112,6 +141,7 @@ export interface StateNode {
|
|
|
112
141
|
|
|
113
142
|
export interface ComplexStateNode extends StateNode {
|
|
114
143
|
initial: string;
|
|
144
|
+
history: false;
|
|
115
145
|
}
|
|
116
146
|
|
|
117
147
|
export interface LeafStateNode extends StateNode {
|
|
@@ -121,6 +151,11 @@ export interface LeafStateNode extends StateNode {
|
|
|
121
151
|
parent: StateNode;
|
|
122
152
|
}
|
|
123
153
|
|
|
154
|
+
export interface HistoryStateNode extends StateNode {
|
|
155
|
+
history: 'shallow' | 'deep';
|
|
156
|
+
target: StateValue | undefined;
|
|
157
|
+
}
|
|
158
|
+
|
|
124
159
|
export interface Machine extends StateNode {
|
|
125
160
|
id: string;
|
|
126
161
|
initial: string | undefined;
|
|
@@ -145,6 +180,11 @@ export interface ActionMap {
|
|
|
145
180
|
onExit: Action[];
|
|
146
181
|
}
|
|
147
182
|
|
|
183
|
+
export interface EntryExitStates {
|
|
184
|
+
entry: Set<StateNode>;
|
|
185
|
+
exit: Set<StateNode>;
|
|
186
|
+
}
|
|
187
|
+
|
|
148
188
|
export interface ActivityMap {
|
|
149
189
|
[activityKey: string]: boolean;
|
|
150
190
|
}
|
|
@@ -154,11 +194,12 @@ export type MaybeStateValueActionsTuple = [
|
|
|
154
194
|
ActivityMap | undefined
|
|
155
195
|
];
|
|
156
196
|
|
|
197
|
+
// tslint:disable-next-line:class-name
|
|
157
198
|
export interface StateTransition {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
199
|
+
value: StateValue | undefined;
|
|
200
|
+
entryExitStates: EntryExitStates | undefined;
|
|
201
|
+
actions: Action[];
|
|
202
|
+
paths: string[][];
|
|
162
203
|
}
|
|
163
204
|
|
|
164
205
|
export interface TransitionData {
|
package/src/utils.ts
CHANGED
|
@@ -16,7 +16,9 @@ export function getActionType(action: Action): ActionType {
|
|
|
16
16
|
try {
|
|
17
17
|
return typeof action === 'string' || typeof action === 'number'
|
|
18
18
|
? `${action}`
|
|
19
|
-
: typeof action === 'function'
|
|
19
|
+
: typeof action === 'function'
|
|
20
|
+
? action.name
|
|
21
|
+
: action.type;
|
|
20
22
|
} catch (e) {
|
|
21
23
|
throw new Error(
|
|
22
24
|
'Events must be strings or objects with a string event.type property.'
|
|
@@ -89,34 +91,73 @@ export function mapValues<T, P>(
|
|
|
89
91
|
return result;
|
|
90
92
|
}
|
|
91
93
|
|
|
94
|
+
export function mapFilterValues<T, P>(
|
|
95
|
+
collection: { [key: string]: T },
|
|
96
|
+
iteratee: (item: T, key: string, collection: { [key: string]: T }) => P,
|
|
97
|
+
predicate: (item: T) => boolean
|
|
98
|
+
): { [key: string]: P } {
|
|
99
|
+
const result = {};
|
|
100
|
+
|
|
101
|
+
Object.keys(collection).forEach(key => {
|
|
102
|
+
const item = collection[key];
|
|
103
|
+
|
|
104
|
+
if (!predicate(item)) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
result[key] = iteratee(item, key, collection);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
return result;
|
|
112
|
+
}
|
|
113
|
+
|
|
92
114
|
/**
|
|
93
115
|
* Retrieves a value at the given path.
|
|
94
116
|
* @param props The deep path to the prop of the desired value
|
|
95
117
|
*/
|
|
96
|
-
export const path = (props: string[]): any =>
|
|
118
|
+
export const path = <T extends Record<string, any>>(props: string[]): any => (
|
|
97
119
|
object: T
|
|
98
120
|
): any => {
|
|
99
|
-
let result:
|
|
121
|
+
let result: T = object;
|
|
100
122
|
|
|
101
123
|
for (const prop of props) {
|
|
102
|
-
result = result[prop];
|
|
124
|
+
result = result[prop as keyof typeof result];
|
|
103
125
|
}
|
|
104
126
|
|
|
105
127
|
return result;
|
|
106
128
|
};
|
|
107
129
|
|
|
130
|
+
/**
|
|
131
|
+
* Retrieves a value at the given path via the nested accessor prop.
|
|
132
|
+
* @param props The deep path to the prop of the desired value
|
|
133
|
+
*/
|
|
134
|
+
export function nestedPath<T extends Record<string, any>>(
|
|
135
|
+
props: string[],
|
|
136
|
+
accessorProp: keyof T
|
|
137
|
+
): (object: T) => T {
|
|
138
|
+
return object => {
|
|
139
|
+
let result: T = object;
|
|
140
|
+
|
|
141
|
+
for (const prop of props) {
|
|
142
|
+
result = result[accessorProp][prop];
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return result;
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
108
149
|
export const toStatePaths = (stateValue: StateValue): string[][] => {
|
|
109
150
|
if (typeof stateValue === 'string') {
|
|
110
151
|
return [[stateValue]];
|
|
111
152
|
}
|
|
112
153
|
|
|
113
|
-
const result =
|
|
114
|
-
.map(key => {
|
|
154
|
+
const result = flatMap(
|
|
155
|
+
Object.keys(stateValue).map(key => {
|
|
115
156
|
return toStatePaths(stateValue[key]).map(subPath => {
|
|
116
157
|
return [key].concat(subPath);
|
|
117
158
|
});
|
|
118
159
|
})
|
|
119
|
-
|
|
160
|
+
);
|
|
120
161
|
|
|
121
162
|
return result;
|
|
122
163
|
};
|
|
@@ -145,3 +186,7 @@ export const pathsToStateValue = (paths: string[][]): StateValue => {
|
|
|
145
186
|
|
|
146
187
|
return result;
|
|
147
188
|
};
|
|
189
|
+
|
|
190
|
+
export const flatMap = <T>(array: T[][]): T[] => {
|
|
191
|
+
return array.reduce((a, b) => a.concat(b), []);
|
|
192
|
+
};
|
package/test/actions.test.ts
CHANGED
|
@@ -311,7 +311,7 @@ describe('onEntry/onExit actions', () => {
|
|
|
311
311
|
assert.isEmpty(pingPong.transition('ping.foo', 'TACK').actions);
|
|
312
312
|
});
|
|
313
313
|
|
|
314
|
-
|
|
314
|
+
it('with an absolute transition', () => {
|
|
315
315
|
assert.isEmpty(
|
|
316
316
|
pingPong.transition('ping.foo', 'ABSOLUTE_TACK').actions
|
|
317
317
|
);
|
|
@@ -319,3 +319,26 @@ describe('onEntry/onExit actions', () => {
|
|
|
319
319
|
});
|
|
320
320
|
});
|
|
321
321
|
});
|
|
322
|
+
|
|
323
|
+
describe('actions on invalid transition', () => {
|
|
324
|
+
const stopMachine = Machine({
|
|
325
|
+
initial: 'idle',
|
|
326
|
+
states: {
|
|
327
|
+
idle: {
|
|
328
|
+
on: {
|
|
329
|
+
STOP: {
|
|
330
|
+
stop: {
|
|
331
|
+
actions: ['action1']
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
},
|
|
336
|
+
stop: {}
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
it('should not recall previous actions', () => {
|
|
341
|
+
const nextState = stopMachine.transition('idle', 'STOP');
|
|
342
|
+
assert.isEmpty(stopMachine.transition(nextState, 'INVALID').actions);
|
|
343
|
+
});
|
|
344
|
+
});
|