npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

fsm-state-manager

v1.1.2

Published

A Flow FSM using a finite state machine pattern

Downloads

25

Readme

State Transition Manager

A simple, flexible and strongly-typed finite state machine manager for managing state transitions in Angular or any TypeScript-based application.


Latest Updates

v1.1.2 — State Subscriptions Added, Error Handling Improved, and Subscription Mode Toggle Introduced


🌐 Table of Contents


🧠 What is a State

In this implementation, a state represents the current phase of an action the user is performing. This could be processing an HTTP request or the status of a filter the user has opened.

Example:

type HttpRequestStateType = 'init' | 'loading' | 'loaded' | 'error';

const initialState = {
  state: 'init',
  appliedData: [],
};

Each state describes a phase of your application logic — e.g., "loading" during a data fetch or "error" when something goes wrong.


⚡ What is an Event

An event is an action that triggers a state change. Thanks to events, we can precisely control transitions between states via defined transition rules.

For example, consider an HTTP request. The initial state before the user clicks a button might be init. When the user clicks the button, an event like fetch is triggered, transitioning to the loading state — because from init, we can only go to loading. Then from loading, we can go to either loaded or error, depending on the outcome (success or failure events).

Example:

type HttpRequestEventType = 'fetch' | 'success' | 'failure' | 'retry';

Events are mapped to states, and when fired, they initiate a transition based on defined rules.


🔁 What is a Transition

Transitions define how we move from one state to another, triggered by events. Transitions are controlled by a transition function. For better type safety and consistency, it’s recommended to define a reusable type for them.

The function takes two arguments: the current state (including any data), and an optional payload used to shape the next state.

Transition Function Signature

type TransitionBaseStateFn<Config extends FSMConfigI> = (
    data: StateDataI<Config>,
    payload?: { appliedData?: Config['data'] } 
  ) => StateDataI<Config>;

🧾 What are Transition Rules

Transition rules define how the state machine should transition between states when specific events occur. If an event happens in a certain state and there's a matching transition rule for it, the rule is executed.

Type Definition

type TransitionRulesType<Config extends FSMConfigI> = {
  [State in Config['state']]: Partial<Record<Config['event'], TransitionRule<Config>>>;
};

🧾 What is FSM Config

FSMConfig is the interface that defines the configuration for a particular implementation of the state manager. For instance, a state manager for HTTP requests will have a different config than one managing filter state.

Type Definition

interface HttpRequestFSMConfigI extends FSMConfigI{
    state: HttpRequestStateType,
    event: HttpRequestEventType,
    rule: TransitionHttpRequestStateFn<HttpRequestFSMConfigI>, 
    data: any // data type coming from the backend
}

export default HttpRequestFSMConfigI;

🧾 What is Transition Guard

A TransitionGuard is a function that prevents a state change based on some condition. It’s useful for scenarios like preventing unauthenticated users from transitioning or validating a state or event before a transition occurs.

Type Definition

type TransitionGuardFn<Config extends FSMConfigI> = (
    currentState?: Config['state'],
    event?: Config['event'],
) => boolean;

Use Case

success: {
  transitionAction: (data: any, payload: any) =>
    payload?.appliedData
      ? { state: 'loaded', appliedData: payload.appliedData }
      : data,
  transitionGuard: () => doSomething(), // some function returning a boolean
}

🧾 What are Options

Options are additional settings that can be optionally passed when creating a state manager instance. They allow enabling devMode or logging transitions. By default, both are set to false.

Definition

new StateManagerFSM(HttpRequestTransitionRules, { devMode: true, logTransitions: true });

What is transition

The transition method is the core feature that performs state changes based on transition rules. It does several safety checks and ensures the transition is valid. When devMode or logTransitions are enabled, it also logs state transitions to the console.

Definition

stateManager.transition('fetch');

stateManager.transition('success', ['data1', 'data2']);

What is canTransition

canTransition is a public method that checks if a transition from the current state is possible using a specific event.

Definition

stateManager.canTransition('failure');

What are setStateData and getStateData

setStateData is a setter method for initializing or overriding the current state. getStateData is a getter method to retrieve the current state data.

Definition

stateManager.setStateData({ state: 'init', appliedData: [] });

stateManager.getStateData(); // { state: 'init', appliedData: [] }

Subscription on changing state

You can subscribe to a specific state change and pass a function that will be triggered when the transition to the desired state occurs. This can be useful for displaying loading notifications, error messages, or any other scenarios — the only limit is your imagination. To subscribe, simply call the subscribe method on the state manager, passing two arguments: the first is the state you want to listen to, and the second is the callback function that should execute when the state changes. To unsubscribe, refer to the variable where you stored the subscription and call the unsubscribe method on it.

This is optional with subscriptionMode.

const stateManager = new StateManagerFSM<Config>(transitionRules, { subscriptionMode: true })

const subscribtion = stateManager.subscribe('loaded', () => {})

benchmarkTransition('fetch');
benchmarkTransition('success', ['data1', 'data2']);

subscribtion.unsubscribe();

benchmarkTransition('fetch');
benchmarkTransition('success', ['data1', 'data2']);

📦 Example of Transition Rules for HTTP Requests

Here’s a complete example that demonstrates how you could define transition rules for managing the lifecycle of an HTTP request:

const HttpRequestTransitionRules: TransitionRulesType<HttpRequestFSMConfigI> = {
  init: {
    fetch: {
      transitionAction: () => ({
        state: 'loading',
        appliedData: [],
      }),
    },
  },
  loading: {
    success: {
      transitionAction: (data: any, payload: any) =>
        payload?.appliedData
          ? { state: 'loaded', appliedData: payload.appliedData }
          : data,
      transitionGuard: () => doSomething(),
    },
    failure: {
      transitionAction: (data: any, payload: any) =>
        payload?.appliedData
          ? { state: 'error', appliedData: payload.appliedData }
          : data,
    },
  },
  loaded: {
    fetch: {
      transitionAction: () => ({
        state: 'loading',
        appliedData: [],
      }),
    },
  },
  error: {
    retry: {
      transitionAction: () => ({
        state: 'loading',
        appliedData: [],
      }),
    },
  },
};

Each transition function returns either a new state or the current one (if unchanged). This makes the state machine predictable, easy to test, and highly reusable.


Use case

type HttpRequestStateType = 'init' | 'loading' | 'loaded' | 'error';

type HttpRequestEventType = 'fetch' | 'success' | 'failure' | 'retry';

type TransitionHttpRequestStateFn<Config extends HttpRequestFSMConfigI> = (
  data: Config['data'],
  payload?: { appliedData?: Config['data'] }
) => Config['data'];

interface HttpRequestFSMConfigI extends FSMConfigI {
  state: HttpRequestStateType,
  event: HttpRequestEventType,
  rule: TransitionHttpRequestStateFn<HttpRequestFSMConfigI>,
  data: any
}

const stateManager = new StateManagerFSM(HttpRequestTransitionRules, { devMode: true, logTransitions: true });

stateManager.setStateData({ state: 'init', appliedData: [] });

stateManager.transition('fetch'); 
// [FSM] Transition: 'init' state → 'loading' state triggered by 'fetch' event

stateManager.transition('success', ['data1', 'data2']); 
// [FSM] Transition: 'loading' state → 'loaded' state triggered by 'success' event

console.log(stateManager.canTransition('failure')); 
// [FSM Warn] We can't transition to another state with event 'failure' from state 'loaded' → false

console.log(stateManager.getStateData().appliedData); 
// ['data1', 'dota2']

Трішки про швидкість

Переходи між стейтами відбуваються майже миттево, завдяки цьому ви можете швидше маніпулювати даними та UI вашого застосунку що буде утримувати користувачів залишатися разом з вами

2518.5655ms
[FSM] Transition: 'init' state → 'loading' state triggered by 'fetch' event
[FSM Benchmark] Transition for event 'fetch' took 0.1023ms
[FSM] Transition: 'loading' state → 'loaded' state triggered by 'success' event
[FSM Benchmark] Transition for event 'success' took 0.0898ms
[FSM] Transition: 'loaded' state → 'loading' state triggered by 'fetch' event
[FSM Benchmark] Transition for event 'fetch' took 0.3693ms
[FSM] Transition: 'loading' state → 'loaded' state triggered by 'success' event
[FSM Benchmark] Transition for event 'success' took 0.0184ms

🧠 Що таке стейт

Стейт у цій реалізації відображає поточний стан якоїсь дії яку виконує користувач. Це може бути або обробка http запиту, або стан в якому знаходиться фільтр відкритий користувачем.

Example:

type HttpRequestStateType = 'init' | 'loading' | 'loaded' | 'error';

const initialState = {
  state: 'init',
  appliedData: [], // гіпотетичні дані які прийдуть з серверу ( або будь які дані які будуть вкладені в стейт )
};

⚡ Що таке івент

Івент це дія, яка спричиняє зміну стану. Саме завдяки ньому ми можемо чітко переходити між станами завдяки правилам переходу.

Наприклад є у нас запит на бекенд, початковий стан до того як користувач натисне на кнопку буде "init". Після того як користувач натисне на кнопку то ініт запустить event, наприклад fetch, який повертає стейт loading, бо з init ми можемо перейти тільки у loading. З loading ми можемо перейти у loaded або error ( завдяки як раз таки івентам success або failure відповідно ).

Example

type HttpRequestEventType = 'fetch' | 'success' | 'failure' | 'retry';

🔁 Що таке переходи

Transitions це як зрозуміло з назви процес переходу з одного стейту в інший враховуючи дію яка відбулась. Для переходів треба використовувати функцію переходу, під яку бажано використовувати окремий тип для більшої консистентності даних. Вона використовує два аргумента одним з яких ж інформація про поточний стан ( його стан та можливі дані які він містить ), та не обовʼязковий аргумент payload який є передаваємими даними в новий стейт.

Transition Function Signature

type TransitionBaseStateFn<Config extends FSMConfigI> = (
    data: StateDataI<Config>,
    payload?: { appliedData?: Config['data'] } 
  ) => StateDataI<Config>;

export default TransitionBaseStateFn;

🧾 Що таке правила переходів

Правила переходів показують машині шляхи, за якими вона повина переключати стейти через івенти ( про які було сказано вище ). Це дозволяє доволі гнучко налаштовувати ці самі переходи. Якщо під час певного стану відбувається подія і для цієї події існує правило переходу, то воно виконується.

Type Definition

type TransitionRulesType<Config extends FSMConfigI> = {
  [State in Config['state']]: Partial<Record<Config['event'], TransitionRule<Config>>>;
};

🧾 Що таке FSMConfig

FSMConfig це інтерфейс який визначає конфігурацію для конкретної імплементації стейт менеджера. Якщо це стейт менеджер для обробки запитів, то це одна конфігурація зі своїми типами стейтів, івентів та функцій для переходу, якщо це стейт менеджер для обробки фільтрів, то це ще одна конфігурація.

Type Definition

interface HttpRequestFSMConfigI {
    state: HttpRequestStateType,
    event: HttpRequestEventType,
    rule: TransitionHttpRequestStateFn<HttpRequestFSMConfigI>, 
    data: any // тип даних які ловимо з бекенду 
}

export default HttpRequestFSMConfigI;

Що таке TransitionsGuard

TransitionsGuardd це тип функції, який дозволяє нам не дати перейти на інший стейт за якоїсь умови, наприклад за відсутності авторизації у користувача, або при умові невалідного івенту або стану. Це дає нам більш гнучко налаштовувати наші переходи між станами, та навіть при валідному правилі переходу не дати це зробити (перехід).

Type Definition

type TransitionGuardFn<Config extends FSMConfigI> = (
    currentState?: Config['state'],
    event?: Config['event'],
) => boolean

export default HttpRequestFSMConfigI;

Use case

      success: {
        transitionAction: (data: any, payload: any) =>
            payload?.appliedData
              ? { state: 'loaded', appliedData: payload.appliedData }
              : data,
        transitionGuard: () => doSomething(), // якась функція що повертає boolean 
      },

Що таке options

options це додаткові налаштування, які ми можемо опціонально вмикати/вимикати в нашому стейт менеджері, прокидуючи в конструктор обʼєкт з двома полями. Поле devMode та поле logTransition. За замовчуванням їх треба ставити false, якщо вам не треба логування, або попереджувальні логи.

Definition

  new StateManagerFSM(HttpRequestTransitionRules, { devMode: true, logTransitions: true });

Що таке transition

transition це головний метод, який нам дозволяє робити всю магію, та використовуючи правила переходів перемикати стейти. Він робить декілька перевірок для того щоб зробити це коректно та не дасть перейти в стейт якщо івент який ми хочемо виконати не є валідним. Також ми можемо уввімкнути devMode та logMode і при використанні цього методу спостерігати за тим які стейти і як перемикаються.

Definition

  stateManager.transition('fetch');

  stateManager.transition('success', ['data1', 'data2']);

Що таке canTransition

canTransition це public функція самого стейт менеджеру яка перевіряє чи є можливість зробити перехід з поточного стану на той, який ми вкажемо в аргументі цієї функції.

Definition

  stateManager.canTransition('failure')

Що таке setStateData та GetStateData

setStateData та getStateData це два методи які є сетером та геттером і дають можливість ініціалізувати поточний стан та отримати значення з поточного стану

Definition

  stateManager.setStateData({ state: 'init', appliedData: [] });

  stateManager.getStateData(); // { state: 'init', appliedData: [] }

📦 Правила переходів для HTTP Requests

Підсумуючи все вище зазначене, можна розглянути приклад правил переходу між станами для http запиту:

const HttpRequestTransitionRules: TransitionRulesType<HttpRequestFSMConfigI> = {
    init: {
      fetch: {
        transitionAction: () => ({
            state: 'loading',
            appliedData: [],
        })
      },
    },
    loading: {
      success: {
        transitionAction: (data: any, payload: any) =>
            payload?.appliedData
              ? { state: 'loaded', appliedData: payload.appliedData }
              : data,
        transitionGuard: () => doSomething(),
      },
      failure: {
        transitionAction: (data: any, payload: any) =>
            payload?.appliedData
              ? { state: 'error', appliedData: payload.appliedData }
              : data,
      }
    },
    loaded: {
      fetch: {
        transitionAction: () => ({
            state: 'loading',
            appliedData: [],
          }),
      }
    },
    error: {
      retry: {
        transitionAction: () => ({
            state: 'loading',
            appliedData: [],
          }),
      }
    },
};

Кожна функція повертає новий стан або поточний, якщо змін не відбулося. Це робить автомат передбачуваним, який легко тестувати та багаторазово перевикористовувати код.


Підписки на зміну станів

Ви можете підписатися на зміну конкретного стану й передати туди функцію, яка спрацює в момент переходу до потрібного вам стану. Це може бути корисно для відображення сповіщень під час завантаження, повідомлень про помилки або будь-яких інших сценаріїв — обмеження тільки у вашій уяві. Щоб підписатися, достатньо звернутися до стейт-менеджера та викликати метод subscribe, передавши в нього два аргументи: перший — це стан, на який потрібно реагувати, другий — функція-колбек, яка виконається під час зміни на цей стан. Щоб відписатися, зверніться до змінної у яку ви зберегли підписку та використайте метод unsubscribe.

Це необовʼязковий фукнціонал, він вимкнений за замовчуванням, для його роботи треба в конструкторі дописати subscriptionMode: true

const stateManager = new StateManagerFSM<Config>(transitionRules, { subscriptionMode: true })

const subscribtion = stateManager.subscribe('loaded', () => {})

benchmarkTransition('fetch');
benchmarkTransition('success', ['data1', 'data2']);

subscribtion.unsubscribe();

benchmarkTransition('fetch');
benchmarkTransition('success', ['data1', 'data2']);

Приклад використання

type HttpRequestStateType = 'init' | 'loading' | 'loaded' | 'error';

type HttpRequestEventType = 'fetch' | 'success' | 'failure' | 'retry';

type TransitionHttpRequestStateFn<Config extends HttpRequestFSMConfigI> = (
    data: Config['data'],
    payload?: { appliedData?: Config['data'] } 
  ) => Config['data'];

interface HttpRequestFSMConfigI extends FSMConfigI{
    state: HttpRequestStateType,
    event: HttpRequestEventType,
    rule: TransitionHttpRequestStateFn<HttpRequestFSMConfigI>, 
    data: any
}

const HttpRequestTransitionRules: TransitionRulesType<HttpRequestFSMConfigI> = {
    init: {
      fetch: {
        transitionAction: () => ({
            state: 'loading',
            appliedData: [],
        })
      },
    },
    loading: {
      success: {
        transitionAction: (data: any, payload: any) =>
            payload?.appliedData
              ? { state: 'loaded', appliedData: payload.appliedData }
              : data,
        transitionGuard: () => doSomething(),
      },
      failure: {
        transitionAction: (data: any, payload: any) =>
            payload?.appliedData
              ? { state: 'error', appliedData: payload.appliedData }
              : data,
      }
    },
    loaded: {
      fetch: {
        transitionAction: () => ({
            state: 'loading',
            appliedData: [],
          }),
      }
    },
    error: {
      retry: {
        transitionAction: () => ({
            state: 'loading',
            appliedData: [],
          }),
      }
    },
  };

const doSomething = () => { return true }

const stateManager = new StateManagerFSM(HttpRequestTransitionRules, { devMode: true, logTransitions: true });

stateManager.setStateData({ state: 'init', appliedData: [] });

stateManager.transition('fetch'); //[FSM] Transition: 'init' state → 'loading' state triggered by 'fetch' event

stateManager.transition('success', ['data1', 'data2']); //[FSM] Transition: 'loading' state → 'loaded' state triggered by 'success' event

console.log(stateManager.canTransition('failure')); //[FSM Warn] We can't transition to another state with event 'failure' from state 'loaded' (false in console.log)

console.log(stateManager.getStateData().appliedData); // ['data1, 'data2']

📝 License

MIT