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

@evanshortiss/tstate-machine

v2.0.1

Published

TypeScript implementation of StateMachine

Downloads

12

Readme

tstate-machine

StateMachine implementation in TypeScript. This is an updated fork of the excellent SoEasy/tstate-machine.

Overview

Class-based, declarative, strongly typed state machine with hard declared transitions and without autocomplete problems.

Install

npm install reflect-metadata @evanshortiss/tstate-machine --save

Example

A complete example can be found in example/traffic.ts. Run it by issuing an npm run example command.

// A Reflect.defineMetata polyfill is required by tstate-machine
import 'reflect-metadata';

import { StateMachine, PartialProperties } from '@evanshortiss/tstate-machine';

enum Colours {
  Red = 'Red',
  Orange = 'Orange',
  Green = 'Green'
};

// Properties of the TrafficLightStateMachine
type TrafficLightProps = {
  safe: boolean
  message: string
}
// Can be used to represent partial state changes
type PartialTrafficLightProps = PartialProperties<TrafficLightProps>

// A union type that defines valid states for the machine
type TrafficLightStates = Colours.Red|Colours.Orange|Colours.Green

class TrafficLightStateMachine extends StateMachine<TrafficLightProps, TrafficLightStates> {
  /**
   * Define the Red state:
   *  - Inherits the initial properties (passed in constructor)
   *  - Overwrites the initial "message" property value with "STOP"
   *  - Can only transition to the Green state
   */
  @StateMachine.extend(StateMachine.INITIAL, [Colours.Green])
  [Colours.Red]: PartialTrafficLightProps = { message: 'STOP' }

  @StateMachine.extend(StateMachine.INITIAL, [Colours.Green, Colours.Red])
  [Colours.Orange]: PartialTrafficLightProps = { message: 'CAUTION' }

  @StateMachine.extend(StateMachine.INITIAL, [Colours.Orange])
  [Colours.Green]: PartialTrafficLightProps = { message: 'GO', safe: true }

  constructor () {
    super({
      // Tells the state machine what state(s) it can initially transition to
      initialTransitions: [Colours.Green],

      // Define the initial state values for the machine
      props: {
        message: 'OFF',
        safe: false
      }
    })
  }
}

const machine = new TrafficLightStateMachine()

machine.transitTo(Colours.Green)
console.log(`Light is ${machine.currentState}. Message is ${machine.props.message}.`)

machine.transitTo(Colours.Orange)
console.log(`Light is ${machine.currentState}. Message is ${machine.props.message}.`)

machine.transitTo(Colours.Red)
console.log(`Light is ${machine.currentState}. Message is ${machine.props.message}.`)

Usage

  1. Create class that extends StateMachine<ValidProps, ValidStates> and calls the super constructor.
  2. Define states on your class using @StateMachine.extend(parent, transitions)
  3. Use the state machine, and register optional callbacks.

#1 Create your own StateMachine

To create your own state machine you must create class and inherit it from StateMachine class.

All instances of StateMachine start in the default StateMachine.INITIAL state. You must pass initialTransitions and initialProps to the super constructor to correctly initialise the machine.

enum Colours {
  Red = 'Red',
  Orange = 'Orange',
  Green = 'Green'
}

type TrafficLightProps = {
  safe: boolean
  message: string
}
type PartialTrafficLightProps = PartialProperties<TrafficLightProps>
type TrafficLightStates = Colours.Red|Colours.Orange|Colours.Green

class TrafficLightStateMachine extends StateMachine<TrafficLightProps, TrafficLightStates> {

  constructor() {
    super({
      // What state(s) the machine can initially transition to
      initialTransitions: [Colours.Green],

      // Define the initial property values for the machine
      initialProps: {
        message: 'OFF',
        safe: false
      }
    })
  }
}

#2 Define States

  • States are defined as properties on the class.
  • The property name is the state name.
  • Use @StateMachine.extend to define a state. It accepts two arguments:
    • A state to inherit from. This can be State.INITIAL or another state.
    • The state, or states, to which this state can transition.
  • PartialProperties utility is used to:
    • Extract a type containing class properties.
    • Pass this type as a Generic to the StateMachine.
    • This type is used for safety in defining state properties, and the initial state in the super constructor.

The following snippet defines the Green state on this TrafficLightStateMachine.

enum Colours {
  Red = 'Red',
  Orange = 'Orange',
  Green = 'Green'
}

type TrafficLightProps = {
  safe: boolean
  message: string
}
type PartialTrafficLightProps = PartialProperties<TrafficLightProps>
type TrafficLightStates = Colours.Red|Colours.Orange|Colours.Green

class TrafficLightStateMachine extends StateMachine<TrafficLightProps, TrafficLightStates> {

  /**
   * Define the "Green" state (Colours.Green).
   *
   * Extends the initial state, but we override both properties:
   *  - Property "safe=true", because false is overwritten
   *  - Property "message="GO", because "OFF" is overwritten
   *
   * This state can transition to the "Orange" state.
   */
  @StateMachine.extend(StateMachine.INITIAL, [Colours.Orange])
  [Colours.Green]: PartialTrafficLightProps = {
    message: 'GO',
    safe: true
  }

  constructor() {
    super({
      // Tells the state machine what state(s) it can initially transition to
      initialTransitions: [Colours.Green],

      // Define the initial state values for the machine
      initialProps: {
        message: 'OFF',
        safe: false
      }
    })
  }
}

The Red state could be defined as shown below. Only the message property is defined, since this state is uses the inherited false value for safe.

/**
 * Define the "Red" state (Colours.Red).
 *
 * Extends the initial state:
 *  - Property "safe=false", because it's inherited from the initial state
 *  - Property "message="STOP", because it's overwritten
 *
 * This state can transition to the "Green" state.
 */
@StateMachine.extend(StateMachine.INITIAL, [Colours.Green])
[Colours.Red]: PartialTrafficLightProps = {
  message: 'STOP'
}

#3.1 Transition Between States

Call transitTo(targetState: string, ...args: Array<any>) to transition between states. Arguments passed to transitTo() are passed to the onEnter transition callback(s).

const machine = TrafficLightStateMachine()

const ORANGE_TIMEOUT = 4 * 1000 // 4000 milliseconds

machine.transitTo(Colors.Green)
machine.transitTo(Colors.Orange, ORANGE_TIMEOUT)
machine.transitTo(Colors.Red)

Attempting to transition to an invalid state will fail. This can be detected by checking the return value of transitTo.

const machine = new TrafficLightStateMachine()

// Will return undefined, since this is a valid transition
machine.transitTo(Colors.Green)

// Result is "InvalidTransition" since Green => Red is not a valid path
const greenToRedError = machine.transitTo(Colors.Red)

if (greenToRedError) {
  console.log(`Failed to transition due to error: ${greenToRedError}`)
}

#3.2 Transition Callbacks (onEnter and onLeave)

Multiple callbacks can be registered for onLeave and onEnter phases of a state transition.

The onLeave callbacks are called before the transition has occurred. This means that the machine properties have not been updated when the callback is invoked.

The onEnter callbacks are called after the transition has occurred. This means that the machine properties have been updated when the callback is invoked.

const machine = new TrafficLightStateMachine();

const ORANGE_TIMEOUT = 4 * 1000; // 4000 milliseconds

const removeOrangeEnterCb = machine.onEnter(Colours.Orange, (timeout: number) => {
  // Change to Red state after the given timeout
  setTimeout(() => machine.transitTo(Colous.Red), timeout);
});

machine.onLeave(Colours.Orange, () => console.log('Leaving Orange state'));

// Transition to green, then orange (this will trigger a callback)
machine.transitTo(Colors.Green);
machine.transitTo(Colors.Orange, ORANGE_TIMEOUT);

// Remove a registered callback
removeOrangeEnterCb();

API

StateMachine Static Methods

  • @StateMachine.hide() - decorator for wrapping fields/methods that are not related to the state
  • @StateMachine.extend(parentState: string, toStates: Array<string>) - Declares new state. Inherit class properties from parentState. Possible transitions are to states.

StateMachine Instance Methods

  • transitTo(targetState, ...args) - Transition to targetState. Supports optional variadic arguments that will be passed
  • currentState: string - Get the current state the machine is in.
  • is(stateName: string): boolean - Check if current state is stateName.
  • can(stateNameL string): boolean - Check if it's possible to transition to stateName.
  • transitions(): Array<string> - Returns a list possible transitions from the current state.
  • onEnter(stateName: string, cb: (...args: Array<any>) => void): () => void - add onEnter callback
  • onLeave(stateName: string, cb: () => void): () => void - add onLeave callback

Thanks

The interface is peeked here: JS FSM.