GithubHelp home page GithubHelp logo

davestewart / javascript-state-machine Goto Github PK

View Code? Open in Web Editor NEW
343.0 14.0 25.0 2.16 MB

An expressive, feature-rich, event-driven JavaScript finite-state machine

Home Page: http://statemachine.davestewart.io

JavaScript 100.00%

javascript-state-machine's Introduction

JavaScript State Machine

state-machine-map

Abstract

State Machine is a library for managing a finite set of states, and moving between them via actions and transitions.

From its intuitive configuration through its powerful event-based architecture and rich API, State Machine makes it easy to describe and manage interaction with complex state-dependent systems like components, multi-step forms, purchase funnels, visualisations or games.

Features

State Machine has been designed from the outset to feel intuitive and fun to use:

  • Easily-configurable via JavaScript config or instance methods
  • DSL for shorthand transition and handler assignment
  • Add and remove states and actions on the fly
  • Pause, resume, cancel or end transitions at any point
  • Handle system, state, action and transition events
  • Rich API and system introspection
  • Object-oriented architecture, fully-inspectable in DevTools

Demo

View the live demo at:

To run / tinker with the demo locally, see the Development section.

Installation

State Machine can be used directly in the browser, or in a Browserify, Node or ES6 project.

Install via NPM using:

npm install state-machine

Note: If you are expecting the package wheeyls/stateMachine it has now been deprecated. To continue to use that package in your project, ensure you use the version 0.3.0 in your package.json.

Docs

View the documentation at:

Development

Installation

Clone the repo using:

git clone https://github.com/davestewart/javascript-state-machine

Tasks

The following NPM tasks are available, via npm run <task>:

  • dev - compile and watch the source to state-machine.js
  • build - compile the source to state-machine.min.js
  • demo - compile, watch and copy the development build to demo/ and serve demo files at http://localhost:8888
  • test - run all tests

Testing

To run a single or set of tests, use the following syntax:

  • npm run test -- --grep="<filename>"

Mentions

Inspired by @jakesgordon's JavaScript State Machine.

Special thanks to Michael Wheeler (@wheeyls) who very kindly donated the NPM package name state-machine.

javascript-state-machine's People

Contributors

davestewart avatar eaguad1337 avatar justinmchase avatar quantacorpwim avatar stevenblack avatar stonecypher avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

javascript-state-machine's Issues

Ability to capture cause of error on transition cancellation

Howdy - I was starting to integrate state-machine into a project but it seems to be lacking a feature I wanted: at simplest, it's the ability to capture and propagate the cause of a transition cancellation.

My actual desire is the ability to treat a call to fsm.do('action') like a promise: if the transition succeeds, do X, if the transition fails, do Y, with an argument explaining why the transition failed. I think I could probably emulate this with an argument passable to fsm.cancel(error) that is then propagated to the cancel transition event. (I'd also appreciate a system-level cancel event to hook into.)

Anyway I'll probably just implement my own (far simpler) state machine but this project seems to have a lot of care behind it so thought you might appreciate the idea. No worries if it is not a use case you are interested in supporting. Thanks!

Create an `actions` object

Rather than fsm.do('foo') a more expressive syntax would be something like fsm.foo().

However, I don't want to pollute the main StateMachine instance, so what about populating an actions object?

var a = fsm.actions
if(a.next()) {
  a.reset()
}

Should be simple to implement, maybe even as a helper.

I just forked master and 3 tests are failing

I have a bug I want to fix but before I do that I am trying to get the current tests up and running. It appears that the code right out of master is failing tests however.

I could easily be doing something wrong though but I don't see a contributors guide so I'm not sure what step I may be missing.

I did:

git clone ... jsm; cd jsm
npm i
npm test

Is there a step I am missing?

Here are the test failures I'm seeing:

  34 passing (41ms)
  3 failing

  1) Given an instance of my library "before all" hook for "results in 'state.a.next state.b.next '":
     TypeError: Cannot read property 'hasOwnProperty' of undefined
      at src/core/classes/Config.js:9:24
      at Array.forEach (<anonymous>)
      at new Config (src/core/classes/Config.js:7:10)
      at StateMachine.initialize (src/StateMachine.js:88:27)
      at new StateMachine (src/StateMachine.js:18:10)
      at Context.<anonymous> (test/library.spec.js:13:11)

  2) Given an instance of my library when I set a property it should return the name:
     ReferenceError: fsm is not defined
      at Context.<anonymous> (test/valuemap.spec.js:22:20)

  3) Given an instance of my library when I need the blah should return the name:
     ReferenceError: fsm is not defined
      at Context.<anonymous> (test/valuemap.spec.js:28:20)

Question: Serialise / deserialise state

Hi,
Sorry for raising an issue - I was't sure where to ask the question...

I'm looking for a simple way (API?) to be able to save/serialise the state externally and then create a new state machine at a later time (possible separate process) using the previously saved state.

It's for use in a stateless context over multiple executions.
Any pointers/suggestions?

Great project by the way!

Using vue-router with StateHelper pushes state names as route paths instead of as route names

Steps to reproduce:

Modify the route paths in the example demo/html/examples/vue/vue-router.html vue-router configuration lines 59-69:

var router = new VueRouter({
    mode: 'hash',

    // note that route names match state names
    routes: [
        { path: '/state-a', name: 'a', component: StateA },
        { path: '/state-b', name: 'b', component: StateB },
        { path: '/state-c', name: 'c', component: StateC },
        { path: '*',  name: '*', component: NotFound }
    ]
});

Run:

npm run demo

package.json: Incorrect "main" value causing "Cannot find module 'state-machine'" error

I've been struggling with integrating this package into a Node app; I've encountered a couple of problems:

  1. Error: Cannot find module 'state-machine'
    I assume that this is caused by an incorrect "main" value in package.json (the file, lib/state-machine.js) is not included in the package. Thinking that the file must be built, I ran npm build to no avail.

Possible solution: Change "main" value to "src/StateMachine.js"

  1. "SyntaxError: Unexpected token import"
    So I changed "main" to "src/StateMachine.js" and I received the following error which I assume is caused by Babel not being loaded.

Possible solution: Unsure

Please advise on how I should proceed.

Aside: It would be really helpful to have installation and configuration instructions on the GitHub page, and to ensure that the instructions function properly in vanilla environments. You may have lots of features and decent documentation, but unless one can integrate the package quickly into multiple popular environments (e.g., Node, Web ), I suspect that most people would look for another package.

Feedback on StateMachine Beta

I have a reasonably large laundry list of changes and improvements I'd like to implement before the final 1.0 release (and perhaps a 1.1 release).

I've shared them all here:

Primarily I'm interested in my proposed usability tweaks for 1.0 and proposed (breaking) changes for 1.1

Cheers,
Dave

Finite State Machine Observations

My thoughts after looking at the JSM and having an understanding of Vue/Vuex and a fascination with FSMs are many. Presently, most frameworks are data driven and D3/Data-Driven-Documents was the first experience I had with this revolutionary paradigm shift.

Then React came along and we all got views that were a function of state. Now, Vue/Vuex takes this to the next stage and I'm beginning to see the world as just state/info driving different types of machines (for rendering, navigation, etc).

I have pondered the FSM concept for sometime and each one is built for it's own purpose it seems. In the end I'm inclined to use as few dependancies as possible and define FSMs in the most open-ended and universal way if possible.

IMO a FSM for the purpose of navigation/transitions is just another Vuex store module, having methods for it's own unique purpose. Then states are just open ended nodes defined in a JSON (a universal format) document, which is then loaded into it's specific store module. The state nodes can specify almost anything, routes to push to the router, etc. not only transitions.

Once loaded a given node/state can be activated and subsequently processed by RxJS or standard Vuex method types to ready the state for Vue components to consume. From there it is just a matter of mapping/injecting the state into the components that require it using the Vuex provided helpers.

This is how I have distilled the FSM concept for my usage, trying my best to implement as much as possible with as few multi faceted APIs as possible. I think I'm getting better at it, especially with Vue and RxJS covering most of my bases now :)

readable example?

Hi,

the demo is not readable as it is webpack compiled...
do you have an example that does not require all the browser bullshit to be in the way?

How to use this for backend rendering

Hi @davestewart,

I really like this project and would like to use it for server side rendering (expressjs) with the rendering logic all handled by the state machine. My app requires sessions and storing each users session and the data they submit across each page. So i guess what i could also do is store the state of the state machine as session data after each request by using StateHelper.object(this.fsm).data - however, how do i instantiate a new StateMachine and reuse the state data I have stored as the new starting point?

Main issue is trying to persist the state machine and the associated state for each session for server side rendering. If you have any ideas that would be great.

thanks

Deva

npm outdated

A constant struggle, to be sure.

Steps to reproduce:

  1. make a fresh git clone of the repo.
  2. $ npm install.
  3. Post install, $ npm outdated

Observe: Out of the box, the following packages are lagging. Some significantly.

$ npm outdated
Package                          Current  Wanted  Latest  Location
babel                             6.3.13  6.3.13   6.5.2  state-machine
babel-core                        6.1.18  6.1.18  6.18.2  state-machine
babel-eslint                       5.0.0   5.0.0   7.1.1  state-machine
babel-loader                       6.1.0   6.1.0   6.2.8  state-machine
babel-plugin-add-module-exports    0.1.2   0.1.2   0.2.1  state-machine
babel-preset-es2015               6.3.13  6.3.13  6.18.0  state-machine
chai                               3.4.1   3.4.1   3.5.0  state-machine
eslint                             1.7.2   1.7.2  3.10.2  state-machine
eslint-loader                      1.1.0   1.1.0   1.6.1  state-machine
mocha                              2.3.4   2.3.4   3.1.2  state-machine
webpack                           1.12.9  1.12.9  1.13.3  state-machine
yargs                             3.32.0  3.32.0   6.4.0  state-machine

Aside: This will also eliminate some of the deprecation warnings during $ npm install

Reduce bundle size

The minified bundle is 25K, which I think is too big.

It would be nice to reduce this, either at source or by optimising for tree-shaking.

Extract DSL parsing to a separate class

The StateMachine class would then only take Object config. String options could be pre-processed and saved as JSON, then used in size-critical builds:

import { parse } from 'state-machine'
const config = parse(options) // could be saved as json
var fsm = new StateMachine(config)

Another option might be to create this using a plugin system like Vue, i.e.:

StateMachine.use(ParseConfig)

All files needed for parsing config would then be omitted from the final bundle.

Could do some rudimentary tests first to see the impact

Refactor to be simpler

Right now, ValueMap is used to set and get values. Could this be simplified, or target nodes targeted using concatenation rather than parsing, then the class omitted?

var target = transitions[foo][bar]

Should check the code to see how much of an impact this actually is. Could be a tradeoff too far.

Reduce lines of code

Not sure how feasible this is

Complete documentation

Complete documentation

Main

  • Overview
  • Setup
  • Usage

Config

  • Options
  • Transition shorthand
  • Handler shorthand

API

  • StateMachine
  • Events
  • TransitionMap

Does this fit your JSM concept?

I like what you're doing here @davestewart.

Can I solicit your comment on something?

I have parts, thousands per month, that fit into approx 30 different partType and these parttype breakdown into maybe 10 possible workflows like [ordered > received > shipped > installed] or [fabricated > shipped > installed]. The complete transit can take between days and months.

So a workflow is a state machine through which a part traverses.

EXCEPT โ€” and this is my problem โ€” I haven't yet found a JSM I can use as follows:

I'd like to fetch a part record from the database and, given its current state and knowing its parttype, pass it to a JSM instance that has

  • a method I can invoke that returns a collection of possible immediately subsequent states,
  • a method I can invoke that returns an ordered collection of the chain of all possible subsequent states, and
  • (nice to have) methods as above that return similar collections for immediately prior or all prior states in the workflow for that parttype.

As you might guess, I'd like to use JSM instances to help me build a smart UI so parts can be pushed from state to state.

Most JSMs seem to assume a lifecycle of transitions, and don't seem to support the the notion of arbitrarily mutating state and then providing prior/subsequent transitions as information, in addition to raising events as transitions happen.

I hope this makes sense :smile

Any thoughts on this? Have you seen a JSM that can provide prior and subsequent states as info?

x = new MyFSM( options );
x.state = "shipped";

console.log( x.next() );  // "installed"
console.log( x.nextAll() );  // ["installed", "inspected"]
console.log( x.prior() );  // "fabricated"
console.log( x.priorAll() );  // ["planned", "approved", "fabricated"]

In practice the return values from these methods would be objects, essentially the sub-hash of the settings of MyFSM for each state.

When using start() to go to the initial state, only the system handlers are triggered

Hi - me again

As I am progressing in my project, I really get into the deeps of this project.

My question is: when calling start() on the FSM, it goes to the initial state, but does only trigger system:start and system:change. Is there a reason why not all the other transition handlers are triggered?

Context:
I am creating a fsm, where every state is an object. When the state becomes active, the state:enter trigger will cause the task to invoke an action. Registering transitions and handlers is done in the
parent class State. So when starting the FSM does not fire the enter handler, it breaks the - for me expected - flow.

An example of state:
`class CheckBalance extends State {
constructor(fsm) {
super(fsm);
fsm.config.initial = CheckBalance.id;
}

async enter() {
    this.fsm.pause();
    const b = await ex.getBalance('eur');
    this.fsm.resume();
}

leave() {
   // wrap up
}

next(fsm) {
    return Final.id; // id of the next state object
}

}`

(any ideas on this are also welcome )

Programmatic transition not firing state:a:enter handler

Hi Mr Stewart

After some research I decided to use your implementation of an FSM for a project of mine. Where I stumbled upon the following issue:

When a programmatic transition is used, as in this example , the state:a:enter event is not fired.

An example of this is shown in the following gist.run:
https://gist.run/?id=6c69f5a4fa2787a95cfbb65dfd4a4c50

My suspicion is that during the creation of the transition, for the state:a:enter the to param is a function, while handlers array creation uses regex replacement as if it were a string.

Please advise, I might be able to fix the issue with some guidance.

Best regards
Wim

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.