@ue-too/being 0.9.5 → 0.11.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 +11 -8
- package/index.d.ts +110 -0
- package/index.js +2 -104
- package/index.js.map +3 -3
- package/interface.d.ts +346 -70
- package/package.json +1 -1
- package/vending-machine-example.d.ts +1 -1
package/README.md
CHANGED
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
# being
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@ue-too/being)
|
|
4
|
+
[](https://github.com/ue-too/ue-too/blob/main/LICENSE.txt)
|
|
5
|
+
|
|
6
|
+
This is a library that helps with building finite state machines.
|
|
4
7
|
|
|
5
8
|
> Disclaimer: I am not an expert on finite state machines; this is just what I use and it works for me, and the features are tailored to what I need. You would probably be better off using a library like [xstate](https://stately.ai/docs).
|
|
6
9
|
|
|
7
10
|
If you still want to try it out, here is an example of how to use it:
|
|
8
11
|
|
|
9
|
-
Let's say we want to build a state machine for a vending machine.
|
|
12
|
+
Let's say we want to build a state machine for a vending machine.
|
|
10
13
|
|
|
11
14
|
To make it simple, the vending machine only accepts dollar bills and sells 3 types of items at $1, $2, and $3.
|
|
12
15
|
|
|
13
|
-
The items are:
|
|
16
|
+
The items are:
|
|
14
17
|
- Coke (1 dollar)
|
|
15
18
|
- Red Bull (2 dollars)
|
|
16
19
|
- Water (3 dollars)
|
|
@@ -46,7 +49,7 @@ const VENDING_MACHINE_STATES = ["IDLE", "ONE_DOLLAR_INSERTED", "TWO_DOLLARS_INSE
|
|
|
46
49
|
export type VendingMachineStates = CreateStateType<typeof VENDING_MACHINE_STATES>;
|
|
47
50
|
|
|
48
51
|
```
|
|
49
|
-
Next, we should define all the possible events and their payload.
|
|
52
|
+
Next, we should define all the possible events and their payload.
|
|
50
53
|
|
|
51
54
|
```ts
|
|
52
55
|
type VendingMachineEvents = {
|
|
@@ -60,7 +63,7 @@ type VendingMachineEvents = {
|
|
|
60
63
|
|
|
61
64
|
Sometimes we need variables to keep track of certain attributes that can persists across different states; that's where the context comes along.
|
|
62
65
|
|
|
63
|
-
For this example we don't need keep tabs on any attribute, so we can just use the `BaseContext` as our interface of context.
|
|
66
|
+
For this example we don't need keep tabs on any attribute, so we can just use the `BaseContext` as our interface of context.
|
|
64
67
|
|
|
65
68
|
The interface `State` and `StateMachine` only accepts context extending or implements the `BaseContext` to ensure that context has a `setup` and `cleanup` method.
|
|
66
69
|
|
|
@@ -73,9 +76,9 @@ const context: BaseContext = {
|
|
|
73
76
|
}
|
|
74
77
|
```
|
|
75
78
|
|
|
76
|
-
Next, we can start implementing the different states of the state machine.
|
|
79
|
+
Next, we can start implementing the different states of the state machine.
|
|
77
80
|
|
|
78
|
-
To do that we can use the `TemplateState` as a starting point.
|
|
81
|
+
To do that we can use the `TemplateState` as a starting point.
|
|
79
82
|
|
|
80
83
|
`TemplateState` is an abstract class that covers most of the boilerplate code. You just need to define the reaction corresponding to the events.
|
|
81
84
|
|
|
@@ -88,7 +91,7 @@ It's an object with the key being the event name and the value being the reactio
|
|
|
88
91
|
The `EventReactions` looks like this:
|
|
89
92
|
```ts
|
|
90
93
|
export type EventReactions<EventPayloadMapping, Context extends BaseContext, States extends string> = {
|
|
91
|
-
[K in keyof Partial<EventPayloadMapping>]: {
|
|
94
|
+
[K in keyof Partial<EventPayloadMapping>]: {
|
|
92
95
|
action: (context: Context, event: EventPayloadMapping[K], stateMachine: StateMachine<EventPayloadMapping, Context, States>) => void;
|
|
93
96
|
defaultTargetState?: States;
|
|
94
97
|
};
|
package/index.d.ts
CHANGED
|
@@ -1 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @packageDocumentation
|
|
3
|
+
* State machine library for TypeScript.
|
|
4
|
+
*
|
|
5
|
+
* @remarks
|
|
6
|
+
* The `@ue-too/being` package provides a type-safe, flexible finite state machine implementation
|
|
7
|
+
* for TypeScript. It enables you to model complex stateful behavior with clear state transitions,
|
|
8
|
+
* event handling, and conditional logic through guards.
|
|
9
|
+
*
|
|
10
|
+
* ## Core Concepts
|
|
11
|
+
*
|
|
12
|
+
* - **States**: Discrete modes that your system can be in (e.g., IDLE, ACTIVE, LOADING)
|
|
13
|
+
* - **Events**: Triggers that cause state transitions (e.g., "start", "stop", "load")
|
|
14
|
+
* - **Context**: Shared data that persists across state transitions
|
|
15
|
+
* - **Guards**: Conditional logic for dynamic state transitions
|
|
16
|
+
* - **Event Reactions**: Handlers that define what happens when an event occurs in a state
|
|
17
|
+
*
|
|
18
|
+
* ## Key Features
|
|
19
|
+
*
|
|
20
|
+
* - **Type Safety**: Full TypeScript type inference for events, payloads, and states
|
|
21
|
+
* - **Event Outputs**: Actions can return values accessible in event results
|
|
22
|
+
* - **Lifecycle Hooks**: `uponEnter` and `beforeExit` callbacks for state transitions
|
|
23
|
+
* - **Conditional Transitions**: Guards enable context-based routing to different states
|
|
24
|
+
* - **Flexible Architecture**: States only define handlers for relevant events
|
|
25
|
+
*
|
|
26
|
+
* ## Main Exports
|
|
27
|
+
*
|
|
28
|
+
* - {@link TemplateStateMachine}: Concrete state machine implementation
|
|
29
|
+
* - {@link TemplateState}: Abstract base class for creating states
|
|
30
|
+
* - {@link BaseContext}: Context interface for shared state data
|
|
31
|
+
* - {@link EventResult}: Type for event handling results
|
|
32
|
+
* - {@link createStateGuard}: Utility for creating type guards
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* Basic state machine
|
|
36
|
+
* ```typescript
|
|
37
|
+
* import { TemplateStateMachine, TemplateState, BaseContext } from '@ue-too/being';
|
|
38
|
+
*
|
|
39
|
+
* // Define events and payloads
|
|
40
|
+
* type Events = {
|
|
41
|
+
* start: {};
|
|
42
|
+
* stop: {};
|
|
43
|
+
* tick: { delta: number };
|
|
44
|
+
* };
|
|
45
|
+
*
|
|
46
|
+
* // Define states
|
|
47
|
+
* type States = "IDLE" | "RUNNING" | "PAUSED";
|
|
48
|
+
*
|
|
49
|
+
* // Define context
|
|
50
|
+
* interface TimerContext extends BaseContext {
|
|
51
|
+
* elapsed: number;
|
|
52
|
+
* setup() { this.elapsed = 0; }
|
|
53
|
+
* cleanup() {}
|
|
54
|
+
* }
|
|
55
|
+
*
|
|
56
|
+
* // Create state classes
|
|
57
|
+
* class IdleState extends TemplateState<Events, TimerContext, States> {
|
|
58
|
+
* eventReactions = {
|
|
59
|
+
* start: {
|
|
60
|
+
* action: (context) => {
|
|
61
|
+
* console.log('Starting timer');
|
|
62
|
+
* context.elapsed = 0;
|
|
63
|
+
* },
|
|
64
|
+
* defaultTargetState: "RUNNING"
|
|
65
|
+
* }
|
|
66
|
+
* };
|
|
67
|
+
* }
|
|
68
|
+
*
|
|
69
|
+
* class RunningState extends TemplateState<Events, TimerContext, States> {
|
|
70
|
+
* eventReactions = {
|
|
71
|
+
* tick: {
|
|
72
|
+
* action: (context, event) => {
|
|
73
|
+
* context.elapsed += event.delta;
|
|
74
|
+
* }
|
|
75
|
+
* },
|
|
76
|
+
* stop: {
|
|
77
|
+
* action: (context) => {
|
|
78
|
+
* console.log('Stopped at:', context.elapsed);
|
|
79
|
+
* },
|
|
80
|
+
* defaultTargetState: "IDLE"
|
|
81
|
+
* }
|
|
82
|
+
* };
|
|
83
|
+
* }
|
|
84
|
+
*
|
|
85
|
+
* // Create and use the state machine
|
|
86
|
+
* const context: TimerContext = {
|
|
87
|
+
* elapsed: 0,
|
|
88
|
+
* setup() { this.elapsed = 0; },
|
|
89
|
+
* cleanup() {}
|
|
90
|
+
* };
|
|
91
|
+
*
|
|
92
|
+
* const timer = new TemplateStateMachine<Events, TimerContext, States>(
|
|
93
|
+
* {
|
|
94
|
+
* IDLE: new IdleState(),
|
|
95
|
+
* RUNNING: new RunningState(),
|
|
96
|
+
* PAUSED: new PausedState()
|
|
97
|
+
* },
|
|
98
|
+
* "IDLE",
|
|
99
|
+
* context
|
|
100
|
+
* );
|
|
101
|
+
*
|
|
102
|
+
* // Trigger events
|
|
103
|
+
* timer.happens("start");
|
|
104
|
+
* timer.happens("tick", { delta: 16 });
|
|
105
|
+
* timer.happens("stop");
|
|
106
|
+
* ```
|
|
107
|
+
*
|
|
108
|
+
* @see {@link TemplateStateMachine} for the main state machine class
|
|
109
|
+
* @see {@link TemplateState} for creating state implementations
|
|
110
|
+
*/
|
|
1
111
|
export * from "./interface";
|
package/index.js
CHANGED
|
@@ -1,105 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
var NO_OP = () => {};
|
|
1
|
+
var x=()=>{};class d{_currentState;_states;_context;_statesArray;_stateChangeCallbacks;_happensCallbacks;_timeouts=void 0;constructor(e,n,t){this._states=e,this._currentState=n,this._context=t,this._statesArray=Object.keys(e),this._stateChangeCallbacks=[],this._happensCallbacks=[],this._states[n].uponEnter(t,this,n)}switchTo(e){this._currentState=e}happens(...e){if(this._timeouts)clearTimeout(this._timeouts);this._happensCallbacks.forEach((t)=>t(e,this._context));let n=this._states[this._currentState].handles(e,this._context,this);if(n.handled&&n.nextState!==void 0&&n.nextState!==this._currentState){let t=this._currentState;this._states[this._currentState].beforeExit(this._context,this,n.nextState),this.switchTo(n.nextState),this._states[this._currentState].uponEnter(this._context,this,t),this._stateChangeCallbacks.forEach((o)=>o(t,this._currentState))}return n}onStateChange(e){this._stateChangeCallbacks.push(e)}onHappens(e){this._happensCallbacks.push(e)}get currentState(){return this._currentState}setContext(e){this._context=e}get possibleStates(){return this._statesArray}get states(){return this._states}}class c{_guards={};_eventGuards={};_delay=void 0;get guards(){return this._guards}get eventGuards(){return this._eventGuards}get delay(){return this._delay}uponEnter(e,n,t){}beforeExit(e,n,t){}handles(e,n,t){let o=e[0],r=e[1];if(this.eventReactions[o]){let s=this.eventReactions[o].action(n,r,t),a=this.eventReactions[o].defaultTargetState,m=this._eventGuards[o];if(m){let p=m.find((i)=>{if(this.guards[i.guard])return this.guards[i.guard](n);return!1});return p?{handled:!0,nextState:p.target,output:s}:{handled:!0,nextState:a,output:s}}return{handled:!0,nextState:a,output:s}}return{handled:!1}}}function l(e){return(n)=>e.includes(n)}export{l as createStateGuard,d as TemplateStateMachine,c as TemplateState,x as NO_OP};
|
|
3
2
|
|
|
4
|
-
|
|
5
|
-
_currentState;
|
|
6
|
-
_states;
|
|
7
|
-
_context;
|
|
8
|
-
_statesArray;
|
|
9
|
-
_stateChangeCallbacks;
|
|
10
|
-
_happensCallbacks;
|
|
11
|
-
_timeouts = undefined;
|
|
12
|
-
constructor(states, initialState, context) {
|
|
13
|
-
this._states = states;
|
|
14
|
-
this._currentState = initialState;
|
|
15
|
-
this._context = context;
|
|
16
|
-
this._statesArray = Object.keys(states);
|
|
17
|
-
this._stateChangeCallbacks = [];
|
|
18
|
-
this._happensCallbacks = [];
|
|
19
|
-
this._states[initialState].uponEnter(context, this, initialState);
|
|
20
|
-
}
|
|
21
|
-
switchTo(state) {
|
|
22
|
-
this._currentState = state;
|
|
23
|
-
}
|
|
24
|
-
happens(...args) {
|
|
25
|
-
if (this._timeouts) {
|
|
26
|
-
clearTimeout(this._timeouts);
|
|
27
|
-
}
|
|
28
|
-
this._happensCallbacks.forEach((callback) => callback(args, this._context));
|
|
29
|
-
const result = this._states[this._currentState].handles(args, this._context, this);
|
|
30
|
-
if (result.handled && result.nextState !== undefined && result.nextState !== this._currentState) {
|
|
31
|
-
const originalState = this._currentState;
|
|
32
|
-
this._states[this._currentState].beforeExit(this._context, this, result.nextState);
|
|
33
|
-
this.switchTo(result.nextState);
|
|
34
|
-
this._states[this._currentState].uponEnter(this._context, this, originalState);
|
|
35
|
-
this._stateChangeCallbacks.forEach((callback) => callback(originalState, this._currentState));
|
|
36
|
-
}
|
|
37
|
-
return result;
|
|
38
|
-
}
|
|
39
|
-
onStateChange(callback) {
|
|
40
|
-
this._stateChangeCallbacks.push(callback);
|
|
41
|
-
}
|
|
42
|
-
onHappens(callback) {
|
|
43
|
-
this._happensCallbacks.push(callback);
|
|
44
|
-
}
|
|
45
|
-
get currentState() {
|
|
46
|
-
return this._currentState;
|
|
47
|
-
}
|
|
48
|
-
setContext(context) {
|
|
49
|
-
this._context = context;
|
|
50
|
-
}
|
|
51
|
-
get possibleStates() {
|
|
52
|
-
return this._statesArray;
|
|
53
|
-
}
|
|
54
|
-
get states() {
|
|
55
|
-
return this._states;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
class TemplateState {
|
|
60
|
-
_guards = {};
|
|
61
|
-
_eventGuards = {};
|
|
62
|
-
_delay = undefined;
|
|
63
|
-
get guards() {
|
|
64
|
-
return this._guards;
|
|
65
|
-
}
|
|
66
|
-
get eventGuards() {
|
|
67
|
-
return this._eventGuards;
|
|
68
|
-
}
|
|
69
|
-
get delay() {
|
|
70
|
-
return this._delay;
|
|
71
|
-
}
|
|
72
|
-
uponEnter(context, stateMachine, from) {}
|
|
73
|
-
beforeExit(context, stateMachine, to) {}
|
|
74
|
-
handles(args, context, stateMachine) {
|
|
75
|
-
const eventKey = args[0];
|
|
76
|
-
const eventPayload = args[1];
|
|
77
|
-
if (this.eventReactions[eventKey]) {
|
|
78
|
-
this.eventReactions[eventKey].action(context, eventPayload, stateMachine);
|
|
79
|
-
const targetState = this.eventReactions[eventKey].defaultTargetState;
|
|
80
|
-
const guardToEvaluate = this._eventGuards[eventKey];
|
|
81
|
-
if (guardToEvaluate) {
|
|
82
|
-
const target = guardToEvaluate.find((guard) => {
|
|
83
|
-
if (this.guards[guard.guard]) {
|
|
84
|
-
return this.guards[guard.guard](context);
|
|
85
|
-
}
|
|
86
|
-
return false;
|
|
87
|
-
});
|
|
88
|
-
return target ? { handled: true, nextState: target.target } : { handled: true, nextState: targetState };
|
|
89
|
-
}
|
|
90
|
-
return { handled: true, nextState: targetState };
|
|
91
|
-
}
|
|
92
|
-
return { handled: false };
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
function createStateGuard(set) {
|
|
96
|
-
return (s) => set.includes(s);
|
|
97
|
-
}
|
|
98
|
-
export {
|
|
99
|
-
createStateGuard,
|
|
100
|
-
TemplateStateMachine,
|
|
101
|
-
TemplateState,
|
|
102
|
-
NO_OP
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
//# debugId=85B5A2C72A8BC3ED64756E2164756E21
|
|
3
|
+
//# debugId=391A71F13DC7D12B64756E2164756E21
|
package/index.js.map
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/interface.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"export interface BaseContext {\n setup(): void;\n cleanup(): void;\n}\n\ntype NOOP = () => void;\n\ntype IsEmptyObject<T> = T extends {} ? {} extends T ? true : false : false;\n\n/**\n * @description Utility type to derive a string literal union from a readonly array of string literals.\n * \n * Example:\n * ```ts\n * const TEST_STATES = [\"one\", \"two\", \"three\"] as const;\n * type TestStates = CreateStateType<typeof TEST_STATES>; // \"one\" | \"two\" | \"three\"\n * ```\n */\nexport type CreateStateType<ArrayLiteral extends readonly string[]> = ArrayLiteral[number];\n\nexport type EventArgs<EventPayloadMapping, K> = \n K extends keyof EventPayloadMapping\n ? IsEmptyObject<EventPayloadMapping[K]> extends true\n ? [event: K] // No payload needed\n : [event: K, payload: EventPayloadMapping[K]] // Payload required\n : [event: K, payload?: unknown]; // Unknown events\n\nexport const NO_OP: NOOP = ()=>{};\n\nexport type EventNotHandled = {\n handled: false;\n}\n\nexport type EventHandled<States extends string> = {\n handled: true;\n nextState?: States;\n}\n\nexport type EventHandledResult<States extends string> = EventNotHandled | EventHandled<States>;\n\n/**\n * @description This is the interface for the state machine. The interface takes in a few generic parameters.\n * \n * Generic parameters:\n * - EventPayloadMapping: A mapping of events to their payloads.\n * - Context: The context of the state machine. (which can be used by each state to do calculations that would persist across states)\n * - States: All of the possible states that the state machine can be in. e.g. a string literal union like \"IDLE\" | \"SELECTING\" | \"PAN\" | \"ZOOM\"\n * \n * You can probably get by using the TemplateStateMachine class.\n * The naming is that an event would \"happen\" and the state of the state machine would \"handle\" it.\n *\n * @see {@link TemplateStateMachine}\n * @see {@link KmtInputStateMachine}\n * \n * @category being\n */\nexport interface StateMachine<EventPayloadMapping, Context extends BaseContext, States extends string = 'IDLE'> {\n switchTo(state: States): void;\n // Overload for known events - provides IntelliSense\n happens<K extends keyof EventPayloadMapping>(\n ...args: EventArgs<EventPayloadMapping, K>\n ): EventHandledResult<States>;\n // Overload for unknown events - maintains backward compatibility\n happens<K extends string>(\n ...args: EventArgs<EventPayloadMapping, K>\n ): EventHandledResult<States>;\n setContext(context: Context): void;\n states: Record<States, State<EventPayloadMapping, Context, string extends States ? string : States>>;\n onStateChange(callback: StateChangeCallback<States>): void;\n possibleStates: States[];\n onHappens(callback: (args: EventArgs<EventPayloadMapping, keyof EventPayloadMapping | string>, context: Context) => void): void;\n}\n\n/**\n * @description This is the type for the callback that is called when the state changes.\n *\n * @category being\n */\nexport type StateChangeCallback<States extends string = 'IDLE'> = (currentState: States, nextState: States) => void;\n\n/**\n * @description This is the interface for the state. The interface takes in a few generic parameters:\n * You can probably get by extending the TemplateState class. \n *\n * Generic parameters:\n * - EventPayloadMapping: A mapping of events to their payloads.\n * - Context: The context of the state machine. (which can be used by each state to do calculations that would persist across states)\n * - States: All of the possible states that the state machine can be in. e.g. a string literal union like \"IDLE\" | \"SELECTING\" | \"PAN\" | \"ZOOM\"\n * \n * A state's all possible states can be only a subset of the possible states of the state machine. (a state only needs to know what states it can transition to)\n * This allows for a state to be reusable across different state machines.\n *\n * @see {@link TemplateState}\n * \n * @category being\n */\nexport interface State<EventPayloadMapping, Context extends BaseContext, States extends string = 'IDLE'> { \n uponEnter(context: Context, stateMachine: StateMachine<EventPayloadMapping, Context, States>, from: States): void;\n beforeExit(context: Context, stateMachine: StateMachine<EventPayloadMapping, Context, States>, to: States): void;\n handles<K extends (keyof EventPayloadMapping | string)>(args: EventArgs<EventPayloadMapping, K>, context: Context, stateMachine: StateMachine<EventPayloadMapping, Context, States>): EventHandledResult<States>;\n eventReactions: EventReactions<EventPayloadMapping, Context, States>;\n guards: Guard<Context>;\n eventGuards: Partial<EventGuards<EventPayloadMapping, States, Context, Guard<Context>>>;\n delay: Delay<Context, EventPayloadMapping, States> | undefined;\n}\n\n/**\n * @description This is the type for the event reactions of a state.\n * \n * Generic parameters:\n * - EventPayloadMapping: A mapping of events to their payloads.\n * - Context: The context of the state machine. (which can be used by each state to do calculations that would persist across states)\n * - States: All of the possible states that the state machine can be in. e.g. a string literal union like \"IDLE\" | \"SELECTING\" | \"PAN\" | \"ZOOM\"\n * \n * @category being\n */\nexport type EventReactions<EventPayloadMapping, Context extends BaseContext, States extends string> = {\n [K in keyof Partial<EventPayloadMapping>]: { \n action: (context: Context, event: EventPayloadMapping[K], stateMachine: StateMachine<EventPayloadMapping, Context, States>) => void;\n defaultTargetState?: States;\n };\n};\n\n/**\n * @description This is the type for the guard evaluation when a state transition is happening.\n * \n * Guard evaluations are evaluated after the state has handled the event with the action.\n * Guard evaluations can be defined in an array and the first guard that evaluates to true will be used to determine the next state.\n * \n * Generic parameters:\n * - Context: The context of the state machine. (which can be used by each state to do calculations that would persist across states)\n * \n * @category being\n */\nexport type GuardEvaluation<Context extends BaseContext> = (context: Context) => boolean;\n\n/**\n * @description This is the type for the guard of a state.\n * \n * guard is an object that maps a key to a guard evaluation.\n * K is all the possible keys that can be used to evaluate the guard.\n * K is optional but if it is not provided, typescript won't be able to type guard in the EventGuards type.\n * \n * @category being\n */\nexport type Guard<Context extends BaseContext, K extends string = string> = {\n [P in K]: GuardEvaluation<Context>;\n}\n\nexport type Action<Context extends BaseContext, EventPayloadMapping, States extends string> = {\n action: (context: Context, event: EventPayloadMapping[keyof EventPayloadMapping], stateMachine: StateMachine<EventPayloadMapping, Context, States>) => void;\n defaultTargetState?: States;\n}\n\nexport type Delay<Context extends BaseContext, EventPayloadMapping, States extends string> = {\n time: number;\n action: Action<Context, EventPayloadMapping, States>;\n}\n\n/**\n * @description This is a mapping of a guard to a target state.\n * \n * Generic parameters:\n * - Context: The context of the state machine. (which can be used by each state to do calculations that would persist across states)\n * - G: The guard type.\n * - States: All of the possible states that the state machine can be in. e.g. a string literal union like \"IDLE\" | \"SELECTING\" | \"PAN\" | \"ZOOM\"\n * \n * You probably don't need to use this type directly.\n * \n * @see {@link TemplateState['eventGuards']}\n * \n * @category being\n */\nexport type GuardMapping<Context extends BaseContext, G, States extends string> = {\n guard: G extends Guard<Context, infer K> ? K : never;\n target: States;\n}\n\n/**\n * @description This is a mapping of an event to a guard evaluation.\n * \n * Generic parameters:\n * - EventPayloadMapping: A mapping of events to their payloads.\n * - States: All of the possible states that the state machine can be in. e.g. a string literal union like \"IDLE\" | \"SELECTING\" | \"PAN\" | \"ZOOM\"\n * - Context: The context of the state machine. (which can be used by each state to do calculations that would persist across states)\n * - T: The guard type.\n * \n * You probably don't need to use this type directly.\n * This is a mapping of an event to a guard evaluation.\n * \n * @see {@link TemplateState['eventGuards']}\n * \n * @category being\n */\nexport type EventGuards<EventPayloadMapping, States extends string, Context extends BaseContext, T extends Guard<Context>> = {\n [K in keyof EventPayloadMapping]: GuardMapping<Context, T, States>[];\n}\n\n/**\n * @description This is the template for the state machine.\n * \n * You can use this class to create a state machine. Usually this is all you need for the state machine. Unless you need extra functionality.\n * To create a state machine, just instantiate this class and pass in the states, initial state and context.\n * \n * @see {@link createKmtInputStateMachine} for an example of how to create a state machine.\n * \n * @category being\n */\nexport class TemplateStateMachine<EventPayloadMapping, Context extends BaseContext, States extends string = 'IDLE'> implements StateMachine<EventPayloadMapping, Context, States> {\n\n protected _currentState: States;\n protected _states: Record<States, State<EventPayloadMapping, Context, States>>;\n protected _context: Context;\n protected _statesArray: States[];\n protected _stateChangeCallbacks: StateChangeCallback<States>[];\n protected _happensCallbacks: ((args: EventArgs<EventPayloadMapping, keyof EventPayloadMapping | string>, context: Context) => void)[];\n protected _timeouts: ReturnType<typeof setTimeout> | undefined = undefined;\n\n constructor(states: Record<States, State<EventPayloadMapping, Context, States>>, initialState: States, context: Context){\n this._states = states;\n this._currentState = initialState;\n this._context = context;\n this._statesArray = Object.keys(states) as States[];\n this._stateChangeCallbacks = [];\n this._happensCallbacks = [];\n this._states[initialState].uponEnter(context, this, initialState);\n }\n\n switchTo(state: States): void {\n this._currentState = state;\n }\n \n // Implementation signature - matches both overloads\n happens<K extends keyof EventPayloadMapping>(...args: EventArgs<EventPayloadMapping, K>): EventHandledResult<States>;\n happens<K extends string>(...args: EventArgs<EventPayloadMapping, K>): EventHandledResult<States>;\n happens<K extends keyof EventPayloadMapping | string>(...args: EventArgs<EventPayloadMapping, K>): EventHandledResult<States> {\n if(this._timeouts){\n clearTimeout(this._timeouts);\n }\n this._happensCallbacks.forEach(callback => callback(args, this._context));\n const result = this._states[this._currentState].handles(args, this._context, this);\n if(result.handled && result.nextState !== undefined && result.nextState !== this._currentState){ // TODO: whether or not to transition to the same state (currently no) (uponEnter and beforeExit will still be called if the state is the same)\n const originalState = this._currentState;\n this._states[this._currentState].beforeExit(this._context, this, result.nextState);\n this.switchTo(result.nextState);\n this._states[this._currentState].uponEnter(this._context, this, originalState);\n this._stateChangeCallbacks.forEach(callback => callback(originalState, this._currentState));\n }\n return result;\n }\n\n onStateChange(callback: StateChangeCallback<States>): void {\n this._stateChangeCallbacks.push(callback);\n }\n\n onHappens(callback: (args: EventArgs<EventPayloadMapping, keyof EventPayloadMapping | string>, context: Context) => void): void {\n this._happensCallbacks.push(callback);\n }\n\n get currentState(): States {\n return this._currentState;\n }\n\n setContext(context: Context): void {\n this._context = context;\n }\n\n get possibleStates(): States[] {\n return this._statesArray;\n }\n\n get states(): Record<States, State<EventPayloadMapping, Context, States>> {\n return this._states;\n }\n}\n/**\n * @description This is the template for the state.\n * \n * This is a base template that you can extend to create a state.\n * Unlike the TemplateStateMachine, this class is abstract. You need to implement the specific methods that you need.\n * The core part off the state is the event reactions in which you would define how to handle each event in a state.\n * You can define an eventReactions object that maps only the events that you need. If this state does not need to handle a specific event, you can just not define it in the eventReactions object.\n * \n * @category being\n */\nexport abstract class TemplateState<EventPayloadMapping, Context extends BaseContext, States extends string = 'IDLE'> implements State<EventPayloadMapping, Context, States> {\n\n public abstract eventReactions: EventReactions<EventPayloadMapping, Context, States>;\n protected _guards: Guard<Context> = {} as Guard<Context>;\n protected _eventGuards: Partial<EventGuards<EventPayloadMapping, States, Context, Guard<Context>>> = {} as Partial<EventGuards<EventPayloadMapping, States, Context, Guard<Context>>>;\n protected _delay: Delay<Context, EventPayloadMapping, States> | undefined = undefined;\n\n get guards(): Guard<Context> {\n return this._guards;\n }\n\n get eventGuards(): Partial<EventGuards<EventPayloadMapping, States, Context, Guard<Context>>> {\n return this._eventGuards;\n }\n\n get delay(): Delay<Context, EventPayloadMapping, States> | undefined {\n return this._delay;\n }\n\n uponEnter(context: Context, stateMachine: StateMachine<EventPayloadMapping, Context, States>, from: States): void {\n // console.log(\"enter\");\n }\n\n beforeExit(context: Context, stateMachine: StateMachine<EventPayloadMapping, Context, States>, to: States): void {\n // console.log('leave');\n }\n\n handles<K extends (keyof EventPayloadMapping | string)>(args: EventArgs<EventPayloadMapping, K>, context: Context, stateMachine: StateMachine<EventPayloadMapping, Context, States>): EventHandledResult<States>{\n const eventKey = args[0] as keyof EventPayloadMapping;\n const eventPayload = args[1] as EventPayloadMapping[keyof EventPayloadMapping];\n if (this.eventReactions[eventKey]) {\n this.eventReactions[eventKey].action(context, eventPayload, stateMachine);\n const targetState = this.eventReactions[eventKey].defaultTargetState;\n const guardToEvaluate = this._eventGuards[eventKey];\n if(guardToEvaluate){\n const target = guardToEvaluate.find((guard)=>{\n if(this.guards[guard.guard]){\n return this.guards[guard.guard](context);\n }\n return false;\n });\n return target ? {handled: true, nextState: target.target} : {handled: true, nextState: targetState};\n }\n return {handled: true, nextState: targetState};\n }\n return {handled: false};\n }\n}\n\n\n/**\n * Example usage\n * ```ts\n type TestSubStates = \"subOne\" | \"subTwo\" | \"subThree\";\n const TEST_STATES = [\"one\", \"two\", \"three\"] as const;\n type TestStates = CreateStateType<typeof TEST_STATES>;\n type AllStates = TestStates | TestSubStates;\n\n const isTestState = createStateGuard(TEST_STATES);\n\nfunction test(s: AllStates) {\n\tif (isTestState(s)) {\n\t\t// s: TestStates\n }\n}\n * ```\n\n * @param set \n * @returns \n */\n\nexport function createStateGuard<T extends string>(set: readonly T[]) {\n return (s: string): s is T => set.includes(s as T);\n}\n"
|
|
5
|
+
"/**\n * Base context interface for state machines.\n *\n * @remarks\n * The context is shared across all states in a state machine and can be used to store data\n * that persists between state transitions. All custom contexts must extend this interface.\n *\n * The setup and cleanup methods provide lifecycle hooks for resource management:\n * - `setup()`: Called when the context is initialized\n * - `cleanup()`: Called when the context is destroyed\n *\n * @example\n * ```typescript\n * interface MyContext extends BaseContext {\n * counter: number;\n * data: string[];\n * setup() {\n * this.counter = 0;\n * this.data = [];\n * }\n * cleanup() {\n * this.data = [];\n * }\n * }\n * ```\n *\n * @category Core\n */\nexport interface BaseContext {\n setup(): void;\n cleanup(): void;\n}\n\ntype NOOP = () => void;\n\n/**\n * Utility type to check if an object type is empty.\n * @internal\n */\ntype IsEmptyObject<T> = T extends {} ? {} extends T ? true : false : false;\n\n/**\n * Utility type to derive a string literal union from a readonly array of string literals.\n *\n * @remarks\n * This helper type extracts the element types from a readonly array to create a union type.\n * Useful for defining state machine states from an array.\n *\n * @example\n * ```typescript\n * const TEST_STATES = [\"one\", \"two\", \"three\"] as const;\n * type TestStates = CreateStateType<typeof TEST_STATES>; // \"one\" | \"two\" | \"three\"\n * ```\n *\n * @category Utilities\n */\nexport type CreateStateType<ArrayLiteral extends readonly string[]> = ArrayLiteral[number];\n\n/**\n * Type for event arguments with conditional payload requirement.\n *\n * @remarks\n * This utility type determines whether an event requires a payload argument based on the\n * event payload mapping. If the payload is an empty object, no payload is required.\n *\n * @typeParam EventPayloadMapping - Mapping of event names to their payload types\n * @typeParam K - The event key\n *\n * @category Utilities\n */\nexport type EventArgs<EventPayloadMapping, K> =\n K extends keyof EventPayloadMapping\n ? IsEmptyObject<EventPayloadMapping[K]> extends true\n ? [event: K] // No payload needed\n : [event: K, payload: EventPayloadMapping[K]] // Payload required\n : [event: K, payload?: unknown]; // Unknown events\n\n/**\n * No-operation function constant used as a placeholder for optional actions.\n *\n * @remarks\n * Use this when you need to provide a function but don't want it to do anything,\n * such as for default state transition actions that have no side effects.\n *\n * @category Core\n */\nexport const NO_OP: NOOP = ()=>{};\n\n/**\n * Result type indicating an event was not handled by the current state.\n *\n * @remarks\n * When a state doesn't have a handler defined for a particular event, it returns this type.\n * The state machine will not transition and the event is effectively ignored.\n *\n * @category Core\n */\nexport type EventNotHandled = {\n handled: false;\n}\n\n/**\n * Result type when an event is successfully handled by a state.\n *\n * @remarks\n * This type represents a successful event handling result. It can optionally include:\n * - `nextState`: The state to transition to (if different from current)\n * - `output`: A return value from the event handler\n *\n * @typeParam States - Union of all possible state names in the state machine\n * @typeParam Output - The output type for this event (defaults to void)\n *\n * @example\n * ```typescript\n * // Simple transition without output\n * const result: EventHandled<\"IDLE\" | \"ACTIVE\"> = {\n * handled: true,\n * nextState: \"ACTIVE\"\n * };\n *\n * // With output value\n * const resultWithOutput: EventHandled<\"IDLE\" | \"ACTIVE\", number> = {\n * handled: true,\n * nextState: \"IDLE\",\n * output: 42\n * };\n * ```\n *\n * @category Core\n */\nexport type EventHandled<States extends string, Output = void> = {\n handled: true;\n nextState?: States;\n output?: Output;\n}\n\n/**\n * Discriminated union representing the result of event handling.\n *\n * @remarks\n * Every event handler returns an EventResult, which is either:\n * - {@link EventHandled}: The event was processed successfully\n * - {@link EventNotHandled}: The event was not recognized/handled\n *\n * Use the `handled` discriminant to narrow the type in TypeScript.\n *\n * @typeParam States - Union of all possible state names\n * @typeParam Output - The output type for handled events\n *\n * @category Core\n */\nexport type EventResult<States extends string, Output = void> = EventNotHandled | EventHandled<States, Output>;\n\n/**\n * @description A default output mapping that maps all events to void.\n * Used as default when no output mapping is provided.\n * \n * @category Types\n */\nexport type DefaultOutputMapping<EventPayloadMapping> = {\n [K in keyof EventPayloadMapping]: void;\n};\n\n/**\n * @description This is the interface for the state machine. The interface takes in a few generic parameters.\n * \n * Generic parameters:\n * - EventPayloadMapping: A mapping of events to their payloads.\n * - Context: The context of the state machine. (which can be used by each state to do calculations that would persist across states)\n * - States: All of the possible states that the state machine can be in. e.g. a string literal union like \"IDLE\" | \"SELECTING\" | \"PAN\" | \"ZOOM\"\n * - EventOutputMapping: A mapping of events to their output types. Defaults to void for all events.\n * \n * You can probably get by using the TemplateStateMachine class.\n * The naming is that an event would \"happen\" and the state of the state machine would \"handle\" it.\n *\n * @see {@link TemplateStateMachine}\n * @see {@link KmtInputStateMachine}\n * \n * @category Types\n */\nexport interface StateMachine<\n EventPayloadMapping, \n Context extends BaseContext, \n States extends string = 'IDLE',\n EventOutputMapping extends Partial<Record<keyof EventPayloadMapping, unknown>> = DefaultOutputMapping<EventPayloadMapping>\n> {\n switchTo(state: States): void;\n // Overload for known events - provides IntelliSense with typed output\n happens<K extends keyof EventPayloadMapping>(\n ...args: EventArgs<EventPayloadMapping, K>\n ): EventResult<States, K extends keyof EventOutputMapping ? EventOutputMapping[K] : void>;\n // Overload for unknown events - maintains backward compatibility\n happens<K extends string>(\n ...args: EventArgs<EventPayloadMapping, K>\n ): EventResult<States, unknown>;\n setContext(context: Context): void;\n states: Record<States, State<EventPayloadMapping, Context, string extends States ? string : States, EventOutputMapping>>;\n onStateChange(callback: StateChangeCallback<States>): void;\n possibleStates: States[];\n onHappens(callback: (args: EventArgs<EventPayloadMapping, keyof EventPayloadMapping | string>, context: Context) => void): void;\n}\n\n/**\n * @description This is the type for the callback that is called when the state changes.\n *\n * @category Types\n */\nexport type StateChangeCallback<States extends string = 'IDLE'> = (currentState: States, nextState: States) => void;\n\n/**\n * @description This is the interface for the state. The interface takes in a few generic parameters:\n * You can probably get by extending the TemplateState class. \n *\n * Generic parameters:\n * - EventPayloadMapping: A mapping of events to their payloads.\n * - Context: The context of the state machine. (which can be used by each state to do calculations that would persist across states)\n * - States: All of the possible states that the state machine can be in. e.g. a string literal union like \"IDLE\" | \"SELECTING\" | \"PAN\" | \"ZOOM\"\n * - EventOutputMapping: A mapping of events to their output types. Defaults to void for all events.\n * \n * A state's all possible states can be only a subset of the possible states of the state machine. (a state only needs to know what states it can transition to)\n * This allows for a state to be reusable across different state machines.\n *\n * @see {@link TemplateState}\n * \n * @category Types\n */\nexport interface State<\n EventPayloadMapping, \n Context extends BaseContext, \n States extends string = 'IDLE',\n EventOutputMapping extends Partial<Record<keyof EventPayloadMapping, unknown>> = DefaultOutputMapping<EventPayloadMapping>\n> { \n uponEnter(context: Context, stateMachine: StateMachine<EventPayloadMapping, Context, States, EventOutputMapping>, from: States): void;\n beforeExit(context: Context, stateMachine: StateMachine<EventPayloadMapping, Context, States, EventOutputMapping>, to: States): void;\n handles<K extends (keyof EventPayloadMapping | string)>(args: EventArgs<EventPayloadMapping, K>, context: Context, stateMachine: StateMachine<EventPayloadMapping, Context, States, EventOutputMapping>): EventResult<States, K extends keyof EventOutputMapping ? EventOutputMapping[K] : void>;\n eventReactions: EventReactions<EventPayloadMapping, Context, States, EventOutputMapping>;\n guards: Guard<Context>;\n eventGuards: Partial<EventGuards<EventPayloadMapping, States, Context, Guard<Context>>>;\n delay: Delay<Context, EventPayloadMapping, States, EventOutputMapping> | undefined;\n}\n\n/**\n * @description This is the type for the event reactions of a state.\n * \n * Generic parameters:\n * - EventPayloadMapping: A mapping of events to their payloads.\n * - Context: The context of the state machine. (which can be used by each state to do calculations that would persist across states)\n * - States: All of the possible states that the state machine can be in. e.g. a string literal union like \"IDLE\" | \"SELECTING\" | \"PAN\" | \"ZOOM\"\n * - EventOutputMapping: A mapping of events to their output types. Defaults to void for all events.\n * \n * The action function can now return an output value that will be included in the EventHandledResult.\n * \n * @category Types\n */\nexport type EventReactions<\n EventPayloadMapping, \n Context extends BaseContext, \n States extends string,\n EventOutputMapping extends Partial<Record<keyof EventPayloadMapping, unknown>> = DefaultOutputMapping<EventPayloadMapping>\n> = {\n [K in keyof Partial<EventPayloadMapping>]: { \n action: (\n context: Context, \n event: EventPayloadMapping[K], \n stateMachine: StateMachine<EventPayloadMapping, Context, States, EventOutputMapping>\n ) => K extends keyof EventOutputMapping ? (EventOutputMapping[K] | void) : void;\n defaultTargetState?: States;\n };\n};\n\n/**\n * @description This is the type for the guard evaluation when a state transition is happening.\n * \n * Guard evaluations are evaluated after the state has handled the event with the action.\n * Guard evaluations can be defined in an array and the first guard that evaluates to true will be used to determine the next state.\n * \n * Generic parameters:\n * - Context: The context of the state machine. (which can be used by each state to do calculations that would persist across states)\n * \n * @category Types\n */\nexport type GuardEvaluation<Context extends BaseContext> = (context: Context) => boolean;\n\n/**\n * @description This is the type for the guard of a state.\n * \n * guard is an object that maps a key to a guard evaluation.\n * K is all the possible keys that can be used to evaluate the guard.\n * K is optional but if it is not provided, typescript won't be able to type guard in the EventGuards type.\n * \n * @category Types\n */\nexport type Guard<Context extends BaseContext, K extends string = string> = {\n [P in K]: GuardEvaluation<Context>;\n}\n\nexport type Action<\n Context extends BaseContext, \n EventPayloadMapping, \n States extends string,\n EventOutputMapping extends Partial<Record<keyof EventPayloadMapping, unknown>> = DefaultOutputMapping<EventPayloadMapping>,\n Output = void\n> = {\n action: (context: Context, event: EventPayloadMapping[keyof EventPayloadMapping], stateMachine: StateMachine<EventPayloadMapping, Context, States, EventOutputMapping>) => Output | void;\n defaultTargetState?: States;\n}\n\nexport type Delay<\n Context extends BaseContext, \n EventPayloadMapping, \n States extends string,\n EventOutputMapping extends Partial<Record<keyof EventPayloadMapping, unknown>> = DefaultOutputMapping<EventPayloadMapping>\n> = {\n time: number;\n action: Action<Context, EventPayloadMapping, States, EventOutputMapping>;\n}\n\n/**\n * @description This is a mapping of a guard to a target state.\n * \n * Generic parameters:\n * - Context: The context of the state machine. (which can be used by each state to do calculations that would persist across states)\n * - G: The guard type.\n * - States: All of the possible states that the state machine can be in. e.g. a string literal union like \"IDLE\" | \"SELECTING\" | \"PAN\" | \"ZOOM\"\n * \n * You probably don't need to use this type directly.\n * \n * @see {@link TemplateState['eventGuards']}\n * \n * @category Types\n */\nexport type GuardMapping<Context extends BaseContext, G, States extends string> = {\n guard: G extends Guard<Context, infer K> ? K : never;\n target: States;\n}\n\n/**\n * @description This is a mapping of an event to a guard evaluation.\n * \n * Generic parameters:\n * - EventPayloadMapping: A mapping of events to their payloads.\n * - States: All of the possible states that the state machine can be in. e.g. a string literal union like \"IDLE\" | \"SELECTING\" | \"PAN\" | \"ZOOM\"\n * - Context: The context of the state machine. (which can be used by each state to do calculations that would persist across states)\n * - T: The guard type.\n * \n * You probably don't need to use this type directly.\n * This is a mapping of an event to a guard evaluation.\n * \n * @see {@link TemplateState['eventGuards']}\n * \n * @category Types\n */\nexport type EventGuards<EventPayloadMapping, States extends string, Context extends BaseContext, T extends Guard<Context>> = {\n [K in keyof EventPayloadMapping]: GuardMapping<Context, T, States>[];\n}\n\n/**\n * Concrete implementation of a finite state machine.\n *\n * @remarks\n * This class provides a complete, ready-to-use state machine implementation. It's generic enough\n * to handle most use cases without requiring custom extensions.\n *\n * ## Features\n *\n * - **Type-safe events**: Events and their payloads are fully typed via the EventPayloadMapping\n * - **State transitions**: Automatic state transitions based on event handlers\n * - **Event outputs**: Handlers can return values that are included in the result\n * - **Lifecycle hooks**: States can define `uponEnter` and `beforeExit` callbacks\n * - **State change listeners**: Subscribe to state transitions\n * - **Shared context**: All states access the same context object for persistent data\n *\n * ## Usage Pattern\n *\n * 1. Define your event payload mapping type\n * 2. Define your states as a string union type\n * 3. Create state classes extending {@link TemplateState}\n * 4. Instantiate TemplateStateMachine with your states and initial state\n *\n * @typeParam EventPayloadMapping - Object mapping event names to their payload types\n * @typeParam Context - Context type shared across all states\n * @typeParam States - Union of all possible state names (string literals)\n * @typeParam EventOutputMapping - Optional mapping of events to their output types\n *\n * @example\n * Basic vending machine state machine\n * ```typescript\n * type Events = {\n * insertCoin: { amount: number };\n * selectItem: { itemId: string };\n * cancel: {};\n * };\n *\n * type States = \"IDLE\" | \"PAYMENT\" | \"DISPENSING\";\n *\n * interface VendingContext extends BaseContext {\n * balance: number;\n * setup() { this.balance = 0; }\n * cleanup() {}\n * }\n *\n * const context: VendingContext = {\n * balance: 0,\n * setup() { this.balance = 0; },\n * cleanup() {}\n * };\n *\n * const machine = new TemplateStateMachine<Events, VendingContext, States>(\n * {\n * IDLE: new IdleState(),\n * PAYMENT: new PaymentState(),\n * DISPENSING: new DispensingState()\n * },\n * \"IDLE\",\n * context\n * );\n *\n * // Trigger events\n * machine.happens(\"insertCoin\", { amount: 100 });\n * machine.happens(\"selectItem\", { itemId: \"A1\" });\n * ```\n *\n * @category State Machine Core\n * @see {@link TemplateState} for creating state implementations\n * @see {@link StateMachine} for the interface definition\n */\nexport class TemplateStateMachine<\n EventPayloadMapping, \n Context extends BaseContext, \n States extends string = 'IDLE',\n EventOutputMapping extends Partial<Record<keyof EventPayloadMapping, unknown>> = DefaultOutputMapping<EventPayloadMapping>\n> implements StateMachine<EventPayloadMapping, Context, States, EventOutputMapping> {\n\n protected _currentState: States;\n protected _states: Record<States, State<EventPayloadMapping, Context, States, EventOutputMapping>>;\n protected _context: Context;\n protected _statesArray: States[];\n protected _stateChangeCallbacks: StateChangeCallback<States>[];\n protected _happensCallbacks: ((args: EventArgs<EventPayloadMapping, keyof EventPayloadMapping | string>, context: Context) => void)[];\n protected _timeouts: ReturnType<typeof setTimeout> | undefined = undefined;\n\n constructor(states: Record<States, State<EventPayloadMapping, Context, States, EventOutputMapping>>, initialState: States, context: Context){\n this._states = states;\n this._currentState = initialState;\n this._context = context;\n this._statesArray = Object.keys(states) as States[];\n this._stateChangeCallbacks = [];\n this._happensCallbacks = [];\n this._states[initialState].uponEnter(context, this, initialState);\n }\n\n switchTo(state: States): void {\n this._currentState = state;\n }\n \n // Implementation signature - matches both overloads\n happens<K extends keyof EventPayloadMapping>(...args: EventArgs<EventPayloadMapping, K>): EventResult<States, K extends keyof EventOutputMapping ? EventOutputMapping[K] : void>;\n happens<K extends string>(...args: EventArgs<EventPayloadMapping, K>): EventResult<States, unknown>;\n happens<K extends keyof EventPayloadMapping | string>(...args: EventArgs<EventPayloadMapping, K>): EventResult<States, unknown> {\n if(this._timeouts){\n clearTimeout(this._timeouts);\n }\n this._happensCallbacks.forEach(callback => callback(args, this._context));\n const result = this._states[this._currentState].handles(args, this._context, this);\n if(result.handled && result.nextState !== undefined && result.nextState !== this._currentState){ // TODO: whether or not to transition to the same state (currently no) (uponEnter and beforeExit will still be called if the state is the same)\n const originalState = this._currentState;\n this._states[this._currentState].beforeExit(this._context, this, result.nextState);\n this.switchTo(result.nextState);\n this._states[this._currentState].uponEnter(this._context, this, originalState);\n this._stateChangeCallbacks.forEach(callback => callback(originalState, this._currentState));\n }\n return result;\n }\n\n onStateChange(callback: StateChangeCallback<States>): void {\n this._stateChangeCallbacks.push(callback);\n }\n\n onHappens(callback: (args: EventArgs<EventPayloadMapping, keyof EventPayloadMapping | string>, context: Context) => void): void {\n this._happensCallbacks.push(callback);\n }\n\n get currentState(): States {\n return this._currentState;\n }\n\n setContext(context: Context): void {\n this._context = context;\n }\n\n get possibleStates(): States[] {\n return this._statesArray;\n }\n\n get states(): Record<States, State<EventPayloadMapping, Context, States, EventOutputMapping>> {\n return this._states;\n }\n}\n/**\n * Abstract base class for state machine states.\n *\n * @remarks\n * This abstract class provides the foundation for implementing individual states in a state machine.\n * Each state defines how it responds to events through the `eventReactions` object.\n *\n * ## Key Concepts\n *\n * - **Event Reactions**: Define handlers for events this state cares about. Unhandled events are ignored.\n * - **Guards**: Conditional logic that determines which state to transition to based on context\n * - **Lifecycle Hooks**: `uponEnter` and `beforeExit` callbacks for state transition side effects\n * - **Selective Handling**: Only define reactions for events relevant to this state\n *\n * ## Implementation Pattern\n *\n * 1. Extend this class for each state in your state machine\n * 2. Implement the `eventReactions` property with handlers for relevant events\n * 3. Optionally override `uponEnter` and `beforeExit` for lifecycle logic\n * 4. Optionally define `guards` and `eventGuards` for conditional transitions\n *\n * @typeParam EventPayloadMapping - Object mapping event names to their payload types\n * @typeParam Context - Context type shared across all states\n * @typeParam States - Union of all possible state names (string literals)\n * @typeParam EventOutputMapping - Optional mapping of events to their output types\n *\n * @example\n * Simple state implementation\n * ```typescript\n * class IdleState extends TemplateState<MyEvents, MyContext, MyStates> {\n * eventReactions = {\n * start: {\n * action: (context, event) => {\n * console.log('Starting...');\n * context.startTime = Date.now();\n * },\n * defaultTargetState: \"ACTIVE\"\n * },\n * reset: {\n * action: (context, event) => {\n * context.counter = 0;\n * }\n * // No state transition - stays in IDLE\n * }\n * };\n *\n * uponEnter(context, stateMachine, fromState) {\n * console.log(`Entered IDLE from ${fromState}`);\n * }\n * }\n * ```\n *\n * @example\n * State with guards for conditional transitions\n * ```typescript\n * class PaymentState extends TemplateState<Events, VendingContext, States> {\n * guards = {\n * hasEnoughMoney: (context) => context.balance >= context.itemPrice,\n * needsChange: (context) => context.balance > context.itemPrice\n * };\n *\n * eventReactions = {\n * selectItem: {\n * action: (context, event) => {\n * context.selectedItem = event.itemId;\n * context.itemPrice = getPrice(event.itemId);\n * },\n * defaultTargetState: \"IDLE\" // Fallback if no guard matches\n * }\n * };\n *\n * eventGuards = {\n * selectItem: [\n * { guard: 'hasEnoughMoney', target: 'DISPENSING' },\n * // If hasEnoughMoney is false, uses defaultTargetState (IDLE)\n * ]\n * };\n * }\n * ```\n *\n * @category State Machine Core\n * @see {@link TemplateStateMachine} for the state machine implementation\n * @see {@link EventReactions} for defining event handlers\n */\nexport abstract class TemplateState<\n EventPayloadMapping, \n Context extends BaseContext, \n States extends string = 'IDLE',\n EventOutputMapping extends Partial<Record<keyof EventPayloadMapping, unknown>> = DefaultOutputMapping<EventPayloadMapping>\n> implements State<EventPayloadMapping, Context, States, EventOutputMapping> {\n\n public abstract eventReactions: EventReactions<EventPayloadMapping, Context, States, EventOutputMapping>;\n protected _guards: Guard<Context> = {} as Guard<Context>;\n protected _eventGuards: Partial<EventGuards<EventPayloadMapping, States, Context, Guard<Context>>> = {} as Partial<EventGuards<EventPayloadMapping, States, Context, Guard<Context>>>;\n protected _delay: Delay<Context, EventPayloadMapping, States, EventOutputMapping> | undefined = undefined;\n\n get guards(): Guard<Context> {\n return this._guards;\n }\n\n get eventGuards(): Partial<EventGuards<EventPayloadMapping, States, Context, Guard<Context>>> {\n return this._eventGuards;\n }\n\n get delay(): Delay<Context, EventPayloadMapping, States, EventOutputMapping> | undefined {\n return this._delay;\n }\n\n uponEnter(context: Context, stateMachine: StateMachine<EventPayloadMapping, Context, States, EventOutputMapping>, from: States): void {\n // console.log(\"enter\");\n }\n\n beforeExit(context: Context, stateMachine: StateMachine<EventPayloadMapping, Context, States, EventOutputMapping>, to: States): void {\n // console.log('leave');\n }\n\n handles<K extends (keyof EventPayloadMapping | string)>(args: EventArgs<EventPayloadMapping, K>, context: Context, stateMachine: StateMachine<EventPayloadMapping, Context, States, EventOutputMapping>): EventResult<States, K extends keyof EventOutputMapping ? EventOutputMapping[K] : void>{\n const eventKey = args[0] as keyof EventPayloadMapping;\n const eventPayload = args[1] as EventPayloadMapping[keyof EventPayloadMapping];\n if (this.eventReactions[eventKey]) {\n // Capture the output from the action\n const output = this.eventReactions[eventKey].action(context, eventPayload, stateMachine);\n const targetState = this.eventReactions[eventKey].defaultTargetState;\n const guardsToEvaluate = this._eventGuards[eventKey];\n if(guardsToEvaluate){\n const target = guardsToEvaluate.find((guard)=>{\n if(this.guards[guard.guard]){\n return this.guards[guard.guard](context);\n }\n return false;\n });\n return target \n ? {handled: true, nextState: target.target, output: output as any} \n : {handled: true, nextState: targetState, output: output as any};\n }\n return {handled: true, nextState: targetState, output: output as any};\n }\n return {handled: false};\n }\n}\n\n\n/**\n * Creates a type guard function for checking if a value belongs to a specific set of states.\n *\n * @remarks\n * This utility function generates a TypeScript type guard that narrows a string type\n * to a specific union of string literals. Useful when you have multiple state types\n * and need to distinguish between them at runtime.\n *\n * @typeParam T - String literal type to guard for\n * @param set - Readonly array of string literals defining the valid states\n * @returns A type guard function that checks if a string is in the set\n *\n * @example\n * Creating state guards for hierarchical state machines\n * ```typescript\n * type MainStates = \"idle\" | \"active\" | \"paused\";\n * type SubStates = \"loading\" | \"processing\" | \"complete\";\n * type AllStates = MainStates | SubStates;\n *\n * const MAIN_STATES = [\"idle\", \"active\", \"paused\"] as const;\n * const isMainState = createStateGuard(MAIN_STATES);\n *\n * function handleState(state: AllStates) {\n * if (isMainState(state)) {\n * // TypeScript knows state is MainStates here\n * console.log('Main state:', state);\n * } else {\n * // TypeScript knows state is SubStates here\n * console.log('Sub state:', state);\n * }\n * }\n * ```\n *\n * @category Utilities\n */\nexport function createStateGuard<T extends string>(set: readonly T[]) {\n return (s: string): s is T => set.includes(s as T);\n}\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": "
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": "AAsFO,IAAM,EAAc,IAAI,GAoVxB,MAAM,CAKuE,CAEtE,cACA,QACA,SACA,aACA,sBACA,kBACA,UAAuD,OAEjE,WAAW,CAAC,EAAyF,EAAsB,EAAiB,CACxI,KAAK,QAAU,EACf,KAAK,cAAgB,EACrB,KAAK,SAAW,EAChB,KAAK,aAAe,OAAO,KAAK,CAAM,EACtC,KAAK,sBAAwB,CAAC,EAC9B,KAAK,kBAAoB,CAAC,EAC1B,KAAK,QAAQ,GAAc,UAAU,EAAS,KAAM,CAAY,EAGpE,QAAQ,CAAC,EAAqB,CAC1B,KAAK,cAAgB,EAMzB,OAAqD,IAAI,EAAuE,CAC5H,GAAG,KAAK,UACJ,aAAa,KAAK,SAAS,EAE/B,KAAK,kBAAkB,QAAQ,KAAY,EAAS,EAAM,KAAK,QAAQ,CAAC,EACxE,IAAM,EAAS,KAAK,QAAQ,KAAK,eAAe,QAAQ,EAAM,KAAK,SAAU,IAAI,EACjF,GAAG,EAAO,SAAW,EAAO,YAAc,QAAa,EAAO,YAAc,KAAK,cAAc,CAC3F,IAAM,EAAgB,KAAK,cAC3B,KAAK,QAAQ,KAAK,eAAe,WAAW,KAAK,SAAU,KAAM,EAAO,SAAS,EACjF,KAAK,SAAS,EAAO,SAAS,EAC9B,KAAK,QAAQ,KAAK,eAAe,UAAU,KAAK,SAAU,KAAM,CAAa,EAC7E,KAAK,sBAAsB,QAAQ,KAAY,EAAS,EAAe,KAAK,aAAa,CAAC,EAE9F,OAAO,EAGX,aAAa,CAAC,EAA6C,CACvD,KAAK,sBAAsB,KAAK,CAAQ,EAG5C,SAAS,CAAC,EAAsH,CAC5H,KAAK,kBAAkB,KAAK,CAAQ,KAGpC,aAAY,EAAW,CACvB,OAAO,KAAK,cAGhB,UAAU,CAAC,EAAwB,CAC/B,KAAK,SAAW,KAGhB,eAAc,EAAa,CAC3B,OAAO,KAAK,gBAGZ,OAAM,EAAoF,CAC1F,OAAO,KAAK,QAEpB,CAqFO,MAAe,CAKuD,CAG/D,QAA0B,CAAC,EAC3B,aAA2F,CAAC,EAC5F,OAAsF,UAE5F,OAAM,EAAmB,CACzB,OAAO,KAAK,WAGZ,YAAW,EAA+E,CAC1F,OAAO,KAAK,gBAGZ,MAAK,EAAgF,CACrF,OAAO,KAAK,OAGhB,SAAS,CAAC,EAAkB,EAAsF,EAAoB,EAItI,UAAU,CAAC,EAAkB,EAAsF,EAAkB,EAIrI,OAAuD,CAAC,EAAyC,EAAkB,EAA6K,CAC5R,IAAM,EAAW,EAAK,GAChB,EAAe,EAAK,GAC1B,GAAI,KAAK,eAAe,GAAW,CAE/B,IAAM,EAAS,KAAK,eAAe,GAAU,OAAO,EAAS,EAAc,CAAY,EACjF,EAAc,KAAK,eAAe,GAAU,mBAC5C,EAAmB,KAAK,aAAa,GAC3C,GAAG,EAAiB,CAChB,IAAM,EAAS,EAAiB,KAAK,CAAC,IAAQ,CAC1C,GAAG,KAAK,OAAO,EAAM,OACjB,OAAO,KAAK,OAAO,EAAM,OAAO,CAAO,EAE3C,MAAO,GACV,EACD,OAAO,EACD,CAAC,QAAS,GAAM,UAAW,EAAO,OAAQ,OAAQ,CAAa,EAC/D,CAAC,QAAS,GAAM,UAAW,EAAa,OAAQ,CAAa,EAEvE,MAAO,CAAC,QAAS,GAAM,UAAW,EAAa,OAAQ,CAAa,EAExE,MAAO,CAAC,QAAS,EAAK,EAE9B,CAsCO,SAAS,CAAkC,CAAC,EAAmB,CAClE,MAAO,CAAC,IAAsB,EAAI,SAAS,CAAM",
|
|
8
|
+
"debugId": "391A71F13DC7D12B64756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
package/interface.d.ts
CHANGED
|
@@ -1,29 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base context interface for state machines.
|
|
3
|
+
*
|
|
4
|
+
* @remarks
|
|
5
|
+
* The context is shared across all states in a state machine and can be used to store data
|
|
6
|
+
* that persists between state transitions. All custom contexts must extend this interface.
|
|
7
|
+
*
|
|
8
|
+
* The setup and cleanup methods provide lifecycle hooks for resource management:
|
|
9
|
+
* - `setup()`: Called when the context is initialized
|
|
10
|
+
* - `cleanup()`: Called when the context is destroyed
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* interface MyContext extends BaseContext {
|
|
15
|
+
* counter: number;
|
|
16
|
+
* data: string[];
|
|
17
|
+
* setup() {
|
|
18
|
+
* this.counter = 0;
|
|
19
|
+
* this.data = [];
|
|
20
|
+
* }
|
|
21
|
+
* cleanup() {
|
|
22
|
+
* this.data = [];
|
|
23
|
+
* }
|
|
24
|
+
* }
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* @category Core
|
|
28
|
+
*/
|
|
1
29
|
export interface BaseContext {
|
|
2
30
|
setup(): void;
|
|
3
31
|
cleanup(): void;
|
|
4
32
|
}
|
|
5
33
|
type NOOP = () => void;
|
|
34
|
+
/**
|
|
35
|
+
* Utility type to check if an object type is empty.
|
|
36
|
+
* @internal
|
|
37
|
+
*/
|
|
6
38
|
type IsEmptyObject<T> = T extends {} ? {} extends T ? true : false : false;
|
|
7
39
|
/**
|
|
8
|
-
*
|
|
40
|
+
* Utility type to derive a string literal union from a readonly array of string literals.
|
|
41
|
+
*
|
|
42
|
+
* @remarks
|
|
43
|
+
* This helper type extracts the element types from a readonly array to create a union type.
|
|
44
|
+
* Useful for defining state machine states from an array.
|
|
9
45
|
*
|
|
10
|
-
*
|
|
11
|
-
* ```
|
|
46
|
+
* @example
|
|
47
|
+
* ```typescript
|
|
12
48
|
* const TEST_STATES = ["one", "two", "three"] as const;
|
|
13
49
|
* type TestStates = CreateStateType<typeof TEST_STATES>; // "one" | "two" | "three"
|
|
14
50
|
* ```
|
|
51
|
+
*
|
|
52
|
+
* @category Utilities
|
|
15
53
|
*/
|
|
16
54
|
export type CreateStateType<ArrayLiteral extends readonly string[]> = ArrayLiteral[number];
|
|
55
|
+
/**
|
|
56
|
+
* Type for event arguments with conditional payload requirement.
|
|
57
|
+
*
|
|
58
|
+
* @remarks
|
|
59
|
+
* This utility type determines whether an event requires a payload argument based on the
|
|
60
|
+
* event payload mapping. If the payload is an empty object, no payload is required.
|
|
61
|
+
*
|
|
62
|
+
* @typeParam EventPayloadMapping - Mapping of event names to their payload types
|
|
63
|
+
* @typeParam K - The event key
|
|
64
|
+
*
|
|
65
|
+
* @category Utilities
|
|
66
|
+
*/
|
|
17
67
|
export type EventArgs<EventPayloadMapping, K> = K extends keyof EventPayloadMapping ? IsEmptyObject<EventPayloadMapping[K]> extends true ? [event: K] : [event: K, payload: EventPayloadMapping[K]] : [event: K, payload?: unknown];
|
|
68
|
+
/**
|
|
69
|
+
* No-operation function constant used as a placeholder for optional actions.
|
|
70
|
+
*
|
|
71
|
+
* @remarks
|
|
72
|
+
* Use this when you need to provide a function but don't want it to do anything,
|
|
73
|
+
* such as for default state transition actions that have no side effects.
|
|
74
|
+
*
|
|
75
|
+
* @category Core
|
|
76
|
+
*/
|
|
18
77
|
export declare const NO_OP: NOOP;
|
|
78
|
+
/**
|
|
79
|
+
* Result type indicating an event was not handled by the current state.
|
|
80
|
+
*
|
|
81
|
+
* @remarks
|
|
82
|
+
* When a state doesn't have a handler defined for a particular event, it returns this type.
|
|
83
|
+
* The state machine will not transition and the event is effectively ignored.
|
|
84
|
+
*
|
|
85
|
+
* @category Core
|
|
86
|
+
*/
|
|
19
87
|
export type EventNotHandled = {
|
|
20
88
|
handled: false;
|
|
21
89
|
};
|
|
22
|
-
|
|
90
|
+
/**
|
|
91
|
+
* Result type when an event is successfully handled by a state.
|
|
92
|
+
*
|
|
93
|
+
* @remarks
|
|
94
|
+
* This type represents a successful event handling result. It can optionally include:
|
|
95
|
+
* - `nextState`: The state to transition to (if different from current)
|
|
96
|
+
* - `output`: A return value from the event handler
|
|
97
|
+
*
|
|
98
|
+
* @typeParam States - Union of all possible state names in the state machine
|
|
99
|
+
* @typeParam Output - The output type for this event (defaults to void)
|
|
100
|
+
*
|
|
101
|
+
* @example
|
|
102
|
+
* ```typescript
|
|
103
|
+
* // Simple transition without output
|
|
104
|
+
* const result: EventHandled<"IDLE" | "ACTIVE"> = {
|
|
105
|
+
* handled: true,
|
|
106
|
+
* nextState: "ACTIVE"
|
|
107
|
+
* };
|
|
108
|
+
*
|
|
109
|
+
* // With output value
|
|
110
|
+
* const resultWithOutput: EventHandled<"IDLE" | "ACTIVE", number> = {
|
|
111
|
+
* handled: true,
|
|
112
|
+
* nextState: "IDLE",
|
|
113
|
+
* output: 42
|
|
114
|
+
* };
|
|
115
|
+
* ```
|
|
116
|
+
*
|
|
117
|
+
* @category Core
|
|
118
|
+
*/
|
|
119
|
+
export type EventHandled<States extends string, Output = void> = {
|
|
23
120
|
handled: true;
|
|
24
121
|
nextState?: States;
|
|
122
|
+
output?: Output;
|
|
123
|
+
};
|
|
124
|
+
/**
|
|
125
|
+
* Discriminated union representing the result of event handling.
|
|
126
|
+
*
|
|
127
|
+
* @remarks
|
|
128
|
+
* Every event handler returns an EventResult, which is either:
|
|
129
|
+
* - {@link EventHandled}: The event was processed successfully
|
|
130
|
+
* - {@link EventNotHandled}: The event was not recognized/handled
|
|
131
|
+
*
|
|
132
|
+
* Use the `handled` discriminant to narrow the type in TypeScript.
|
|
133
|
+
*
|
|
134
|
+
* @typeParam States - Union of all possible state names
|
|
135
|
+
* @typeParam Output - The output type for handled events
|
|
136
|
+
*
|
|
137
|
+
* @category Core
|
|
138
|
+
*/
|
|
139
|
+
export type EventResult<States extends string, Output = void> = EventNotHandled | EventHandled<States, Output>;
|
|
140
|
+
/**
|
|
141
|
+
* @description A default output mapping that maps all events to void.
|
|
142
|
+
* Used as default when no output mapping is provided.
|
|
143
|
+
*
|
|
144
|
+
* @category Types
|
|
145
|
+
*/
|
|
146
|
+
export type DefaultOutputMapping<EventPayloadMapping> = {
|
|
147
|
+
[K in keyof EventPayloadMapping]: void;
|
|
25
148
|
};
|
|
26
|
-
export type EventHandledResult<States extends string> = EventNotHandled | EventHandled<States>;
|
|
27
149
|
/**
|
|
28
150
|
* @description This is the interface for the state machine. The interface takes in a few generic parameters.
|
|
29
151
|
*
|
|
@@ -31,6 +153,7 @@ export type EventHandledResult<States extends string> = EventNotHandled | EventH
|
|
|
31
153
|
* - EventPayloadMapping: A mapping of events to their payloads.
|
|
32
154
|
* - Context: The context of the state machine. (which can be used by each state to do calculations that would persist across states)
|
|
33
155
|
* - States: All of the possible states that the state machine can be in. e.g. a string literal union like "IDLE" | "SELECTING" | "PAN" | "ZOOM"
|
|
156
|
+
* - EventOutputMapping: A mapping of events to their output types. Defaults to void for all events.
|
|
34
157
|
*
|
|
35
158
|
* You can probably get by using the TemplateStateMachine class.
|
|
36
159
|
* The naming is that an event would "happen" and the state of the state machine would "handle" it.
|
|
@@ -38,14 +161,14 @@ export type EventHandledResult<States extends string> = EventNotHandled | EventH
|
|
|
38
161
|
* @see {@link TemplateStateMachine}
|
|
39
162
|
* @see {@link KmtInputStateMachine}
|
|
40
163
|
*
|
|
41
|
-
* @category
|
|
164
|
+
* @category Types
|
|
42
165
|
*/
|
|
43
|
-
export interface StateMachine<EventPayloadMapping, Context extends BaseContext, States extends string = 'IDLE'
|
|
166
|
+
export interface StateMachine<EventPayloadMapping, Context extends BaseContext, States extends string = 'IDLE', EventOutputMapping extends Partial<Record<keyof EventPayloadMapping, unknown>> = DefaultOutputMapping<EventPayloadMapping>> {
|
|
44
167
|
switchTo(state: States): void;
|
|
45
|
-
happens<K extends keyof EventPayloadMapping>(...args: EventArgs<EventPayloadMapping, K>):
|
|
46
|
-
happens<K extends string>(...args: EventArgs<EventPayloadMapping, K>):
|
|
168
|
+
happens<K extends keyof EventPayloadMapping>(...args: EventArgs<EventPayloadMapping, K>): EventResult<States, K extends keyof EventOutputMapping ? EventOutputMapping[K] : void>;
|
|
169
|
+
happens<K extends string>(...args: EventArgs<EventPayloadMapping, K>): EventResult<States, unknown>;
|
|
47
170
|
setContext(context: Context): void;
|
|
48
|
-
states: Record<States, State<EventPayloadMapping, Context, string extends States ? string : States>>;
|
|
171
|
+
states: Record<States, State<EventPayloadMapping, Context, string extends States ? string : States, EventOutputMapping>>;
|
|
49
172
|
onStateChange(callback: StateChangeCallback<States>): void;
|
|
50
173
|
possibleStates: States[];
|
|
51
174
|
onHappens(callback: (args: EventArgs<EventPayloadMapping, keyof EventPayloadMapping | string>, context: Context) => void): void;
|
|
@@ -53,7 +176,7 @@ export interface StateMachine<EventPayloadMapping, Context extends BaseContext,
|
|
|
53
176
|
/**
|
|
54
177
|
* @description This is the type for the callback that is called when the state changes.
|
|
55
178
|
*
|
|
56
|
-
* @category
|
|
179
|
+
* @category Types
|
|
57
180
|
*/
|
|
58
181
|
export type StateChangeCallback<States extends string = 'IDLE'> = (currentState: States, nextState: States) => void;
|
|
59
182
|
/**
|
|
@@ -64,22 +187,23 @@ export type StateChangeCallback<States extends string = 'IDLE'> = (currentState:
|
|
|
64
187
|
* - EventPayloadMapping: A mapping of events to their payloads.
|
|
65
188
|
* - Context: The context of the state machine. (which can be used by each state to do calculations that would persist across states)
|
|
66
189
|
* - States: All of the possible states that the state machine can be in. e.g. a string literal union like "IDLE" | "SELECTING" | "PAN" | "ZOOM"
|
|
190
|
+
* - EventOutputMapping: A mapping of events to their output types. Defaults to void for all events.
|
|
67
191
|
*
|
|
68
192
|
* A state's all possible states can be only a subset of the possible states of the state machine. (a state only needs to know what states it can transition to)
|
|
69
193
|
* This allows for a state to be reusable across different state machines.
|
|
70
194
|
*
|
|
71
195
|
* @see {@link TemplateState}
|
|
72
196
|
*
|
|
73
|
-
* @category
|
|
197
|
+
* @category Types
|
|
74
198
|
*/
|
|
75
|
-
export interface State<EventPayloadMapping, Context extends BaseContext, States extends string = 'IDLE'
|
|
76
|
-
uponEnter(context: Context, stateMachine: StateMachine<EventPayloadMapping, Context, States>, from: States): void;
|
|
77
|
-
beforeExit(context: Context, stateMachine: StateMachine<EventPayloadMapping, Context, States>, to: States): void;
|
|
78
|
-
handles<K extends (keyof EventPayloadMapping | string)>(args: EventArgs<EventPayloadMapping, K>, context: Context, stateMachine: StateMachine<EventPayloadMapping, Context, States>):
|
|
79
|
-
eventReactions: EventReactions<EventPayloadMapping, Context, States>;
|
|
199
|
+
export interface State<EventPayloadMapping, Context extends BaseContext, States extends string = 'IDLE', EventOutputMapping extends Partial<Record<keyof EventPayloadMapping, unknown>> = DefaultOutputMapping<EventPayloadMapping>> {
|
|
200
|
+
uponEnter(context: Context, stateMachine: StateMachine<EventPayloadMapping, Context, States, EventOutputMapping>, from: States): void;
|
|
201
|
+
beforeExit(context: Context, stateMachine: StateMachine<EventPayloadMapping, Context, States, EventOutputMapping>, to: States): void;
|
|
202
|
+
handles<K extends (keyof EventPayloadMapping | string)>(args: EventArgs<EventPayloadMapping, K>, context: Context, stateMachine: StateMachine<EventPayloadMapping, Context, States, EventOutputMapping>): EventResult<States, K extends keyof EventOutputMapping ? EventOutputMapping[K] : void>;
|
|
203
|
+
eventReactions: EventReactions<EventPayloadMapping, Context, States, EventOutputMapping>;
|
|
80
204
|
guards: Guard<Context>;
|
|
81
205
|
eventGuards: Partial<EventGuards<EventPayloadMapping, States, Context, Guard<Context>>>;
|
|
82
|
-
delay: Delay<Context, EventPayloadMapping, States> | undefined;
|
|
206
|
+
delay: Delay<Context, EventPayloadMapping, States, EventOutputMapping> | undefined;
|
|
83
207
|
}
|
|
84
208
|
/**
|
|
85
209
|
* @description This is the type for the event reactions of a state.
|
|
@@ -88,12 +212,15 @@ export interface State<EventPayloadMapping, Context extends BaseContext, States
|
|
|
88
212
|
* - EventPayloadMapping: A mapping of events to their payloads.
|
|
89
213
|
* - Context: The context of the state machine. (which can be used by each state to do calculations that would persist across states)
|
|
90
214
|
* - States: All of the possible states that the state machine can be in. e.g. a string literal union like "IDLE" | "SELECTING" | "PAN" | "ZOOM"
|
|
215
|
+
* - EventOutputMapping: A mapping of events to their output types. Defaults to void for all events.
|
|
91
216
|
*
|
|
92
|
-
*
|
|
217
|
+
* The action function can now return an output value that will be included in the EventHandledResult.
|
|
218
|
+
*
|
|
219
|
+
* @category Types
|
|
93
220
|
*/
|
|
94
|
-
export type EventReactions<EventPayloadMapping, Context extends BaseContext, States extends string
|
|
221
|
+
export type EventReactions<EventPayloadMapping, Context extends BaseContext, States extends string, EventOutputMapping extends Partial<Record<keyof EventPayloadMapping, unknown>> = DefaultOutputMapping<EventPayloadMapping>> = {
|
|
95
222
|
[K in keyof Partial<EventPayloadMapping>]: {
|
|
96
|
-
action: (context: Context, event: EventPayloadMapping[K], stateMachine: StateMachine<EventPayloadMapping, Context, States>) => void;
|
|
223
|
+
action: (context: Context, event: EventPayloadMapping[K], stateMachine: StateMachine<EventPayloadMapping, Context, States, EventOutputMapping>) => K extends keyof EventOutputMapping ? (EventOutputMapping[K] | void) : void;
|
|
97
224
|
defaultTargetState?: States;
|
|
98
225
|
};
|
|
99
226
|
};
|
|
@@ -106,7 +233,7 @@ export type EventReactions<EventPayloadMapping, Context extends BaseContext, Sta
|
|
|
106
233
|
* Generic parameters:
|
|
107
234
|
* - Context: The context of the state machine. (which can be used by each state to do calculations that would persist across states)
|
|
108
235
|
*
|
|
109
|
-
* @category
|
|
236
|
+
* @category Types
|
|
110
237
|
*/
|
|
111
238
|
export type GuardEvaluation<Context extends BaseContext> = (context: Context) => boolean;
|
|
112
239
|
/**
|
|
@@ -116,18 +243,18 @@ export type GuardEvaluation<Context extends BaseContext> = (context: Context) =>
|
|
|
116
243
|
* K is all the possible keys that can be used to evaluate the guard.
|
|
117
244
|
* K is optional but if it is not provided, typescript won't be able to type guard in the EventGuards type.
|
|
118
245
|
*
|
|
119
|
-
* @category
|
|
246
|
+
* @category Types
|
|
120
247
|
*/
|
|
121
248
|
export type Guard<Context extends BaseContext, K extends string = string> = {
|
|
122
249
|
[P in K]: GuardEvaluation<Context>;
|
|
123
250
|
};
|
|
124
|
-
export type Action<Context extends BaseContext, EventPayloadMapping, States extends string> = {
|
|
125
|
-
action: (context: Context, event: EventPayloadMapping[keyof EventPayloadMapping], stateMachine: StateMachine<EventPayloadMapping, Context, States>) => void;
|
|
251
|
+
export type Action<Context extends BaseContext, EventPayloadMapping, States extends string, EventOutputMapping extends Partial<Record<keyof EventPayloadMapping, unknown>> = DefaultOutputMapping<EventPayloadMapping>, Output = void> = {
|
|
252
|
+
action: (context: Context, event: EventPayloadMapping[keyof EventPayloadMapping], stateMachine: StateMachine<EventPayloadMapping, Context, States, EventOutputMapping>) => Output | void;
|
|
126
253
|
defaultTargetState?: States;
|
|
127
254
|
};
|
|
128
|
-
export type Delay<Context extends BaseContext, EventPayloadMapping, States extends string
|
|
255
|
+
export type Delay<Context extends BaseContext, EventPayloadMapping, States extends string, EventOutputMapping extends Partial<Record<keyof EventPayloadMapping, unknown>> = DefaultOutputMapping<EventPayloadMapping>> = {
|
|
129
256
|
time: number;
|
|
130
|
-
action: Action<Context, EventPayloadMapping, States>;
|
|
257
|
+
action: Action<Context, EventPayloadMapping, States, EventOutputMapping>;
|
|
131
258
|
};
|
|
132
259
|
/**
|
|
133
260
|
* @description This is a mapping of a guard to a target state.
|
|
@@ -141,7 +268,7 @@ export type Delay<Context extends BaseContext, EventPayloadMapping, States exten
|
|
|
141
268
|
*
|
|
142
269
|
* @see {@link TemplateState['eventGuards']}
|
|
143
270
|
*
|
|
144
|
-
* @category
|
|
271
|
+
* @category Types
|
|
145
272
|
*/
|
|
146
273
|
export type GuardMapping<Context extends BaseContext, G, States extends string> = {
|
|
147
274
|
guard: G extends Guard<Context, infer K> ? K : never;
|
|
@@ -161,81 +288,230 @@ export type GuardMapping<Context extends BaseContext, G, States extends string>
|
|
|
161
288
|
*
|
|
162
289
|
* @see {@link TemplateState['eventGuards']}
|
|
163
290
|
*
|
|
164
|
-
* @category
|
|
291
|
+
* @category Types
|
|
165
292
|
*/
|
|
166
293
|
export type EventGuards<EventPayloadMapping, States extends string, Context extends BaseContext, T extends Guard<Context>> = {
|
|
167
294
|
[K in keyof EventPayloadMapping]: GuardMapping<Context, T, States>[];
|
|
168
295
|
};
|
|
169
296
|
/**
|
|
170
|
-
*
|
|
297
|
+
* Concrete implementation of a finite state machine.
|
|
298
|
+
*
|
|
299
|
+
* @remarks
|
|
300
|
+
* This class provides a complete, ready-to-use state machine implementation. It's generic enough
|
|
301
|
+
* to handle most use cases without requiring custom extensions.
|
|
302
|
+
*
|
|
303
|
+
* ## Features
|
|
304
|
+
*
|
|
305
|
+
* - **Type-safe events**: Events and their payloads are fully typed via the EventPayloadMapping
|
|
306
|
+
* - **State transitions**: Automatic state transitions based on event handlers
|
|
307
|
+
* - **Event outputs**: Handlers can return values that are included in the result
|
|
308
|
+
* - **Lifecycle hooks**: States can define `uponEnter` and `beforeExit` callbacks
|
|
309
|
+
* - **State change listeners**: Subscribe to state transitions
|
|
310
|
+
* - **Shared context**: All states access the same context object for persistent data
|
|
171
311
|
*
|
|
172
|
-
*
|
|
173
|
-
* To create a state machine, just instantiate this class and pass in the states, initial state and context.
|
|
312
|
+
* ## Usage Pattern
|
|
174
313
|
*
|
|
175
|
-
*
|
|
314
|
+
* 1. Define your event payload mapping type
|
|
315
|
+
* 2. Define your states as a string union type
|
|
316
|
+
* 3. Create state classes extending {@link TemplateState}
|
|
317
|
+
* 4. Instantiate TemplateStateMachine with your states and initial state
|
|
176
318
|
*
|
|
177
|
-
* @
|
|
319
|
+
* @typeParam EventPayloadMapping - Object mapping event names to their payload types
|
|
320
|
+
* @typeParam Context - Context type shared across all states
|
|
321
|
+
* @typeParam States - Union of all possible state names (string literals)
|
|
322
|
+
* @typeParam EventOutputMapping - Optional mapping of events to their output types
|
|
323
|
+
*
|
|
324
|
+
* @example
|
|
325
|
+
* Basic vending machine state machine
|
|
326
|
+
* ```typescript
|
|
327
|
+
* type Events = {
|
|
328
|
+
* insertCoin: { amount: number };
|
|
329
|
+
* selectItem: { itemId: string };
|
|
330
|
+
* cancel: {};
|
|
331
|
+
* };
|
|
332
|
+
*
|
|
333
|
+
* type States = "IDLE" | "PAYMENT" | "DISPENSING";
|
|
334
|
+
*
|
|
335
|
+
* interface VendingContext extends BaseContext {
|
|
336
|
+
* balance: number;
|
|
337
|
+
* setup() { this.balance = 0; }
|
|
338
|
+
* cleanup() {}
|
|
339
|
+
* }
|
|
340
|
+
*
|
|
341
|
+
* const context: VendingContext = {
|
|
342
|
+
* balance: 0,
|
|
343
|
+
* setup() { this.balance = 0; },
|
|
344
|
+
* cleanup() {}
|
|
345
|
+
* };
|
|
346
|
+
*
|
|
347
|
+
* const machine = new TemplateStateMachine<Events, VendingContext, States>(
|
|
348
|
+
* {
|
|
349
|
+
* IDLE: new IdleState(),
|
|
350
|
+
* PAYMENT: new PaymentState(),
|
|
351
|
+
* DISPENSING: new DispensingState()
|
|
352
|
+
* },
|
|
353
|
+
* "IDLE",
|
|
354
|
+
* context
|
|
355
|
+
* );
|
|
356
|
+
*
|
|
357
|
+
* // Trigger events
|
|
358
|
+
* machine.happens("insertCoin", { amount: 100 });
|
|
359
|
+
* machine.happens("selectItem", { itemId: "A1" });
|
|
360
|
+
* ```
|
|
361
|
+
*
|
|
362
|
+
* @category State Machine Core
|
|
363
|
+
* @see {@link TemplateState} for creating state implementations
|
|
364
|
+
* @see {@link StateMachine} for the interface definition
|
|
178
365
|
*/
|
|
179
|
-
export declare class TemplateStateMachine<EventPayloadMapping, Context extends BaseContext, States extends string = 'IDLE'
|
|
366
|
+
export declare class TemplateStateMachine<EventPayloadMapping, Context extends BaseContext, States extends string = 'IDLE', EventOutputMapping extends Partial<Record<keyof EventPayloadMapping, unknown>> = DefaultOutputMapping<EventPayloadMapping>> implements StateMachine<EventPayloadMapping, Context, States, EventOutputMapping> {
|
|
180
367
|
protected _currentState: States;
|
|
181
|
-
protected _states: Record<States, State<EventPayloadMapping, Context, States>>;
|
|
368
|
+
protected _states: Record<States, State<EventPayloadMapping, Context, States, EventOutputMapping>>;
|
|
182
369
|
protected _context: Context;
|
|
183
370
|
protected _statesArray: States[];
|
|
184
371
|
protected _stateChangeCallbacks: StateChangeCallback<States>[];
|
|
185
372
|
protected _happensCallbacks: ((args: EventArgs<EventPayloadMapping, keyof EventPayloadMapping | string>, context: Context) => void)[];
|
|
186
373
|
protected _timeouts: ReturnType<typeof setTimeout> | undefined;
|
|
187
|
-
constructor(states: Record<States, State<EventPayloadMapping, Context, States>>, initialState: States, context: Context);
|
|
374
|
+
constructor(states: Record<States, State<EventPayloadMapping, Context, States, EventOutputMapping>>, initialState: States, context: Context);
|
|
188
375
|
switchTo(state: States): void;
|
|
189
|
-
happens<K extends keyof EventPayloadMapping>(...args: EventArgs<EventPayloadMapping, K>):
|
|
190
|
-
happens<K extends string>(...args: EventArgs<EventPayloadMapping, K>):
|
|
376
|
+
happens<K extends keyof EventPayloadMapping>(...args: EventArgs<EventPayloadMapping, K>): EventResult<States, K extends keyof EventOutputMapping ? EventOutputMapping[K] : void>;
|
|
377
|
+
happens<K extends string>(...args: EventArgs<EventPayloadMapping, K>): EventResult<States, unknown>;
|
|
191
378
|
onStateChange(callback: StateChangeCallback<States>): void;
|
|
192
379
|
onHappens(callback: (args: EventArgs<EventPayloadMapping, keyof EventPayloadMapping | string>, context: Context) => void): void;
|
|
193
380
|
get currentState(): States;
|
|
194
381
|
setContext(context: Context): void;
|
|
195
382
|
get possibleStates(): States[];
|
|
196
|
-
get states(): Record<States, State<EventPayloadMapping, Context, States>>;
|
|
383
|
+
get states(): Record<States, State<EventPayloadMapping, Context, States, EventOutputMapping>>;
|
|
197
384
|
}
|
|
198
385
|
/**
|
|
199
|
-
*
|
|
386
|
+
* Abstract base class for state machine states.
|
|
387
|
+
*
|
|
388
|
+
* @remarks
|
|
389
|
+
* This abstract class provides the foundation for implementing individual states in a state machine.
|
|
390
|
+
* Each state defines how it responds to events through the `eventReactions` object.
|
|
200
391
|
*
|
|
201
|
-
*
|
|
202
|
-
* Unlike the TemplateStateMachine, this class is abstract. You need to implement the specific methods that you need.
|
|
203
|
-
* The core part off the state is the event reactions in which you would define how to handle each event in a state.
|
|
204
|
-
* You can define an eventReactions object that maps only the events that you need. If this state does not need to handle a specific event, you can just not define it in the eventReactions object.
|
|
392
|
+
* ## Key Concepts
|
|
205
393
|
*
|
|
206
|
-
*
|
|
394
|
+
* - **Event Reactions**: Define handlers for events this state cares about. Unhandled events are ignored.
|
|
395
|
+
* - **Guards**: Conditional logic that determines which state to transition to based on context
|
|
396
|
+
* - **Lifecycle Hooks**: `uponEnter` and `beforeExit` callbacks for state transition side effects
|
|
397
|
+
* - **Selective Handling**: Only define reactions for events relevant to this state
|
|
398
|
+
*
|
|
399
|
+
* ## Implementation Pattern
|
|
400
|
+
*
|
|
401
|
+
* 1. Extend this class for each state in your state machine
|
|
402
|
+
* 2. Implement the `eventReactions` property with handlers for relevant events
|
|
403
|
+
* 3. Optionally override `uponEnter` and `beforeExit` for lifecycle logic
|
|
404
|
+
* 4. Optionally define `guards` and `eventGuards` for conditional transitions
|
|
405
|
+
*
|
|
406
|
+
* @typeParam EventPayloadMapping - Object mapping event names to their payload types
|
|
407
|
+
* @typeParam Context - Context type shared across all states
|
|
408
|
+
* @typeParam States - Union of all possible state names (string literals)
|
|
409
|
+
* @typeParam EventOutputMapping - Optional mapping of events to their output types
|
|
410
|
+
*
|
|
411
|
+
* @example
|
|
412
|
+
* Simple state implementation
|
|
413
|
+
* ```typescript
|
|
414
|
+
* class IdleState extends TemplateState<MyEvents, MyContext, MyStates> {
|
|
415
|
+
* eventReactions = {
|
|
416
|
+
* start: {
|
|
417
|
+
* action: (context, event) => {
|
|
418
|
+
* console.log('Starting...');
|
|
419
|
+
* context.startTime = Date.now();
|
|
420
|
+
* },
|
|
421
|
+
* defaultTargetState: "ACTIVE"
|
|
422
|
+
* },
|
|
423
|
+
* reset: {
|
|
424
|
+
* action: (context, event) => {
|
|
425
|
+
* context.counter = 0;
|
|
426
|
+
* }
|
|
427
|
+
* // No state transition - stays in IDLE
|
|
428
|
+
* }
|
|
429
|
+
* };
|
|
430
|
+
*
|
|
431
|
+
* uponEnter(context, stateMachine, fromState) {
|
|
432
|
+
* console.log(`Entered IDLE from ${fromState}`);
|
|
433
|
+
* }
|
|
434
|
+
* }
|
|
435
|
+
* ```
|
|
436
|
+
*
|
|
437
|
+
* @example
|
|
438
|
+
* State with guards for conditional transitions
|
|
439
|
+
* ```typescript
|
|
440
|
+
* class PaymentState extends TemplateState<Events, VendingContext, States> {
|
|
441
|
+
* guards = {
|
|
442
|
+
* hasEnoughMoney: (context) => context.balance >= context.itemPrice,
|
|
443
|
+
* needsChange: (context) => context.balance > context.itemPrice
|
|
444
|
+
* };
|
|
445
|
+
*
|
|
446
|
+
* eventReactions = {
|
|
447
|
+
* selectItem: {
|
|
448
|
+
* action: (context, event) => {
|
|
449
|
+
* context.selectedItem = event.itemId;
|
|
450
|
+
* context.itemPrice = getPrice(event.itemId);
|
|
451
|
+
* },
|
|
452
|
+
* defaultTargetState: "IDLE" // Fallback if no guard matches
|
|
453
|
+
* }
|
|
454
|
+
* };
|
|
455
|
+
*
|
|
456
|
+
* eventGuards = {
|
|
457
|
+
* selectItem: [
|
|
458
|
+
* { guard: 'hasEnoughMoney', target: 'DISPENSING' },
|
|
459
|
+
* // If hasEnoughMoney is false, uses defaultTargetState (IDLE)
|
|
460
|
+
* ]
|
|
461
|
+
* };
|
|
462
|
+
* }
|
|
463
|
+
* ```
|
|
464
|
+
*
|
|
465
|
+
* @category State Machine Core
|
|
466
|
+
* @see {@link TemplateStateMachine} for the state machine implementation
|
|
467
|
+
* @see {@link EventReactions} for defining event handlers
|
|
207
468
|
*/
|
|
208
|
-
export declare abstract class TemplateState<EventPayloadMapping, Context extends BaseContext, States extends string = 'IDLE'
|
|
209
|
-
abstract eventReactions: EventReactions<EventPayloadMapping, Context, States>;
|
|
469
|
+
export declare abstract class TemplateState<EventPayloadMapping, Context extends BaseContext, States extends string = 'IDLE', EventOutputMapping extends Partial<Record<keyof EventPayloadMapping, unknown>> = DefaultOutputMapping<EventPayloadMapping>> implements State<EventPayloadMapping, Context, States, EventOutputMapping> {
|
|
470
|
+
abstract eventReactions: EventReactions<EventPayloadMapping, Context, States, EventOutputMapping>;
|
|
210
471
|
protected _guards: Guard<Context>;
|
|
211
472
|
protected _eventGuards: Partial<EventGuards<EventPayloadMapping, States, Context, Guard<Context>>>;
|
|
212
|
-
protected _delay: Delay<Context, EventPayloadMapping, States> | undefined;
|
|
473
|
+
protected _delay: Delay<Context, EventPayloadMapping, States, EventOutputMapping> | undefined;
|
|
213
474
|
get guards(): Guard<Context>;
|
|
214
475
|
get eventGuards(): Partial<EventGuards<EventPayloadMapping, States, Context, Guard<Context>>>;
|
|
215
|
-
get delay(): Delay<Context, EventPayloadMapping, States> | undefined;
|
|
216
|
-
uponEnter(context: Context, stateMachine: StateMachine<EventPayloadMapping, Context, States>, from: States): void;
|
|
217
|
-
beforeExit(context: Context, stateMachine: StateMachine<EventPayloadMapping, Context, States>, to: States): void;
|
|
218
|
-
handles<K extends (keyof EventPayloadMapping | string)>(args: EventArgs<EventPayloadMapping, K>, context: Context, stateMachine: StateMachine<EventPayloadMapping, Context, States>):
|
|
476
|
+
get delay(): Delay<Context, EventPayloadMapping, States, EventOutputMapping> | undefined;
|
|
477
|
+
uponEnter(context: Context, stateMachine: StateMachine<EventPayloadMapping, Context, States, EventOutputMapping>, from: States): void;
|
|
478
|
+
beforeExit(context: Context, stateMachine: StateMachine<EventPayloadMapping, Context, States, EventOutputMapping>, to: States): void;
|
|
479
|
+
handles<K extends (keyof EventPayloadMapping | string)>(args: EventArgs<EventPayloadMapping, K>, context: Context, stateMachine: StateMachine<EventPayloadMapping, Context, States, EventOutputMapping>): EventResult<States, K extends keyof EventOutputMapping ? EventOutputMapping[K] : void>;
|
|
219
480
|
}
|
|
220
481
|
/**
|
|
221
|
-
*
|
|
222
|
-
*
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
function
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
482
|
+
* Creates a type guard function for checking if a value belongs to a specific set of states.
|
|
483
|
+
*
|
|
484
|
+
* @remarks
|
|
485
|
+
* This utility function generates a TypeScript type guard that narrows a string type
|
|
486
|
+
* to a specific union of string literals. Useful when you have multiple state types
|
|
487
|
+
* and need to distinguish between them at runtime.
|
|
488
|
+
*
|
|
489
|
+
* @typeParam T - String literal type to guard for
|
|
490
|
+
* @param set - Readonly array of string literals defining the valid states
|
|
491
|
+
* @returns A type guard function that checks if a string is in the set
|
|
492
|
+
*
|
|
493
|
+
* @example
|
|
494
|
+
* Creating state guards for hierarchical state machines
|
|
495
|
+
* ```typescript
|
|
496
|
+
* type MainStates = "idle" | "active" | "paused";
|
|
497
|
+
* type SubStates = "loading" | "processing" | "complete";
|
|
498
|
+
* type AllStates = MainStates | SubStates;
|
|
499
|
+
*
|
|
500
|
+
* const MAIN_STATES = ["idle", "active", "paused"] as const;
|
|
501
|
+
* const isMainState = createStateGuard(MAIN_STATES);
|
|
502
|
+
*
|
|
503
|
+
* function handleState(state: AllStates) {
|
|
504
|
+
* if (isMainState(state)) {
|
|
505
|
+
* // TypeScript knows state is MainStates here
|
|
506
|
+
* console.log('Main state:', state);
|
|
507
|
+
* } else {
|
|
508
|
+
* // TypeScript knows state is SubStates here
|
|
509
|
+
* console.log('Sub state:', state);
|
|
510
|
+
* }
|
|
511
|
+
* }
|
|
235
512
|
* ```
|
|
236
|
-
|
|
237
|
-
* @
|
|
238
|
-
* @returns
|
|
513
|
+
*
|
|
514
|
+
* @category Utilities
|
|
239
515
|
*/
|
|
240
516
|
export declare function createStateGuard<T extends string>(set: readonly T[]): (s: string) => s is T;
|
|
241
517
|
export {};
|
package/package.json
CHANGED
|
@@ -7,5 +7,5 @@ type VendingMachineEvents = {
|
|
|
7
7
|
cancelTransaction: {};
|
|
8
8
|
};
|
|
9
9
|
type VendingMachineStates = "IDLE" | "ONE_DOLLAR_INSERTED" | "TWO_DOLLARS_INSERTED" | "THREE_DOLLARS_INSERTED";
|
|
10
|
-
export declare const createVendingMachine: () => TemplateStateMachine<VendingMachineEvents, BaseContext, VendingMachineStates
|
|
10
|
+
export declare const createVendingMachine: () => TemplateStateMachine<VendingMachineEvents, BaseContext, VendingMachineStates, import("./interface").DefaultOutputMapping<VendingMachineEvents>>;
|
|
11
11
|
export {};
|