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

@iamabdulmoiz/fsm-types

v0.1.0

Published

Compile-time state machine enforcement for TypeScript — invalid transitions are type errors

Downloads

175

Readme

fsm-types

Compile-time state machine enforcement for TypeScript. Invalid transitions are type errors — not runtime crashes. Zero dependencies. No XState. No class instances.

Install

npm install @iamabdulmoiz/fsm-types
# or
pnpm add @iamabdulmoiz/fsm-types

The problem

type OrderStatus = 'pending' | 'confirmed' | 'shipped' | 'delivered'

// Nothing stops this. TypeScript has no opinion.
order.status = 'shipped'  // order was still 'pending'
await db.orders.update({ status: 'shipped' })  // skipped 'confirmed'

The transition logic lives in a comment, a Notion doc, or someone's memory. Add a new status and forget a case somewhere — nobody finds out until a customer's order breaks.

The solution

import { createMachine, send, transition } from '@iamabdulmoiz/fsm-types'
import type { MachineState } from '@iamabdulmoiz/fsm-types'

const orderMachine = createMachine({
  initial: 'pending',
  states: {
    pending:   { on: { CONFIRM: 'confirmed', CANCEL: 'cancelled' } },
    confirmed: { on: { SHIP: 'shipped',      CANCEL: 'cancelled' } },
    shipped:   { on: { DELIVER: 'delivered'                      } },
    delivered: { terminal: true },
    cancelled: { terminal: true },
  },
} as const)

// Extract typed status for your domain model
type OrderStatus = MachineState<typeof orderMachine.config>

// Valid — compiles
const confirmed = send(orderMachine, 'CONFIRM')
const shipped   = send(confirmed,    'SHIP')

// Invalid — TYPE ERROR at compile time
const broken = send(orderMachine, 'SHIP')
// Argument of type '"SHIP"' is not assignable to parameter of type 'never'
// because 'SHIP' is not a valid event from state 'pending'

API

createMachine(config)

Creates a typed machine instance at the initial state.

const machine = createMachine({
  initial: 'draft',
  states: {
    draft:     { on: { PUBLISH: 'published', ARCHIVE: 'archived' } },
    published: { on: { UNPUBLISH: 'draft',   ARCHIVE: 'archived' } },
    archived:  { terminal: true },
  },
} as const)

send(machine, event)

Transitions the machine. Returns a new machine with updated state type. Produces a compile-time type error if the event is not valid from the current state.

const published = send(machine, 'PUBLISH')
// published.state: 'published'

send(machine, 'UNPUBLISH')
// Type error — UNPUBLISH is not valid from 'draft'

transition(config, currentState, event)

For database-driven patterns where you have a string status field. Returns a typed result object instead of throwing.

const result = transition(orderMachine.config, order.status, 'CONFIRM')

if (result.ok) {
  await db.orders.update({ status: result.nextState })
  // result.nextState is typed — not just string
}

can(machine, event)

Runtime check — useful for showing/hiding UI buttons.

const canShip = can(machine, 'SHIP')  // boolean

matches(machine, state)

Type-narrows a machine to a specific state.

if (matches(machine, 'confirmed')) {
  // machine.state is 'confirmed' here
}

inState(entity, state)

Type-narrows any object with a status field.

if (inState(order, 'confirmed')) {
  // order.status is 'confirmed' — TypeScript knows
  await shipOrder(order)
}

Why not XState?

XState is a full runtime state management system — actors, services, parallel states, history states, invoked promises. It is the right tool for complex UI state that needs to be visualized and debugged.

fsm-types is for enforcing business logic transitions at the type level. No runtime overhead, no configuration syntax to learn, no actor model. If your use case is "make invalid order status transitions a compile error", fsm-types is ten lines of config.

License

MIT