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 🙏

© 2024 – Pkg Stats / Ryan Hefner

xmachina

v0.0.8

Published

Simple Typesafe State Machine

Downloads

9

Readme

xmachina

Simple State Machine - Small footprint - no dependencies. Easy to create and includes built-in optional event subscriptions.

NPM version Coverage Status

Allows working with a typesafe state machine in either node or browser. Although you can use strings to represent states, also allows numbers/enums. Pub/sub mechanism for events is also typesafe.

100% code coverage. Fluent API for building state machines or you can create you own with a Map (and extra edge properties by extending Transition).

Can declare callbacks for onEnter and onLeave for states as well as per transition, but the powerful subscription mechanism makes it easy to track all state/transition changes.

To include in your project:

yarn add xmachina

| Create a lightswitch that starts out on and then turn it off.

const LightState = {
  On: 'On',
  Off: 'Off',
}

const LightTransition = {
  TurnOff: 'TurnOff',
  TurnOn: 'TurnOn'
}

const machina = createMachina(LightState.On)
  .addState(LightState.On, {
    description: 'turn off light switch',
    edge: LightTransition.TurnOff,
    nextState: LightState.Off
  })
  .addState(LightState.Off, {
    description: 'turn on light switch'
    edge: LightTransition.TurnOn,
    nextState: LightState.On
  })
  .build();

// before calling start() you can register for notifications (you can register 'after' start(), but will miss events from before you subscribe)
machina.subscribe((eventData) => console.log(`received: ${eventData.event} -> ${eventData.value.new}`));
// there are optional subscribe parameters that are strongly typed to State/Transition
machina.subscribe((eventData) => console.log(`single: ${eventData.event} -> ${eventData.value.new}`), NotificationType.StateEnter, LightState.On);
machina.start();
// all: StateEnter -> On
// single: StateEnter -> On
const newState = machina.trigger(LightTransition.TurnOff);
// all: StateEnter -> Off
// string enums are optional - supports all enum types
enum LightState {
  On = 'On',
  Off = 'Off'
};

enum LightTransition {
  TurnOff = 'TurnOff',
  TurnOn = 'TurnOn'
}

const machina = createMachina<LightState, LightTransition>(LightState.On)
  .addState(LightState.On, {
    edge: LightTransition.TurnOff,
    nextState: LightState.Off,
    description: 'turn off light switch'
  })
  .addState(LightState.Off, {
    edge: LightTransition.TurnOn,
    nextState: LightState.On,
    description: 'turn on light switch'
  })
  .build();

// before calling start() you can register for notifications (you can register 'after' start(), but will miss events from before you subscribe)
machina.subscribe((eventData: EventData<LightState | LightTransition>) => console.log(`received: ${eventData.event} -> ${eventData.value.new}`));
// there are optional subscribe parameters that are strongly typed to State/Transition
machina.subscribe((eventData: EventData<LightState | LightTransition>) => console.log(`single: ${eventData.event} -> ${eventData.value.new}`), NotificationType.StateEnter, LightState.On);
machina.start();
// all: StateEnter -> On
// single: StateEnter -> On
const newState = machina.trigger(LightTransition.TurnOff);
// all: StateEnter -> Off

The same state machine can be built declaratively without the fluent builder - the declaration is a bit lengthy to allow extending transitions with custom properties/method.:

const machina = new Machina(LightState.On, new Map([
  [LightState.On,
  {
    outEdges: [{
      description: 'turn off light',
      nextState: LightState.Off,
      on: LightEdge.TurnOff
    }]
  }],
  [LightState.Off, {
    outEdges: [{
      description: 'turn on light',
      nextState: LightState.On,
      on: LightEdge.TurnOn
    }]
  }]
]))
const machina = new Machina(LightState.On, new Map<LightState, NodeState<LightState, LightEdge, Transition<LightState, LightEdge>>>([
  [LightState.On,
  {
    outEdges: [{
      description: 'turn off light',
      nextState: LightState.Off,
      on: LightEdge.TurnOff
    }]
  }],
  [LightState.Off, {
    outEdges: [{
      description: 'turn on light',
      nextState: LightState.On,
      on: LightEdge.TurnOn
    }]
  }]
]))

The code examples don't show the full API, but besides registering for events via pub/sub you can also pass in callbacks for onEnter/onLeave of State and when onTransition is traversed between states.

const LightState = {
  On: 'On',
  Off: 'Off',
}

const LightTransition = {
  TurnOff: 'TurnOff',
  TurnOn: 'TurnOn'
}

// with fluent/builder API
const machina = createMachina(LightState.On)
  .addState(LightState.On, {
      description: 'turn off light switch',
      edge: LightTransition.TurnOff,
      nextState: LightState.Off,
      onTransition: async () => console.log('TurnOff transition')
    },
    async () => console.log('Enter "On" state'),
    async () => console.log('Leave "On" state')
  )
  .addState(LightState.Off, {
      description: 'turn on light switch',
      edge: LightTransition.TurnOn,
      nextState: LightState.On,
      onTransition: async () => console.log('TurnOn transition')
    },
    async () => console.log('Enter "Off" state'),
    async () => console.log('Leave "Off" state')
  )
  .buildAndStart();

// with Machina constructor
const machina = new Machina(LightState.On, new Map([
  [LightState.On,
  {
    outEdges: [{
      on: LightTransition.TurnOff,
      description: 'turn off light',
      nextState: LightState.Off,
      onTransition: async () => console.log('TurnOff transition')
    }],
    onEnter: async () => console.log('Enter "On" state'),
    onLeave: async () => console.log('Leave "On" state')
  }],
  [LightState.Off, {
    outEdges: [{
      on: LightTransition.TurnOn,
      description: 'turn on light',
      nextState: LightState.On,
      onTransition: async () => console.log('TurnOn transition')
    }],
    onEnter: async () => console.log('Enter "Off" state'),
    onLeave: async () => console.log('Leave "Off" state')
  }]
]))
machina.start();

Name inspired from the movie ex-machina, but a tribute to popular library xstate (did not find machina.js till after - it does not look to be actively maintained). Why create a new library when there was already so many alternatives?

  1. :white_check_mark: small footprint ~40kB NPM (includes maps and typings)
  2. :white_check_mark: allow enumerations/numbers as first class citizens (not just strings)
  3. :white_check_mark: strong typing without forcing strings values on transitions
  4. :white_check_mark: easy pub/sub that supports subscriptions optionally with filters at subscription time
  5. :white_check_mark: async transitions. can choose to just call or await/handle promise
  6. :white_check_mark: nested hierarchies

The library is intentionally minimalistic. It is intentional that application state is managed outside of the state machine - will be showing examples of that in the recipes.

TODO:

  • add api/recipes page