@ue-too/being 0.9.4 → 0.10.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 CHANGED
@@ -1,16 +1,19 @@
1
1
  # being
2
2
 
3
- This is a library that helps with building finite state machines.
3
+ [![npm version](https://img.shields.io/npm/v/@ue-too/being.svg)](https://www.npmjs.com/package/@ue-too/being)
4
+ [![license](https://img.shields.io/npm/l/@ue-too/being.svg)](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
- // src/interface.ts
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
- class TemplateStateMachine {
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": ";AA2BO,IAAM,QAAc,MAAI;AAAA;AAqLxB,MAAM,qBAAqK;AAAA,EAEpK;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,YAAuD;AAAA,EAEjE,WAAW,CAAC,QAAqE,cAAsB,SAAiB;AAAA,IACpH,KAAK,UAAU;AAAA,IACf,KAAK,gBAAgB;AAAA,IACrB,KAAK,WAAW;AAAA,IAChB,KAAK,eAAe,OAAO,KAAK,MAAM;AAAA,IACtC,KAAK,wBAAwB,CAAC;AAAA,IAC9B,KAAK,oBAAoB,CAAC;AAAA,IAC1B,KAAK,QAAQ,cAAc,UAAU,SAAS,MAAM,YAAY;AAAA;AAAA,EAGpE,QAAQ,CAAC,OAAqB;AAAA,IAC1B,KAAK,gBAAgB;AAAA;AAAA,EAMzB,OAAqD,IAAI,MAAqE;AAAA,IAC1H,IAAG,KAAK,WAAU;AAAA,MACd,aAAa,KAAK,SAAS;AAAA,IAC/B;AAAA,IACA,KAAK,kBAAkB,QAAQ,cAAY,SAAS,MAAM,KAAK,QAAQ,CAAC;AAAA,IACxE,MAAM,SAAS,KAAK,QAAQ,KAAK,eAAe,QAAQ,MAAM,KAAK,UAAU,IAAI;AAAA,IACjF,IAAG,OAAO,WAAW,OAAO,cAAc,aAAa,OAAO,cAAc,KAAK,eAAc;AAAA,MAC3F,MAAM,gBAAgB,KAAK;AAAA,MAC3B,KAAK,QAAQ,KAAK,eAAe,WAAW,KAAK,UAAU,MAAM,OAAO,SAAS;AAAA,MACjF,KAAK,SAAS,OAAO,SAAS;AAAA,MAC9B,KAAK,QAAQ,KAAK,eAAe,UAAU,KAAK,UAAU,MAAM,aAAa;AAAA,MAC7E,KAAK,sBAAsB,QAAQ,cAAY,SAAS,eAAe,KAAK,aAAa,CAAC;AAAA,IAC9F;AAAA,IACA,OAAO;AAAA;AAAA,EAGX,aAAa,CAAC,UAA6C;AAAA,IACvD,KAAK,sBAAsB,KAAK,QAAQ;AAAA;AAAA,EAG5C,SAAS,CAAC,UAAsH;AAAA,IAC5H,KAAK,kBAAkB,KAAK,QAAQ;AAAA;AAAA,MAGpC,YAAY,GAAW;AAAA,IACvB,OAAO,KAAK;AAAA;AAAA,EAGhB,UAAU,CAAC,SAAwB;AAAA,IAC/B,KAAK,WAAW;AAAA;AAAA,MAGhB,cAAc,GAAa;AAAA,IAC3B,OAAO,KAAK;AAAA;AAAA,MAGZ,MAAM,GAAgE;AAAA,IACtE,OAAO,KAAK;AAAA;AAEpB;AAAA;AAWO,MAAe,cAAuJ;AAAA,EAG/J,UAA0B,CAAC;AAAA,EAC3B,eAA2F,CAAC;AAAA,EAC5F,SAAkE;AAAA,MAExE,MAAM,GAAmB;AAAA,IACzB,OAAO,KAAK;AAAA;AAAA,MAGZ,WAAW,GAA+E;AAAA,IAC1F,OAAO,KAAK;AAAA;AAAA,MAGZ,KAAK,GAA4D;AAAA,IACjE,OAAO,KAAK;AAAA;AAAA,EAGhB,SAAS,CAAC,SAAkB,cAAkE,MAAoB;AAAA,EAIlH,UAAU,CAAC,SAAkB,cAAkE,IAAkB;AAAA,EAIjH,OAAuD,CAAC,MAAyC,SAAkB,cAA6F;AAAA,IAC5M,MAAM,WAAW,KAAK;AAAA,IACtB,MAAM,eAAe,KAAK;AAAA,IAC1B,IAAI,KAAK,eAAe,WAAW;AAAA,MAC/B,KAAK,eAAe,UAAU,OAAO,SAAS,cAAc,YAAY;AAAA,MACxE,MAAM,cAAc,KAAK,eAAe,UAAU;AAAA,MAClD,MAAM,kBAAkB,KAAK,aAAa;AAAA,MAC1C,IAAG,iBAAgB;AAAA,QACf,MAAM,SAAS,gBAAgB,KAAK,CAAC,UAAQ;AAAA,UACzC,IAAG,KAAK,OAAO,MAAM,QAAO;AAAA,YACxB,OAAO,KAAK,OAAO,MAAM,OAAO,OAAO;AAAA,UAC3C;AAAA,UACA,OAAO;AAAA,SACV;AAAA,QACD,OAAO,SAAS,EAAC,SAAS,MAAM,WAAW,OAAO,OAAM,IAAI,EAAC,SAAS,MAAM,WAAW,YAAW;AAAA,MACtG;AAAA,MACA,OAAO,EAAC,SAAS,MAAM,WAAW,YAAW;AAAA,IACjD;AAAA,IACA,OAAO,EAAC,SAAS,MAAK;AAAA;AAE9B;AAwBO,SAAS,gBAAkC,CAAC,KAAmB;AAAA,EAClE,OAAO,CAAC,MAAsB,IAAI,SAAS,CAAM;AAAA;",
8
- "debugId": "85B5A2C72A8BC3ED64756E2164756E21",
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
- * @description Utility type to derive a string literal union from a readonly array of string literals.
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
- * Example:
11
- * ```ts
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
- export type EventHandled<States extends string> = {
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 being
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>): EventHandledResult<States>;
46
- happens<K extends string>(...args: EventArgs<EventPayloadMapping, K>): EventHandledResult<States>;
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 being
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 being
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>): EventHandledResult<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
- * @category being
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 being
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 being
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 being
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 being
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
- * @description This is the template for the state machine.
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
- * 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.
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
- * @see {@link createKmtInputStateMachine} for an example of how to create a state machine.
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
- * @category being
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'> implements StateMachine<EventPayloadMapping, Context, States> {
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>): EventHandledResult<States>;
190
- happens<K extends string>(...args: EventArgs<EventPayloadMapping, K>): EventHandledResult<States>;
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
- * @description This is the template for the state.
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
- * This is a base template that you can extend to create a state.
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
- * @category being
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'> implements State<EventPayloadMapping, Context, States> {
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>): EventHandledResult<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
- * Example usage
222
- * ```ts
223
- type TestSubStates = "subOne" | "subTwo" | "subThree";
224
- const TEST_STATES = ["one", "two", "three"] as const;
225
- type TestStates = CreateStateType<typeof TEST_STATES>;
226
- type AllStates = TestStates | TestSubStates;
227
-
228
- const isTestState = createStateGuard(TEST_STATES);
229
-
230
- function test(s: AllStates) {
231
- if (isTestState(s)) {
232
- // s: TestStates
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
- * @param set
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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ue-too/being",
3
3
  "type": "module",
4
- "version": "0.9.4",
4
+ "version": "0.10.0",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "https://github.com/ue-too/ue-too.git"
@@ -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 {};