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

fseh

v4.2.0

Published

A minimal Finite State Event Handler for JavaScript

Downloads

845

Readme

fseh

A minimal Finite State Event Handler for JavaScript

travis build codecov coverage npm version

Have you ever had to handle an event differently depending on the state of your application? fseh lets you create state-aware event handler that execute different code depending on the current state of your application.

It's basically an implementation of a Finite State Machine, optimized to be used where callbacks and event handlers are expected.

How to Install

npm install fseh

Introductory example

Here's a quick Node.js/Expressjs example. Suppose you what your web application to automatically respond with a standard error message when the application is not ready or down for maintenance: you could check for the current state in all your routes:

var express = require('express')
  , app = express()
  , ready = false;

app.get('/', function(req, res) {
  if (ready) {
    res.send("Thanks for GETting");
  } else {
    res.status(503).send("Sorry, I'm not ready yet...");
  }
});  
app.post('/', function(req, res) {
  if (ready) {
    res.send("Thanks for POSTing");
  } else {
    res.status(503).send("Sorry, I'm not ready yet...");
  }
});  
app.listen(8080);

// connect to a db and perform other init stuff...
// ... or just wait 5 seconds
setTimeout(function() {
  ready = true;
}, 5000);

A better approach would be to create a middleware to do the job:

function checkIfReady(req, res, next) {
  if (ready) {
    next();
  } else {
    res.status(503).send("Sorry, I'm not ready yet...");
  }
}

app.get('/', checkIfReady, function(req, res) {
  res.send("Thanks for GETting");
});  
app.post('/', checkIfReady, function(req, res) {
  res.send("Thanks for POSTing");
});  

Things would get immediately more complicated if you'd like to handle different types of requests differently or if there's a greater variety of states that just ready or not. fseh least you define a Finite State Machine (FSM) and event handlers for each state.

var Machine = require('fseh').Machine
  , express = require('express')
  , app = express()

var fsm = new Machine({
  "setup": {
    "request": function(req, res) {
      res.status(503).send("Sorry, I'm not ready yet...");
    },
    "setup-completed": function() {
      this.enter('ready');
    }
  },
  "ready": {
    "request": function(req, res, next) {
      next();
    }
  }
}, 'setup');

app.use(fsm.eventHandler('request'));

app.get('/', function(req, res) {
  res.send("Thanks for GETting");
});  
app.post('/', function(req, res) {
  res.send("Thanks for POSTing");
});  
app.listen(8080);

setTimeout(fsm.eventHandler('setup-completed'), 5000);

In the example above we've defined a state machine that supports two states, called setup and ready, and the handles the request event differently depending on the state. In the example, the initial state is set up setup in the constructor and the machine transits to the ready state after 5 seconds, when a timer expire triggering a setup-completed event. We then create an event handler for the request event that it's simply used as a Expressjs middleware.

Simple FSM

This is just a very simple scenario: fseh can be used to handle much more complex state machines, like, for instance, a WebRTC negotiation, where several messages need to be exchanged between two or more parties and the receipt of a message might need a radically different handling depending on the current negotiation state.

Usage

Finite State Machines are created instantiating a Machine with a StateTable, that is a collection of states and the corresponding events for each state.

var Machine = require('fseh').Machine

var fsm = new Machine({
  "start": {
    "setup": () => {
      this.enter("ready");
    }
  },
  "ready": {
    "introduce": () => {
      return `Hi, I'm a state machine`;
    },
    "greet": name => {
      return `Hi ${name}!`;
    },
    "meet": name => {
      return `Nice to meet you, ${name}!`;
    }
  }
});

The example above, creates a FSM with two states: a start state, that can only handle a setup event, which will make it transit into the ready state. The ready state supports three events (introduce, greet and meet), all of which return strings.

There are three special event that can be defined in each state:

  • entry, triggered when the FSM enters the state
  • exit, triggered when the FSM is leaving a state
  • *, default event handler, triggered when a specific event handler was not found. If a default handler is not defined and an unknown event is received, an Error('unhandled') is returned.

There are also two special event handlers:

  • defer, the event is queue and the machine will attempt to precess it as soon as another state is entered. Defererred events are processed after the entry event, if defined.
  • noop, the event is ignored, without throwing an error
var Machine = require('fseh').Machine

var fsm = new Machine({
  "start": {
    "setup": () => {
      this.enter("ready");
    },
    "exit": () => {
      console.log('setup completed');
    },
    "introduce": "defer",
    "*": () => {
      return `Sorry, can't talk right now`;
    }
  },
  "ready": {
    "entry": () => {
      console.log('ready to talk');
    },
    "introduce": () => {
      return `Hi, I'm a state machine`;
    },
    "greet": name => {
      return `Hi ${name}!`;
    },
    "meet": name => {
      return `Nice to meet you, ${name}!`;
    }
  }
});

In the above example, the FSM will print to the console fsetup completed and ready to talk when transiting from setup to ready and it'll return Sorry, can't talk right now is, for example, a greet event is received while in the setup state. On the other hand, an introduce event received in the start state will be deferred, i.e. queued and processed in the next state, which in turn could handle it, deferred it again, ignore it (noop) or throw an error.

The StateTable is defined in TypeScript as follows:

export type EventHandler = (...args: any[]) => any;

export interface Handlers {
  ['exit']?: EventHandler
  ['entry']?: EventHandler
  ['*']?: EventHandler | 'defer' | 'noop';
  [name:string]: EventHandler | 'defer' | 'noop' | undefined
}

export type StateTable = { [name:string]: Handlers };

Machine(states:StateTable, initialState?:string)

Creates a new state machine with the definition passed as states and, optionally, transits it to the initialState.

Machine.process(name:string, ...args:any[]):Promise

Processes an event of type name in the current state: args are passed to the event handler, if any, and its return value is wrapped into a Promise. If no handler is found and no default handler is defined, the promise is rejected with Error('unhandled).

Machine.enter(state:string, ...args: any[]):Promise

Transits the machine to the specified state: args are passed to the entry event handler if defined its return value is wrapped into a Promise. If the state is unknown, the promise is rejected with Error('unknown_state').

Machine.eventHandler(name:string): (...args: any[]) => Promise

Creates an event handler for the event name. An event handler is a function the can be passed as a callback to any javascript function or methods and that will trigger the execution of the correct event handler, based on the current state of the state machine. The return value of the actual event handler is wrapped in a promise, and so is any exception that might be throw.

Machine.callbackEventHandler(name:string): (...args: any[]) => void

Creates an event handler similar to the ones created by Machine.eventHandler, with the only exception that if the last argument is a function, it treats it as a node-style callback function(err, data) and uses it to return the outcome of the promise received by the event handler.