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

stack-fsm-reducer

v1.1.4

Published

Stack finite state machine reducer

Downloads

6

Readme

stack-fsm-reducer

This is simple implementation of stack based finite state machine.

#Instalation using npm:

npm i stack-fsm-reducer --save

or using yarn:

yarn add stack-fsm-reducer

#Motivation

Classic FSM allows you only to transition between states without keeping track of previous states. This reducer can be used to implement the wizard dialog. Let say you have to fill out some data on each stage, and you are not allowed to go to the next form until you finish filling out all fields on current form. Such mechanism can be perfectly described using Stack FSM.

#Example

Let say we are trying to do a RPG game and we need player to define a race of his character and class, not all races can be mages so we need to be sure that we are not presenting to the user wrong options.

const state = { 
  raceId: undefined,
  classId: undefined  
}

Now we want to:

  1. Enter Setup state
  2. Ask user to fill in raceId, stay in this state until he finishes the job
  3. Pass the raceId to the next state
  4. Ask user to select classId, stay in this state until he finishes selection.
  5. Pass both raceId and classId to the next state

Because each state must know to which state transition next it might be difficult to write reusable composable code.

We can do better. Imagine that we could somehow remember from which state we are coming and return to that state with the data retrieved from the user. This can be achieved using Stack FSM, a natural evolution of FSM.

Instead of just switching to a new state we will push the state to a stack.

So we will do:

  1. Push Setup state to stack which will contain our properties to fill out.
  2. If raceId is undefined, push QueryInput state to stack initialized with some query data.
  3. Stay in QueryInput until user sends onResponse action with selected data.
  4. If user sends valid onResponse action pop current state from stack going back to Setup state filling out raceId in that state
  5. If raceId is not undefined and classId is undefined, push once again QueryInput to the stack filling out the state with the query data
  6. Stay in QueryInput until user sends onResponse action with selected data.
  7. If user sends valid onResponse action pop current state from stack going back to Setup state filling out classId in that state
  8. Finally if both raceId and classId is not undefined in we can replace current state with Game state, filling that state with raceId and classId

Notice how QueryInput can be reused in this example.

Create stack FSM reducer:

import {createStackReducer, head, push, pop, splitLastTwo} from 'stack-fsm-reducer'

const stackFSMReducer = createStackReducer(mapOfStates)

Define map of states to reducers. Notice:

  1. Initial state is just empty string
  2. Each state must have stateId property
  3. If you want to match exactly stack with 2 states you need to separate state ids with /
const mapOfStates = {
  '':(state)=>push(state, {stateId: 'setup', raceId:undefined, classId:undefined}),
  'setup':setupReducer,
  'setup/queryInput':queryInputReducer, 
  'game':gameReducer
}

Define state reducers:

const setupReducer = (state, action) => {
  const setupState = head(state)
  if(setupState.raceId === undefined){
    return push(state, {stateId: 'queryInput', query: 'Please enter raceId', options:['human', 'dwarf', 'orc'], writeResponseTo: 'raceId'})
  } else if(setupState.classId === undefined){
    let classOptions
    if(setupState.raceId === 'orc'){
      classOptions = ['warrior', 'warlock']
    } else if(setupState.raceId === 'human'){
      classOptions = ['warrior', 'paladin' ,'mage']
    } else {
      classOptions = ['warrior', 'paladin']
    }
    return push(state, {stateId: 'queryInput', query: 'Please enter classId', options:classOptions, writeResponseTo: 'classId'})
  } else if(setupState.raceId  && setupState.classId) {
    return push(pop(state), {stateId:'game', raceId: setupState.raceId, classId:setupState.classId })
  }
}

const queryInputReducer = (state, action) => {
  switch(action.type){
    case 'onResponse': {
      const [tail, prevState, queryInputState] = splitLastTwo(stack)
      const newPrevState = {
        ...prevState, 
        [queryInputState.writeResponseTo]: queryInputState.options[action.optionId]
      }
      return [...tail, newPrevState]
    }
    default: return state;
  }
}

const gameReducer = (state, action) => {
  //implementation of the game
}

Notice couple of things, stackFSMReducer expects state to be JavaScript array, This library comes with selection of useful functions to manipulate the stack, but since stack is just an array you can use VanillaJS to work with it. You can create infinite loop, be careful with the definition of state transitions.

Inside map of states you can use minimatch like for example:

const mapOfStates = {
  '**/setup':setupReducer,
  '**/queryInput':queryInputReducer,
  '**/game':gameReducer
}

This way you can reuse your reducers no matter the state of the stack.

#License

MIT