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

dipper.js

v0.3.5

Published

Tiny event-driven finite state machine library for TypeScript/JavaScript

Downloads

21

Readme

dipper.js

A tiny event-driven finite state machine library for TypeScript/JavaScript.

Instalation

npm install dipper.js

Usage

dipper.js hadles state management while consolidating a few key concepts: states, state machine, events, transitions and hooks.

States

import { State } from 'dipper.js';

const stateOne = new State({ name: 'state one' });

const stateTwo = new State();   // naming states is optional,
                                // it is there to facilitate debugging  
                                // and for future visualization tools

Hooks

Defines state behaviors, most of time the you'll need to setup the hooks enter and/or leave, which the state machine executes by default. Custom hooks will be exectuted when triggered through the state machine.

import { Hook } from 'dipper.js';

const sayHiOnEnter: Hook = {
    name: 'enter',
    action: () => {
        console.log('hi');
    }
}

const sayByeOnLeave: Hook = {
    name: 'leave',
    action: () => {
        console.log('bye');
    }
}

const screamOnEscape: Hook = {
    name: 'escape',
    action: () => {
        console.log('AAAAAARRRGH');
    }
}

Hooks and States

This is how to assiciate a hook with a state

import { State, Hook } from 'dipper.js';

const greeter = (new State())
    .hook(sayHiOnEnter)
    .hook(sayByeOnLeave)
    .hook(screamOnEscape);

or

import { State, Hook } from 'dipper.js';

const greeter = (new State())
    .hook({
        name: 'enter',
        action: () => {
            console.log('hi');
        }
    })
    .hook({
        name: 'leave',
        action: () => {
            console.log('bye');
        }
    })
    .hook({
        name: 'escape',
        action: () => {
            console.log('AAAAAARRRGH');
        }
    });

State Machine

Basics

import { StateMachine } from 'dipper.js';

const stateMachine = new StateMachine();

stateMachine.run({initialState: greeter});

Invoking custom Hooks

stateMachine.trigger(`escape`);

Subscriptions

You may use the subscriptions attribute to hold any state relevant subscriptions, the state machine will automatically unsubscribe from them before moving to the next state.

const subscription = $someObservable.subscribe(() => {
    // do something
});

stateMachine.subscriptions.add(subscription);

Helpers 'before' & 'after'

If you have actions that need to be taken before and/or after every state you may do so by setting the before() and after() callbacks. Those callbacks also have access to the state machine context

stateMachine.before = (context) => {
    console.log(`about to enter a state`, context);
}

stateMachine.after = (context) => {
    console.log(`just left a state`, context);
}

Transitions

Consider the case of a traffic light that has three states:

  • green
  • yellow
  • red
import { StateMachine } from 'dipper.js';
import { green, red, yellow } from './traffic-light.states';

const stateMachine = (new StateMachine())
    .transit({ from: green, to: yellow, on: 'next' })
    .transit({ from: yellow, to: red, on: 'next' })
    .transit({ from: red, to: green, on: 'next' });

stateMachine.run({ initialState: green });

The event next triggers the transition between states.

Events

Events can be emitted in two different ways:

  • Directly into a state
state.emit('event-name');
  • Into the current state through a state machine
stateMachine.emit('event-name');

.emit() vs .trigger()

  • .emit() will send down an event to a state and may or may not trigger a state transition
  • .trigger() will execute a state hook

Context

Local

Local data (context) is emitted to the current state through the emit.

const state = (new State())
    .hook({
        name: 'enter',
        action: (context) => console.log(`hi ${context.local.personName}`);
    })

...

state.emit('some event', { personName: 'John' }); // hi John

Global

The global context is exposed to every state in by the state machine.

const stateMachine = new StateMachine();
stateMachine.context = { country: 'Italy' };

...

const state = (new State())
    .hook({
        name: 'enter',
        action: (context) => console.log(`${context.local.personName} lives in ${context.global.country}`);
    });

...

state.emit('some event', { personName: 'Jessica' }); // Jessica lives in Italy

Typing Contexts

Typing is optional but will allow you to take full advantage of the development tools.

The global and local component of ActionContext can be typed in a number of ways. Below are some examples:

When creating a state

interface GlobalContext {
    country: string;
}

interface LocalContext {
    personName: string;
}

const state = (new State<GlobalContext, LocalContext>())
    .hook({
        name: 'enter',
        action: (context) => {
            console.log(`${context.local.personName} lives in ${context.global.country}`);
        }
    });

However, often times you'll need to get different data to different hooks, therefore you might not want to type the local context upon creating the state. Alternatively it can be typed within the hook action like so:

const state = (new State<GlobalContext>())
    .hook({
        name: 'enter',
        action: (context: ActionContext<GlobalContext, LocalContext>) => {
            // global and local contexts are type here
            console.log(`${context.local.personName} lives in ${context.global.country}`);
        }
    })
    .hook({
        name: 'escape',
        action: (context) {
            // global context is still typed here!
        }
    });

Whe creating a state machine

const stateMachine = new StateMachine<GlobalContext>();

...

const context = stateMachine.context; // context is of type GlobalContext

Therefore:

context.global.country          // ✅
context.global.personName       // ❌

Code Sample

TODO:

  • Unit Tests
  • JSDocs