# finestate

![Version](https://img.shields.io/gitlab/v/release/ymiroshnyk/finestate?include_prereleases) ![Build Status](https://gitlab.com/ymiroshnyk/finestate/badges/master/pipeline.svg) ![Coverage](https://gitlab.com/ymiroshnyk/finestate/badges/master/coverage.svg) ![License](https://img.shields.io/gitlab/license/ymiroshnyk/finestate)

**finestate** is a flexible and extensible Finite State Machine (FSM) library for TypeScript. It helps you model and manage stateful logic in a clear, maintainable way. Whether you're building game logic, complex UI flows, or any state-based application, finestate provides a structured approach with support for hierarchical states, parallel (orthogonal) states, and event-driven transitions. The library is lightweight and general-purpose, making it suitable for a wide range of domains.

## Quick Usage

Get started quickly by defining your states as classes and specifying how they transition on events. Below are a couple of simple examples to illustrate usage.

**Example 1: Toggle between two states**  
Imagine a simple on/off switch that toggles state on a `"toggle"` event:

![Example2 Diagram](https://gitlab.com/ymiroshnyk/finestate/-/raw/51ac462/img/quick_usage1.png)

```ts
import { EventType, Fsm, State, stateDesc } from 'finestate';

class OffState extends State {
  // Entry hook (called when state is entered)
  init() {
    console.log('Entering Off');
  }
  // Event handling
  processEvent(evt: EventType) {
    if (evt === 'toggle') {
      // Transition to OnState
      // You must return on transit() immediately
      return this.transit(OnState); // it will return 'event handled'
    }
    // You may skip returning false and return nothing to specify the event is not processed
    // return false;             // event not handled
  }
  // Exit hook (called when state is exited)
  destroy() {
    console.log('Exiting Off');
  }
}

class OnState extends State {
  init() {
    console.log('Entering On');
  }
  processEvent(evt: EventType) {
    if (evt === 'toggle') {
      return this.transit(OffState);  // Transition back to OffState
    }
  }
  destroy() {
    console.log('Exiting On');
  }
}

// Set up the FSM with OffState as the initial state and OnState as an 
// alternative
const fsm = new Fsm([
  stateDesc(OffState),   // initial state
  stateDesc(OnState)     // another possible state at the same level
]);
fsm.init();              // initialize the state machine (enters OffState)

// Dispatch events to the state machine
fsm.dispatch('toggle');  // triggers transition from OffState to OnState
fsm.dispatch('toggle');  // triggers transition from OnState back to OffState
```

In this example, we define two state classes `OffState` and `OnState` by extending the base `State` class. The `processEvent` method in each class handles the `"toggle"` event by transitioning to the other state. We create an `Fsm` with `OffState` as the starting state. Calling `fsm.dispatch('toggle')` causes the FSM to switch states and call the appropriate entry/exit hooks (`init`/`destroy`).

**Example 2: Hierarchical & Parallel states in a game menu**  
Consider a simple game flow: the game begins with a splash screen, moves to a loading phase, and then enters a main menu. When resources loading is finished, the game goes to `Loaded` state. It has two parallel regions: one for the menu UI (which can be active showing either the main menu or a settings screen, or inactive/hidden) and another for the game content (either no game running or a game in progress). We'll use string events like `"begin"`, `"ready"`, `"play"`, `"quit"`, `"openMenu"`, `"closeMenu"`, `"settings"`, and `"back"` to transition between these states, but in the real life you would use some other markers, like `all resources loaded` or `button clicked`:

![Example2 Diagram](https://gitlab.com/ymiroshnyk/finestate/-/raw/51ac462/img/quick_usage2.png)

```ts
import { EventType, Fsm, State, stateDesc } from 'finestate';

// Root states
class Splash extends State {
  init() {
    console.log('Showing splash screen.');
  }
  processEvent(evt: EventType) {
    if (evt === 'begin') {
      return this.transit(Loading);
    }
  }
  destroy() {
    console.log('Leaving splash screen.');
  }
}

class Loading extends State {
  init() {
    console.log('Loading game resources...');
  }
  processEvent(evt: EventType) {
    if (evt === 'ready') {
      return this.transit(Loaded);
    }
  }
  destroy() {
    console.log('Loading complete.');
  }
}

class Loaded extends State {
  init() {
    console.log('Initialize resource shared to Menu and Game states.');
  }
  // Loaded may handle global events if needed (none in this example)
}

// Menu region states (under Loaded)
class MenuActive extends State {
  init() {
    console.log('Menu is now active.');
  }
  processEvent(evt: EventType) {
    if (evt === 'closeMenu') {
      return this.transit(MenuInactive);
    }
  }
  destroy() {
    console.log('Menu deactivated.');
  }
}

class MenuInactive extends State {
  init() {
    console.log('Menu is inactive.');
  }
  processEvent(evt: EventType) {
    if (evt === 'openMenu') {
      return this.transit(MenuActive);
    }
  }
}

class MenuMain extends State {
  init() {
    console.log('Show main menu screen.');
  }
  processEvent(evt: EventType) {
    if (evt === 'settings') {
      return this.transit(MenuSettings);
    }
  }
  destroy() {
    console.log('Hide main menu screen.');
  }
}

class MenuSettings extends State {
  init() {
    console.log('Show settings screen.');
  }
  processEvent(evt: EventType) {
    if (evt === 'back') {
      return this.transit(MenuMain);
    }
  }
  destroy() {
    console.log('Hide settings screen.');
  }
}

// Game region states (under Loaded)
class NoGame extends State {
  processEvent(evt: EventType) {
    if (evt === 'play') {
      return this.transit(Game);
    }
  }
}

class Game extends State {
  init() {
    console.log('Init resource for the game.');
  }
  processEvent(evt: EventType) {
    if (evt === 'quit') {
      return this.transit(NoGame);
    }
  }
  destroy() {
    console.log('Destroy resources for the game.');
  }
}

// Set up the hierarchical state machine with parallel regions in Loaded
const fsm = new Fsm([
  stateDesc(Splash),    // initial root state
  stateDesc(Loading),
  stateDesc(Loaded, [
    // First child region (Menu): default MenuActive with its sub-states
    stateDesc(MenuActive, [
      stateDesc(MenuMain),
      stateDesc(MenuSettings)
    ]),
    stateDesc(MenuInactive)
  ], [
    // Second child region (Game): default NoGame
    stateDesc(NoGame),
    stateDesc(Game, [
      // Other game substates could be listed here
    ])
  ])
]);
fsm.init();  // enters Splash state initially

// Example event sequence:
fsm.dispatch('begin');      // Splash -> Loading
fsm.dispatch('ready');      // Loading -> Loaded (enters MenuActive/MenuMain and NoGame)
fsm.dispatch('settings');   // MenuMain -> MenuSettings
fsm.dispatch('back');       // MenuSettings -> MenuMain
fsm.dispatch('play');       // NoGame -> Game (game starts, menu still active)
fsm.dispatch('closeMenu');  // MenuActive -> MenuInactive (menu hidden, game still running)
fsm.dispatch('openMenu');   // MenuInactive -> MenuActive (menu shown again, enters MenuMain)
fsm.dispatch('quit');       // Game -> NoGame (game ended, menu still active)
```

In this example, the FSM starts in the `Splash` state. A `"begin"` event causes a transition to `Loading`, and when loading is complete a `"ready"` event transitions into the `Loaded` state. 

The `Loaded` state has two orthogonal child regions running in parallel: a **Menu** region (`MenuActive` vs `MenuInactive`) and a **Game** region (`NoGame` vs `Game`). Upon entering `Loaded`, both regions are initialized: the menu region starts in `MenuActive` (which itself enters its default substate `MenuMain`), and the game region starts in `NoGame`. A `"settings"` event triggers a transition from `MenuMain` to `MenuSettings` (within the menu region), and a `"back"` event returns from `MenuSettings` to `MenuMain`. Independently, a `"play"` event in the game region transitions from `NoGame` to `Game`. The menu can be toggled off with `"closeMenu"`, transitioning from `MenuActive` to `MenuInactive` while the `Game` state remains active. An `"openMenu"` event brings back the menu by transitioning `MenuInactive` to `MenuActive` (entering `MenuMain` by default), and finally a `"quit"` event from `Game` transitions back to `NoGame`. Throughout these transitions, the `init` and `destroy` methods log the state changes. This demonstrates a hierarchical state machine with parallel regions: the menu and game states operate concurrently under the `Loaded` parent state.

## Installation

Install **finestate** via npm (or Yarn):

```bash
npm install finestate
# or with yarn:
yarn add finestate
```

The package includes type declarations and can be used in both Node.js and browser environments. It supports both CommonJS and ESModule imports:

- **ESM Import (TypeScript/modern JS):**

  ```ts
  import { Fsm, State } from 'finestate';
  ```

- **CommonJS require:**

  ```js
  const { Fsm, State } = require('finestate');
  ```

No additional configuration is needed – just install and import.

Additionally, **finestate** provides a UMD (Universal Module Definition) build, suitable for older browsers or scenarios where you want to load the library directly via a `<script>` tag without using a module bundler. In this setup, a global variable named **`FineState`** is exposed on the window object, allowing you to access `Fsm`, `State`, and other exports. This can be useful for quick demos, legacy codebases, or any environment that does not yet support modern bundling or module imports. For example:

```html
<script src="finestate.umd.js"></script>
<script>
  ...
  // Now we can create an FSM using the global FineState object
  const fsm = new FineState.Fsm([
    FineState.stateDesc(MyState)
    // ... other states ...
  ]);

  fsm.init();
  fsm.dispatch('start');
</script>
```

## Core Concepts

**finestate** employs a class-based approach to define FSMs. Here are the core concepts to understand when using the library:

### States as Classes

Each distinct state in your machine is represented by a class that extends the `State` base class. In these classes, you can override lifecycle hooks and event handlers:

- **`init()`** – Called when the state is entered. Use this for entry side-effects or initializing state-specific data.  
- **`processEvent(event)`** – Called when an event is dispatched to this state. Implement your state's event logic here. Return `true` if the event is handled (to prevent further propagation), or `false` if not handled (so parent or sibling states can process it). This is where you can trigger transitions to other states.  
- **`destroy()`** – Called when the state is about to be exited. Use this for cleanup or exit side-effects.

You may define additional methods or properties in your state classes as needed for your application logic. Each state instance can access:
  - **`this.fsm`** – Reference to the `Fsm` controlling the state machine (useful if you need to access global FSM context).
  - **`this.parent`** – The parent state in a hierarchy (or `null` if this state is at the root).
  - **`this.children`** – An array of child state instances (for substates/orthogonal regions).

#### Hierarchical (Nested) States

States can be nested inside other states to form a hierarchy. A parent state can have **substates** (child states) that are active only when the parent is active. For example, a `Moving` state might contain `Walking` and `Running` substates. When the parent state is entered, one of its child states is initialized immediately (the first child listed is the default). Exiting a parent state will recursively exit its active substates.

You can access ancestor states via the `context()` helper. For instance, from within a substate you might call `this.context(MovingState)` to get a reference to the parent `MovingState` (or `null` if it’s not in that context).

#### Orthogonal (Parallel) States

finestate also supports orthogonal regions (parallel states), meaning a state can have multiple **independent child state regions** active at the same time. This is achieved by providing multiple child state arrays in the state description (see **State Configuration** below). Each orthogonal region will initialize its own child state, and events can be dispatched to all parallel children. By default, finestate will stop propagating an event to other regions once one child handles it (this behavior can be configured).

Parallel states are useful for representing aspects of state that evolve independently. For example, a game character could have an independent **Movement** state and **Mood** state running in parallel. (This is an advanced feature; many use-cases won’t need parallel states.)

### Events and Dispatching

An **event** represents something that causes the state machine to react (e.g. a user action, a timer tick, a message, etc.). finestate allows events to be dispatched as:

- A string or number (for simple event identifiers).
- An object that is an instance of a subclass of the provided `Event` class (for more complex or typed events).

To send an event into the state machine, call **`fsm.dispatch(event)`**. The FSM will propagate the event to the active states, following these rules:

- Events are **propagated depth-first by default**: the most deeply nested active state gets the first chance to handle the event via its `processEvent`. If that state doesn’t handle it (`processEvent` returns false), the event bubbles up to its parent state.
- In states with parallel children, the event is forwarded to each child in order. If one child handles the event (returns true), by default the event will **not** be sent to other parallel children (this is the *StopOnProcessed* policy). If no child handles it, the parent’s own `processEvent` is then called.
- If a state’s `processEvent` returns true (indicating the event was processed and possibly a transition occurred), propagation stops at that state. If it returns false, the event continues up to the next state in the hierarchy.
- The **return value** of `fsm.dispatch(event)` will be a boolean indicating if any state in the machine handled the event (`true` if handled, `false` if unhandled).

Using the event system, your `processEvent` methods can contain logic to decide transitions. You can compare the event to known types or instances (for example, `if (evt instanceof MyCustomEvent) {...}` or `if (evt === 'someEvent') {...}`) and perform actions accordingly.

#### Event Dispatch Order and Parallel Region Policy (Advanced)

By default, events propagate from child states up to parent states (a depth-first approach). You can customize the dispatch order and how events propagate in parallel regions for advanced scenarios:

**Dispatch Order (child-first vs parent-first):** Normally, a parent state's children receive events before the parent does (`DispatchOrder.Ascending`). You can override this by setting the protected `dispatchOrder` property to `DispatchOrder.Descending` in a state class (for example, in its constructor or `init`), causing that parent state to process events before its children. For instance, in the following example the parent logs the event before the child when using descending order:

```ts

class Parent extends State {
  init() {
    // Use parent-first dispatch order:
    this.dispatchOrder = DispatchOrder.Descending;
  }
  ...
}
```
or
```ts

class Parent extends State {
  protected dispatchOrder = DispatchOrder.Descending;
  ...
}
```

**Parallel Regions Policy:** If a state has multiple orthogonal child regions, the default policy `DispatchOrthoPolicy.StopOnProcessed` means that if one region handles an event (its `processEvent` returns true), the event will *not* be dispatched to the other regions. To broadcast events to all regions regardless of one handling it, set the state's `dispatchOrthoPolicy` to `DispatchOrthoPolicy.DontStopOnProcessed`. In the example below, the first child handles the event, but because we use `DontStopOnProcessed`, the second child still receives it:

```ts
class ParallelParent extends State {
  protected dispatchOrthoPolicy = DispatchOrthoPolicy.DontStopOnProcessed;
  ...
}
```

**Custom Dispatch Override:** In advanced cases, you can override a state’s `dispatch(event)` method to intercept or filter events. This is rarely needed, but it allows custom control. For example, a state could log every event and then use the default dispatch behavior:

```ts
class LoggingState extends State {
  dispatch(event) {
    console.log(`LoggingState saw event: ${event}`);
    // Perform custom handling or filtering if needed
    return super.dispatch(event); // continue with normal dispatch logic
  }
}
```

Generally, you will not override `dispatch` in typical usage, but the option is available if you need to customize event propagation.

### Entry and Exit Hooks

As mentioned, each state class can implement `init()` and `destroy()` as **entry** and **exit** hooks. finestate calls these at the appropriate times:

- **`init()`** – Called after the state object is constructed and added into the state machine. This is a good place to trigger any actions that should happen upon entering the state (e.g. resetting timers, sending a message, updating UI).
- **`destroy()`** – Called right before the state object is removed from the state machine. Use this to clean up (e.g. clear timers, remove listeners, revert changes made in init).

There is also an `initParams(params: StateParams)` method you can override if you need to handle initialization data passed via the `params` object on `fsm.init()` or `transit()`. By default, `initParams` simply calls `init()`, ignoring any parameters, but you can override it to extract data from `params` when needed.

These hooks allow each state to manage its own enter/exit side effects in isolation, keeping your state logic encapsulated.

### State Configuration

To create an FSM, you need to provide a description of the state structure to the `Fsm` constructor. finestate uses an array-based configuration to define the hierarchy and parallel regions of states. The formal type definition for a state description is:

```ts
// StateType = State class (constructor)
type StateDesc = [ StateType, ...StateDesc[][] ];
```

In simpler terms, a **StateDesc** is a tuple where the first element is a state class (constructor), and the remaining elements (if any) are arrays that describe that state’s child regions. (In code, you can use the helper function `stateDesc(StateClass, ...children)` to construct these tuples more readably.)

- The **first element** of a `StateDesc` is always a `State` subclass (the state itself).
- If a state has *no substates*, its description is just that state (e.g. `` `stateDesc(MyState)` `` with no child arrays).
- If a state has substates, include one array for each orthogonal region:
  - For a simple hierarchy (one region of substates), include one array containing the child state descriptions. The first state in that array will be the default initial substate.
  - For parallel states, include multiple arrays (as separate regions), one per independent region of substates.

**Examples:**

- `` `stateDesc(OffState)` `` – A state with no children.
- `` `stateDesc(MovingState, [ stateDesc(WalkingState, [ stateDesc(RunningState) ]) ])` `` – `MovingState` has one child region. In that region, `WalkingState` is listed first (so it’s the default substate), and `RunningState` is another possible substate in the same region.
- `` `stateDesc(A, [ stateDesc(B) ], [ stateDesc(C) ])` `` – State `A` has two child regions running in parallel. The first region’s default state is `B`, and the second region’s default state is `C` (these would be active concurrently when `A` is active).

When you instantiate the `Fsm` with an array of `StateDesc`, the **first element of the array is taken as the root state** of the state machine. For example:

```ts
const fsm = new Fsm([
  stateDesc(RootState, [
    // Child states of RootState (single region)
    stateDesc(ChildState1, [
      // Child states of ChildState1 (single region)
      stateDesc(GrandChildState)
    ])
  ])
]);
```

This defines `RootState` as the top-level state. `RootState` has one child region containing `ChildState1`, which in turn has a child region containing `GrandChildState`. Calling `fsm.init()` will construct this hierarchy: entering `RootState`, then `ChildState1`, then `GrandChildState`.

You can also define multiple possible root states in the array if your machine can start in different configurations or transition at the top level. In that case, the first state in the list is the initial root, and others are inactive until you transition to them.


### Transitions

A **transition** moves the FSM from one state to another. In finestate, transitions are initiated in code by calling the **`transit()`** method, usually inside a state’s `processEvent`. There are two ways to invoke a transition:

- **From within a state:** Call `this.transit(TargetStateClass, params?)`. This will request the FSM to exit the current state (and any necessary parent states) and enter a new state of type `TargetStateClass`. The optional `params` object can carry data into the new state(s) during initialization.
- **Directly via the Fsm:** You can call `fsm.transit(currentState, TargetStateClass, params?)` if you have a reference to a state and want to transition out of it. However, in practice you rarely need to call this directly; using the state’s own `transit` method is more convenient.

When a transition is triggered, finestate will:
  - Call the `destroy()` hook on the exiting state and any of its substates that are leaving.
  - Instantiate the target state (and any of its parent states, if they weren’t already active) and call `init()` on them.
  - If the new state has its own default substates, those will be created and initialized as well.

Transitions can occur between states at any level of the hierarchy. finestate will automatically determine the proper states to exit and the common ancestor where the new state should be inserted. For example, transitioning from a substate to a sibling substate will exit the current substate and possibly its parent, then enter the target state (and re-enter any necessary parent context if needed).

> **Note:** You should *not* manually instantiate state classes. Always use `transit()` to switch states, so that the FSM can manage the lifecycle (calls to `init()`/`destroy()`) correctly.

> **Note:** When calling `this.transit(OtherState)` inside a `processEvent`, it’s best to **`return`** that call immediately. This ensures the function exits right after initiating the transition (preventing any subsequent logic in `processEvent` from running) and clearly signals that the event was handled.

Below is an example of an extended hierarchical state machine with parallel regions and a step-by-step demonstration of transitions between states. This scenario illustrates how **finestate** manages entry (`init()`) and exit (`destroy()`) hooks across a nested/parallel configuration.

### Transition Sequence Example
Let's suppose we have a state machine with a following diagram:
![Transition Declaration](https://gitlab.com/ymiroshnyk/finestate/-/raw/51ac462/img/transitions_declaration.png)
The declaration will look like this:
```ts
class Root extends State {
  init() { console.log('Root.init()'); }
  destroy() { console.log('Root.destroy()'); }
}

class A1 extends State {
  init() { console.log('A1.init()'); }
  destroy() { console.log('A1.destroy()'); }
}

...

const fsm = new Fsm([
  stateDesc(Root, [
    stateDesc(A1, [
      // Parallel region #1 within A1
      stateDesc(B1),
      stateDesc(C1)
    ], [
      // Parallel region #2 within A1
      stateDesc(D1),
      stateDesc(E1)
    ]),
    stateDesc(A2, [
      // Parallel region #1 within A2
      stateDesc(B2),
      stateDesc(C2)
    ], [
      // Parallel region #2 within A2
      stateDesc(D2),
      stateDesc(E2)
    ])
  ])
]);
```

Below is a hypothetical series of transitions that highlights how **init/destroy** logs are generated. We assume each transition is triggered via something like `this.transit(TargetState)` inside a `processEvent(...)` method.

1. **Initialization (`fsm.init()`)**  

   After calling `init()`, the FSM enters `Root`, then goes to `A1`. Inside `A1`, the first parallel region defaults to `B1`, and the second to `D1`.  
   
   (Active states are highlighted)

   ![Transition Declaration](https://gitlab.com/ymiroshnyk/finestate/-/raw/51ac462/img/transitions_00_init.png)
   **Console logs:**
   ```
   Root.init()
   A1.init()
   B1.init()
   D1.init()
   ```

2. **Transition `B1 -> C1`**  
   Within the first parallel region of A1, `B1` exits and `C1` enters.  
   ![Transition Declaration](https://gitlab.com/ymiroshnyk/finestate/-/raw/51ac462/img/transitions_01_B1_C1.png)
   **Logs:**
   ```
   B1.destroy()
   C1.init()
   ```

3. **Transition `C1 -> A2`**  
   This exit leaves `D1` and `C1` (the active regions inside A1), and then `A1` itself. We enter `A2` and its default parallel children (`B2` and `D2`).
   ![Transition Declaration](https://gitlab.com/ymiroshnyk/finestate/-/raw/51ac462/img/transitions_02_C1_A2.png)
   **Logs:**
   
   ```
   D1.destroy()
   C1.destroy()
   A1.destroy()
   A2.init()
   B2.init()
   D2.init()
   ```

4. **Transition `D2 -> E1`**  
   Since `E1` belongs to A1, we have to exit the entire A2 branch (`B2`, `D2`, and A2) and then enter A1. This time, for A1’s first region, we get `B1` by default again, and in the second region we specifically enter `E1`.  
   ![Transition Declaration](https://gitlab.com/ymiroshnyk/finestate/-/raw/51ac462/img/transitions_03_D2_E1.png)
   **Logs:**
   ```
   D2.destroy()
   B2.destroy()
   A2.destroy()
   A1.init()
   B1.init()
   E1.init()
   ```

5. **Transition `E1 -> C2`**  
   Again we exit A1 (thus `B1` and `E1`), enter A2, and in the first region we now go directly to `C2` instead of `B2`. The second region defaults to `D2`.  
   ![Transition Declaration](https://gitlab.com/ymiroshnyk/finestate/-/raw/51ac462/img/transitions_04_E1_C2.png)
   **Logs:**
   ```
   E1.destroy()
   B1.destroy()
   A1.destroy()
   A2.init()
   C2.init()
   D2.init()
   ```

6. **Transition `C2 -> A2`**  
   If the transition is defined so that `C2` leads back to the “same” parent (A2) as a fresh entry, we effectively exit `C2`, `D2`, and A2, then re-enter A2 with its default parallel states (`B2` and `D2`).
   ![Transition Declaration](https://gitlab.com/ymiroshnyk/finestate/-/raw/51ac462/img/transitions_05_C2_A2.png)
   **Logs:**
   ```
   D2.destroy()
   C2.destroy()
   A2.destroy()
   A2.init()
   B2.init()
   D2.init()
   ```

6. **Transition `B2 -> B2`**  
   If the transition is defined to itself, the state will exit and re-enter.
   ![Transition Declaration](https://gitlab.com/ymiroshnyk/finestate/-/raw/51ac462/img/transitions_05_C2_A2.png)
   **Logs:**
   ```
   B2.destroy()
   B2.init()
   ```

### Deferred Dispatch and Transition Behavior

finestate prevents reentrant state changes by deferring or forbidding certain calls. If `dispatch()` or `transit()` is invoked in the middle of another operation (such as an ongoing dispatch or transition), the library enforces the following rules:

- **Calling `dispatch()` from within another `dispatch()`:** If an event is dispatched while a previous `dispatch` call is still in progress, the new dispatch call is **deferred**. The event will not be processed immediately; instead, it is queued to run right after the current dispatch cycle completes. In this scenario, the inner `dispatch()` returns `false` immediately, indicating that the event was not handled in the current cycle since it has been scheduled for processing after the ongoing dispatch completes.

- **Calling `dispatch()` from within a `transit()` or `init()`:** If `dispatch()` is invoked during a state transition (i.e., from inside a `transit()` or `init()` call), that dispatch is also deferred until the transition is complete. The `dispatch()` call returns `false` right away in this case as well, since the event will only be handled after the current transition ends.

- **Calling `transit()` within `fsm.init()`:** Starting a state transition during the FSM’s initialization phase is not allowed. If `transit()` is called inside an `fsm.init()` (during the setup of the state machine), **finestate** will throw an error. This restriction ensures that no transitions occur while the state machine is still establishing its initial state(s).

- **Calling `transit()` from within another `transit()`:** Nested state transitions are prohibited. If a transition is already in progress and `transit()` is invoked again (before the first transition finishes), **finestate** will throw an error. Only one transition can occur at a time, so any new transition must wait until the current one has fully completed.

## API Documentation

Below is an overview of the main classes and types provided by finestate, and their usage. The API is designed to be simple – most interactions are via the `Fsm` and your own `State` subclasses.

### Fsm class

- **`new Fsm(stateDescriptions: StateDesc[])`** – Constructs a new state machine using the given state description array. You should provide at least one state description (which will be the root state). Child states and parallel regions are described with nested arrays (or using the `stateDesc` helper function) as detailed above.
- **`init(params?: StateParams): void`** – Initializes the FSM and enters the initial state configuration. This will construct the root state (and its default child states, recursively) and call `init()` on all of them. You can pass an optional `params` object that will be forwarded to all states’ `initParams` for initial setup data.
- **`dispatch(event: EventType): boolean`** – Dispatches an event into the state machine. The event can be an instance of a subclass of `Event`, a string, or a number. Returns `true` if some state handled the event, or `false` if the event was unhandled. Use this to trigger transitions or actions in response to external inputs.
- **`rootState: State | null`** – A property referencing the current root state object. After `init()` has been called, this will be the active root state instance. You can use it to inspect the state hierarchy (for example, `fsm.rootState!.children[0]` to get the first child, etc.). This will change if you transition to a different root state.
- **`transit(fromState: State, toStateType: StateType, params?: StateParams): State`** – Transitions from an existing state to a new state of type `toStateType`. This method handles tearing down the appropriate part of the state tree starting at `fromState` and constructing the new state in its place. In most cases you don’t need to call this directly (use `State.transit` inside `processEvent`).

### State class

To create your own states, subclass `State`. The `State` class provides methods and properties as the interface for each state:

- **Lifecycle hooks**:
  - **`init(): void`** – Override this to define behavior when the state is entered. This is called after the state is constructed and attached to the FSM.
  - **`destroy(): void`** – Override this for behavior when the state is exited and about to be destroyed.
  - **`initParams(params: StateParams): void`** – Override it instead of `init()` if you need to handle custom initialization data. By default it calls `init()`.
- **Event handling**:
  - **`processEvent(event: EventType): boolean`** – Override this to handle incoming events for this state. Return `true` if the event is handled (consumed) by this state (which may include performing a transition or some action). Return `false` to indicate the event wasn’t handled here, allowing the event to propagate to parent/other states. The default implementation in `State` returns `false` (no events handled).
  - **`dispatchChildren(event: EventType): boolean`** - This method is inherited, and you typically don't override it. The method dispatches the event to children with respect to `dispatchOrder` and `dispatchOrthoPolicy` properties of the state. Should return `true` if the event was processed by one of children. Override it if you want to make custom way of the event delivery to the children.
  - **`dispatch(event: EventType): boolean`** – This method is inherited, and you typically don’t override it. It will dispatch the event to this state’s children through `dispatchChildren()` and then to `processEvent` of the current event if the event is not processed by the children. In most cases you will call `fsm.dispatch(...)` from outside the FSM, rather than calling `dispatch` on a state directly. You may override the method to make custom events dispatching process.
- **Transition methods**:
  - **`transit(targetState: StateType, params?: StateParams): boolean`** – Call this from within a state (e.g., in `processEvent`) to transition out of the current state to a new state of type `targetState`. This is essentially a helper that calls the FSM’s `transit` method for you. It returns `true` to indicate the event triggering the transition was handled.
- **Context and hierarchy**:
  - **`fsm: Fsm`** – Property pointing back to the `Fsm` instance managing this state.
  - **`parent: State | null`** – The parent state in the hierarchy (or `null` if this state is the root).
  - **`children: Array<State | null>`** – An array of this state’s child state instances. The length of this array equals the number of orthogonal (parallel) child regions defined for the state. Each entry is either `null` (if that region has no active state) or a reference to the active child state in that region. For example, a state with one child region will have `children[0]` as its single substate (or `null` if uninitialized).
  - **`context<T extends State>(stateClass: new(...args: any[]) => T): T | null`** – A helper method to retrieve an ancestor state of a specific class. If this state or any of its parents is an instance of `stateClass`, that instance is returned. Otherwise, it returns `null`. This is useful to access shared context or data from a parent state without global variables.

- **Event dispatch controls (advanced)**:
  - **`dispatchOrder`** (enum `DispatchOrder`) – A protected property that determines whether events are sent to child states first or to the state itself first. By default, this is `DispatchOrder.Ascending` (child states get the event before the parent does, i.e. depth-first propagation). You can set it to `DispatchOrder.Descending` in a subclass (e.g., in the constructor or `init`) if you need the parent state to process events before children.
  - **`dispatchOrthoPolicy`** (enum `DispatchOrthoPolicy`) – A protected property that controls event propagation in parallel regions. The default is `DispatchOrthoPolicy.StopOnProcessed`, meaning if one child in an orthogonal set handles the event, other children will not receive it. If you set this to `DontStopOnProcessed`, every parallel child state will receive the event regardless of others handling it (useful if events should be broadcast to all active substates).

Most of the time, you will only override `init`, `processEvent`, and `destroy` in your states. The other properties and methods are there if you need introspection or advanced control over the state machine’s behavior.

### Event class and EventType

finestate provides an abstract **`Event`** class which you can extend to create custom event types. This is entirely optional – you can use simple strings or numbers as events if that suits your needs. However, using classes for events can be useful for strong typing and richer data.

- **`class Event`** – Base class for custom events. (It doesn’t define any properties itself; you can subclass it to add event payload data or simply use the class type to distinguish events.)
- **`EventType`** – This is a union type defined as `Event | string | number`. It indicates what can be passed to `fsm.dispatch` or `processEvent`. So, you can dispatch an instance of your custom `Event` subclass, or a string/number identifier.

**Example:** You might define `class JumpEvent extends Event { /*...*/ }` and then in `processEvent(evt)`, check `if (evt instanceof JumpEvent) { ... }` to handle that event type.

### StateParams

The library defines a **`StateParams`** interface (empty by default) that you can extend to pass structured data into state initializations. The `params` object you provide to `fsm.init(params)` or `transit(..., params)` will be received by each new state’s `initParams`. For example:

```ts
interface StateParams {
  MyState?: {
    level: number;
    playerName: string;
  }
}

fsm.init({ MyState: { level: 3, playerName: "Alice" } });

...

class MyState extends State {
  initParams(params: StateParams) {
    const { level, playerName } = params.MyState!;
    // Use parameters for the State initialization
  }
}
```

Then your state could override `initParams` (as shown above) to read those values on entry. This is an advanced usage; many times you may not need to pass parameters at initialization.

## TypeScript Support and Module Compatibility

**finestate** is written in TypeScript and ships with complete type definitions. This means you’ll get **strong typing and IntelliSense** out of the box when using it in a TypeScript project. State classes, events, and the FSM methods all have typed signatures, which helps catch errors at compile time.

Even if you use finestate in a JavaScript project, the type declarations can serve as documentation for the API, and you can refer to them for clarity on how to use the library.

The distributed package includes both **ESM (ES6 modules)** and **CommonJS** builds:
- For modern bundlers or Node.js (ESM), the `module` entry point is used (tree-shaking friendly).
- For older environments or CommonJS usage, the `main` entry point provides a CJS bundle.

This means you can import finestate in whatever way your project requires, and it should work seamlessly. The library has no external runtime dependencies, so it’s straightforward to include in browsers or Node. It’s also framework-agnostic – use it with React, Angular, or plain Node.js scripts as needed.

## License

**finestate** is open source and licensed under the [MIT License](LICENSE). You are free to use it in commercial or personal projects. Please see the `LICENSE` file for the full text of the license.

## Contributing

Contributions are welcome! If you find a bug or have an idea for an improvement, feel free to open an issue or submit a pull request on GitHub. When contributing code, please ensure that all tests pass (`npm test`) and add new tests for any new functionality.

We appreciate the community’s help in making finestate better. Whether it’s reporting issues, writing documentation, or adding features, any contribution is valuable.

Happy state modeling with **finestate**!
