@ue-too/being 0.14.1 → 0.16.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 +117 -64
- package/hierarchical-example.d.ts +0 -12
- package/hierarchical.d.ts +3 -3
- package/index.d.ts +3 -3
- package/index.js +363 -2
- package/index.js.map +5 -5
- package/interface.d.ts +19 -11
- package/package.json +1 -1
- package/schema-factory-example.d.ts +0 -8
- package/schema-factory.d.ts +2 -2
- package/vending-machine-example.d.ts +2 -2
package/README.md
CHANGED
|
@@ -14,11 +14,13 @@ Let's say we want to build a state machine for a vending machine.
|
|
|
14
14
|
To make it simple, the vending machine only accepts dollar bills and sells 3 types of items at $1, $2, and $3.
|
|
15
15
|
|
|
16
16
|
The items are:
|
|
17
|
+
|
|
17
18
|
- Coke (1 dollar)
|
|
18
19
|
- Red Bull (2 dollars)
|
|
19
20
|
- Water (3 dollars)
|
|
20
21
|
|
|
21
22
|
There are only 3 kinds of actions that the user can take:
|
|
23
|
+
|
|
22
24
|
- insert coins
|
|
23
25
|
- select an item (we can break it into multiple events; each event representing a different item)
|
|
24
26
|
- cancel the transaction
|
|
@@ -26,15 +28,18 @@ There are only 3 kinds of actions that the user can take:
|
|
|
26
28
|
With the above information, we can create a state machine for the vending machine.
|
|
27
29
|
|
|
28
30
|
For the `@ue-too/being` library, there are 3 main things that we need to define and be clear on in order to create a state machine:
|
|
31
|
+
|
|
29
32
|
- All possible states of the state machine.
|
|
30
33
|
- All possible events that can happen in the state machine.
|
|
31
34
|
- The context of the state machine.
|
|
32
35
|
- The rules for the state transitions.
|
|
33
36
|
|
|
34
37
|
Let's start with the all possible states of the state machine.
|
|
38
|
+
|
|
35
39
|
> There are many ways to represent the vending machine in a state machine. My way is only one possible way but you can probably come up with a better way or at least what makes sense to you.
|
|
36
40
|
|
|
37
41
|
I'm defining the states as follows:
|
|
42
|
+
|
|
38
43
|
- IDLE
|
|
39
44
|
- 1 Dollar Inserted
|
|
40
45
|
- 2 Dollars Inserted
|
|
@@ -43,12 +48,19 @@ I'm defining the states as follows:
|
|
|
43
48
|
To create a type that is a string literal union of the states, we can use the utilty type `CreateStateType`.
|
|
44
49
|
|
|
45
50
|
```ts
|
|
46
|
-
import { CreateStateType } from
|
|
47
|
-
|
|
48
|
-
const VENDING_MACHINE_STATES = [
|
|
49
|
-
|
|
50
|
-
|
|
51
|
+
import { CreateStateType } from '@ue-too/being';
|
|
52
|
+
|
|
53
|
+
const VENDING_MACHINE_STATES = [
|
|
54
|
+
'IDLE',
|
|
55
|
+
'ONE_DOLLAR_INSERTED',
|
|
56
|
+
'TWO_DOLLARS_INSERTED',
|
|
57
|
+
'THREE_DOLLARS_INSERTED',
|
|
58
|
+
] as const;
|
|
59
|
+
export type VendingMachineStates = CreateStateType<
|
|
60
|
+
typeof VENDING_MACHINE_STATES
|
|
61
|
+
>;
|
|
51
62
|
```
|
|
63
|
+
|
|
52
64
|
Next, we should define all the possible events and their payload.
|
|
53
65
|
|
|
54
66
|
```ts
|
|
@@ -58,7 +70,7 @@ type VendingMachineEvents = {
|
|
|
58
70
|
selectRedBull: {};
|
|
59
71
|
selectWater: {};
|
|
60
72
|
cancelTransaction: {};
|
|
61
|
-
}
|
|
73
|
+
};
|
|
62
74
|
```
|
|
63
75
|
|
|
64
76
|
Sometimes we need variables to keep track of certain attributes that can persists across different states; that's where the context comes along.
|
|
@@ -89,10 +101,19 @@ There's only one thing required to override in the abstract class which is the `
|
|
|
89
101
|
It's an object with the key being the event name and the value being the reaction and the default target state to transition to after the reaction.
|
|
90
102
|
|
|
91
103
|
The `EventReactions` looks like this:
|
|
104
|
+
|
|
92
105
|
```ts
|
|
93
|
-
export type EventReactions<
|
|
106
|
+
export type EventReactions<
|
|
107
|
+
EventPayloadMapping,
|
|
108
|
+
Context extends BaseContext,
|
|
109
|
+
States extends string,
|
|
110
|
+
> = {
|
|
94
111
|
[K in keyof Partial<EventPayloadMapping>]: {
|
|
95
|
-
action: (
|
|
112
|
+
action: (
|
|
113
|
+
context: Context,
|
|
114
|
+
event: EventPayloadMapping[K],
|
|
115
|
+
stateMachine: StateMachine<EventPayloadMapping, Context, States>
|
|
116
|
+
) => void;
|
|
96
117
|
defaultTargetState?: States;
|
|
97
118
|
};
|
|
98
119
|
};
|
|
@@ -105,138 +126,171 @@ Now let's implement the `IdleState`.
|
|
|
105
126
|
In the idle state, we only care about the `insertBills` event.
|
|
106
127
|
|
|
107
128
|
```ts
|
|
108
|
-
import {
|
|
109
|
-
|
|
110
|
-
class IdleState extends TemplateState<
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
129
|
+
import { EventReactions, TemplateState } from '@ue-too/being';
|
|
130
|
+
|
|
131
|
+
class IdleState extends TemplateState<
|
|
132
|
+
VendingMachineEvents,
|
|
133
|
+
BaseContext,
|
|
134
|
+
VendingMachineStates
|
|
135
|
+
> {
|
|
136
|
+
public eventReactions: EventReactions<
|
|
137
|
+
VendingMachineEvents,
|
|
138
|
+
BaseContext,
|
|
139
|
+
VendingMachineStates
|
|
140
|
+
> = {
|
|
141
|
+
insertBills: {
|
|
114
142
|
action: (context, event, stateMachine) => {
|
|
115
|
-
console.log(
|
|
143
|
+
console.log('inserted bills');
|
|
116
144
|
},
|
|
117
|
-
defaultTargetState:
|
|
145
|
+
defaultTargetState: 'ONE_DOLLAR_INSERTED',
|
|
118
146
|
},
|
|
119
|
-
}
|
|
147
|
+
};
|
|
120
148
|
}
|
|
121
149
|
```
|
|
122
150
|
|
|
123
151
|
After that we can implement the `OneDollarInsertedState`.
|
|
124
152
|
|
|
125
153
|
```ts
|
|
126
|
-
import {
|
|
127
|
-
|
|
128
|
-
class OneDollarInsertedState extends TemplateState<
|
|
129
|
-
|
|
130
|
-
|
|
154
|
+
import { EventReactions, TemplateState } from '@ue-too/being';
|
|
155
|
+
|
|
156
|
+
class OneDollarInsertedState extends TemplateState<
|
|
157
|
+
VendingMachineEvents,
|
|
158
|
+
BaseContext,
|
|
159
|
+
VendingMachineStates
|
|
160
|
+
> {
|
|
161
|
+
public eventReactions: EventReactions<
|
|
162
|
+
VendingMachineEvents,
|
|
163
|
+
BaseContext,
|
|
164
|
+
VendingMachineStates
|
|
165
|
+
> = {
|
|
166
|
+
insertBills: {
|
|
131
167
|
action: (context, event, stateMachine) => {
|
|
132
|
-
console.log(
|
|
168
|
+
console.log('inserted bills');
|
|
133
169
|
},
|
|
134
|
-
defaultTargetState:
|
|
170
|
+
defaultTargetState: 'TWO_DOLLARS_INSERTED',
|
|
135
171
|
},
|
|
136
|
-
|
|
172
|
+
selectCoke: {
|
|
137
173
|
action: (context, event, stateMachine) => {
|
|
138
|
-
console.log(
|
|
174
|
+
console.log('selected coke');
|
|
139
175
|
},
|
|
140
|
-
defaultTargetState:
|
|
176
|
+
defaultTargetState: 'IDLE',
|
|
141
177
|
},
|
|
142
|
-
|
|
178
|
+
selectRedBull: {
|
|
143
179
|
action: (context, event, stateMachine) => {
|
|
144
180
|
console.log('not enough money, 1 dollar short');
|
|
145
181
|
},
|
|
146
182
|
},
|
|
147
|
-
|
|
183
|
+
selectWater: {
|
|
148
184
|
action: (context, event, stateMachine) => {
|
|
149
185
|
console.log('not enough money, 2 dollars short');
|
|
150
186
|
},
|
|
151
187
|
},
|
|
152
|
-
|
|
188
|
+
cancelTransaction: {
|
|
153
189
|
action: (context, event, stateMachine) => {
|
|
154
190
|
console.log('cancelled transaction');
|
|
155
191
|
console.log('refunding 1 dollar');
|
|
156
192
|
},
|
|
157
|
-
defaultTargetState:
|
|
158
|
-
}
|
|
159
|
-
}
|
|
193
|
+
defaultTargetState: 'IDLE',
|
|
194
|
+
},
|
|
195
|
+
};
|
|
160
196
|
}
|
|
161
197
|
```
|
|
198
|
+
|
|
162
199
|
For the implementation of the `TwoDollarsInsertedState` and `ThreeDollarsInsertedState`, it's very similar to the `OneDollarInsertedState`.
|
|
163
200
|
|
|
164
201
|
```ts
|
|
165
|
-
import {
|
|
166
|
-
|
|
167
|
-
class TwoDollarsInsertedState extends TemplateState<
|
|
168
|
-
|
|
169
|
-
|
|
202
|
+
import { EventReactions, TemplateState } from '@ue-too/being';
|
|
203
|
+
|
|
204
|
+
class TwoDollarsInsertedState extends TemplateState<
|
|
205
|
+
VendingMachineEvents,
|
|
206
|
+
BaseContext,
|
|
207
|
+
VendingMachineStates
|
|
208
|
+
> {
|
|
209
|
+
public eventReactions: EventReactions<
|
|
210
|
+
VendingMachineEvents,
|
|
211
|
+
BaseContext,
|
|
212
|
+
VendingMachineStates
|
|
213
|
+
> = {
|
|
214
|
+
insertBills: {
|
|
170
215
|
action: (context, event, stateMachine) => {
|
|
171
|
-
console.log(
|
|
216
|
+
console.log('inserted bills');
|
|
172
217
|
},
|
|
173
|
-
defaultTargetState:
|
|
218
|
+
defaultTargetState: 'THREE_DOLLARS_INSERTED',
|
|
174
219
|
},
|
|
175
|
-
|
|
220
|
+
selectCoke: {
|
|
176
221
|
action: (context, event, stateMachine) => {
|
|
177
|
-
console.log(
|
|
222
|
+
console.log('selected coke');
|
|
178
223
|
},
|
|
179
|
-
defaultTargetState:
|
|
224
|
+
defaultTargetState: 'IDLE',
|
|
180
225
|
},
|
|
181
|
-
|
|
226
|
+
selectRedBull: {
|
|
182
227
|
action: (context, event, stateMachine) => {
|
|
183
228
|
console.log('selected red bull');
|
|
184
229
|
},
|
|
185
|
-
defaultTargetState:
|
|
230
|
+
defaultTargetState: 'IDLE',
|
|
186
231
|
},
|
|
187
|
-
|
|
232
|
+
selectWater: {
|
|
188
233
|
action: (context, event, stateMachine) => {
|
|
189
234
|
console.log('not enough money, 1 dollars short');
|
|
190
235
|
},
|
|
191
236
|
},
|
|
192
|
-
|
|
237
|
+
cancelTransaction: {
|
|
193
238
|
action: (context, event, stateMachine) => {
|
|
194
239
|
console.log('cancelled transaction');
|
|
195
240
|
console.log('refunding 2 dollars');
|
|
196
241
|
},
|
|
197
|
-
defaultTargetState:
|
|
198
|
-
}
|
|
199
|
-
}
|
|
242
|
+
defaultTargetState: 'IDLE',
|
|
243
|
+
},
|
|
244
|
+
};
|
|
200
245
|
}
|
|
201
246
|
|
|
202
|
-
class ThreeDollarsInsertedState extends TemplateState<
|
|
203
|
-
|
|
204
|
-
|
|
247
|
+
class ThreeDollarsInsertedState extends TemplateState<
|
|
248
|
+
VendingMachineEvents,
|
|
249
|
+
BaseContext,
|
|
250
|
+
VendingMachineStates
|
|
251
|
+
> {
|
|
252
|
+
public eventReactions: EventReactions<
|
|
253
|
+
VendingMachineEvents,
|
|
254
|
+
BaseContext,
|
|
255
|
+
VendingMachineStates
|
|
256
|
+
> = {
|
|
257
|
+
insertBills: {
|
|
205
258
|
action: (context, event, stateMachine) => {
|
|
206
259
|
console.log('not taking more bills');
|
|
207
260
|
console.log('returning the inserted bills');
|
|
208
261
|
},
|
|
209
262
|
},
|
|
210
|
-
|
|
263
|
+
selectCoke: {
|
|
211
264
|
action: (context, event, stateMachine) => {
|
|
212
|
-
console.log(
|
|
265
|
+
console.log('selected coke');
|
|
213
266
|
console.log('no change');
|
|
214
267
|
},
|
|
215
|
-
defaultTargetState:
|
|
268
|
+
defaultTargetState: 'IDLE',
|
|
216
269
|
},
|
|
217
|
-
|
|
270
|
+
selectRedBull: {
|
|
218
271
|
action: (context, event, stateMachine) => {
|
|
219
272
|
console.log('selected red bull');
|
|
220
273
|
console.log('change: 1 dollar');
|
|
221
274
|
},
|
|
222
|
-
defaultTargetState:
|
|
275
|
+
defaultTargetState: 'IDLE',
|
|
223
276
|
},
|
|
224
|
-
|
|
277
|
+
selectWater: {
|
|
225
278
|
action: (context, event, stateMachine) => {
|
|
226
279
|
console.log('selected water');
|
|
227
280
|
console.log('no change');
|
|
228
281
|
},
|
|
229
282
|
},
|
|
230
|
-
|
|
283
|
+
cancelTransaction: {
|
|
231
284
|
action: (context, event, stateMachine) => {
|
|
232
285
|
console.log('cancelled transaction');
|
|
233
286
|
console.log('refunding 3 dollars');
|
|
234
287
|
},
|
|
235
|
-
defaultTargetState:
|
|
236
|
-
}
|
|
237
|
-
}
|
|
288
|
+
defaultTargetState: 'IDLE',
|
|
289
|
+
},
|
|
290
|
+
};
|
|
238
291
|
}
|
|
239
292
|
```
|
|
293
|
+
|
|
240
294
|
With all the states implemented, we can now create the state machine.
|
|
241
295
|
|
|
242
296
|
```ts
|
|
@@ -261,4 +315,3 @@ vendingMachine.happens("selectCoke");
|
|
|
261
315
|
```
|
|
262
316
|
|
|
263
317
|
For the full complete example code, please refer to the `src/vending-machine-example.ts` file.
|
|
264
|
-
|
|
@@ -1,13 +1 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Example: Hierarchical State Machine POC
|
|
3
|
-
*
|
|
4
|
-
* @remarks
|
|
5
|
-
* This example demonstrates how to use hierarchical state machines with
|
|
6
|
-
* composite states that contain child state machines.
|
|
7
|
-
*
|
|
8
|
-
* Scenario: A media player with hierarchical states
|
|
9
|
-
* - Top level: IDLE, PLAYING, PAUSED
|
|
10
|
-
* - PLAYING contains: BUFFERING, STREAMING, ERROR
|
|
11
|
-
* - Events can be handled at child or parent level
|
|
12
|
-
*/
|
|
13
1
|
export declare function runHierarchicalStateMachineExample(): void;
|
package/hierarchical.d.ts
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
*
|
|
17
17
|
* @category Hierarchical State Machines
|
|
18
18
|
*/
|
|
19
|
-
import { BaseContext,
|
|
19
|
+
import { BaseContext, DefaultOutputMapping, EventArgs, EventResult, StateMachine, TemplateState, TemplateStateMachine } from './interface';
|
|
20
20
|
/**
|
|
21
21
|
* Represents a hierarchical state path using dot notation.
|
|
22
22
|
* Example: "PARENT.CHILD" means we're in CHILD state within PARENT state.
|
|
@@ -93,8 +93,8 @@ export declare abstract class CompositeState<EventPayloadMapping = any, Context
|
|
|
93
93
|
* Gets the full hierarchical path of the current state.
|
|
94
94
|
*/
|
|
95
95
|
getStatePath(parentState: ParentStates): HierarchicalStatePath<ParentStates, ChildStates>;
|
|
96
|
-
uponEnter(context: Context, stateMachine: StateMachine<EventPayloadMapping, Context, ParentStates, EventOutputMapping>, from: ParentStates |
|
|
97
|
-
beforeExit(context: Context, stateMachine: StateMachine<EventPayloadMapping, Context, ParentStates, EventOutputMapping>, to: ParentStates |
|
|
96
|
+
uponEnter(context: Context, stateMachine: StateMachine<EventPayloadMapping, Context, ParentStates, EventOutputMapping>, from: ParentStates | 'INITIAL'): void;
|
|
97
|
+
beforeExit(context: Context, stateMachine: StateMachine<EventPayloadMapping, Context, ParentStates, EventOutputMapping>, to: ParentStates | 'TERMINAL'): void;
|
|
98
98
|
handles<K extends keyof EventPayloadMapping | string>(args: EventArgs<EventPayloadMapping, K>, context: Context, stateMachine: StateMachine<EventPayloadMapping, Context, ParentStates, EventOutputMapping>): EventResult<ParentStates, K extends keyof EventOutputMapping ? EventOutputMapping[K] : void>;
|
|
99
99
|
}
|
|
100
100
|
/**
|
package/index.d.ts
CHANGED
|
@@ -108,6 +108,6 @@
|
|
|
108
108
|
* @see {@link TemplateStateMachine} for the main state machine class
|
|
109
109
|
* @see {@link TemplateState} for creating state implementations
|
|
110
110
|
*/
|
|
111
|
-
export * from
|
|
112
|
-
export * from
|
|
113
|
-
export * from
|
|
111
|
+
export * from './interface';
|
|
112
|
+
export * from './schema-factory';
|
|
113
|
+
export * from './hierarchical';
|