npm package: @osvec/nostate
Description: A state machine library for typescript.
Latest version: 0.1.1

# NoState

A state machine library for typescript.


## Install

npm
```
npm install @osvec/nostate
```

yarn

```
yarn add @osvec/nostate
```


## Example


Suppose you are implementing a player module, the state diagram may look like this.


![state diagram](http://image.osvec.com/state-machine-demo.png)


Then, your code will be as follows, these actions are performed only in the appropriate state.


```typescript

import { Event, StateMachine } from "@osvec/nostate";

// define state
type State = "idle" | "playing" | "paused" | "released"; 

export interface StateChangedCallback<S> {
  (preState: S, targetState: S): void;
}

export interface FireFailedCallback<S> {
  (currentState: S, eventName: string): void;
}

export class Player {
  private stateMachine: StateMachine<State>;
  private stateChangedCallback?: StateChangedCallback<State>;
  private fireFailedCallback?: FireFailedCallback<State>;

  // define event
  private startEvent: Event<string>;
  private stopEvent: Event;
  private pauseEvent: Event;
  private resumeEvent: Event;
  private releaseEvent: Event;

  constructor() {
    this.stateMachine = new StateMachine<State>();

    this.startEvent = {
      name: "start",
      action: this.realStart,
    };
    this.stopEvent = {
      name: "stop",
      action: this.realStop,
    };
    this.pauseEvent = {
      name: "pause",
      action: this.realPause,
    };
    this.resumeEvent = {
      name: "resume",
      action: this.realResume,
    };
    this.releaseEvent = {
      name: "release",
      action: this.realRelease,
    };

    // config transitions and initial state
    this.stateMachine.configure({
      initialState: "idle",
      transitions: [
        { source: "idle", event: this.startEvent, target: "playing" },
        { source: "idle", event: this.releaseEvent, target: "released" },
        { source: "playing", event: this.pauseEvent, target: "paused" },
        { source: "playing", event: this.stopEvent, target: "idle" },
        { source: "playing", event: this.releaseEvent, target: "released" },
        { source: "paused", event: this.resumeEvent, target: "playing" },
        { source: "paused", event: this.stopEvent, target: "idle" },
        { source: "paused", event: this.releaseEvent, target: "released" },
      ],
    });

    this.stateMachine.on("stateChanged", (preState, targetState) => {
      console.log(`on state changed ${preState} -> ${targetState}`);
      this.stateChangedCallback?.(preState, targetState);
    });
    this.stateMachine.on("fireFailed", (currentState, event) => {
      console.log(`can't call ${event} at state ${currentState}`);
      this.fireFailedCallback?.(currentState, event);
    });
  }

  start(album: string) {
    this.stateMachine.fire(this.startEvent, album);
  }

  stop() {
    this.stateMachine.fire(this.stopEvent);
  }

  pause() {
    this.stateMachine.fire(this.pauseEvent);
  }

  resume() {
    this.stateMachine.fire(this.resumeEvent);
  }

  release() {
    this.stateMachine.fire(this.releaseEvent);
  }

  getState() {
    return this.stateMachine.getCurrentState();
  }

  on(type: "stateChanged", callback: StateChangedCallback<State>): void;
  on(type: "fireFailed", callback: FireFailedCallback<State>): void;
  on(type: "stateChanged" | "fireFailed", callback: StateChangedCallback<State> | FireFailedCallback<State>) {
    if (type === "stateChanged") {
      this.stateChangedCallback = callback as StateChangedCallback<State>;
    } else if (type === "fireFailed") {
      this.fireFailedCallback = callback as FireFailedCallback<State>;
    }
  }

  private realStart(album: string) {
    console.log(`real start: ${album}`);
  }
  private realStop() {
    console.log(`real stop`);
  }
  private realPause() {
    console.log(`real pause`);
  }
  private realResume() {
    console.log(`real resume`);
  }
  private realRelease() {
    console.log(`real release`);
  }
}



```