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

madux

v0.1.17

Published

Predictable state container with finite state machine

Downloads

51

Readme

Madux is an easy way to represent the internal state of your application as a finite state machine. It can be used in lots of different ways. If you are looking to use madux in your own application, I advise you to take a look at Madux-bind which allows you to connect functions to different transitions of the internal state. This makes it easy to create an application with madux in some kind of declarative way. I am currently writing a real-life command line tool with madux and I will publish a link here when it is finished. I do assume in the example below that you already know the basics of Redux.

Note that this is just a proof-of-concept and not a commercial release so it might be possible that there is some functionality that can be implemented in a more efficient way. However, this library should be stable (run yarn test to see for yourself or check the build badge).

Install it via npm - $ npm install --save madux

import { createMachine } from 'madux';

// Create a basic state machine.
const machine = createMachine('green', 'red');
machine.from('green').to('red').on('error');
machine.from('red').to('green').on('fix');

// Convert it to a store for your state.
const store = machine.buildStore();

// Subscribe to the state.
store.subscribe((prev, action, next) => {
  console.log(prev);
  console.log(action);
  console.log(next);
});

// Have fun!
store.dispatch({ type: 'error' });  // Will log...
store.dispatch({ type: 'fix' });    // Will log...

Defining States

The first thing you should do when creating a state machine with madux, is thinking about the possible states. In our example we will have three states: OUTSIDE, HOUSE, and ROOM. Let's start these as plain JavaScript objects.

// A state is just a plain javascript object with a name property.
const OUTSIDE = { name: 'OUTSIDE' };
const HOUSE = { name: 'HOUSE' };
const ROOM = { name: 'ROOM' };

In some use cases, these basic states might do the trick. In our example we need a bit more data. For example when we are in a HOUSE, we also want to know the number of the house we are in. To do so, we can add props to different states. Note that when we are in the ROOM state we need to have the houseNumber and roomNumber data in order to know the exact room we are in.

// Update the states to have some basic properties. As you can see below, it
// is also possible to add optional properties by setting required to false.

const OUTSIDE = { name: 'OUTSIDE' };

const HOUSE = {
  name: 'HOUSE',
  props: [{
    name: 'houseNumber',
    required: true,
  }],
};

const ROOM = {
  name: 'ROOM',
  props: [{
    name: 'houseNumber',
    required: true,
  }, {
    name: 'roomNumber',
    required: true,
  }],
};

Note that there is one small issue in the definition of these states that we will fix later. For now, let's assume that these states will do the trick.

Actions

Different transitions of the finite state machine can be triggered by different actions. An action is a simple JavaScript object with a type property. The type of the Action should be a predefined string. Let's start by creating the different action types.

// Action types can be represented as simple strings. It is advised to store
// them in variables to prevent some stupid errors. In our example, we only need
// four different action types.
const ENTER_HOUSE = 'ENTER_HOUSE'
const LEAVE_HOUSE = 'LEAVE_HOUSE';
const ENTER_ROOM = 'ENTER_ROOM';
const LEAVE_ROOM = 'LEAVE_ROOM';

// A simple ENTER_HOUSE Action would now look like this.
const action = { type: ENTER_HOUSE };

Note that in order to go from one state to the other with a given action, the parameters of the action should fullfil the properties of the destination state. This means that in order to be able to go from OUTSIDE to ROOM, the action should look like this.

// Note that the houseNumber can be whatever you want.
const action = {
  type: ENTER_HOUSE,
  params: {
    houseNumber: 5,
  },
};

Action Creators

In order to be consistent throughout your application, it is advised to use action creators. These are simple functions that accept parameters in order to create actions. For our four action types, we need four action creators.

// Create some basic action creators.
const enterHouse = houseNumber => ({ type: ENTER_HOUSE, params: { houseNumber } });
const enterRoom = roomNumber => ({ type: ENTER_ROOM, params: { roomNumber } });
const leaveHouse = () => ({ type: LEAVE_HOUSE });
const leaveRoom = () => ({ type: LEAVE_ROOM });

Note that we did not set a houseNumber parameter for the ENTER_ROOM action. As you have seen before, it will now be impossible to access the ROOM state without the houseNumber parameter. It's trivial to see that you don't want to pass the roomNumber and houseNumber to the state when it should already know in which house you are. This can be solved by the build-in merge feature.

For now, a property that we define in the definition of a state has only a name and a value that represents whether or not the property is required. We will add a third (optional) value, namely merge. When merge is true, it will merge the current value of the prop with the properties of the action that is dispatched. So by making roomNumber a merged property, it will automatically be added to all outgoing actions so future states know from which room the action came!

// Lets update our ROOM state to this.
const HOUSE = {
  name: 'HOUSE',
  props: [{
    name: 'houseNumber',
    required: true,
    merge: true,
  }],
};

So from now on we don't have to pass the houseNumber anymore when we leave from the HOUSE state.

Recap

Up till now, we have defined our states, action types that might trigger some transitions and the action creators. Because we are working in a single file for this example, our file now looks like this.


const OUTSIDE = { name: 'OUTSIDE' };
const HOUSE = {
  name: 'HOUSE',
  props: [{
    name: 'houseNumber',
    required: true,
    merge: true,
  }],
};
const ROOM = {
  name: 'ROOM',
  props: [{
    name: 'houseNumber',
    required: true,
  }, {
    name: 'roomNumber',
    required: true,
  }],
};

const ENTER_HOUSE = 'ENTER_HOUSE'
const LEAVE_HOUSE = 'LEAVE_HOUSE';
const ENTER_ROOM = 'ENTER_ROOM';
const LEAVE_ROOM = 'LEAVE_ROOM';

const enterHouse = houseNumber => ({ type: ENTER_HOUSE, params: { houseNumber } });
const enterRoom = roomNumber => ({ type: ENTER_ROOM, params: { roomNumber } });
const leaveHouse = () => ({ type: LEAVE_HOUSE });
const leaveRoom = () => ({ type: LEAVE_ROOM });

Defining The State Machine

We have all the elements for our state machine right now, but haven't defined our state machine itself. This is possible by importing the createMachine function from madux and use the builder pattern as shown below. Note that the order of the states are not important, however the first state will be the initial state.

import { createMachine } from 'madux';

// Define our machine!
const machine = createMachine(OUTSIDE, HOUSE, ROOM);
machine.from(OUTSIDE).to(HOUSE).on(ENTER_HOUSE);
machine.from(HOUSE).to(OUTSIDE).on(LEAVE_HOUSE);
machine.from(HOUSE).to(ROOM).on(ENTER_ROOM);
machine.From(ROOM).to(HOUSE).on(LEAVE_HOUSE);

Building a store

Right now, the only thing we still have to do is build a store which holds our state. You can see the store as some kind of redux-like store, but it works different internally. So dispatch any actions you want from now on!

const store = machine.buildStore();

// You can also subscribe to the store.
// The subscribe function will return a function that makes it possible to unsubscribe.
const unsubscribe = store.subscribe((prev, action, next) => {
  // This function will now be called on every transition!
});

store.dispatch(enterHouse(5));
store.disptach(enterRoom(3));
store.dispatch(leaveRoom());

unsubscribe();

Middleware

It is also possible to add middleware. I will add more documentation about this in the future. If you really need it, check the tests. Basically it can be done like this.

const store = machine.buildStore().bindMiddlewares(middlewareA, middlewareB);

More info

Ask me on Twitter or Gitter.