0.2.5 • Published 11 months ago

micro-fsm v0.2.5

Weekly downloads
-
License
MIT
Repository
gitlab
Last release
11 months ago

micro-fsm

A minimalistic yet powerful Finite State Machine (FSM) library for JavaScript/TypeScript projects. Only 227 bytes.

A Finite State Machine (FSM) is a computational model that represents a system as a set of distinct states and the transitions between those states. The system can only be in one state at a time, and it transitions from one state to another based on specific inputs or events. FSMs are useful for modeling systems with well-defined behaviors, such as user interfaces, communication protocols, or game logic. They help in designing, implementing, and reasoning about the behavior of such systems.

Features

  • Type-safe transitions: Leverages TypeScript's advanced type system to infer valid states and transitions, enabling compile-time checks.
  • Minimalistic API: Provides a simple and intuitive interface for defining and interacting with state machines.
  • Customizable: Allows for custom logic execution during state transitions through optional functions.
  • Lightweight: Minimal footprint, only 227 bytes minimized. No dependencies.

Installation

npm install micro-fsm

Example

import fsm from "micro-fsm";

const machine = fsm("stopped", {
  start: { from: ["stopped", "paused"], to: "started" },
  stop: { from: ["started", "paused"], to: "stopped" },
  pause: { from: ["started"], to: "paused" },
  resume: { from: ["paused"], to: "started" },
  log: {
    fn: (message: string) => console.log(`[${machine.current}] ${message}`),
  },
});

// The type of `machine` is automatically inferred by TypeScript. The following
// assertion shows the inferred type.
machine satisfies {
  current: "stopped" | "paused" | "started";
  prev: "stopped" | "paused" | "started";
  can(eventName: "start" | "stop" | "pause" | "resume" | "log"): boolean;

  start(): void;
  stop(): void;
  pause(): void;
  resume(): void;
  log(message: string): void;
};

// Starting from 'stopped'
machine.start(); // State changes to 'started'

// Pausing from 'started'
machine.pause(); // State changes to 'paused'

// Resuming from 'paused'
machine.resume(); // State changes to 'started'

// Stopping from 'started'
machine.stop(); // State changes to 'stopped'

// Logging state from 'stopped'
machine.log("Hello, world"); // Logs '[stopped] Hello, world'

Usage

The machine

  • machine.current: This property holds the current state of the FSM.
  • machine.prev: This property stores the previous state before the last transition.
  • machine.can(eventName): This method checks if a given event can be executed from the current state. It returns true if the transition is allowed according to the FSM's configuration, and false otherwise.
  • machine.<transition>(): Perform the transition. Throws if not allowed. If the event's config has a custom function fn, that function is called as part of the transition.

Setting up the FSM

The FSM is initialized with fsm(initialState, config), where initialState is the starting state of the machine, and config is an object defining the states, transitions, and optional actions.

Each key in the config object represents an event name, and its value is an object specifying the conditions for the transition.

  • from: An array listing the states from which the transition can occur. It's optional; if omitted, the transition can happen from any state.

  • to: Specifies the target state of the transition. It's also optional; omitting it means the event doesn't change the state but can still execute logic via fn.

  • fn: A function that executes when the event occurs. This allows for custom logic during transitions, such as logging, validation, or triggering side effects. Like from and to, it's optional.

Advanced state transitions

The FSM allows for defining transitions with custom logic through the fn property. This can be leveraged for various purposes such as validation, logging, or triggering side effects during transitions.

Example: Validation during transition

Imagine you have a state machine for a simple workflow system with states like draft, review, and published. You might want to validate certain conditions before transitioning from review to published.

Only synchronous checks are supported.

import fsm from "micro-fsm";

const workflowMachine = fsm("draft", {
  submit: { from: ["draft"], to: "review" },
  publish: {
    from: ["review"],
    to: "published",
    fn: () => {
      // Custom validation logic here
      if (!someValidationCondition()) {
        throw new Error("Cannot publish due to validation failure.");
      }
    },
  },
});

workflowMachine.submit(); // Moves from 'draft' to 'review'
try {
  workflowMachine.publish(); // Attempts to move from 'review' to 'published'
} catch (error) {
  console.error(error.message); // Handles validation failure
}

API

The library exports a single function that creates a Finite State Machine.

export default function fsm<
  TState extends string,
  TConfig extends Config<TState, Function>,
>(initialState: TState, config: TConfig): FiniteStateMachine<TState, TConfig>;

interface Config<TState extends string> {
  [eventName: string]: EventConfig<TState>;
}

interface EventConfig<TState extends string> {
  from?: readonly TState[];
  to?: TState;
  fn?: Function;
}

type FiniteStateMachine<
  TState extends string,
  TConfig extends Config<TState, Function>,
> = FiniteStateMachineBase<TState, keyof TConfig>;

interface FiniteStateMachineBase<
  TState extends string,
  TEventName extends string,
> {
  readonly current: TState;
  readonly prev: TState;
  can(eventName: TEventName): boolean;
}

type GetEventsFromConfig<T extends Config<string>> = {
  [K in keyof T]: T[K] extends { fn: infer F } ? F : () => void;
};

Note: The actual TypeScript types differ from the API above, in order to infer all types, in order to not have to specify the complex type parameters.

0.2.1

11 months ago

0.2.2

11 months ago

0.2.5

11 months ago

0.2.4

11 months ago

0.2.0

11 months ago

0.1.8

4 years ago

0.1.7

4 years ago

0.1.6

4 years ago

0.1.5

4 years ago

0.1.4

4 years ago

0.1.3

4 years ago