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

aze.state-machine

v1.0.1

Published

Base implementation for a state machine handling events

Downloads

5

Readme

          __          __
  _______/  |______ _/  |_  ____
 /  ___/\   __\__  \\   __\/ __ \
 \___ \  |  |  / __ \|  | \  ___/
/____  > |__| (____  /__|  \_____>__    .__
     \/      _____ \/___    ____ |  |__ |__| ____   ____
            /     \\__  \ _/ ___\|  |  \|  |/    \_/ __ \
           |  Y Y  \/ __ \\  \___|   Y  \  |   |  \  ___/
           |__|_|  (____  /\___  >___|  /__|___|  /\___  >
                 \/     \/     \/     \/        \/     \/

Simple API for implementing event-processing state machines in JavaScript.

Installation

npm install --save aze.state-machine

or

npm install --save git+https://github.com/svvac/aze.state-machine.git

Then

const StateMachineBase = require('aze.state-machine');

Usage

Definition

In a nutshell, extend StateMachineBase and supply to the super constructor the parameters defining the state machine.

const StateMachineBase = require('aze.state-machine');
class MyStateMachine extends StateMachineBase {
    constructor(config) {
        super({
            // Define the list of the states here. Convention is caps.
            states: [ 'A', 'B', 'C' ],
            // The state the machine starts in. Defaults to the first state
            init_state: 'A',
            // If set to a positive number, the machine will tick with a
            // heartbeat event (null event) on that interval (milliseconds)
            heartbeat: 1000,
            // Set this to print debug info for the state machine
            debug: true,
        });

        // Do the your initialization process here
    }
}

Method and property names starting with $$ are reserved for internal use by the engine and should not be read or written to, nor called.

Method and property names starting with $ are reserved for the public API of the engine. These can be read or called, but should only written to when a setter property exists.

The rest of the namespace is yours. We put an empty object at this.state if you want to put stuff there.

State

The current state of the machine is exposed by this.$state. It returns the id of the of the current state. To get the name, lookup this.$state_name.

Transitionning to a different state is done by setting this.$state. You can use state IDs or state names transparently.

There is also the .$transition(state) method that does exactly the same thing.

If you need to check if the machine is in a state (or in any of the states in a list), use the .$is(states...) method:

if (this.$is('B', 2)) {
    // We're in B or C state
}
if (this.$is([ 'B', 2 ])) {
    // Same thing
}

Events

You can tick the state machine by feeding it events through the .$push() method. Supply it an event name (a string) and an optional payload (anything).

this.$push('wakeup', 42);

By convention, event names are lowercase.

The heartbeat parameter to the constructor creates a timer that will feed the special null event to the state machine at that interval. You can also set this.$heartbeat to change it afterwards (set to null to deactivate).

Watchers

You can bind an event handler to a state change event, i.e. state activation or deactivation. While the machine remains in the same state, no events are called (even if e.g. you add a A listener while in state A).

The .$on(states, handler) method registers handler to be called when one of the states in states are activated. Conversely, .$off() does the same for state deactivation.

There are also .$once() and .$once_off() that do the same thing but immediately removes the handler after it has been called.

These four methods return a handler identifier that can be passed to .$remove_listener() to unregister it.

Handlers

For every processed event, the engine will look for a handler method in that order:

  1. A method __STATE_NAME__event_name() than handles event_name events when in state STATE_NAME;
  2. A method __STATE_NAME__() that handles all events in state STATE_NAME;
  3. A method ____event_name() that handles all event_name events.

If a string property exists with one of these names, the engine will lookup a method with that name. This is useful for having several states share the same implementation.

The handler methods are passed two arguments; the event name and the payload.

Cascading handlers

By default, the handler resolution stops at the first method found, which is then responsible for handling the event.

If cascade_handlers: true was passed to the constructor however, the engine will lookup consecutive handlers (as per the rules above) in case an event was not handled by a more specific handler.

Handler capacity

The handler can do pretty much anything to the state machine, including transitionning and feeding events.

Zero-transitions

Sometimes an event might need examination by several states before processing the next event. In order to tell the engine to retick with the same event, return false from your handler method.

You can also return a string which will be interpreted as an event name to do a zero-transition with, or a [ event, payload ] array.

This will (re-)add the event at the top of the event queue and tick the engine.

Defensive programming

The engine is implemented such that not explicitely handling a condition will trigger a state machine exception (SMEXCEPT) that will block the engine. In that case, this.$state is undefined.

You must then return true from your handler functions to tell the engine that you have handled a case. Together with early-return programming style, this helps avoid entering unexpected conditions that have not been explicitely handled.

Doing a zero-transition is considered handling an event.

If the null (heartbeat) event is not handled, it is ignored and the machine will not enter SMEXCEPT.

You can react to entering SMEXCEP by implementing the .$panic_handler() method.