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

finite-state-machine-ts

v0.1.0

Published

[![CI](https://github.com/alysivji/finite-state-machine-ts/actions/workflows/ci.yml/badge.svg)](https://github.com/alysivji/finite-state-machine-ts/actions/workflows/ci.yml) [![codecov](https://codecov.io/gh/alysivji/finite-state-machine-ts/graph/badge.sv

Readme

finite-state-machine-ts

CI codecov npm version License: MIT Code style: Biome

finite-state-machine-ts is a lightweight, decorator-based finite state machine for TypeScript. Define transitions directly on class methods, keep state on the instance, and let runtime validation enforce allowed state changes.

Installation

npm install finite-state-machine-ts

Make sure your tsconfig.json enables decorators:

{
  "compilerOptions": {
    "experimentalDecorators": true
  }
}

This library is tested with TypeScript 5.0.4, 5.9.3, and 6.0.2.

Basic Usage

import { StateMachine, transition } from "finite-state-machine-ts";

const BackgroundJobState = {
  Queued: "queued",
  Running: "running",
  Completed: "completed",
  Failed: "failed",
} as const;

type BackgroundJobState =
  (typeof BackgroundJobState)[keyof typeof BackgroundJobState];

class BackgroundJob extends StateMachine<BackgroundJobState> {
  static initialState: BackgroundJobState = BackgroundJobState.Queued;
  shouldFail = false;

  @transition<BackgroundJobState, BackgroundJob, [], void>({
    source: BackgroundJobState.Queued,
    target: BackgroundJobState.Running,
  })
  start() {}

  @transition<BackgroundJobState, BackgroundJob, [], void>({
    source: BackgroundJobState.Running,
    target: BackgroundJobState.Completed,
    onError: BackgroundJobState.Failed,
  })
  process() {
    if (this.shouldFail) {
      throw new Error("job failed");
    }
  }

  @transition<BackgroundJobState, BackgroundJob, [], void>({
    source: BackgroundJobState.Failed,
    target: BackgroundJobState.Queued,
  })
  retry() {}
}

const job = new BackgroundJob();

job.start();
console.log(job.state); // "running"

job.process();
console.log(job.state); // "completed"

try {
  const failingJob = new BackgroundJob();
  failingJob.start();
  failingJob.shouldFail = true;
  failingJob.process();
} catch (error) {
  console.error(error); // TransitionExecutionError
  console.log((error as Error).cause); // Error: job failed
}

new Machine() starts from static initialState. Passing a state still restores a persisted machine from any valid state: new BackgroundJob(BackgroundJobState.Failed).

Defining States

This library works with string-valued states. You can define them in whatever TypeScript style fits your codebase:

Preferred: as const object

The examples in this repo use an as const object because it gives you named state values while staying close to plain TypeScript objects.

const JobState = {
  Queued: "queued",
  Running: "running",
  Completed: "completed",
  Failed: "failed",
} as const;

type JobState = (typeof JobState)[keyof typeof JobState];

String union

This is the smallest option and works well if you do not need named constants.

type JobState = "queued" | "running" | "completed" | "failed";

String enum

This keeps the state set explicit and centralized if your codebase prefers enums.

enum JobState {
  Queued = "queued",
  Running = "running",
  Completed = "completed",
  Failed = "failed",
}

All three approaches are supported. Pick the one that matches your team's TypeScript style.

Example Docs

The repo includes a small set of worked examples with Mermaid diagrams and annotated code:

How It Works

The @transition decorator wraps a method and applies runtime checks in this order:

  1. Confirm this.state matches the configured source.
  2. Reject overlapping transitions on the same instance while async work is still pending.
  3. Run every condition function in declaration order.
  4. Execute the original method.
  5. If the method succeeds, set this.state = target.
  6. If a condition or method throws or rejects, optionally set this.state = onError and throw a TransitionExecutionError with the original error attached as cause.

There is no central machine config or separate state graph. Transitions live where the behavior lives: on the methods that perform the work.

Decorated methods stay synchronous when every condition and the method body are synchronous. If any condition is async, or the body returns a promise, the decorated method returns a promise instead, so async-guarded methods should be declared with a Promise return type.

Async Transitions

Conditions and transition bodies can both be synchronous or asynchronous.

import {
  ConcurrentTransitionError,
  StateMachine,
  transition,
} from "finite-state-machine-ts";

const DeploymentState = {
  Pending: "pending",
  Running: "running",
  Completed: "completed",
} as const;

type DeploymentState = (typeof DeploymentState)[keyof typeof DeploymentState];

class Deployment extends StateMachine<DeploymentState> {
  static initialState: DeploymentState = DeploymentState.Pending;

  @transition<DeploymentState, Deployment, [], Promise<string>>({
    source: DeploymentState.Pending,
    target: DeploymentState.Running,
    conditions: [
      async () => {
        await Promise.resolve();
        return true;
      },
    ],
  })
  async start() {
    await new Promise((resolve) => setTimeout(resolve, 50));
    return "started";
  }
}

const deployment = new Deployment();
const pending = deployment.start();

console.log(deployment.state); // "pending"

try {
  deployment.start();
} catch (error) {
  console.error(error instanceof ConcurrentTransitionError); // true
}

await pending;
console.log(deployment.state); // "running"

While an async condition or async body is pending, the machine stays in the source state and blocks other transitions on that same instance with ConcurrentTransitionError. Other machine instances are unaffected.

Why Use This Instead of a Heavier FSM Library?

Use this library when you want a small runtime abstraction, not a full workflow engine. It keeps the API close to normal class methods and avoids the configuration overhead common in more feature-rich state machine libraries.

State Diagrams

You can generate Mermaid state diagrams directly from the transitions declared on a state machine class.

Programmatic API

import { generateStateDiagram } from "finite-state-machine-ts";

const diagram = generateStateDiagram(BackgroundJob, { initialState: "queued" });

console.log(diagram);

Example output:

stateDiagram-v2
  state "queued" as state_0
  state "running" as state_1
  state "completed" as state_2
  state "failed" as state_3
  [*] --> state_0
  state_0 --> state_1: start
  state_1 --> state_2: process
  state_1 --> state_3: process (error)
  state_3 --> state_0: retry

CLI

After building the package, use the bundled command:

fsm-draw-state-diagram --class ./dist/path/to/your-machine.js:YourStateMachine --initial-state off

The --class argument matches the Python library's shape: <module-path>:<export-name>.

API

StateMachine<S>

A minimal base class that stores the current state.

declare class StateMachine<S extends string> {
  static initialState?: string;
  state: S;
  constructor(state?: S);
}

transition(config)

Decorator for transition methods. Decorated methods can be synchronous or asynchronous, and conditions can return boolean or Promise<boolean>.

Use SyncCondition<TMachine> for extracted sync-only guards that should preserve a synchronous transition signature. The broader Condition<TMachine> type allows async guards and should be paired with a promise-returning transition method.

interface TransitionConfig<
  S extends string,
  TMachine extends StateMachine<S> = StateMachine<S>,
  TCondition extends Condition<TMachine> = SyncCondition<TMachine>,
> {
  source: S | readonly S[];
  target: S;
  conditions?: readonly TCondition[];
  onError?: S;
}

generateStateDiagram(machineClass, options?)

Returns Mermaid state diagram markdown for the transitions defined on the class.

Errors

Transition failures use these exported error types:

  • InvalidSourceStateError when the current state is not in source.
  • TransitionConditionFailedError when any configured condition returns or resolves to false.
  • TransitionExecutionError when a condition or the decorated method throws or rejects. The original error is available as error.cause.
  • ConcurrentTransitionError when another async transition is already in progress on the same machine instance.

Contributing

See CONTRIBUTING.md for local setup and contribution guidelines.

Inspiration