master 2ae84bbbaad1 cached
67 files
302.3 KB
79.8k tokens
50 symbols
1 requests
Download .txt
Showing preview only (320K chars total). Download the full file or copy to clipboard to get everything.
Repository: jakesgordon/javascript-state-machine
Branch: master
Commit: 2ae84bbbaad1
Files: 67
Total size: 302.3 KB

Directory structure:
gitextract_31ic2yb8/

├── .ackrc
├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── RELEASE_NOTES.md
├── bin/
│   ├── examples
│   └── minify
├── dist/
│   ├── state-machine-history.js
│   ├── state-machine-visualize.js
│   └── state-machine.js
├── docs/
│   ├── async-transitions.md
│   ├── contributing.md
│   ├── data-and-methods.md
│   ├── error-handling.md
│   ├── initialization.md
│   ├── lifecycle-events.md
│   ├── state-history.md
│   ├── state-machine-factory.md
│   ├── states-and-transitions.md
│   ├── upgrading-from-v2.md
│   └── visualization.md
├── examples/
│   ├── atm.dot
│   ├── atm.js
│   ├── demo/
│   │   ├── demo.css
│   │   └── demo.js
│   ├── horizontal_door.dot
│   ├── horizontal_door.js
│   ├── matter.dot
│   ├── matter.js
│   ├── vertical_door.dot
│   ├── vertical_door.js
│   ├── wizard.dot
│   └── wizard.js
├── index.html
├── lib/
│   ├── history.js
│   ├── state-machine.js
│   └── visualize.js
├── package.json
├── src/
│   ├── app.js
│   ├── config.js
│   ├── jsm.js
│   ├── plugin/
│   │   ├── history.js
│   │   └── visualize.js
│   ├── plugin.js
│   └── util/
│       ├── camelize.js
│       ├── exception.js
│       └── mixin.js
├── test/
│   ├── basics.js
│   ├── construction.js
│   ├── defaults.js
│   ├── empty.js
│   ├── errors.js
│   ├── goto.js
│   ├── helpers/
│   │   └── lifecycle_logger.js
│   ├── introspection.js
│   ├── issues.js
│   ├── lifecycle.js
│   ├── observers.js
│   ├── plugin/
│   │   ├── history.js
│   │   └── visualize.js
│   ├── plugins.js
│   ├── transitions.js
│   ├── util/
│   │   ├── camelize.js
│   │   └── mixin.js
│   └── wildcards.js
└── webpack.config.js

================================================
FILE CONTENTS
================================================

================================================
FILE: .ackrc
================================================
--ignore-dir=coverage
--ignore-dir=node_modules
--ignore-dir=.nyc_output



================================================
FILE: .gitignore
================================================
node_modules
coverage
.nyc_output
*.swp


================================================
FILE: .travis.yml
================================================
language: node_js
node_js:
 - '4'
 - '6'
sudo: false


================================================
FILE: LICENSE
================================================
Copyright (c) 2012, 2013, 2014, 2015, 2016, 2017, 2018, Jake Gordon and contributors

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.



================================================
FILE: README.md
================================================
# Javascript State Machine

[![NPM version](https://badge.fury.io/js/javascript-state-machine.svg)](https://badge.fury.io/js/javascript-state-machine)
[![Build Status](https://travis-ci.org/jakesgordon/javascript-state-machine.svg?branch=master)](https://travis-ci.org/jakesgordon/javascript-state-machine)

A library for finite state machines.

![matter state machine](examples/matter.png)

<br>

### NOTE for existing users

> **VERSION 3.0** Is a significant rewrite from earlier versions.
  Existing 2.x users should be sure to read the [Upgrade Guide](docs/upgrading-from-v2.md).

<br>

# Installation

In a browser:

```html
  <script src='state-machine.js'></script>
```

> after downloading the [source](dist/state-machine.js) or the [minified version](dist/state-machine.min.js)

Using npm:

```shell
  npm install --save-dev javascript-state-machine
```

In Node.js:

```javascript
  var StateMachine = require('javascript-state-machine');
```

# Usage

A state machine can be constructed using:

```javascript
  var fsm = new StateMachine({
    init: 'solid',
    transitions: [
      { name: 'melt',     from: 'solid',  to: 'liquid' },
      { name: 'freeze',   from: 'liquid', to: 'solid'  },
      { name: 'vaporize', from: 'liquid', to: 'gas'    },
      { name: 'condense', from: 'gas',    to: 'liquid' }
    ],
    methods: {
      onMelt:     function() { console.log('I melted')    },
      onFreeze:   function() { console.log('I froze')     },
      onVaporize: function() { console.log('I vaporized') },
      onCondense: function() { console.log('I condensed') }
    }
  });
```

... which creates an object with a current state property:

  * `fsm.state`

... methods to transition to a different state:

  * `fsm.melt()`
  * `fsm.freeze()`
  * `fsm.vaporize()`
  * `fsm.condense()`

... observer methods called automatically during the lifecycle of a transition:

  * `onMelt()`
  * `onFreeze()`
  * `onVaporize()`
  * `onCondense()`

... along with the following helper methods:

  * `fsm.is(s)`            - return true if state `s` is the current state
  * `fsm.can(t)`           - return true if transition `t` can occur from the current state
  * `fsm.cannot(t)`        - return true if transition `t` cannot occur from the current state
  * `fsm.transitions()`    - return list of transitions that are allowed from the current state
  * `fsm.allTransitions()` - return list of all possible transitions
  * `fsm.allStates()`      - return list of all possible states

# Terminology

A state machine consists of a set of [**States**](docs/states-and-transitions.md)

  * solid
  * liquid
  * gas

A state machine changes state by using [**Transitions**](docs/states-and-transitions.md)

  * melt
  * freeze
  * vaporize
  * condense

A state machine can perform actions during a transition by observing [**Lifecycle Events**](docs/lifecycle-events.md)

  * onBeforeMelt
  * onAfterMelt
  * onLeaveSolid
  * onEnterLiquid
  * ...

A state machine can also have arbitrary [**Data and Methods**](docs/data-and-methods.md).

Multiple instances of a state machine can be created using a [**State Machine Factory**](docs/state-machine-factory.md).

# Documentation

Read more about

  * [States and Transitions](docs/states-and-transitions.md)
  * [Data and Methods](docs/data-and-methods.md)
  * [Lifecycle Events](docs/lifecycle-events.md)
  * [Asynchronous Transitions](docs/async-transitions.md)
  * [Initialization](docs/initialization.md)
  * [Error Handling](docs/error-handling.md)
  * [State History](docs/state-history.md)
  * [Visualization](docs/visualization.md)
  * [State Machine Factory](docs/state-machine-factory.md)
  * [Upgrading from 2.x](docs/upgrading-from-v2.md)
 
# Contributing

You can [Contribute](docs/contributing.md) to this project with issues or pull requests.

# Release Notes

See [RELEASE NOTES](RELEASE_NOTES.md) file.

# License

See [MIT LICENSE](https://github.com/jakesgordon/javascript-state-machine/blob/master/LICENSE) file.

# Contact

If you have any ideas, feedback, requests or bug reports, you can reach me at
[jakesgordon@gmail.com](mailto:jakesgordon@gmail.com), or via
my website: [jakesgordon.com](https://jakesgordon.com/)


================================================
FILE: RELEASE_NOTES.md
================================================
Version 3.1.0 (July 12th 2018)
------------------------------

 * Changed back to MIT license

Version 3.0.1 (June 10th 2017)
------------------------------

 * First 3.x release - see 3.0.0-rc.1 release notes below

 * fix issue #109 - rejection from async lifecycle method does not reject transitions promise
 * fix issue #106 - async transition: forward resolved value
 * fix issue #107 - lifecycle event name breaks for all uppercase 

Version 3.0.0-rc.1 (January 10 2017)
------------------------------------

**IMPORTANT NOTE**: this version includes **breaking changes** that will require code updates.

Please read [UPGRADING FROM 2.x](docs/upgrading-from-v2.md) for details. Highlights include:

  * Improved Construction.
  * Arbitrary Data and Methods.
  * Observable Transitions
  * Conditional Transitions
  * Promise-based Asynchronous Transitions
  * Improved Transition Lifecycle Events
  * State History
  * Visualization
  * Webpack build system
  * ...

<br>
<br>

Version 2.4.0 (November 20 2016)
--------------------------------

 * added npm install instructions to readme
 * fix for javascript error when running in jasmine/node (issue #88)
 * exclude build files from bower install (pull request #75)
 * ensure WILDCARD events are included in list of available transitions() (issue #93)
 * fix FSM getting stuck into "*" state when using double wildcard (issue #64)
 * function (fsm.states) returning list of all available states in the machine would help automated testing (issue #54)
 * state machine hides callback exceptions (issue #62)
 * replaced (dev dependency) YUI compressor with uglify-js for building minified version

Version 2.3.5 (January 20 2014)
-------------------------------

 * fix for broken transitions() method (issue #74)

Version 2.3.4 (January 17 2014)
-------------------------------

 * helper method to list which events are allowed from the current state (issue #71 - thanks to @mgoldsborough and @chopj)

Version 2.3.3 (October 17 2014)
-------------------------------

 * added web worker compatability (issue #65 - thanks to @offirmo)

Version 2.3.2 (March 16 2014)
-----------------------------

 * had to bump the version number after messing up npmjs.org package registration

Version 2.3.0 (March 15 2014)
-----------------------------

 * Added support for bower
 * Added support for nodejs (finally)
 * Added ability to run tests in console via nodejs ("npm install" to get node-qunit, then "node test/runner.js")

Version 2.2.0 (January 26th 2013)
---------------------------------
 
 * Added optional `final` state(s) and `isFinished()` helper method (issue #23)
 * extended `fsm.is()` to accept an array of states (in addition to a single state)
 * Added generic event callbacks 'onbeforeevent' and 'onafterevent' (issue #28)
 * Added generic state callbacks 'onleavestate' and 'onenterstate'  (issue #28)
 * Fixed 'undefined' event return codes (issue #34) - pull from gentooboontoo (thanks!)
 * Allow async event transition to be cancelled (issue #22)
 * [read more...](https://jakesgordon.com/writing/javascript-state-machine-v2-2-0/)

Version 2.1.0 (January 7th 2012)
--------------------------------

 * Wrapped in self executing function to be more easily used with loaders like `require.js` or `curl.js` (issue #15)
 * Allow event to be cancelled by returning `false` from `onleavestate` handler (issue #13) - WARNING: this breaks backward compatibility for async transitions (you now need to return `StateMachine.ASYNC` instead of `false`)
 * Added explicit return values for event methods (issue #12)
 * Added support for wildcard events that can be fired 'from' any state (issue #11)
 * Added support for no-op events that transition 'to' the same state  (issue #5)
 * extended custom error callback to handle any exceptions caused by caller provided callbacks
 * added custom error callback to override exception when an illegal state transition is attempted (thanks to cboone)
 * fixed typos (thanks to cboone)
 * fixed issue #4 - ensure before/after event hooks are called even if the event doesn't result in a state change 

Version 2.0.0 (August 19th 2011)
--------------------------------

 * adding support for asynchronous state transitions (see README) - with lots of qunit tests (see test/async.js).
 * consistent arguments for ALL callbacks, first 3 args are ALWAYS event name, from state and to state, followed by whatever arguments the user passed to the original event method.
 * added a generic `onchangestate(event,from,to)` callback to detect all state changes with a single function.
 * allow callbacks to be declared at creation time (instead of having to attach them afterwards)
 * renamed 'hooks' => 'callbacks'
 * [read more...](https://jakesgordon.com/writing/javascript-state-machine-v2/)

Version 1.2.0 (June 21st 2011)
------------------------------
 * allows the same event to transition to different states, depending on the current state (see 'Multiple...' section in README.md)
 * [read more...](https://jakesgordon.com/writing/javascript-state-machine-v1-2-0/)

Version 1.0.0 (June 1st 2011)
-----------------------------
 * initial version
 * [read more...](https://jakesgordon.com/writing/javascript-state-machine/)


================================================
FILE: bin/examples
================================================
#!/usr/bin/env node

//=================================================================================================
//
// This script is used to regenerate the example visualizations
//
//=================================================================================================

var fs    = require('fs'),
    path  = require('path'),
    child = require('child_process');

//-------------------------------------------------------------------------------------------------

fs.readdirSync('examples')
  .filter(function(file) { return path.extname(file) === ".js" })
  .map(visualize);

//-------------------------------------------------------------------------------------------------

function visualize(example) {
  var name = path.basename(example, '.js'),
      fsm  = require('../examples/' + example),
      dot  = fsm.visualize(),
      svg  = dot2svg(dot),
      png  = dot2png(dot);
  console.log('visualizing examples/' + example);
  fs.writeFileSync('examples/' + name + '.dot', dot);
  fs.writeFileSync('examples/' + name + '.svg', svg);
  fs.writeFileSync('examples/' + name + '.png', png, 'binary');
}

//-------------------------------------------------------------------------------------------------

function dot2svg(dot) {
  var result = child.spawnSync("dot", ["-Tsvg"], { input: dot });
  if (result.error)
    dotError(result.error.errno);
  return result.stdout.toString();
}

//-------------------------------------------------------------------------------------------------

function dot2png(dot) {
  var result = child.spawnSync("dot", ["-Tpng"], { input: dot });
  if (result.error)
    dotError(result.error.errno);
  return result.stdout;
}

//-------------------------------------------------------------------------------------------------

function dotError(errno) {
  if (errno === 'ENOENT')
    throw new Error("dot program not found. Install graphviz (http://graphviz.org)")
  else
    throw new Error("unexpected error: " + errno)
}

//-------------------------------------------------------------------------------------------------


================================================
FILE: bin/minify
================================================
#!/usr/bin/env node

//=================================================================================================
//
// This script is used (by npm run build) to minify the distributed source code
//
//=================================================================================================

var fs     = require('fs-sync'),
    path   = require('path'),
    uglify = require('uglify-js'),
    target = 'dist';

//-------------------------------------------------------------------------------------------------

fs.expand("lib/**/*.js")
    .map(minify);

//-------------------------------------------------------------------------------------------------

function minify(file) {
  var name     = output_name(file),
      expanded = path.join(target, name + '.js'),
      minified = path.join(target, name + '.min.js')

  console.log('copied ' + file + ' to ' + expanded + ' and minified as ' + minified);

  fs.copy(file, expanded, { force: true });
  fs.write(minified, uglify.minify(expanded).code);
}

function output_name(file) {
  var name = path.basename(file, '.js');
  if (name === 'state-machine')
    return 'state-machine'
  else
    return 'state-machine-' + name
}

//-------------------------------------------------------------------------------------------------


================================================
FILE: dist/state-machine-history.js
================================================
(function webpackUniversalModuleDefinition(root, factory) {
	if(typeof exports === 'object' && typeof module === 'object')
		module.exports = factory();
	else if(typeof define === 'function' && define.amd)
		define("StateMachineHistory", [], factory);
	else if(typeof exports === 'object')
		exports["StateMachineHistory"] = factory();
	else
		root["StateMachineHistory"] = factory();
})(this, function() {
return /******/ (function(modules) { // webpackBootstrap
/******/ 	// The module cache
/******/ 	var installedModules = {};
/******/
/******/ 	// The require function
/******/ 	function __webpack_require__(moduleId) {
/******/
/******/ 		// Check if module is in cache
/******/ 		if(installedModules[moduleId]) {
/******/ 			return installedModules[moduleId].exports;
/******/ 		}
/******/ 		// Create a new module (and put it into the cache)
/******/ 		var module = installedModules[moduleId] = {
/******/ 			i: moduleId,
/******/ 			l: false,
/******/ 			exports: {}
/******/ 		};
/******/
/******/ 		// Execute the module function
/******/ 		modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ 		// Flag the module as loaded
/******/ 		module.l = true;
/******/
/******/ 		// Return the exports of the module
/******/ 		return module.exports;
/******/ 	}
/******/
/******/
/******/ 	// expose the modules object (__webpack_modules__)
/******/ 	__webpack_require__.m = modules;
/******/
/******/ 	// expose the module cache
/******/ 	__webpack_require__.c = installedModules;
/******/
/******/ 	// identity function for calling harmony imports with the correct context
/******/ 	__webpack_require__.i = function(value) { return value; };
/******/
/******/ 	// define getter function for harmony exports
/******/ 	__webpack_require__.d = function(exports, name, getter) {
/******/ 		if(!__webpack_require__.o(exports, name)) {
/******/ 			Object.defineProperty(exports, name, {
/******/ 				configurable: false,
/******/ 				enumerable: true,
/******/ 				get: getter
/******/ 			});
/******/ 		}
/******/ 	};
/******/
/******/ 	// getDefaultExport function for compatibility with non-harmony modules
/******/ 	__webpack_require__.n = function(module) {
/******/ 		var getter = module && module.__esModule ?
/******/ 			function getDefault() { return module['default']; } :
/******/ 			function getModuleExports() { return module; };
/******/ 		__webpack_require__.d(getter, 'a', getter);
/******/ 		return getter;
/******/ 	};
/******/
/******/ 	// Object.prototype.hasOwnProperty.call
/******/ 	__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ 	// __webpack_public_path__
/******/ 	__webpack_require__.p = "";
/******/
/******/ 	// Load entry module and return exports
/******/ 	return __webpack_require__(__webpack_require__.s = 1);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";


//-------------------------------------------------------------------------------------------------

function camelize(label) {

  if (label.length === 0)
    return label;

  var n, result, word, words = label.split(/[_-]/);

  // single word with first character already lowercase, return untouched
  if ((words.length === 1) && (words[0][0].toLowerCase() === words[0][0]))
    return label;

  result = words[0].toLowerCase();
  for(n = 1 ; n < words.length ; n++) {
    result = result + words[n].charAt(0).toUpperCase() + words[n].substring(1).toLowerCase();
  }

  return result;
}

//-------------------------------------------------------------------------------------------------

camelize.prepended = function(prepend, label) {
  label = camelize(label);
  return prepend + label[0].toUpperCase() + label.substring(1);
}

//-------------------------------------------------------------------------------------------------

module.exports = camelize;


/***/ }),
/* 1 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";


//-------------------------------------------------------------------------------------------------

var camelize = __webpack_require__(0);

//-------------------------------------------------------------------------------------------------

module.exports = function(options) { options = options || {};

  var past       = camelize(options.name || options.past   || 'history'),
      future     = camelize(                options.future || 'future'),
      clear      = camelize.prepended('clear', past),
      back       = camelize.prepended(past,   'back'),
      forward    = camelize.prepended(past,   'forward'),
      canBack    = camelize.prepended('can',   back),
      canForward = camelize.prepended('can',   forward),
      max        = options.max;

  var plugin = {

    configure: function(config) {
      config.addTransitionLifecycleNames(back);
      config.addTransitionLifecycleNames(forward);
    },

    init: function(instance) {
      instance[past]   = [];
      instance[future] = [];
    },

    lifecycle: function(instance, lifecycle) {
      if (lifecycle.event === 'onEnterState') {
        instance[past].push(lifecycle.to);
        if (max && instance[past].length > max)
          instance[past].shift();
        if (lifecycle.transition !== back && lifecycle.transition !== forward)
          instance[future].length = 0;
      }
    },

    methods:    {},
    properties: {}

  }

  plugin.methods[clear] = function() {
    this[past].length = 0
    this[future].length = 0
  }

  plugin.properties[canBack] = {
    get: function() {
      return this[past].length > 1
    }
  }

  plugin.properties[canForward] = {
    get: function() {
      return this[future].length > 0
    }
  }

  plugin.methods[back] = function() {
    if (!this[canBack])
      throw Error('no history');
    var from = this[past].pop(),
        to   = this[past].pop();
    this[future].push(from);
    this._fsm.transit(back, from, to, []);
  }

  plugin.methods[forward] = function() {
    if (!this[canForward])
      throw Error('no history');
    var from = this.state,
        to = this[future].pop();
    this._fsm.transit(forward, from, to, []);
  }

  return plugin;

}


/***/ })
/******/ ]);
});

================================================
FILE: dist/state-machine-visualize.js
================================================
(function webpackUniversalModuleDefinition(root, factory) {
	if(typeof exports === 'object' && typeof module === 'object')
		module.exports = factory();
	else if(typeof define === 'function' && define.amd)
		define("StateMachineVisualize", [], factory);
	else if(typeof exports === 'object')
		exports["StateMachineVisualize"] = factory();
	else
		root["StateMachineVisualize"] = factory();
})(this, function() {
return /******/ (function(modules) { // webpackBootstrap
/******/ 	// The module cache
/******/ 	var installedModules = {};
/******/
/******/ 	// The require function
/******/ 	function __webpack_require__(moduleId) {
/******/
/******/ 		// Check if module is in cache
/******/ 		if(installedModules[moduleId]) {
/******/ 			return installedModules[moduleId].exports;
/******/ 		}
/******/ 		// Create a new module (and put it into the cache)
/******/ 		var module = installedModules[moduleId] = {
/******/ 			i: moduleId,
/******/ 			l: false,
/******/ 			exports: {}
/******/ 		};
/******/
/******/ 		// Execute the module function
/******/ 		modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ 		// Flag the module as loaded
/******/ 		module.l = true;
/******/
/******/ 		// Return the exports of the module
/******/ 		return module.exports;
/******/ 	}
/******/
/******/
/******/ 	// expose the modules object (__webpack_modules__)
/******/ 	__webpack_require__.m = modules;
/******/
/******/ 	// expose the module cache
/******/ 	__webpack_require__.c = installedModules;
/******/
/******/ 	// identity function for calling harmony imports with the correct context
/******/ 	__webpack_require__.i = function(value) { return value; };
/******/
/******/ 	// define getter function for harmony exports
/******/ 	__webpack_require__.d = function(exports, name, getter) {
/******/ 		if(!__webpack_require__.o(exports, name)) {
/******/ 			Object.defineProperty(exports, name, {
/******/ 				configurable: false,
/******/ 				enumerable: true,
/******/ 				get: getter
/******/ 			});
/******/ 		}
/******/ 	};
/******/
/******/ 	// getDefaultExport function for compatibility with non-harmony modules
/******/ 	__webpack_require__.n = function(module) {
/******/ 		var getter = module && module.__esModule ?
/******/ 			function getDefault() { return module['default']; } :
/******/ 			function getModuleExports() { return module; };
/******/ 		__webpack_require__.d(getter, 'a', getter);
/******/ 		return getter;
/******/ 	};
/******/
/******/ 	// Object.prototype.hasOwnProperty.call
/******/ 	__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ 	// __webpack_public_path__
/******/ 	__webpack_require__.p = "";
/******/
/******/ 	// Load entry module and return exports
/******/ 	return __webpack_require__(__webpack_require__.s = 1);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";


module.exports = function(target, sources) {
  var n, source, key;
  for(n = 1 ; n < arguments.length ; n++) {
    source = arguments[n];
    for(key in source) {
      if (source.hasOwnProperty(key))
        target[key] = source[key];
    }
  }
  return target;
}


/***/ }),
/* 1 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";


//-------------------------------------------------------------------------------------------------

var mixin = __webpack_require__(0)

//-------------------------------------------------------------------------------------------------

function visualize(fsm, options) {
  return dotify(dotcfg(fsm, options));
}

//-------------------------------------------------------------------------------------------------

function dotcfg(fsm, options) {

  options = options || {}

  var config      = dotcfg.fetch(fsm),
      name        = options.name,
      rankdir     = dotcfg.rankdir(options.orientation),
      states      = dotcfg.states(config, options),
      transitions = dotcfg.transitions(config, options),
      result      = { }

  if (name)
    result.name = name

  if (rankdir)
    result.rankdir = rankdir

  if (states && states.length > 0)
    result.states = states

  if (transitions && transitions.length > 0)
    result.transitions = transitions

  return result
}

//-------------------------------------------------------------------------------------------------

dotcfg.fetch = function(fsm) {
  return (typeof fsm === 'function') ? fsm.prototype._fsm.config
                                     : fsm._fsm.config
}

dotcfg.rankdir = function(orientation) {
  if (orientation === 'horizontal')
    return 'LR';
  else if (orientation === 'vertical')
    return 'TB';
}

dotcfg.states = function(config, options) {
  var index, states = config.states;
  if (!options.init) { // if not showing init transition, then slice out the implied init :from state
    index  = states.indexOf(config.init.from);
    states = states.slice(0, index).concat(states.slice(index+1));
  }
  return states;
}

dotcfg.transitions = function(config, options) {
  var n, max, transition,
      init        = config.init,
      transitions = config.options.transitions || [], // easier to visualize using the ORIGINAL transition declarations rather than our run-time mapping
      output = [];
  if (options.init && init.active)
    dotcfg.transition(init.name, init.from, init.to, init.dot, config, options, output)
  for (n = 0, max = transitions.length ; n < max ; n++) {
    transition = config.options.transitions[n]
    dotcfg.transition(transition.name, transition.from, transition.to, transition.dot, config, options, output)
  }
  return output
}

dotcfg.transition = function(name, from, to, dot, config, options, output) {
  var n, max, wildcard = config.defaults.wildcard

  if (Array.isArray(from)) {
    for(n = 0, max = from.length ; n < max ; n++)
      dotcfg.transition(name, from[n], to, dot, config, options, output)
  }
  else if (from === wildcard || from === undefined) {
    for(n = 0, max = config.states.length ; n < max ; n++)
      dotcfg.transition(name, config.states[n], to, dot, config, options, output)
  }
  else if (to === wildcard || to === undefined) {
    dotcfg.transition(name, from, from, dot, config, options, output)
  }
  else if (typeof to === 'function') {
    // do nothing, can't display conditional transition
  }
  else {
    output.push(mixin({}, { from: from, to: to, label: pad(name) }, dot || {}))
  }

}

//-------------------------------------------------------------------------------------------------

function pad(name) {
  return " " + name + " "
}

function quote(name) {
  return "\"" + name + "\""
}

function dotify(dotcfg) {

  dotcfg = dotcfg || {};

  var name        = dotcfg.name || 'fsm',
      states      = dotcfg.states || [],
      transitions = dotcfg.transitions || [],
      rankdir     = dotcfg.rankdir,
      output      = [],
      n, max;

  output.push("digraph " + quote(name) + " {")
  if (rankdir)
    output.push("  rankdir=" + rankdir + ";")
  for(n = 0, max = states.length ; n < max ; n++)
    output.push(dotify.state(states[n]))
  for(n = 0, max = transitions.length ; n < max ; n++)
    output.push(dotify.edge(transitions[n]))
  output.push("}")
  return output.join("\n")

}

dotify.state = function(state) {
  return "  " + quote(state) + ";"
}

dotify.edge = function(edge) {
  return "  " + quote(edge.from) + " -> " + quote(edge.to) + dotify.edge.attr(edge) + ";"
}

dotify.edge.attr = function(edge) {
  var n, max, key, keys = Object.keys(edge).sort(), output = [];
  for(n = 0, max = keys.length ; n < max ; n++) {
    key = keys[n];
    if (key !== 'from' && key !== 'to')
      output.push(key + "=" + quote(edge[key]))
  }
  return output.length > 0 ? " [ " + output.join(" ; ") + " ]" : ""
}

//-------------------------------------------------------------------------------------------------

visualize.dotcfg = dotcfg;
visualize.dotify = dotify;

//-------------------------------------------------------------------------------------------------

module.exports = visualize;

//-------------------------------------------------------------------------------------------------


/***/ })
/******/ ]);
});

================================================
FILE: dist/state-machine.js
================================================
(function webpackUniversalModuleDefinition(root, factory) {
	if(typeof exports === 'object' && typeof module === 'object')
		module.exports = factory();
	else if(typeof define === 'function' && define.amd)
		define("StateMachine", [], factory);
	else if(typeof exports === 'object')
		exports["StateMachine"] = factory();
	else
		root["StateMachine"] = factory();
})(this, function() {
return /******/ (function(modules) { // webpackBootstrap
/******/ 	// The module cache
/******/ 	var installedModules = {};
/******/
/******/ 	// The require function
/******/ 	function __webpack_require__(moduleId) {
/******/
/******/ 		// Check if module is in cache
/******/ 		if(installedModules[moduleId]) {
/******/ 			return installedModules[moduleId].exports;
/******/ 		}
/******/ 		// Create a new module (and put it into the cache)
/******/ 		var module = installedModules[moduleId] = {
/******/ 			i: moduleId,
/******/ 			l: false,
/******/ 			exports: {}
/******/ 		};
/******/
/******/ 		// Execute the module function
/******/ 		modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ 		// Flag the module as loaded
/******/ 		module.l = true;
/******/
/******/ 		// Return the exports of the module
/******/ 		return module.exports;
/******/ 	}
/******/
/******/
/******/ 	// expose the modules object (__webpack_modules__)
/******/ 	__webpack_require__.m = modules;
/******/
/******/ 	// expose the module cache
/******/ 	__webpack_require__.c = installedModules;
/******/
/******/ 	// identity function for calling harmony imports with the correct context
/******/ 	__webpack_require__.i = function(value) { return value; };
/******/
/******/ 	// define getter function for harmony exports
/******/ 	__webpack_require__.d = function(exports, name, getter) {
/******/ 		if(!__webpack_require__.o(exports, name)) {
/******/ 			Object.defineProperty(exports, name, {
/******/ 				configurable: false,
/******/ 				enumerable: true,
/******/ 				get: getter
/******/ 			});
/******/ 		}
/******/ 	};
/******/
/******/ 	// getDefaultExport function for compatibility with non-harmony modules
/******/ 	__webpack_require__.n = function(module) {
/******/ 		var getter = module && module.__esModule ?
/******/ 			function getDefault() { return module['default']; } :
/******/ 			function getModuleExports() { return module; };
/******/ 		__webpack_require__.d(getter, 'a', getter);
/******/ 		return getter;
/******/ 	};
/******/
/******/ 	// Object.prototype.hasOwnProperty.call
/******/ 	__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ 	// __webpack_public_path__
/******/ 	__webpack_require__.p = "";
/******/
/******/ 	// Load entry module and return exports
/******/ 	return __webpack_require__(__webpack_require__.s = 5);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";


module.exports = function(target, sources) {
  var n, source, key;
  for(n = 1 ; n < arguments.length ; n++) {
    source = arguments[n];
    for(key in source) {
      if (source.hasOwnProperty(key))
        target[key] = source[key];
    }
  }
  return target;
}


/***/ }),
/* 1 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";


//-------------------------------------------------------------------------------------------------

var mixin = __webpack_require__(0);

//-------------------------------------------------------------------------------------------------

module.exports = {

  build: function(target, config) {
    var n, max, plugin, plugins = config.plugins;
    for(n = 0, max = plugins.length ; n < max ; n++) {
      plugin = plugins[n];
      if (plugin.methods)
        mixin(target, plugin.methods);
      if (plugin.properties)
        Object.defineProperties(target, plugin.properties);
    }
  },

  hook: function(fsm, name, additional) {
    var n, max, method, plugin,
        plugins = fsm.config.plugins,
        args    = [fsm.context];

    if (additional)
      args = args.concat(additional)

    for(n = 0, max = plugins.length ; n < max ; n++) {
      plugin = plugins[n]
      method = plugins[n][name]
      if (method)
        method.apply(plugin, args);
    }
  }

}

//-------------------------------------------------------------------------------------------------


/***/ }),
/* 2 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";


//-------------------------------------------------------------------------------------------------

function camelize(label) {

  if (label.length === 0)
    return label;

  var n, result, word, words = label.split(/[_-]/);

  // single word with first character already lowercase, return untouched
  if ((words.length === 1) && (words[0][0].toLowerCase() === words[0][0]))
    return label;

  result = words[0].toLowerCase();
  for(n = 1 ; n < words.length ; n++) {
    result = result + words[n].charAt(0).toUpperCase() + words[n].substring(1).toLowerCase();
  }

  return result;
}

//-------------------------------------------------------------------------------------------------

camelize.prepended = function(prepend, label) {
  label = camelize(label);
  return prepend + label[0].toUpperCase() + label.substring(1);
}

//-------------------------------------------------------------------------------------------------

module.exports = camelize;


/***/ }),
/* 3 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";


//-------------------------------------------------------------------------------------------------

var mixin    = __webpack_require__(0),
    camelize = __webpack_require__(2);

//-------------------------------------------------------------------------------------------------

function Config(options, StateMachine) {

  options = options || {};

  this.options     = options; // preserving original options can be useful (e.g visualize plugin)
  this.defaults    = StateMachine.defaults;
  this.states      = [];
  this.transitions = [];
  this.map         = {};
  this.lifecycle   = this.configureLifecycle();
  this.init        = this.configureInitTransition(options.init);
  this.data        = this.configureData(options.data);
  this.methods     = this.configureMethods(options.methods);

  this.map[this.defaults.wildcard] = {};

  this.configureTransitions(options.transitions || []);

  this.plugins = this.configurePlugins(options.plugins, StateMachine.plugin);

}

//-------------------------------------------------------------------------------------------------

mixin(Config.prototype, {

  addState: function(name) {
    if (!this.map[name]) {
      this.states.push(name);
      this.addStateLifecycleNames(name);
      this.map[name] = {};
    }
  },

  addStateLifecycleNames: function(name) {
    this.lifecycle.onEnter[name] = camelize.prepended('onEnter', name);
    this.lifecycle.onLeave[name] = camelize.prepended('onLeave', name);
    this.lifecycle.on[name]      = camelize.prepended('on',      name);
  },

  addTransition: function(name) {
    if (this.transitions.indexOf(name) < 0) {
      this.transitions.push(name);
      this.addTransitionLifecycleNames(name);
    }
  },

  addTransitionLifecycleNames: function(name) {
    this.lifecycle.onBefore[name] = camelize.prepended('onBefore', name);
    this.lifecycle.onAfter[name]  = camelize.prepended('onAfter',  name);
    this.lifecycle.on[name]       = camelize.prepended('on',       name);
  },

  mapTransition: function(transition) {
    var name = transition.name,
        from = transition.from,
        to   = transition.to;
    this.addState(from);
    if (typeof to !== 'function')
      this.addState(to);
    this.addTransition(name);
    this.map[from][name] = transition;
    return transition;
  },

  configureLifecycle: function() {
    return {
      onBefore: { transition: 'onBeforeTransition' },
      onAfter:  { transition: 'onAfterTransition'  },
      onEnter:  { state:      'onEnterState'       },
      onLeave:  { state:      'onLeaveState'       },
      on:       { transition: 'onTransition'       }
    };
  },

  configureInitTransition: function(init) {
    if (typeof init === 'string') {
      return this.mapTransition(mixin({}, this.defaults.init, { to: init, active: true }));
    }
    else if (typeof init === 'object') {
      return this.mapTransition(mixin({}, this.defaults.init, init, { active: true }));
    }
    else {
      this.addState(this.defaults.init.from);
      return this.defaults.init;
    }
  },

  configureData: function(data) {
    if (typeof data === 'function')
      return data;
    else if (typeof data === 'object')
      return function() { return data; }
    else
      return function() { return {};  }
  },

  configureMethods: function(methods) {
    return methods || {};
  },

  configurePlugins: function(plugins, builtin) {
    plugins = plugins || [];
    var n, max, plugin;
    for(n = 0, max = plugins.length ; n < max ; n++) {
      plugin = plugins[n];
      if (typeof plugin === 'function')
        plugins[n] = plugin = plugin()
      if (plugin.configure)
        plugin.configure(this);
    }
    return plugins
  },

  configureTransitions: function(transitions) {
    var i, n, transition, from, to, wildcard = this.defaults.wildcard;
    for(n = 0 ; n < transitions.length ; n++) {
      transition = transitions[n];
      from  = Array.isArray(transition.from) ? transition.from : [transition.from || wildcard]
      to    = transition.to || wildcard;
      for(i = 0 ; i < from.length ; i++) {
        this.mapTransition({ name: transition.name, from: from[i], to: to });
      }
    }
  },

  transitionFor: function(state, transition) {
    var wildcard = this.defaults.wildcard;
    return this.map[state][transition] ||
           this.map[wildcard][transition];
  },

  transitionsFor: function(state) {
    var wildcard = this.defaults.wildcard;
    return Object.keys(this.map[state]).concat(Object.keys(this.map[wildcard]));
  },

  allStates: function() {
    return this.states;
  },

  allTransitions: function() {
    return this.transitions;
  }

});

//-------------------------------------------------------------------------------------------------

module.exports = Config;

//-------------------------------------------------------------------------------------------------


/***/ }),
/* 4 */
/***/ (function(module, exports, __webpack_require__) {


var mixin      = __webpack_require__(0),
    Exception  = __webpack_require__(6),
    plugin     = __webpack_require__(1),
    UNOBSERVED = [ null, [] ];

//-------------------------------------------------------------------------------------------------

function JSM(context, config) {
  this.context   = context;
  this.config    = config;
  this.state     = config.init.from;
  this.observers = [context];
}

//-------------------------------------------------------------------------------------------------

mixin(JSM.prototype, {

  init: function(args) {
    mixin(this.context, this.config.data.apply(this.context, args));
    plugin.hook(this, 'init');
    if (this.config.init.active)
      return this.fire(this.config.init.name, []);
  },

  is: function(state) {
    return Array.isArray(state) ? (state.indexOf(this.state) >= 0) : (this.state === state);
  },

  isPending: function() {
    return this.pending;
  },

  can: function(transition) {
    return !this.isPending() && !!this.seek(transition);
  },

  cannot: function(transition) {
    return !this.can(transition);
  },

  allStates: function() {
    return this.config.allStates();
  },

  allTransitions: function() {
    return this.config.allTransitions();
  },

  transitions: function() {
    return this.config.transitionsFor(this.state);
  },

  seek: function(transition, args) {
    var wildcard = this.config.defaults.wildcard,
        entry    = this.config.transitionFor(this.state, transition),
        to       = entry && entry.to;
    if (typeof to === 'function')
      return to.apply(this.context, args);
    else if (to === wildcard)
      return this.state
    else
      return to
  },

  fire: function(transition, args) {
    return this.transit(transition, this.state, this.seek(transition, args), args);
  },

  transit: function(transition, from, to, args) {

    var lifecycle = this.config.lifecycle,
        changed   = this.config.options.observeUnchangedState || (from !== to);

    if (!to)
      return this.context.onInvalidTransition(transition, from, to);

    if (this.isPending())
      return this.context.onPendingTransition(transition, from, to);

    this.config.addState(to);  // might need to add this state if it's unknown (e.g. conditional transition or goto)

    this.beginTransit();

    args.unshift({             // this context will be passed to each lifecycle event observer
      transition: transition,
      from:       from,
      to:         to,
      fsm:        this.context
    });

    return this.observeEvents([
                this.observersForEvent(lifecycle.onBefore.transition),
                this.observersForEvent(lifecycle.onBefore[transition]),
      changed ? this.observersForEvent(lifecycle.onLeave.state) : UNOBSERVED,
      changed ? this.observersForEvent(lifecycle.onLeave[from]) : UNOBSERVED,
                this.observersForEvent(lifecycle.on.transition),
      changed ? [ 'doTransit', [ this ] ]                       : UNOBSERVED,
      changed ? this.observersForEvent(lifecycle.onEnter.state) : UNOBSERVED,
      changed ? this.observersForEvent(lifecycle.onEnter[to])   : UNOBSERVED,
      changed ? this.observersForEvent(lifecycle.on[to])        : UNOBSERVED,
                this.observersForEvent(lifecycle.onAfter.transition),
                this.observersForEvent(lifecycle.onAfter[transition]),
                this.observersForEvent(lifecycle.on[transition])
    ], args);
  },

  beginTransit: function()          { this.pending = true;                 },
  endTransit:   function(result)    { this.pending = false; return result; },
  failTransit:  function(result)    { this.pending = false; throw result;  },
  doTransit:    function(lifecycle) { this.state = lifecycle.to;           },

  observe: function(args) {
    if (args.length === 2) {
      var observer = {};
      observer[args[0]] = args[1];
      this.observers.push(observer);
    }
    else {
      this.observers.push(args[0]);
    }
  },

  observersForEvent: function(event) { // TODO: this could be cached
    var n = 0, max = this.observers.length, observer, result = [];
    for( ; n < max ; n++) {
      observer = this.observers[n];
      if (observer[event])
        result.push(observer);
    }
    return [ event, result, true ]
  },

  observeEvents: function(events, args, previousEvent, previousResult) {
    if (events.length === 0) {
      return this.endTransit(previousResult === undefined ? true : previousResult);
    }

    var event     = events[0][0],
        observers = events[0][1],
        pluggable = events[0][2];

    args[0].event = event;
    if (event && pluggable && event !== previousEvent)
      plugin.hook(this, 'lifecycle', args);

    if (observers.length === 0) {
      events.shift();
      return this.observeEvents(events, args, event, previousResult);
    }
    else {
      var observer = observers.shift(),
          result = observer[event].apply(observer, args);
      if (result && typeof result.then === 'function') {
        return result.then(this.observeEvents.bind(this, events, args, event))
                     .catch(this.failTransit.bind(this))
      }
      else if (result === false) {
        return this.endTransit(false);
      }
      else {
        return this.observeEvents(events, args, event, result);
      }
    }
  },

  onInvalidTransition: function(transition, from, to) {
    throw new Exception("transition is invalid in current state", transition, from, to, this.state);
  },

  onPendingTransition: function(transition, from, to) {
    throw new Exception("transition is invalid while previous transition is still in progress", transition, from, to, this.state);
  }

});

//-------------------------------------------------------------------------------------------------

module.exports = JSM;

//-------------------------------------------------------------------------------------------------


/***/ }),
/* 5 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";


//-----------------------------------------------------------------------------------------------

var mixin    = __webpack_require__(0),
    camelize = __webpack_require__(2),
    plugin   = __webpack_require__(1),
    Config   = __webpack_require__(3),
    JSM      = __webpack_require__(4);

//-----------------------------------------------------------------------------------------------

var PublicMethods = {
  is:                  function(state)       { return this._fsm.is(state)                                     },
  can:                 function(transition)  { return this._fsm.can(transition)                               },
  cannot:              function(transition)  { return this._fsm.cannot(transition)                            },
  observe:             function()            { return this._fsm.observe(arguments)                            },
  transitions:         function()            { return this._fsm.transitions()                                 },
  allTransitions:      function()            { return this._fsm.allTransitions()                              },
  allStates:           function()            { return this._fsm.allStates()                                   },
  onInvalidTransition: function(t, from, to) { return this._fsm.onInvalidTransition(t, from, to)              },
  onPendingTransition: function(t, from, to) { return this._fsm.onPendingTransition(t, from, to)              },
}

var PublicProperties = {
  state: {
    configurable: false,
    enumerable:   true,
    get: function() {
      return this._fsm.state;
    },
    set: function(state) {
      throw Error('use transitions to change state')
    }
  }
}

//-----------------------------------------------------------------------------------------------

function StateMachine(options) {
  return apply(this || {}, options);
}

function factory() {
  var cstor, options;
  if (typeof arguments[0] === 'function') {
    cstor   = arguments[0];
    options = arguments[1] || {};
  }
  else {
    cstor   = function() { this._fsm.apply(this, arguments) };
    options = arguments[0] || {};
  }
  var config = new Config(options, StateMachine);
  build(cstor.prototype, config);
  cstor.prototype._fsm.config = config; // convenience access to shared config without needing an instance
  return cstor;
}

//-------------------------------------------------------------------------------------------------

function apply(instance, options) {
  var config = new Config(options, StateMachine);
  build(instance, config);
  instance._fsm();
  return instance;
}

function build(target, config) {
  if ((typeof target !== 'object') || Array.isArray(target))
    throw Error('StateMachine can only be applied to objects');
  plugin.build(target, config);
  Object.defineProperties(target, PublicProperties);
  mixin(target, PublicMethods);
  mixin(target, config.methods);
  config.allTransitions().forEach(function(transition) {
    target[camelize(transition)] = function() {
      return this._fsm.fire(transition, [].slice.call(arguments))
    }
  });
  target._fsm = function() {
    this._fsm = new JSM(this, config);
    this._fsm.init(arguments);
  }
}

//-----------------------------------------------------------------------------------------------

StateMachine.version  = '3.0.1';
StateMachine.factory  = factory;
StateMachine.apply    = apply;
StateMachine.defaults = {
  wildcard: '*',
  init: {
    name: 'init',
    from: 'none'
  }
}

//===============================================================================================

module.exports = StateMachine;


/***/ }),
/* 6 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";


module.exports = function(message, transition, from, to, current) {
  this.message    = message;
  this.transition = transition;
  this.from       = from;
  this.to         = to;
  this.current    = current;
}


/***/ })
/******/ ]);
});

================================================
FILE: docs/async-transitions.md
================================================
# Asynchronous Transitions

> You should be familiar with the state machine [Lifecycle Events](lifecycle-events.md) before reading this article.

Sometimes, you need to execute some asynchronous code during a state transition and ensure the new
state is not entered until your code has completed. A good example of this is when you transition
out of a state and want to gradually fade a UI component away, or slide it off the screen, and
don't want to transition to the next state until after that animation has completed.

You can achieve this by returning a [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)
object from any of the [Lifecycle Events](lifecycle-events.md).

Returning a Promise from a lifecycle event will cause the lifecycle for that transition to
pause. It can be continued by resolving the promise, or cancelled by rejecting the promise.

For example (using jQuery effects):

```javascript
  var fsm = new StateMachine({

    init: 'menu',

    transitions: [
      { name: 'play', from: 'menu', to: 'game' },
      { name: 'quit', from: 'game', to: 'menu' }
    ],

    methods: {

      onEnterMenu: function() {
        return new Promise(function(resolve, reject) {
          $('#menu').fadeIn('fast', resolve)
        })
      },

      onEnterGame: function() {
        return new Promise(function(resolve, reject) {
          $('#game').fadeIn('fast', resolve)
        })
      },

      onLeaveMenu: function() {
        return new Promise(function(resolve, reject) {
          $('#menu').fadeOut('fast', resolve)
        })
      },

      onLeaveGame: function() {
        return new Promise(function(resolve, reject) {
          $('#game').fadeOut('fast', resolve)
        })
      }
    }

  })
```

> Be sure that you always resolve (or reject) your Promise eventually, otherwise the state
  machine will be stuck forever within that pending transition.


================================================
FILE: docs/contributing.md
================================================
# Contributing

The `javascript-state-machine` library is built using:

  * [Webpack 2](https://webpack.js.org/concepts/) - for bundling javascript modules together
  * [UglifyJS2](https://github.com/mishoo/UglifyJS2) - for minifying bundled javascript files
  * [Ava](https://github.com/avajs/ava) - for testing

The directory structure includes:

```shell
  /bin              # - build scripts
  /dist             # - minified bundles for distribution
  /docs             # - documentation
  /examples         # - example visualizations
  /lib              # - bundled source code for npm
  /src              # - source code
  /test             # - unit tests

  package.json      # - npm configuration
  webpack.config.js # - webpack configuration

  LICENSE           # - the project licensing terms
  README.md         # - the project readme
  RELEASE_NOTES.md  # - the project release notes

```

Build time dependencies can be installed using npm:

```shell
  > npm install
```

A number of npm scripts are available:

```shell
  > npm run test    # run unit tests
  > npm run build   # bundle and minify files for distribution
  > npm run watch   # run tests if source files change
```

## Source Code

The source code is written in es5 syntax and should be supported by all [es5 compatible browsers](http://caniuse.com/#feat=es5).
[Babel](https://babeljs.io/) is **NOT** used for this project. Webpack is used to
bundle modules together for distribution.

## Submitting Pull Requests

Generally speaking, please raise an issue first and lets discuss the problem and the
proposed solution. The next step would be a pull-request - fantastic and thank you for helping out - but
please try to...

  * ensure the tests pass (`npm test`).
  * rebuild distribution files (`npm run build`).
  * include tests for your changes.
  * include documentation for your changes.
  * include a great commit message.


================================================
FILE: docs/data-and-methods.md
================================================
# Data and Methods

In addition to [States](states-and-transitions.md) and [Transitions](states-and-transitions.md), a state machine can
also contain arbitrary data and methods:

```javascript
  var fsm = new StateMachine({
    init: 'A',
    transitions: [
      { name: 'step', from: 'A', to: 'B' }
    ],
    data: {
      color: 'red'
    },
    methods: {
      describe: function() {
        console.log('I am ' + this.color);
      }
    }
  });

  fsm.state;      // 'A'
  fsm.color;      // 'red'
  fsm.describe(); // 'I am red'
```

## Data and State Machine Factories

If you are constructing multiple instances from a [State Machine Factory](state-machine-factory.md) then the
`data` object will be shared amongst them. This is almost certainly **NOT** what you want! To
ensure that each instance gets unique data you should use a `data` method instead:

```javascript
  var FSM = StateMachine.factory({
    init: 'A',
    transitions: [
      { name: 'step', from: 'A', to: 'B' }
    ],
    data: function(color) {      //  <-- use a method that can be called for each instance
      return {
        color: color
      }
    },
    methods: {
      describe: function() {
        console.log('I am ' + this.color);
      }
    }
  });

  var a = new FSM('red'),
      b = new FSM('blue');

  a.state; // 'A'
  b.state; // 'A'

  a.color; // 'red'
  b.color; // 'blue'

  a.describe(); // 'I am red'
  b.describe(); // 'I am blue'
```

> NOTE: that arguments used when constructing each instance are passed thru to the `data` method directly.


================================================
FILE: docs/error-handling.md
================================================
# Error Handling

## Invalid Transitions

By default, if you try to fire a transition that is not allowed in the current state, the
state machine will throw an exception. If you prefer to handle the problem yourself, you can
define a custom `onInvalidTransition` handler:

```javascript
  var fsm = new StateMachine({
    init: 'A',
    transitions: [
      { name: 'step',  from: 'A', to: 'B' },
      { name: 'reset', from: 'B', to: 'A' }
    ],
    methods: {
      onInvalidTransition: function(transition, from, to) {
        throw new Exception("transition not allowed from that state");
      }
    }
  });

  fsm.state;        // 'A'
  fsm.can('step');  // true
  fsm.can('reset'); // false

  fsm.reset();      //  <-- throws "transition not allowed from that state"
```

## Pending Transitions

By default, if you try to fire a transition during a [Lifecycle Event](lifecycle-events.md) for a
pending transition, the state machine will throw an exception. If you prefer to handle the problem
yourself, you can define a custom `onPendingTransition` handler:

```javascript
  var fsm = new StateMachine({
    init: 'A',
    transitions: [
      { name: 'step', from: 'A', to: 'B' },
      { name: 'step', from: 'B', to: 'C' }
    ],
    methods: {
      onLeaveA: function() {
        this.step();    //  <-- uh oh, trying to transition from within a lifecycle event is not allowed
      },
      onPendingTransition: function(transition, from, to) {
        throw new Exception("transition already in progress");
      }
    }
  });

  fsm.state;       // 'A'
  fsm.can('step'), // true
  fsm.step();      //  <-- throws "transition already in progress"
```


================================================
FILE: docs/initialization.md
================================================
# Initialization Options

## Explicit Init Transition

By default, if you don't specify an initial state, the state machine will be in the `none`
state, no lifecycle events will fire during construction, and you will need to provide an
explicit transition to advance out of this state:

```javascript
  var fsm = new StateMachine({
    transitions: [
      { name: 'init', from: 'none', to: 'A' },
      { name: 'step', from: 'A',    to: 'B' },
      { name: 'step', from: 'B',    to: 'C' }
    ]
  });
  fsm.state;    // 'none'
  fsm.init();   // 'init()' transition is fired explicitly
  fsm.state;    // 'A'
```

## Implicit Init Transition

If you specify the name of your initial state (as in most of the examples in this documentation),
then an implicit `init` transition will be created for you and fired (along with appropriate
lifecycle events) when the state machine is constructed.

> This is the most common initialization strategy, and the one you should use 90% of the time

```javascript
  var fsm = new StateMachine({
    init: 'A',
    transitions: [
      { name: 'step', from: 'A', to: 'B' },
      { name: 'step', from: 'B', to: 'C' }
    ]
  });           // 'init()' transition fires from 'none' to 'A' during construction
  fsm.state;    // 'A'
```

## Initialization and State Machine Factories

For [State Machine Factories](state-machine-factory.md), the `init` transition
is triggered for each constructed instance.

```javascript
  var FSM = StateMachine.factory({
    init: 'A',
    transitions: [
      { name: 'step', from: 'A', to: 'B' },
      { name: 'step', from: 'B', to: 'C' }
    ]
  });

  var fsm1 = new FSM(),   // 'init()' transition fires from 'none' to 'A' for fsm1
      fsm2 = new FSM();   // 'init()' transition fires from 'none' to 'A' for fsm2
```


================================================
FILE: docs/lifecycle-events.md
================================================
# Lifecycle Events

In order to track or perform an action when a transition occurs, five
general-purpose lifecycle events can be observed:

  * `onBeforeTransition` - fired before any transition
  * `onLeaveState`       - fired when leaving any state
  * `onTransition`       - fired during any transition
  * `onEnterState`       - fired when entering any state
  * `onAfterTransition`  - fired after any transition

In addition to the general-purpose events, transitions can be observed
using your specific transition and state names:

  * `onBefore<TRANSITION>` - fired before a specific TRANSITION begins
  * `onLeave<STATE>`       - fired when leaving a specific STATE
  * `onEnter<STATE>`       - fired when entering a specific STATE
  * `onAfter<TRANSITION>`  - fired after a specific TRANSITION completes

For convenience, the 2 most useful events can be shortened:

  * `on<TRANSITION>` - convenience shorthand for `onAfter<TRANSITION>`
  * `on<STATE>`      - convenience shorthand for `onEnter<STATE>`

## Observing Lifecycle Events

Individual lifecycle events can be observed using an observer method:

```javascript
  fsm.observe('onStep', function() {
    console.log('stepped');
  });
```

Multiple events can be observed using an observer object:

```javascript
  fsm.observe({
    onStep: function() { console.log('stepped');         }
    onA:    function() { console.log('entered state A'); }
    onB:    function() { console.log('entered state B'); }
  });
```

A state machine always observes its own lifecycle events:

```javascript
  var fsm = new StateMachine({
    init: 'A',
    transitions: [
      { name: 'step', from: 'A', to: 'B' }
    ],
    methods: {
      onStep: function() { console.log('stepped');         }
      onA:    function() { console.log('entered state A'); }
      onB:    function() { console.log('entered state B'); }
    }
  });
```

## Lifecycle Event Arguments

Observers will be passed a single argument containing a `lifecycle` object with the following attributes:

  * **transition** - the transition name
  * **from**       - the previous state
  * **to**         - the next state

In addition to the `lifecycle` argument, the observer will receive any arbitrary arguments passed
into the transition method

```javascript
  var fsm = new StateMachine({
    transitions: [
      { name: 'step', from: 'A', to: 'B' }
    ],
    methods: {
      onTransition: function(lifecycle, arg1, arg2) {
        console.log(lifecycle.transition); // 'step'
        console.log(lifecycle.from);       // 'A'
        console.log(lifecycle.to);         // 'B'
        console.log(arg1);                 // 42
        console.log(arg2);                 // 'hello'
      }
    }
  });

  fsm.step(42, 'hello');
```

## Lifecycle Event Names

Lifecycle event names always use standard javascipt camelCase, even if your transition and
state names do not:

```javascript
  var fsm = new StateMachine({
    transitions: [
      { name: 'do-with-dash',       from: 'has-dash',        to: 'has_underscore'   },
      { name: 'do_with_underscore', from: 'has_underscore',  to: 'alreadyCamelized' },
      { name: 'doAlreadyCamelized', from: 'alreadyCamelize', to: 'has-dash'         }
    ],
    methods: {
      onBeforeDoWithDash:         function() { /* ... */ },
      onBeforeDoWithUnderscore:   function() { /* ... */ },
      onBeforeDoAlreadyCamelized: function() { /* ... */ },
      onLeaveHasDash:             function() { /* ... */ },
      onLeaveHasUnderscore:       function() { /* ... */ },
      onLeaveAlreadyCamelized:    function() { /* ... */ },
      onEnterHasDash:             function() { /* ... */ },
      onEnterHasUnderscore:       function() { /* ... */ },
      onEnterAlreadyCamelized:    function() { /* ... */ },
      onAfterDoWithDash:          function() { /* ... */ },
      onAfterDoWithUnderscore:    function() { /* ... */ },
      onAfterDoAlreadyCamelized:  function() { /* ... */ }
    }
  });
```

# Lifecycle Events Listed in Order

To recap, the lifecycle of a transition occurs in the following order:

  * `onBeforeTransition`   - fired before any transition
  * `onBefore<TRANSITION>` - fired before a specific TRANSITION
  * `onLeaveState`         - fired when leaving any state
  * `onLeave<STATE>`       - fired when leaving a specific STATE
  * `onTransition`         - fired during any transition
  * `onEnterState`         - fired when entering any state
  * `onEnter<STATE>`       - fired when entering a specific STATE
  * `on<STATE>`            - convenience shorthand for `onEnter<STATE>`
  * `onAfterTransition`    - fired after any transition
  * `onAfter<TRANSITION>`  - fired after a specific TRANSITION
  * `on<TRANSITION>`       - convenience shorthand for `onAfter<TRANSITION>`

# Cancelling a Transition

Any observer can cancel a transition by explicitly returning `false` during any of the following
lifecycle events:

  * `onBeforeTransition`
  * `onBefore<TRANSITION>`
  * `onLeaveState`
  * `onLeave<STATE>`
  * `onTransition`

All subsequent lifecycle events will be cancelled and the state will remain unchanged.



================================================
FILE: docs/state-history.md
================================================
# Remembering State History

By default, a state machine only tracks its current state. If you wish to track the state history
you can extend the state machine with the `state-machine-history` plugin.

```javascript
  var StateMachineHistory = require('javascript-state-machine/lib/history')
```

```javascript

  var fsm = new StateMachine({
    init: 'A',
    transitions: [
      { name: 'step', from: 'A', to: 'B' },
      { name: 'step', from: 'B', to: 'C' },
      { name: 'step', from: 'C', to: 'D' }
    ],
    plugins: [
      new StateMachineHistory()     //  <-- plugin enabled here
    ]
  })

  fsm.history;        // [ 'A' ]
  fsm.step();
  fsm.history;        // [ 'A', 'B' ]
  fsm.step();
  fsm.history;        // [ 'A', 'B', 'C' ]

  fsm.clearHistory();

  fsm.history;        // [ ]

```
## Traversing History

You can traverse back through history using the `historyBack` and `historyForward` methods:

```javascript
  var fsm = new StateMachine({
    init: 'A',
    transitions: [
      { name: 'step', from: 'A', to: 'B' },
      { name: 'step', from: 'B', to: 'C' },
      { name: 'step', from: 'C', to: 'D' }
    ]
  })

  fsm.step();
  fsm.step();
  fsm.step();

  fsm.state;    //                  'D'
  fsm.history;  // [ 'A', 'B', 'C', 'D' ]

  fsm.historyBack();

  fsm.state;    //             'C'
  fsm.history;  // [ 'A', 'B', 'C' ]

  fsm.historyBack();

  fsm.state;    //        'B'
  fsm.history;  // [ 'A', 'B' ]

  fsm.historyForward();

  fsm.state;    // 'C'
  fsm.history;  // [ 'A', 'B', 'C' ]
```

You can test if history traversal is allowed using the following properties:

```javascript
  fsm.canHistoryBack;     // true/false
  fsm.canHistoryForward;  // true/false
```

A full set of [Lifecycle Events](lifecycle-events.md) will still apply when traversing history with
`historyBack` and `historyForward`.

## Limiting History

By default, the state machine history is unbounded and will continue to grow until cleared. You
can limit storage to only the last N states by configuring the plugin:

``` javascript
  var fsm = new StateMachine({
    plugins: [
      new StateMachineHistory({ max: 100 })      //  <-- plugin configuration
    ]
  })
```

## Customizing History

If the `history` terminology clashes with your existing state machine attributes or methods, you
can enable the plugin with a different name:

```javascript
  var fsm = new StateMachine({
    init: 'A',
    transitions: [
      { name: 'step', from: 'A', to: 'B' },
      { name: 'step', from: 'B', to: 'C' },
      { name: 'step', from: 'C', to: 'D' }
    ],
    plugins: [
      new StateMachineHistory({ name: 'memory' })
    ]
  })

  fsm.step();
  fsm.step();

  fsm.memory;         // [ 'A', 'B', 'C' ]

  fsm.memoryBack();
  fsm.memory;         // [ 'A', 'B' ]

  fsm.memoryForward();
  fsm.memory;         // [ 'A', 'B', 'C' ]

  fsm.clearMemory();
  fsm.memory;         // [ ]
```



================================================
FILE: docs/state-machine-factory.md
================================================
# State Machine Factory

Most examples in this documentation construct a single state machine instance, for example:

```javascript
  var fsm = new StateMachine({
    init: 'solid',
    transitions: [
      { name: 'melt',     from: 'solid',  to: 'liquid' },
      { name: 'freeze',   from: 'liquid', to: 'solid'  },
      { name: 'vaporize', from: 'liquid', to: 'gas'    },
      { name: 'condense', from: 'gas',    to: 'liquid' }
    ]
  });
```

If you wish to construct multiple instances using the same configuration you should use a State
Machine Factory. A State Machine Factory provides a javascript constructor function (e.g. a 'class')
that can be instantiated multiple times:

```javascript
  var Matter = StateMachine.factory({     //  <-- the factory is constructed here
      init: 'solid',
      transitions: [
        { name: 'melt',     from: 'solid',  to: 'liquid' },
        { name: 'freeze',   from: 'liquid', to: 'solid'  },
        { name: 'vaporize', from: 'liquid', to: 'gas'    },
        { name: 'condense', from: 'gas',    to: 'liquid' }
      ]
  });

  var a = new Matter(),    //  <-- instances are constructed here
      b = new Matter(),
      c = new Matter();

  b.melt();
  c.melt();
  c.vaporize();

  a.state;    // solid
  b.state;    // liquid
  c.state;    // gas
```

Using the factory, each state machine instance is a unique javascript object. Each instance manages
its own `state` property, but methods are shared via the normal javascript prototype mechanism.

> NOTE: be aware of special case handling required for [Data and State Machine Factories](data-and-methods.md#data-and-state-machine-factories)
 
## Applying State Machine Behavior to Existing Objects

Occasionally, you may wish to apply state machine behavior to an already existing
object (e.g. a react component). You can achieve this using the `StateMachine.apply` method:

```javascript
  var component = { /* ... */ };

  StateMachine.apply(component, {
    init: 'A',
    transitions: {
      { name: 'step', from: 'A', to: 'B' }
    }
  });
```

> Be careful not to use state or transition names that will clash with existing object properties.

## Applying State Machine Factory Behavior to Existing Classes

You can also apply state machine factory behavior to an existing class, however you must now
take responsibility for initialization by calling `this._fsm()` from within your class
constructor method:

```javascript
  function Person(name) {
    this.name = name;
    this._fsm(); //  <-- IMPORTANT
  }

  Person.prototype = {
    speak: function() {
      console.log('my name is ' + this.name + ' and I am ' + this.state);
    }
  }

  StateMachine.factory(Person, {
    init: 'idle',
    transitions: {
      { name: 'sleep', from: 'idle',     to: 'sleeping' },
      { name: 'wake',  from: 'sleeping', to: 'idle'     }
    }
  });

  var amy = new Person('amy'),
      bob = new Person('bob');

  bob.sleep();

  amy.state;   // 'idle'
  bob.state;   // 'sleeping'

  amy.speak(); // 'my name is amy and I am idle'
  bob.speak(); // 'my name is bob and I am sleeping'
```


================================================
FILE: docs/states-and-transitions.md
================================================
# States and Transitions

![matter state machine](../examples/matter.png)

A state machine consists of a set of **states**, e.g:

  * solid
  * liquid
  * gas

.. and a set of **transitions**, e.g:

  * melt
  * freeze
  * vaporize
  * condense

```javascript
  var fsm = new StateMachine({
    init: 'solid',
    transitions: [
      { name: 'melt',     from: 'solid',  to: 'liquid' },
      { name: 'freeze',   from: 'liquid', to: 'solid'  },
      { name: 'vaporize', from: 'liquid', to: 'gas'    },
      { name: 'condense', from: 'gas',    to: 'liquid' }
    ]
  });

  fsm.state;             // 'solid'
  fsm.melt();
  fsm.state;             // 'liquid'
  fsm.vaporize();
  fsm.state;             // 'gas'
```

## Multiple states for a transition

![wizard state machine](../examples/wizard.png)

If a transition is allowed `from` multiple states then declare the transitions with the same name:

```javascript
  { name: 'step',  from: 'A', to: 'B' },
  { name: 'step',  from: 'B', to: 'C' },
  { name: 'step',  from: 'C', to: 'D' }
```

If a transition with multiple `from` states always transitions `to` the same state, e.g:

```javascript
  { name: 'reset', from: 'B', to: 'A' },
  { name: 'reset', from: 'C', to: 'A' },
  { name: 'reset', from: 'D', to: 'A' }
```

... then it can be abbreviated using an array of `from` states:

```javascript
  { name: 'reset', from: [ 'B', 'C', 'D' ], to: 'A' }
```

Combining these into a single example:

```javascript
  var fsm = new StateMachine({
    init: 'A',
    transitions: [
      { name: 'step',  from: 'A',               to: 'B' },
      { name: 'step',  from: 'B',               to: 'C' },
      { name: 'step',  from: 'C',               to: 'D' },
      { name: 'reset', from: [ 'B', 'C', 'D' ], to: 'A' }
    ]
  })
```

This example will create an object with 2 transition methods:

  * `fsm.step()`
  * `fsm.reset()`

The `reset` transition will always end up in the `A` state, while the `step` transition
will end up in a state that is dependent on the current state.

## Wildcard Transitions

If a transition is appropriate from **any** state, then a wildcard '*' `from` state can be used:

```javascript
  var fsm = new StateMachine({
    transitions: [
      // ...
      { name: 'reset', from: '*', to: 'A' }
    ]
  });
```

## Conditional Transitions

A transition can choose the target state at run-time by providing a function as the `to` attribute:

```javascript
  var fsm = new StateMachine({
    init: 'A',
    transitions: [
      { name: 'step', from: '*', to: function(n) { return increaseCharacter(this.state, n || 1) } }
    ]
  });

  fsm.state;      // A
  fsm.step();
  fsm.state;      // B
  fsm.step(5);
  fsm.state;      // G

  // helper method to perform (c = c + n) on the 1st character in str
  function increaseCharacter(str, n) {
    return String.fromCharCode(str.charCodeAt(0) + n);
  }
```

The `allStates` method will only include conditional states once they have been seen at run-time:

```javascript
  fsm.state;        // A
  fsm.allStates();  // [ 'A' ]
  fsm.step();
  fsm.state;        // B
  fsm.allStates();  // [ 'A', 'B' ]
  fsm.step(5);
  fsm.state;        // G
  fsm.allStates();  // [ 'A', 'B', 'G' ]
```

## GOTO - Changing State Without a Transition

You can use a conditional transition, combined with a wildcard `from`, to implement
arbitrary `goto` behavior:

```javascript
  var fsm = new StateMachine({
    init: 'A'
    transitions: [
      { name: 'step', from: 'A', to: 'B'                      },
      { name: 'step', from: 'B', to: 'C'                      },
      { name: 'step', from: 'C', to: 'D'                      },
      { name: 'goto', from: '*', to: function(s) { return s } }
    ]
  })

  fsm.state;     // 'A'
  fsm.goto('D');
  fsm.state;     // 'D'
```

A full set of [Lifecycle Events](lifecycle-events.md) still apply when using `goto`.



================================================
FILE: docs/upgrading-from-v2.md
================================================
# Upgrading from Version 2.x

Version 3.0 is a significant rewrite from earlier versions in order to support more
advanced use cases and to improve the existing use cases. Unfortunately, many of these
updates are incompatible with earlier versions, so changes are required in your code when you upgrade
to version 3.x. We want to tackle those all in one swoop and avoid any more big-bang changes
in the future.

Please read this article carefully if you are upgrading from version 2.x to 3.x.

> A [summary](#upgrade-summary) of the changes required can be found at the end of the article.

### Table of Contents

  * [**Construction**](#construction) - constructing single instances follows a more idomatic javascript pattern.
  * [**State Machine Factory**](#state-machine-factory) - constructing multiple instances from a class has been simplified.
  * [**Data and Methods**](#data-and-methods) - A state machine can now have additional data and methods.
  * [**Renamed Terminology**](#renamed-terminology) - A more consistent terminology has been applied.
  * [**Lifecycle Events**](#lifecycle-events) - (previously called 'callbacks') are camelCased and observable.
  * [**Async Transitions**](#promise-based-asynchronous-transitions) - Asynchronous transitions now use standard Promises.
  * [**Conditional Transitions**](#conditional-transitions) - A transition can now dynamically choose its target state at run-time.
  * [**Goto**](#goto) - The state can be changed without a defined transition using `goto`.
  * [**State History**](#state-history) - The state history can now be retained and traversed with back/forward semantics.
  * [**Visualization**](#visualization) - A state machine can now be visualized using GraphViz.
  * [**Build System**](#build-system) - A new webpack-based build system has been implemented.

## Construction

Constructing a single state machine now follows a more idiomatic javascript pattern:

Version 2.x:

```javascript
  var fsm = StateMachine.create({ /* ... */ })
```

**Version 3.x**:

```javascript
  var fsm = new StateMachine({ /* ... */ })    //  <-- more idomatic 
```

## State Machine Factory

Constructing multiple instances from a state machine 'class' has been simplified:

Version 2.x:

```javascript
  function FSM() { }

  StateMachine.create({
    target: FSM.prototype,
    // ...
  })

  var a = new FSM(),
      b = new FSM();
```

**Version 3.x**:

```javascript
  var FSM = StateMachine.factory({ /* ... */ }),    //  <-- generate a factory (a constructor function)
      a   = new FSM(),                              //  <-- then create instances
      b   = new FSM();
```

## Data and Methods

A state machine can now have additional (arbitrary) data and methods defined:

Version 2.x: _not supported_.

**Version 3.x**:

```javascript
  var fsm = new StateMachine({
    data: {
      color: 'red'
    },
    methods: {
      speak: function() { console.log('hello') }
    }
  });

  fsm.color;   // 'red'
  fsm.speak(); // 'hello'
```

## Renamed Terminology

A more consistent terminology has been applied:

  * A state machine consists of a set of [**States**](states-and-transitions.md).
  * A state machine changes state by using [**Transitions**](states-and-transitions.md).
  * A state machine can perform actions during a transition by observing [**Lifecycle Events**](lifecycle-events.md).
  * A state machine can also have arbitrary [**Data and Methods**](data-and-methods.md).

Version 2.x:

```javascript
  var fsm = StateMachine.create({
    initial: 'ready',
    events:     [ /* ... */ ],
    callbacks:  { /* ... */ }
  });

  fsm.current;  // 'ready'
```

**Version 3.x**:

```javascript
  var fsm = new StateMachine({
    init:        'ready',             //  <-- renamed s/initial/init/
    transitions: [ /* ... */ ],       //  <-- renamed s/events/transitions/
    data:        { /* ... */ },       //  <-- new
    methods:     { /* ... */ }        //  <-- renamed s/callbacks/methods/
                                      //      ... which can contain arbitrary methods AND lifecycle event callbacks
  });

  fsm.state;  // 'ready'              //  <-- renamed s/current/state/
```

## Lifecycle Events

**Callbacks** have been renamed **Lifecycle Events** and are now declared as `methods` on the
state machine using a more traditional javascript camelCase for the method names:

Version 2.x:

```javascript
  var fsm = StateMachine.create({
    initial: 'initial-state',
    events: [
      { name: 'do-something', from: 'initial-state', to: 'final-state' }
    ],
    callbacks: {
      onbeforedosomething: function() { /* ... */ },
      onleaveinitialstate: function() { /* ... */ },
      onenterfinalstate:   function() { /* ... */ },
      onafterdosomething:  function() { /* ... */ }
    }
  })
```

**Version 3.x**:

```javascript
  var fsm = new StateMachine({
    init: 'initial-state',
    transitions: [
      { name: 'do-something', from: 'initial-state', to: 'final-state' }
    ],
    methods: {                                         //  <-- renamed s/callbacks/methods/
      onBeforeDoSomething: function() { /* ... */ },   //  <-- camelCase naming convention
      onLeaveInitialState: function() { /* ... */ },   //  <--
      onEnterFinalState:   function() { /* ... */ },   //  <--
      onAfterDoSomething:  function() { /* ... */ }    //  <--
    }
  })
```

<hr>
Lifecycle events are now passed information in a single `lifecycle` argument:

Version 2.x:

```javascript
  var fsm = StateMachine.create({
    events: [
      { name: 'step', from: 'none', to: 'complete' }
    ],
    callbacks: {
      onbeforestep: function(event, from, to) {
        console.log('event: ' + event);   // 'step'
        console.log('from: '  + from);    // 'none'
        console.log('to: '    + to);      // 'complete'
      },
    }
  });
```

**Version 3.x**:

```javascript
  var fsm = new StateMachine({
    transitions: [
      { name: 'step', from: 'none', to: 'complete' }
    ],
    methods: {
      onBeforeStep: function(lifecycle) {                   //  <-- combined into a single argument
        console.log('transition: ' + lifecycle.transition); //  'step'
        console.log('from: '       + lifecycle.from);       //  'none'
        console.log('to: '         + lifecycle.to);         //  'complete'
      }
    }
  });
```

> This change allows us to include additional information in the future without having to have a ridiculous
number of arguments to lifecycle event observer methods

<hr>
Lifecycle events are also now observable by others:
 
Version 2.x: _not supported_.

**Version 3.x**:

```javascript
  var fsm = new StateMachine({ /* ... */ });

  // observe individual lifecycle events with observer methods
  fsm.observe('onBeforeTransition', function() { /* ... */ });
  fsm.observe('onLeaveState',       function() { /* ... */ });

  // or observe multiple lifecycle events with an observer object
  fsm.observe({
    onBeforeTransition: function() { /* ... */ },
    onLeaveState:       function() { /* ... */ }
  });
```

<hr>
The general purpose lifecycle events now use the word `transition` instead of `event` and
occur **before** their specialized versions:

Version 2.x, the lifecycle order was:

  * `onbefore<EVENT>`
  * `onbeforeevent`
  * `onleave<STATE>`
  * `onleavestate`
  * `onenter<STATE>`
  * `onenterstate`
  * `on<STATE>`
  * `onafter<EVENT>`
  * `onafterevent`
  * `on<EVENT>`

**Version 3.x**, the lifecycle order is:

  * `onBeforeTransition`   - fired before any transition
  * `onBefore<TRANSITION>` - fired before a specific TRANSITION
  * `onLeaveState`         - fired when leaving any state
  * `onLeave<STATE>`       - fired when leaving a specific STATE
  * `onTransition`         - fired during any transition
  * `onEnterState`         - fired when entering any state
  * `onEnter<STATE>`       - fired when entering a specific STATE
  * `on<STATE>`            - convenience shorthand for `onEnter<STATE>`
  * `onAfterTransition`    - fired after any transition
  * `onAfter<TRANSITION>`  - fired after a specific TRANSITION
  * `on<TRANSITION>`       - convenience shorthand for `onAfter<TRANSITION>`

> For more details, read [Lifecycle Events](lifecycle-events.md)

## Promise-Based Asynchronous Transitions

Asynchronous transitions are now implemented using standard javascript [Promises](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise).

If you return a Promise from **any** lifecycle event then the entire lifecycle for that transition
is put on hold until that Promise gets resolved. If the promise is rejected then the transition
is cancelled.

Version 2.x:

```javascript
  var fsm = StateMachine.create({
    events: [
      { name: 'step', from: 'none', to: 'complete' }
    ],
    callbacks: {
      onbeforestep: function() {
        $('#ui').fadeOut('fast', function() {
          fsm.transition();
        });
        return StateMachine.ASYNC;
      }
    }
  });
```

**Version 3.x**:

```javascript
  var fsm = new StateMachine({
    transitions: [
      { name: 'step', from: 'none', to: 'complete' }
    ],
    methods: {
      onBeforeStep: function() {
        return new Promise(function(resolve, reject) {  //  <-- return a Promise instead of StateMachine.ASYNC
          $('#ui').fadeOut('fast', resolve);            //  <-- resolve the promise instead of calling .transition()
        });
      }
    }
  });
```

> For more details, read [Asynchronous Transitions](async-transitions.md)

## Conditional Transitions

A transition can now be conditional and choose the target state at run-time by providing a function
as the `to` attribute.

Version 2.x: _not supported_.

**Version 3.x**: See [Conditional Transitions](states-and-transitions.md#conditional-transitions)

## Goto

The state can now be changed without the need for a predefined transition using a conditional `goto`
transition:

Version 2.x: _not_supported_.

**Version 3.x**: See [Goto](states-and-transitions.md#goto---changing-state-without-a-transition)

## State History

A state machine can now track and traverse (back/forward) its state history.

Version 2.x: _not supported_.

**Version 3.x**: See [State History](state-history.md)

## Visualization

A state machine can now be visualized as a directed graph using GraphViz `.dot` syntax.

Version 2.x: _not_supported_.

**Version 3.x**: See [Visualization](visualization.md)

## Build System

A new [Webpack](https://webpack.js.org/concepts/) based build system has been provided along
with an [Ava](https://github.com/avajs/ava) based unit test suite.

Version 2.x: _not_supported_.

**Version 3.x**: See [Contributing](contributing.md)

## Other Breaking Changes in Version 3.0

`isFinished` is no longer built-in, you can easily add it to your state machine with a custom method:

```javascript
  var fsm = new StateMachine({
    methods: {
      isFinished: function() { return this.state === 'done' }
    }
  })
```

# UPGRADE SUMMARY

The following list summarizes the above changes you might need when upgrading to version 3.0

  * replace `StateMachine.create()` with `new StateMachine()`
  * rename:
    * `initial` to `init`
    * `events` to `transitions`
    * `callbacks` to `methods`
    * `fsm.current` to `fsm.state`
  * update your callback methods:
    * rename them to use traditional javascript `camelCasing`
    * refactor them to use the single `lifecycle` argument instead of individual `event,from,to` arguments
  * update any asynchronous callback methods:
    * return a `Promise` instead of `StateMachine.ASYNC`
    * `resolve()` the promise when ready instead of calling `fsm.transition()`
  * replace `StateMachine.create({ target: FOO })` with:
    * if FOO is a class - `StateMachine.factory(FOO, {})`
    * if FOO is an object - `StateMachine.apply(FOO, {})`



================================================
FILE: docs/visualization.md
================================================
# Visualization

It can be very helpful to visualize your state machine as a directed graph. This is possible
with the open source [GraphViz](http://www.graphviz.org/) library if we convert from our
state machine configuration to the `.dot` language expected by GraphViz using the
`visualize` method:

```javascript
  var visualize = require('javascript-state-machine/lib/visualize');

  var fsm = new StateMachine({
    init: 'open',
    transitions: [
      { name: 'close', from: 'open',   to: 'closed' },
      { name: 'open',  from: 'closed', to: 'open'   }
    ]
  });

  visualize(fsm)
```

Generates the following .dot syntax:

```dot
  digraph "fsm" {
    "closed";
    "open";
    "closed" -> "open" [ label=" open " ];
    "open" -> "closed" [ label=" close " ];
  }
```

Which GraphViz displays as:

![door](../examples/vertical_door.png)

## Enhanced Visualization

You can customize the generated `.dot` output - and hence the graphviz visualization - by attaching 
`dot` attributes to your transitions and (optionally) declaring an `orientation`:

```javascript
  var fsm = new StateMachine({
    init: 'closed',
    transitions: [
      { name: 'open',  from: 'closed', to: 'open',   dot: { color: 'blue', headport: 'n', tailport: 'n' } },
      { name: 'close', from: 'open',   to: 'closed', dot: { color: 'red',  headport: 's', tailport: 's' } }
    ]
  });
  visualize(fsm, { name: 'door', orientation: 'horizontal' });
```

Generates the following (enhanced) `.dot` syntax:

```dot
  digraph "door" {
    rankdir=LR;
    "closed";
    "open";
    "closed" -> "open" [ color="blue" ; headport="n" ; label=" open " ; tailport="n" ];
    "open" -> "closed" [ color="red" ; headport="s" ; label=" close " ; tailport="s" ];
  }
```

Which GraphViz displays as:

![door](../examples/horizontal_door.png)

## Visualizing State Machine Factories

You can use the same `visualize` method to generate `.dot` output for a state machine factory:

```javascript
  var Matter = StateMachine.factory({
    init: 'solid',
    transitions: [
      { name: 'melt',     from: 'solid',  to: 'liquid', dot: { headport: 'nw' } },
      { name: 'freeze',   from: 'liquid', to: 'solid',  dot: { headport: 'se' } },
      { name: 'vaporize', from: 'liquid', to: 'gas',    dot: { headport: 'nw' } },
      { name: 'condense', from: 'gas',    to: 'liquid', dot: { headport: 'se' } }
    ]
  });

  visualize(Matter, { name: 'matter', orientation: 'horizontal' })
```

Generates the following .dot syntax:

```dot
  digraph "matter" {
    rankdir=LR;
    "solid";
    "liquid";
    "gas";
    "solid" -> "liquid" [ headport="nw" ; label=" melt " ];
    "liquid" -> "solid" [ headport="se" ; label=" freeze " ];
    "liquid" -> "gas" [ headport="nw" ; label=" vaporize " ];
    "gas" -> "liquid" [ headport="se" ; label=" condense " ];
  }
```

Which GraphViz displays as:

![matter](../examples/matter.png)

## Other Examples

```javascript
  var Wizard = StateMachine.factory({
    init: 'A',
    transitions: [
      { name: 'step',  from: 'A',               to: 'B', dot: { headport: 'w',  tailport: 'ne' } },
      { name: 'step',  from: 'B',               to: 'C', dot: { headport: 'w',  tailport: 'e' } },
      { name: 'step',  from: 'C',               to: 'D', dot: { headport: 'w',  tailport: 'e' } },
      { name: 'reset', from: [ 'B', 'C', 'D' ], to: 'A', dot: { headport: 'se', tailport: 's' } }
    ]
  });

  visualize(Wizard, { orientation: 'horizontal' })
```

Generates:

```dot
  digraph "wizard" {
    rankdir=LR;
    "A";
    "B";
    "C";
    "D";
    "A" -> "B" [ headport="w" ; label=" step " ; tailport="ne" ];
    "B" -> "C" [ headport="w" ; label=" step " ; tailport="e" ];
    "C" -> "D" [ headport="w" ; label=" step " ; tailport="e" ];
    "B" -> "A" [ headport="se" ; label=" reset " ; tailport="s" ];
    "C" -> "A" [ headport="se" ; label=" reset " ; tailport="s" ];
    "D" -> "A" [ headport="se" ; label=" reset " ; tailport="s" ];
  }
```

Displays:

![wizard](../examples/wizard.png)

```javascript
  var ATM = StateMachine.factory({
    init: 'ready',
    transitions: [
      { name: 'insert-card', from: 'ready',              to: 'pin'                },
      { name: 'confirm',     from: 'pin',                to: 'action'             },
      { name: 'reject',      from: 'pin',                to: 'return-card'        },
      { name: 'withdraw',    from: 'return-card',        to: 'ready'              },

      { name: 'deposit',     from: 'action',             to: 'deposit-account'    },
      { name: 'provide',     from: 'deposit-account',    to: 'deposit-amount'     },
      { name: 'provide',     from: 'deposit-amount',     to: 'confirm-deposit'    },
      { name: 'confirm',     from: 'confirm-deposit',    to: 'collect-envelope'   },
      { name: 'provide',     from: 'collect-envelope',   to: 'continue'           },

      { name: 'withdraw',    from: 'action',             to: 'withdrawal-account' },
      { name: 'provide',     from: 'withdrawal-account', to: 'withdrawal-amount'  },
      { name: 'provide',     from: 'withdrawal-amount',  to: 'confirm-withdrawal' },
      { name: 'confirm',     from: 'confirm-withdrawal', to: 'dispense-cash'      },
      { name: 'withdraw',    from: 'dispense-cash',      to: 'continue'           },

      { name: 'continue',    from: 'continue',           to: 'action'             },
      { name: 'finish',      from: 'continue',           to: 'return-card'        }
    ]
  })

  visualize(ATM)
```

Generates:

```dot
  digraph "ATM" {
    "ready";
    "pin";
    "action";
    "return-card";
    "deposit-account";
    "deposit-amount";
    "confirm-deposit";
    "collect-envelope";
    "continue";
    "withdrawal-account";
    "withdrawal-amount";
    "confirm-withdrawal";
    "dispense-cash";
    "ready" -> "pin" [ label=" insert-card " ];
    "pin" -> "action" [ label=" confirm " ];
    "pin" -> "return-card" [ label=" reject " ];
    "return-card" -> "ready" [ label=" withdraw " ];
    "action" -> "deposit-account" [ label=" deposit " ];
    "deposit-account" -> "deposit-amount" [ label=" provide " ];
    "deposit-amount" -> "confirm-deposit" [ label=" provide " ];
    "confirm-deposit" -> "collect-envelope" [ label=" confirm " ];
    "collect-envelope" -> "continue" [ label=" provide " ];
    "action" -> "withdrawal-account" [ label=" withdraw " ];
    "withdrawal-account" -> "withdrawal-amount" [ label=" provide " ];
    "withdrawal-amount" -> "confirm-withdrawal" [ label=" provide " ];
    "confirm-withdrawal" -> "dispense-cash" [ label=" confirm " ];
    "dispense-cash" -> "continue" [ label=" withdraw " ];
    "continue" -> "action" [ label=" continue " ];
    "continue" -> "return-card" [ label=" finish " ];
  }
```

Displays:

![atm](../examples/atm.png)


================================================
FILE: examples/atm.dot
================================================
digraph "ATM" {
  "ready";
  "pin";
  "action";
  "return-card";
  "deposit-account";
  "deposit-amount";
  "confirm-deposit";
  "collect-envelope";
  "continue";
  "withdrawal-account";
  "withdrawal-amount";
  "confirm-withdrawal";
  "dispense-cash";
  "ready" -> "pin" [ label=" insert-card " ];
  "pin" -> "action" [ label=" confirm " ];
  "pin" -> "return-card" [ label=" reject " ];
  "return-card" -> "ready" [ label=" withdraw " ];
  "action" -> "deposit-account" [ label=" deposit " ];
  "deposit-account" -> "deposit-amount" [ label=" provide " ];
  "deposit-amount" -> "confirm-deposit" [ label=" provide " ];
  "confirm-deposit" -> "collect-envelope" [ label=" confirm " ];
  "collect-envelope" -> "continue" [ label=" provide " ];
  "action" -> "withdrawal-account" [ label=" withdraw " ];
  "withdrawal-account" -> "withdrawal-amount" [ label=" provide " ];
  "withdrawal-amount" -> "confirm-withdrawal" [ label=" provide " ];
  "confirm-withdrawal" -> "dispense-cash" [ label=" confirm " ];
  "dispense-cash" -> "continue" [ label=" withdraw " ];
  "continue" -> "action" [ label=" continue " ];
  "continue" -> "return-card" [ label=" finish " ];
}

================================================
FILE: examples/atm.js
================================================
var StateMachine = require('../src/app'),
    visualize    = require('../src/plugin/visualize');

var ATM = StateMachine.factory({
  init: 'ready',
  transitions: [
    { name: 'insert-card', from: 'ready',                     to: 'pin'               },
    { name: 'confirm',     from: 'pin',               to: 'action'             },
    { name: 'reject',      from: 'pin',               to: 'return-card'               },
    { name: 'withdraw',    from: 'return-card',               to: 'ready'                     },

    { name: 'deposit',     from: 'action',             to: 'deposit-account'    },
    { name: 'provide',     from: 'deposit-account',    to: 'deposit-amount'     },
    { name: 'provide',     from: 'deposit-amount',     to: 'confirm-deposit'    },
    { name: 'confirm',     from: 'confirm-deposit',    to: 'collect-envelope'          },
    { name: 'provide',     from: 'collect-envelope',          to: 'continue'                  },

    { name: 'withdraw',    from: 'action',             to: 'withdrawal-account' },
    { name: 'provide',     from: 'withdrawal-account', to: 'withdrawal-amount'  },
    { name: 'provide',     from: 'withdrawal-amount',  to: 'confirm-withdrawal' },
    { name: 'confirm',     from: 'confirm-withdrawal', to: 'dispense-cash'             },
    { name: 'withdraw',    from: 'dispense-cash',             to: 'continue'                  },

    { name: 'continue',    from: 'continue',                  to: 'action'             },
    { name: 'finish',      from: 'continue',                  to: 'return-card'               }
  ]
})

ATM.visualize = function() {
  return visualize(ATM, { name: 'ATM' })
}

module.exports = ATM


================================================
FILE: examples/demo/demo.css
================================================
#demo { width: 400px; margin: 0 auto; text-align: center; }

#controls { text-align: center; }

#demo #notes   { margin-bottom: 1em; }
#demo #diagram { width: 400px; height: 275px; }
#demo #output  { width: 100%;  height: 30em;  }

#demo.green  #diagram { background: url(images/alerts.green.png);  }
#demo.yellow #diagram { background: url(images/alerts.yellow.png); }
#demo.red    #diagram { background: url(images/alerts.red.png);    }



================================================
FILE: examples/demo/demo.js
================================================
Demo = function() {

  var output = document.getElementById('output'),
      demo   = document.getElementById('demo'),
      panic  = document.getElementById('panic'),
      warn   = document.getElementById('warn'),
      calm   = document.getElementById('calm'),
      clear  = document.getElementById('clear'),
      count  = 0;

  var log = function(msg, separate) {
    count = count + (separate ? 1 : 0);
    output.value = count + ": " + msg + "\n" + (separate ? "\n" : "") + output.value;
    refreshUI();
  };

  var refreshUI = function() {
    setTimeout(function() {
      demo.className = fsm.state;
      panic.disabled = fsm.cannot('panic', true);
      warn.disabled  = fsm.cannot('warn',  true);
      calm.disabled  = fsm.cannot('calm',  true);
      clear.disabled = fsm.cannot('clear', true);
    }, 0); // defer until end of current tick to allow fsm to complete transaction
  };

  var fsm = new StateMachine({

    transitions: [
      { name: 'start', from: 'none',   to: 'green'  },
      { name: 'warn',  from: 'green',  to: 'yellow' },
      { name: 'panic', from: 'green',  to: 'red'    },
      { name: 'panic', from: 'yellow', to: 'red'    },
      { name: 'calm',  from: 'red',    to: 'yellow' },
      { name: 'clear', from: 'red',    to: 'green'  },
      { name: 'clear', from: 'yellow', to: 'green'  },
    ],

    methods: {

      onBeforeTransition: function(lifecycle) {
        log("BEFORE: " + lifecycle.transition, true);
      },

      onLeaveState: function(lifecycle) {
        log("LEAVE: " + lifecycle.from);
      },

      onEnterState: function(lifecycle) {
        log("ENTER: " + lifecycle.to);
      },

      onAfterTransition: function(lifecycle) {
        log("AFTER: " + lifecycle.transition);
      },

      onTransition: function(lifecycle) {
        log("DURING: " + lifecycle.transition + " (from " + lifecycle.from + " to " + lifecycle.to + ")");
      },

      onLeaveRed: function(lifecycle) {
        return new Promise(function(resolve, reject) {
          var msg = lifecycle.transition + ' to ' + lifecycle.to;
          log("PENDING " + msg + " in ...3");
          setTimeout(function() {
            log("PENDING " + msg + " in ...2");
            setTimeout(function() {
              log("PENDING " + msg + " in ...1");
              setTimeout(function() {
                resolve();
              }, 1000);
            }, 1000);
          }, 1000);
        });
      }

    }
  });

  fsm.start();
  return fsm;

}();



================================================
FILE: examples/horizontal_door.dot
================================================
digraph "door" {
  rankdir=LR;
  "closed";
  "open";
  "closed" -> "open" [ color="blue" ; headport="n" ; label=" open " ; tailport="n" ];
  "open" -> "closed" [ color="red" ; headport="s" ; label=" close " ; tailport="s" ];
}

================================================
FILE: examples/horizontal_door.js
================================================
var StateMachine = require('../src/app'),
    visualize    = require('../src/plugin/visualize');

var Door = StateMachine.factory({
  init: 'closed',
  transitions: [
    { name: 'open',  from: 'closed', to: 'open',   dot: { color: 'blue', headport: 'n', tailport: 'n' } },
    { name: 'close', from: 'open',   to: 'closed', dot: { color: 'red',   headport: 's', tailport: 's' } }
  ]
});

Door.visualize = function() {
  return visualize(Door, { name: 'door', orientation: 'horizontal' })
}

module.exports = Door


================================================
FILE: examples/matter.dot
================================================
digraph "matter" {
  rankdir=LR;
  "solid";
  "liquid";
  "gas";
  "solid" -> "liquid" [ headport="nw" ; label=" melt " ];
  "liquid" -> "solid" [ headport="se" ; label=" freeze " ];
  "liquid" -> "gas" [ headport="nw" ; label=" vaporize " ];
  "gas" -> "liquid" [ headport="se" ; label=" condense " ];
}

================================================
FILE: examples/matter.js
================================================
var StateMachine = require('../src/app'),
    visualize    = require('../src/plugin/visualize');

var Matter = StateMachine.factory({
  init: 'solid',
  transitions: [
    { name: 'melt',     from: 'solid',  to: 'liquid', dot: { headport: 'nw' } },
    { name: 'freeze',   from: 'liquid', to: 'solid',  dot: { headport: 'se' } },
    { name: 'vaporize', from: 'liquid', to: 'gas',    dot: { headport: 'nw' } },
    { name: 'condense', from: 'gas',    to: 'liquid', dot: { headport: 'se' } }
  ]
});

Matter.visualize = function() {
  return visualize(Matter, { name: 'matter', orientation: 'horizontal' })
}

module.exports = Matter


================================================
FILE: examples/vertical_door.dot
================================================
digraph "fsm" {
  "closed";
  "open";
  "closed" -> "open" [ label=" open " ];
  "open" -> "closed" [ label=" close " ];
}

================================================
FILE: examples/vertical_door.js
================================================
var StateMachine = require('../src/app'),
    visualize    = require('../src/plugin/visualize');

var Door = StateMachine.factory({
  init: 'closed',
  transitions: [
    { name: 'open',  from: 'closed', to: 'open'   },
    { name: 'close', from: 'open',   to: 'closed' }
  ]
});

Door.visualize = function() {
  return visualize(Door)
}

module.exports = Door


================================================
FILE: examples/wizard.dot
================================================
digraph "wizard" {
  rankdir=LR;
  "A";
  "B";
  "C";
  "D";
  "A" -> "B" [ headport="w" ; label=" step " ; tailport="ne" ];
  "B" -> "C" [ headport="w" ; label=" step " ; tailport="e" ];
  "C" -> "D" [ headport="w" ; label=" step " ; tailport="e" ];
  "B" -> "A" [ headport="se" ; label=" reset " ; tailport="s" ];
  "C" -> "A" [ headport="se" ; label=" reset " ; tailport="s" ];
  "D" -> "A" [ headport="se" ; label=" reset " ; tailport="s" ];
}

================================================
FILE: examples/wizard.js
================================================
var StateMachine = require('../src/app'),
    visualize    = require('../src/plugin/visualize');

var Wizard = StateMachine.factory({
  init: 'A',
  transitions: [
    { name: 'step',  from: 'A',               to: 'B', dot: { headport: 'w',  tailport: 'ne' } },
    { name: 'step',  from: 'B',               to: 'C', dot: { headport: 'w',  tailport: 'e' } },
    { name: 'step',  from: 'C',               to: 'D', dot: { headport: 'w',  tailport: 'e' } },
    { name: 'reset', from: [ 'B', 'C', 'D' ], to: 'A', dot: { headport: 'se', tailport: 's' } }
  ]
});

Wizard.visualize = function() {
  return visualize(Wizard, { name: 'wizard', orientation: 'horizontal' })
}

module.exports = Wizard


================================================
FILE: index.html
================================================
<!DOCTYPE html> 
<html>
<head>
  <title>Javascript Finite State Machine</title>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> 
  <link href="examples/demo/demo.css" media="screen, print" rel="stylesheet" type="text/css" /> 
</head> 
 
<body> 

  <div id="demo" class='green'>

    <h1> Finite State Machine </h1>

    <div id="controls">
      <button id="clear" onclick="Demo.clear();">clear</button>
      <button id="calm"  onclick="Demo.calm();">calm</button>
      <button id="warn"  onclick="Demo.warn();">warn</button>
      <button id="panic" onclick="Demo.panic();">panic!</button>
    </div>

    <div id="diagram">
    </div>

    <div id="notes">
      <i>dashed lines are asynchronous state transitions (3 seconds)</i>
    </div>

    <textarea id="output">
    </textarea>

  </div>


  <script src="dist/state-machine.js"></script>
  <script src="examples/demo/demo.js"></script>

</body> 
</html>


================================================
FILE: lib/history.js
================================================
(function webpackUniversalModuleDefinition(root, factory) {
	if(typeof exports === 'object' && typeof module === 'object')
		module.exports = factory();
	else if(typeof define === 'function' && define.amd)
		define("StateMachineHistory", [], factory);
	else if(typeof exports === 'object')
		exports["StateMachineHistory"] = factory();
	else
		root["StateMachineHistory"] = factory();
})(this, function() {
return /******/ (function(modules) { // webpackBootstrap
/******/ 	// The module cache
/******/ 	var installedModules = {};
/******/
/******/ 	// The require function
/******/ 	function __webpack_require__(moduleId) {
/******/
/******/ 		// Check if module is in cache
/******/ 		if(installedModules[moduleId]) {
/******/ 			return installedModules[moduleId].exports;
/******/ 		}
/******/ 		// Create a new module (and put it into the cache)
/******/ 		var module = installedModules[moduleId] = {
/******/ 			i: moduleId,
/******/ 			l: false,
/******/ 			exports: {}
/******/ 		};
/******/
/******/ 		// Execute the module function
/******/ 		modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ 		// Flag the module as loaded
/******/ 		module.l = true;
/******/
/******/ 		// Return the exports of the module
/******/ 		return module.exports;
/******/ 	}
/******/
/******/
/******/ 	// expose the modules object (__webpack_modules__)
/******/ 	__webpack_require__.m = modules;
/******/
/******/ 	// expose the module cache
/******/ 	__webpack_require__.c = installedModules;
/******/
/******/ 	// identity function for calling harmony imports with the correct context
/******/ 	__webpack_require__.i = function(value) { return value; };
/******/
/******/ 	// define getter function for harmony exports
/******/ 	__webpack_require__.d = function(exports, name, getter) {
/******/ 		if(!__webpack_require__.o(exports, name)) {
/******/ 			Object.defineProperty(exports, name, {
/******/ 				configurable: false,
/******/ 				enumerable: true,
/******/ 				get: getter
/******/ 			});
/******/ 		}
/******/ 	};
/******/
/******/ 	// getDefaultExport function for compatibility with non-harmony modules
/******/ 	__webpack_require__.n = function(module) {
/******/ 		var getter = module && module.__esModule ?
/******/ 			function getDefault() { return module['default']; } :
/******/ 			function getModuleExports() { return module; };
/******/ 		__webpack_require__.d(getter, 'a', getter);
/******/ 		return getter;
/******/ 	};
/******/
/******/ 	// Object.prototype.hasOwnProperty.call
/******/ 	__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ 	// __webpack_public_path__
/******/ 	__webpack_require__.p = "";
/******/
/******/ 	// Load entry module and return exports
/******/ 	return __webpack_require__(__webpack_require__.s = 1);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";


//-------------------------------------------------------------------------------------------------

function camelize(label) {

  if (label.length === 0)
    return label;

  var n, result, word, words = label.split(/[_-]/);

  // single word with first character already lowercase, return untouched
  if ((words.length === 1) && (words[0][0].toLowerCase() === words[0][0]))
    return label;

  result = words[0].toLowerCase();
  for(n = 1 ; n < words.length ; n++) {
    result = result + words[n].charAt(0).toUpperCase() + words[n].substring(1).toLowerCase();
  }

  return result;
}

//-------------------------------------------------------------------------------------------------

camelize.prepended = function(prepend, label) {
  label = camelize(label);
  return prepend + label[0].toUpperCase() + label.substring(1);
}

//-------------------------------------------------------------------------------------------------

module.exports = camelize;


/***/ }),
/* 1 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";


//-------------------------------------------------------------------------------------------------

var camelize = __webpack_require__(0);

//-------------------------------------------------------------------------------------------------

module.exports = function(options) { options = options || {};

  var past       = camelize(options.name || options.past   || 'history'),
      future     = camelize(                options.future || 'future'),
      clear      = camelize.prepended('clear', past),
      back       = camelize.prepended(past,   'back'),
      forward    = camelize.prepended(past,   'forward'),
      canBack    = camelize.prepended('can',   back),
      canForward = camelize.prepended('can',   forward),
      max        = options.max;

  var plugin = {

    configure: function(config) {
      config.addTransitionLifecycleNames(back);
      config.addTransitionLifecycleNames(forward);
    },

    init: function(instance) {
      instance[past]   = [];
      instance[future] = [];
    },

    lifecycle: function(instance, lifecycle) {
      if (lifecycle.event === 'onEnterState') {
        instance[past].push(lifecycle.to);
        if (max && instance[past].length > max)
          instance[past].shift();
        if (lifecycle.transition !== back && lifecycle.transition !== forward)
          instance[future].length = 0;
      }
    },

    methods:    {},
    properties: {}

  }

  plugin.methods[clear] = function() {
    this[past].length = 0
    this[future].length = 0
  }

  plugin.properties[canBack] = {
    get: function() {
      return this[past].length > 1
    }
  }

  plugin.properties[canForward] = {
    get: function() {
      return this[future].length > 0
    }
  }

  plugin.methods[back] = function() {
    if (!this[canBack])
      throw Error('no history');
    var from = this[past].pop(),
        to   = this[past].pop();
    this[future].push(from);
    this._fsm.transit(back, from, to, []);
  }

  plugin.methods[forward] = function() {
    if (!this[canForward])
      throw Error('no history');
    var from = this.state,
        to = this[future].pop();
    this._fsm.transit(forward, from, to, []);
  }

  return plugin;

}


/***/ })
/******/ ]);
});

================================================
FILE: lib/state-machine.js
================================================
(function webpackUniversalModuleDefinition(root, factory) {
	if(typeof exports === 'object' && typeof module === 'object')
		module.exports = factory();
	else if(typeof define === 'function' && define.amd)
		define("StateMachine", [], factory);
	else if(typeof exports === 'object')
		exports["StateMachine"] = factory();
	else
		root["StateMachine"] = factory();
})(this, function() {
return /******/ (function(modules) { // webpackBootstrap
/******/ 	// The module cache
/******/ 	var installedModules = {};
/******/
/******/ 	// The require function
/******/ 	function __webpack_require__(moduleId) {
/******/
/******/ 		// Check if module is in cache
/******/ 		if(installedModules[moduleId]) {
/******/ 			return installedModules[moduleId].exports;
/******/ 		}
/******/ 		// Create a new module (and put it into the cache)
/******/ 		var module = installedModules[moduleId] = {
/******/ 			i: moduleId,
/******/ 			l: false,
/******/ 			exports: {}
/******/ 		};
/******/
/******/ 		// Execute the module function
/******/ 		modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ 		// Flag the module as loaded
/******/ 		module.l = true;
/******/
/******/ 		// Return the exports of the module
/******/ 		return module.exports;
/******/ 	}
/******/
/******/
/******/ 	// expose the modules object (__webpack_modules__)
/******/ 	__webpack_require__.m = modules;
/******/
/******/ 	// expose the module cache
/******/ 	__webpack_require__.c = installedModules;
/******/
/******/ 	// identity function for calling harmony imports with the correct context
/******/ 	__webpack_require__.i = function(value) { return value; };
/******/
/******/ 	// define getter function for harmony exports
/******/ 	__webpack_require__.d = function(exports, name, getter) {
/******/ 		if(!__webpack_require__.o(exports, name)) {
/******/ 			Object.defineProperty(exports, name, {
/******/ 				configurable: false,
/******/ 				enumerable: true,
/******/ 				get: getter
/******/ 			});
/******/ 		}
/******/ 	};
/******/
/******/ 	// getDefaultExport function for compatibility with non-harmony modules
/******/ 	__webpack_require__.n = function(module) {
/******/ 		var getter = module && module.__esModule ?
/******/ 			function getDefault() { return module['default']; } :
/******/ 			function getModuleExports() { return module; };
/******/ 		__webpack_require__.d(getter, 'a', getter);
/******/ 		return getter;
/******/ 	};
/******/
/******/ 	// Object.prototype.hasOwnProperty.call
/******/ 	__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ 	// __webpack_public_path__
/******/ 	__webpack_require__.p = "";
/******/
/******/ 	// Load entry module and return exports
/******/ 	return __webpack_require__(__webpack_require__.s = 5);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";


module.exports = function(target, sources) {
  var n, source, key;
  for(n = 1 ; n < arguments.length ; n++) {
    source = arguments[n];
    for(key in source) {
      if (source.hasOwnProperty(key))
        target[key] = source[key];
    }
  }
  return target;
}


/***/ }),
/* 1 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";


//-------------------------------------------------------------------------------------------------

var mixin = __webpack_require__(0);

//-------------------------------------------------------------------------------------------------

module.exports = {

  build: function(target, config) {
    var n, max, plugin, plugins = config.plugins;
    for(n = 0, max = plugins.length ; n < max ; n++) {
      plugin = plugins[n];
      if (plugin.methods)
        mixin(target, plugin.methods);
      if (plugin.properties)
        Object.defineProperties(target, plugin.properties);
    }
  },

  hook: function(fsm, name, additional) {
    var n, max, method, plugin,
        plugins = fsm.config.plugins,
        args    = [fsm.context];

    if (additional)
      args = args.concat(additional)

    for(n = 0, max = plugins.length ; n < max ; n++) {
      plugin = plugins[n]
      method = plugins[n][name]
      if (method)
        method.apply(plugin, args);
    }
  }

}

//-------------------------------------------------------------------------------------------------


/***/ }),
/* 2 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";


//-------------------------------------------------------------------------------------------------

function camelize(label) {

  if (label.length === 0)
    return label;

  var n, result, word, words = label.split(/[_-]/);

  // single word with first character already lowercase, return untouched
  if ((words.length === 1) && (words[0][0].toLowerCase() === words[0][0]))
    return label;

  result = words[0].toLowerCase();
  for(n = 1 ; n < words.length ; n++) {
    result = result + words[n].charAt(0).toUpperCase() + words[n].substring(1).toLowerCase();
  }

  return result;
}

//-------------------------------------------------------------------------------------------------

camelize.prepended = function(prepend, label) {
  label = camelize(label);
  return prepend + label[0].toUpperCase() + label.substring(1);
}

//-------------------------------------------------------------------------------------------------

module.exports = camelize;


/***/ }),
/* 3 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";


//-------------------------------------------------------------------------------------------------

var mixin    = __webpack_require__(0),
    camelize = __webpack_require__(2);

//-------------------------------------------------------------------------------------------------

function Config(options, StateMachine) {

  options = options || {};

  this.options     = options; // preserving original options can be useful (e.g visualize plugin)
  this.defaults    = StateMachine.defaults;
  this.states      = [];
  this.transitions = [];
  this.map         = {};
  this.lifecycle   = this.configureLifecycle();
  this.init        = this.configureInitTransition(options.init);
  this.data        = this.configureData(options.data);
  this.methods     = this.configureMethods(options.methods);

  this.map[this.defaults.wildcard] = {};

  this.configureTransitions(options.transitions || []);

  this.plugins = this.configurePlugins(options.plugins, StateMachine.plugin);

}

//-------------------------------------------------------------------------------------------------

mixin(Config.prototype, {

  addState: function(name) {
    if (!this.map[name]) {
      this.states.push(name);
      this.addStateLifecycleNames(name);
      this.map[name] = {};
    }
  },

  addStateLifecycleNames: function(name) {
    this.lifecycle.onEnter[name] = camelize.prepended('onEnter', name);
    this.lifecycle.onLeave[name] = camelize.prepended('onLeave', name);
    this.lifecycle.on[name]      = camelize.prepended('on',      name);
  },

  addTransition: function(name) {
    if (this.transitions.indexOf(name) < 0) {
      this.transitions.push(name);
      this.addTransitionLifecycleNames(name);
    }
  },

  addTransitionLifecycleNames: function(name) {
    this.lifecycle.onBefore[name] = camelize.prepended('onBefore', name);
    this.lifecycle.onAfter[name]  = camelize.prepended('onAfter',  name);
    this.lifecycle.on[name]       = camelize.prepended('on',       name);
  },

  mapTransition: function(transition) {
    var name = transition.name,
        from = transition.from,
        to   = transition.to;
    this.addState(from);
    if (typeof to !== 'function')
      this.addState(to);
    this.addTransition(name);
    this.map[from][name] = transition;
    return transition;
  },

  configureLifecycle: function() {
    return {
      onBefore: { transition: 'onBeforeTransition' },
      onAfter:  { transition: 'onAfterTransition'  },
      onEnter:  { state:      'onEnterState'       },
      onLeave:  { state:      'onLeaveState'       },
      on:       { transition: 'onTransition'       }
    };
  },

  configureInitTransition: function(init) {
    if (typeof init === 'string') {
      return this.mapTransition(mixin({}, this.defaults.init, { to: init, active: true }));
    }
    else if (typeof init === 'object') {
      return this.mapTransition(mixin({}, this.defaults.init, init, { active: true }));
    }
    else {
      this.addState(this.defaults.init.from);
      return this.defaults.init;
    }
  },

  configureData: function(data) {
    if (typeof data === 'function')
      return data;
    else if (typeof data === 'object')
      return function() { return data; }
    else
      return function() { return {};  }
  },

  configureMethods: function(methods) {
    return methods || {};
  },

  configurePlugins: function(plugins, builtin) {
    plugins = plugins || [];
    var n, max, plugin;
    for(n = 0, max = plugins.length ; n < max ; n++) {
      plugin = plugins[n];
      if (typeof plugin === 'function')
        plugins[n] = plugin = plugin()
      if (plugin.configure)
        plugin.configure(this);
    }
    return plugins
  },

  configureTransitions: function(transitions) {
    var i, n, transition, from, to, wildcard = this.defaults.wildcard;
    for(n = 0 ; n < transitions.length ; n++) {
      transition = transitions[n];
      from  = Array.isArray(transition.from) ? transition.from : [transition.from || wildcard]
      to    = transition.to || wildcard;
      for(i = 0 ; i < from.length ; i++) {
        this.mapTransition({ name: transition.name, from: from[i], to: to });
      }
    }
  },

  transitionFor: function(state, transition) {
    var wildcard = this.defaults.wildcard;
    return this.map[state][transition] ||
           this.map[wildcard][transition];
  },

  transitionsFor: function(state) {
    var wildcard = this.defaults.wildcard;
    return Object.keys(this.map[state]).concat(Object.keys(this.map[wildcard]));
  },

  allStates: function() {
    return this.states;
  },

  allTransitions: function() {
    return this.transitions;
  }

});

//-------------------------------------------------------------------------------------------------

module.exports = Config;

//-------------------------------------------------------------------------------------------------


/***/ }),
/* 4 */
/***/ (function(module, exports, __webpack_require__) {


var mixin      = __webpack_require__(0),
    Exception  = __webpack_require__(6),
    plugin     = __webpack_require__(1),
    UNOBSERVED = [ null, [] ];

//-------------------------------------------------------------------------------------------------

function JSM(context, config) {
  this.context   = context;
  this.config    = config;
  this.state     = config.init.from;
  this.observers = [context];
}

//-------------------------------------------------------------------------------------------------

mixin(JSM.prototype, {

  init: function(args) {
    mixin(this.context, this.config.data.apply(this.context, args));
    plugin.hook(this, 'init');
    if (this.config.init.active)
      return this.fire(this.config.init.name, []);
  },

  is: function(state) {
    return Array.isArray(state) ? (state.indexOf(this.state) >= 0) : (this.state === state);
  },

  isPending: function() {
    return this.pending;
  },

  can: function(transition) {
    return !this.isPending() && !!this.seek(transition);
  },

  cannot: function(transition) {
    return !this.can(transition);
  },

  allStates: function() {
    return this.config.allStates();
  },

  allTransitions: function() {
    return this.config.allTransitions();
  },

  transitions: function() {
    return this.config.transitionsFor(this.state);
  },

  seek: function(transition, args) {
    var wildcard = this.config.defaults.wildcard,
        entry    = this.config.transitionFor(this.state, transition),
        to       = entry && entry.to;
    if (typeof to === 'function')
      return to.apply(this.context, args);
    else if (to === wildcard)
      return this.state
    else
      return to
  },

  fire: function(transition, args) {
    return this.transit(transition, this.state, this.seek(transition, args), args);
  },

  transit: function(transition, from, to, args) {

    var lifecycle = this.config.lifecycle,
        changed   = this.config.options.observeUnchangedState || (from !== to);

    if (!to)
      return this.context.onInvalidTransition(transition, from, to);

    if (this.isPending())
      return this.context.onPendingTransition(transition, from, to);

    this.config.addState(to);  // might need to add this state if it's unknown (e.g. conditional transition or goto)

    this.beginTransit();

    args.unshift({             // this context will be passed to each lifecycle event observer
      transition: transition,
      from:       from,
      to:         to,
      fsm:        this.context
    });

    return this.observeEvents([
                this.observersForEvent(lifecycle.onBefore.transition),
                this.observersForEvent(lifecycle.onBefore[transition]),
      changed ? this.observersForEvent(lifecycle.onLeave.state) : UNOBSERVED,
      changed ? this.observersForEvent(lifecycle.onLeave[from]) : UNOBSERVED,
                this.observersForEvent(lifecycle.on.transition),
      changed ? [ 'doTransit', [ this ] ]                       : UNOBSERVED,
      changed ? this.observersForEvent(lifecycle.onEnter.state) : UNOBSERVED,
      changed ? this.observersForEvent(lifecycle.onEnter[to])   : UNOBSERVED,
      changed ? this.observersForEvent(lifecycle.on[to])        : UNOBSERVED,
                this.observersForEvent(lifecycle.onAfter.transition),
                this.observersForEvent(lifecycle.onAfter[transition]),
                this.observersForEvent(lifecycle.on[transition])
    ], args);
  },

  beginTransit: function()          { this.pending = true;                 },
  endTransit:   function(result)    { this.pending = false; return result; },
  failTransit:  function(result)    { this.pending = false; throw result;  },
  doTransit:    function(lifecycle) { this.state = lifecycle.to;           },

  observe: function(args) {
    if (args.length === 2) {
      var observer = {};
      observer[args[0]] = args[1];
      this.observers.push(observer);
    }
    else {
      this.observers.push(args[0]);
    }
  },

  observersForEvent: function(event) { // TODO: this could be cached
    var n = 0, max = this.observers.length, observer, result = [];
    for( ; n < max ; n++) {
      observer = this.observers[n];
      if (observer[event])
        result.push(observer);
    }
    return [ event, result, true ]
  },

  observeEvents: function(events, args, previousEvent, previousResult) {
    if (events.length === 0) {
      return this.endTransit(previousResult === undefined ? true : previousResult);
    }

    var event     = events[0][0],
        observers = events[0][1],
        pluggable = events[0][2];

    args[0].event = event;
    if (event && pluggable && event !== previousEvent)
      plugin.hook(this, 'lifecycle', args);

    if (observers.length === 0) {
      events.shift();
      return this.observeEvents(events, args, event, previousResult);
    }
    else {
      var observer = observers.shift(),
          result = observer[event].apply(observer, args);
      if (result && typeof result.then === 'function') {
        return result.then(this.observeEvents.bind(this, events, args, event))
                     .catch(this.failTransit.bind(this))
      }
      else if (result === false) {
        return this.endTransit(false);
      }
      else {
        return this.observeEvents(events, args, event, result);
      }
    }
  },

  onInvalidTransition: function(transition, from, to) {
    throw new Exception("transition is invalid in current state", transition, from, to, this.state);
  },

  onPendingTransition: function(transition, from, to) {
    throw new Exception("transition is invalid while previous transition is still in progress", transition, from, to, this.state);
  }

});

//-------------------------------------------------------------------------------------------------

module.exports = JSM;

//-------------------------------------------------------------------------------------------------


/***/ }),
/* 5 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";


//-----------------------------------------------------------------------------------------------

var mixin    = __webpack_require__(0),
    camelize = __webpack_require__(2),
    plugin   = __webpack_require__(1),
    Config   = __webpack_require__(3),
    JSM      = __webpack_require__(4);

//-----------------------------------------------------------------------------------------------

var PublicMethods = {
  is:                  function(state)       { return this._fsm.is(state)                                     },
  can:                 function(transition)  { return this._fsm.can(transition)                               },
  cannot:              function(transition)  { return this._fsm.cannot(transition)                            },
  observe:             function()            { return this._fsm.observe(arguments)                            },
  transitions:         function()            { return this._fsm.transitions()                                 },
  allTransitions:      function()            { return this._fsm.allTransitions()                              },
  allStates:           function()            { return this._fsm.allStates()                                   },
  onInvalidTransition: function(t, from, to) { return this._fsm.onInvalidTransition(t, from, to)              },
  onPendingTransition: function(t, from, to) { return this._fsm.onPendingTransition(t, from, to)              },
}

var PublicProperties = {
  state: {
    configurable: false,
    enumerable:   true,
    get: function() {
      return this._fsm.state;
    },
    set: function(state) {
      throw Error('use transitions to change state')
    }
  }
}

//-----------------------------------------------------------------------------------------------

function StateMachine(options) {
  return apply(this || {}, options);
}

function factory() {
  var cstor, options;
  if (typeof arguments[0] === 'function') {
    cstor   = arguments[0];
    options = arguments[1] || {};
  }
  else {
    cstor   = function() { this._fsm.apply(this, arguments) };
    options = arguments[0] || {};
  }
  var config = new Config(options, StateMachine);
  build(cstor.prototype, config);
  cstor.prototype._fsm.config = config; // convenience access to shared config without needing an instance
  return cstor;
}

//-------------------------------------------------------------------------------------------------

function apply(instance, options) {
  var config = new Config(options, StateMachine);
  build(instance, config);
  instance._fsm();
  return instance;
}

function build(target, config) {
  if ((typeof target !== 'object') || Array.isArray(target))
    throw Error('StateMachine can only be applied to objects');
  plugin.build(target, config);
  Object.defineProperties(target, PublicProperties);
  mixin(target, PublicMethods);
  mixin(target, config.methods);
  config.allTransitions().forEach(function(transition) {
    target[camelize(transition)] = function() {
      return this._fsm.fire(transition, [].slice.call(arguments))
    }
  });
  target._fsm = function() {
    this._fsm = new JSM(this, config);
    this._fsm.init(arguments);
  }
}

//-----------------------------------------------------------------------------------------------

StateMachine.version  = '3.0.1';
StateMachine.factory  = factory;
StateMachine.apply    = apply;
StateMachine.defaults = {
  wildcard: '*',
  init: {
    name: 'init',
    from: 'none'
  }
}

//===============================================================================================

module.exports = StateMachine;


/***/ }),
/* 6 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";


module.exports = function(message, transition, from, to, current) {
  this.message    = message;
  this.transition = transition;
  this.from       = from;
  this.to         = to;
  this.current    = current;
}


/***/ })
/******/ ]);
});

================================================
FILE: lib/visualize.js
================================================
(function webpackUniversalModuleDefinition(root, factory) {
	if(typeof exports === 'object' && typeof module === 'object')
		module.exports = factory();
	else if(typeof define === 'function' && define.amd)
		define("StateMachineVisualize", [], factory);
	else if(typeof exports === 'object')
		exports["StateMachineVisualize"] = factory();
	else
		root["StateMachineVisualize"] = factory();
})(this, function() {
return /******/ (function(modules) { // webpackBootstrap
/******/ 	// The module cache
/******/ 	var installedModules = {};
/******/
/******/ 	// The require function
/******/ 	function __webpack_require__(moduleId) {
/******/
/******/ 		// Check if module is in cache
/******/ 		if(installedModules[moduleId]) {
/******/ 			return installedModules[moduleId].exports;
/******/ 		}
/******/ 		// Create a new module (and put it into the cache)
/******/ 		var module = installedModules[moduleId] = {
/******/ 			i: moduleId,
/******/ 			l: false,
/******/ 			exports: {}
/******/ 		};
/******/
/******/ 		// Execute the module function
/******/ 		modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ 		// Flag the module as loaded
/******/ 		module.l = true;
/******/
/******/ 		// Return the exports of the module
/******/ 		return module.exports;
/******/ 	}
/******/
/******/
/******/ 	// expose the modules object (__webpack_modules__)
/******/ 	__webpack_require__.m = modules;
/******/
/******/ 	// expose the module cache
/******/ 	__webpack_require__.c = installedModules;
/******/
/******/ 	// identity function for calling harmony imports with the correct context
/******/ 	__webpack_require__.i = function(value) { return value; };
/******/
/******/ 	// define getter function for harmony exports
/******/ 	__webpack_require__.d = function(exports, name, getter) {
/******/ 		if(!__webpack_require__.o(exports, name)) {
/******/ 			Object.defineProperty(exports, name, {
/******/ 				configurable: false,
/******/ 				enumerable: true,
/******/ 				get: getter
/******/ 			});
/******/ 		}
/******/ 	};
/******/
/******/ 	// getDefaultExport function for compatibility with non-harmony modules
/******/ 	__webpack_require__.n = function(module) {
/******/ 		var getter = module && module.__esModule ?
/******/ 			function getDefault() { return module['default']; } :
/******/ 			function getModuleExports() { return module; };
/******/ 		__webpack_require__.d(getter, 'a', getter);
/******/ 		return getter;
/******/ 	};
/******/
/******/ 	// Object.prototype.hasOwnProperty.call
/******/ 	__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ 	// __webpack_public_path__
/******/ 	__webpack_require__.p = "";
/******/
/******/ 	// Load entry module and return exports
/******/ 	return __webpack_require__(__webpack_require__.s = 1);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";


module.exports = function(target, sources) {
  var n, source, key;
  for(n = 1 ; n < arguments.length ; n++) {
    source = arguments[n];
    for(key in source) {
      if (source.hasOwnProperty(key))
        target[key] = source[key];
    }
  }
  return target;
}


/***/ }),
/* 1 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";


//-------------------------------------------------------------------------------------------------

var mixin = __webpack_require__(0)

//-------------------------------------------------------------------------------------------------

function visualize(fsm, options) {
  return dotify(dotcfg(fsm, options));
}

//-------------------------------------------------------------------------------------------------

function dotcfg(fsm, options) {

  options = options || {}

  var config      = dotcfg.fetch(fsm),
      name        = options.name,
      rankdir     = dotcfg.rankdir(options.orientation),
      states      = dotcfg.states(config, options),
      transitions = dotcfg.transitions(config, options),
      result      = { }

  if (name)
    result.name = name

  if (rankdir)
    result.rankdir = rankdir

  if (states && states.length > 0)
    result.states = states

  if (transitions && transitions.length > 0)
    result.transitions = transitions

  return result
}

//-------------------------------------------------------------------------------------------------

dotcfg.fetch = function(fsm) {
  return (typeof fsm === 'function') ? fsm.prototype._fsm.config
                                     : fsm._fsm.config
}

dotcfg.rankdir = function(orientation) {
  if (orientation === 'horizontal')
    return 'LR';
  else if (orientation === 'vertical')
    return 'TB';
}

dotcfg.states = function(config, options) {
  var index, states = config.states;
  if (!options.init) { // if not showing init transition, then slice out the implied init :from state
    index  = states.indexOf(config.init.from);
    states = states.slice(0, index).concat(states.slice(index+1));
  }
  return states;
}

dotcfg.transitions = function(config, options) {
  var n, max, transition,
      init        = config.init,
      transitions = config.options.transitions || [], // easier to visualize using the ORIGINAL transition declarations rather than our run-time mapping
      output = [];
  if (options.init && init.active)
    dotcfg.transition(init.name, init.from, init.to, init.dot, config, options, output)
  for (n = 0, max = transitions.length ; n < max ; n++) {
    transition = config.options.transitions[n]
    dotcfg.transition(transition.name, transition.from, transition.to, transition.dot, config, options, output)
  }
  return output
}

dotcfg.transition = function(name, from, to, dot, config, options, output) {
  var n, max, wildcard = config.defaults.wildcard

  if (Array.isArray(from)) {
    for(n = 0, max = from.length ; n < max ; n++)
      dotcfg.transition(name, from[n], to, dot, config, options, output)
  }
  else if (from === wildcard || from === undefined) {
    for(n = 0, max = config.states.length ; n < max ; n++)
      dotcfg.transition(name, config.states[n], to, dot, config, options, output)
  }
  else if (to === wildcard || to === undefined) {
    dotcfg.transition(name, from, from, dot, config, options, output)
  }
  else if (typeof to === 'function') {
    // do nothing, can't display conditional transition
  }
  else {
    output.push(mixin({}, { from: from, to: to, label: pad(name) }, dot || {}))
  }

}

//-------------------------------------------------------------------------------------------------

function pad(name) {
  return " " + name + " "
}

function quote(name) {
  return "\"" + name + "\""
}

function dotify(dotcfg) {

  dotcfg = dotcfg || {};

  var name        = dotcfg.name || 'fsm',
      states      = dotcfg.states || [],
      transitions = dotcfg.transitions || [],
      rankdir     = dotcfg.rankdir,
      output      = [],
      n, max;

  output.push("digraph " + quote(name) + " {")
  if (rankdir)
    output.push("  rankdir=" + rankdir + ";")
  for(n = 0, max = states.length ; n < max ; n++)
    output.push(dotify.state(states[n]))
  for(n = 0, max = transitions.length ; n < max ; n++)
    output.push(dotify.edge(transitions[n]))
  output.push("}")
  return output.join("\n")

}

dotify.state = function(state) {
  return "  " + quote(state) + ";"
}

dotify.edge = function(edge) {
  return "  " + quote(edge.from) + " -> " + quote(edge.to) + dotify.edge.attr(edge) + ";"
}

dotify.edge.attr = function(edge) {
  var n, max, key, keys = Object.keys(edge).sort(), output = [];
  for(n = 0, max = keys.length ; n < max ; n++) {
    key = keys[n];
    if (key !== 'from' && key !== 'to')
      output.push(key + "=" + quote(edge[key]))
  }
  return output.length > 0 ? " [ " + output.join(" ; ") + " ]" : ""
}

//-------------------------------------------------------------------------------------------------

visualize.dotcfg = dotcfg;
visualize.dotify = dotify;

//-------------------------------------------------------------------------------------------------

module.exports = visualize;

//-------------------------------------------------------------------------------------------------


/***/ })
/******/ ]);
});

================================================
FILE: package.json
================================================
{
  "name": "javascript-state-machine",
  "description": "A finite state machine library",
  "homepage": "https://github.com/jakesgordon/javascript-state-machine",
  "repository": {
    "type": "git",
    "url": "git://github.com/jakesgordon/javascript-state-machine.git"
  },
  "keywords": [
    "finite state machine",
    "state machine",
    "server",
    "client"
  ],
  "author": {
    "name": "Jake Gordon",
    "email": "jakesgordon@gmail.com"
  },
  "maintainers": [
    {
      "name": "Jake Gordon",
      "email": "jakesgordon@gmail.com"
    }
  ],
  "license": "MIT",
  "main": "lib/state-machine.js",
  "files": [
    "lib/**/*.js",
    "dist/**/*.js"
  ],
  "directories": {},
  "devDependencies": {
    "ava": "^0.17.0",
    "fs-sync": "^1.0.3",
    "glob": "^7.1.1",
    "nyc": "^10.0.0",
    "pascal-case": "^2.0.0",
    "uglify-js": "^2.7.5",
    "webpack": "^2.2.0-rc.1"
  },
  "version": "3.1.0",
  "scripts": {
    "start": "npm run watch",
    "build": "npm run bundle && npm run minify",
    "bundle": "webpack",
    "minify": "bin/minify",
    "watch": "ava --watch",
    "test": "nyc ava -v && nyc report --reporter=html"
  },
  "ava": {
    "files": [
      "test/**/*.js"
    ],
    "source": [
      "src/**/*.js"
    ]
  }
}


================================================
FILE: src/app.js
================================================
'use strict'

//-----------------------------------------------------------------------------------------------

var mixin    = require('./util/mixin'),
    camelize = require('./util/camelize'),
    plugin   = require('./plugin'),
    Config   = require('./config'),
    JSM      = require('./jsm');

//-----------------------------------------------------------------------------------------------

var PublicMethods = {
  is:                  function(state)       { return this._fsm.is(state)                                     },
  can:                 function(transition)  { return this._fsm.can(transition)                               },
  cannot:              function(transition)  { return this._fsm.cannot(transition)                            },
  observe:             function()            { return this._fsm.observe(arguments)                            },
  transitions:         function()            { return this._fsm.transitions()                                 },
  allTransitions:      function()            { return this._fsm.allTransitions()                              },
  allStates:           function()            { return this._fsm.allStates()                                   },
  onInvalidTransition: function(t, from, to) { return this._fsm.onInvalidTransition(t, from, to)              },
  onPendingTransition: function(t, from, to) { return this._fsm.onPendingTransition(t, from, to)              },
}

var PublicProperties = {
  state: {
    configurable: false,
    enumerable:   true,
    get: function() {
      return this._fsm.state;
    },
    set: function(state) {
      throw Error('use transitions to change state')
    }
  }
}

//-----------------------------------------------------------------------------------------------

function StateMachine(options) {
  return apply(this || {}, options);
}

function factory() {
  var cstor, options;
  if (typeof arguments[0] === 'function') {
    cstor   = arguments[0];
    options = arguments[1] || {};
  }
  else {
    cstor   = function() { this._fsm.apply(this, arguments) };
    options = arguments[0] || {};
  }
  var config = new Config(options, StateMachine);
  build(cstor.prototype, config);
  cstor.prototype._fsm.config = config; // convenience access to shared config without needing an instance
  return cstor;
}

//-------------------------------------------------------------------------------------------------

function apply(instance, options) {
  var config = new Config(options, StateMachine);
  build(instance, config);
  instance._fsm();
  return instance;
}

function build(target, config) {
  if ((typeof target !== 'object') || Array.isArray(target))
    throw Error('StateMachine can only be applied to objects');
  plugin.build(target, config);
  Object.defineProperties(target, PublicProperties);
  mixin(target, PublicMethods);
  mixin(target, config.methods);
  config.allTransitions().forEach(function(transition) {
    target[camelize(transition)] = function() {
      return this._fsm.fire(transition, [].slice.call(arguments))
    }
  });
  target._fsm = function() {
    this._fsm = new JSM(this, config);
    this._fsm.init(arguments);
  }
}

//-----------------------------------------------------------------------------------------------

StateMachine.version  = '3.0.1';
StateMachine.factory  = factory;
StateMachine.apply    = apply;
StateMachine.defaults = {
  wildcard: '*',
  init: {
    name: 'init',
    from: 'none'
  }
}

//===============================================================================================

module.exports = StateMachine;


================================================
FILE: src/config.js
================================================
'use strict'

//-------------------------------------------------------------------------------------------------

var mixin    = require('./util/mixin'),
    camelize = require('./util/camelize');

//-------------------------------------------------------------------------------------------------

function Config(options, StateMachine) {

  options = options || {};

  this.options     = options; // preserving original options can be useful (e.g visualize plugin)
  this.defaults    = StateMachine.defaults;
  this.states      = [];
  this.transitions = [];
  this.map         = {};
  this.lifecycle   = this.configureLifecycle();
  this.init        = this.configureInitTransition(options.init);
  this.data        = this.configureData(options.data);
  this.methods     = this.configureMethods(options.methods);

  this.map[this.defaults.wildcard] = {};

  this.configureTransitions(options.transitions || []);

  this.plugins = this.configurePlugins(options.plugins, StateMachine.plugin);

}

//-------------------------------------------------------------------------------------------------

mixin(Config.prototype, {

  addState: function(name) {
    if (!this.map[name]) {
      this.states.push(name);
      this.addStateLifecycleNames(name);
      this.map[name] = {};
    }
  },

  addStateLifecycleNames: function(name) {
    this.lifecycle.onEnter[name] = camelize.prepended('onEnter', name);
    this.lifecycle.onLeave[name] = camelize.prepended('onLeave', name);
    this.lifecycle.on[name]      = camelize.prepended('on',      name);
  },

  addTransition: function(name) {
    if (this.transitions.indexOf(name) < 0) {
      this.transitions.push(name);
      this.addTransitionLifecycleNames(name);
    }
  },

  addTransitionLifecycleNames: function(name) {
    this.lifecycle.onBefore[name] = camelize.prepended('onBefore', name);
    this.lifecycle.onAfter[name]  = camelize.prepended('onAfter',  name);
    this.lifecycle.on[name]       = camelize.prepended('on',       name);
  },

  mapTransition: function(transition) {
    var name = transition.name,
        from = transition.from,
        to   = transition.to;
    this.addState(from);
    if (typeof to !== 'function')
      this.addState(to);
    this.addTransition(name);
    this.map[from][name] = transition;
    return transition;
  },

  configureLifecycle: function() {
    return {
      onBefore: { transition: 'onBeforeTransition' },
      onAfter:  { transition: 'onAfterTransition'  },
      onEnter:  { state:      'onEnterState'       },
      onLeave:  { state:      'onLeaveState'       },
      on:       { transition: 'onTransition'       }
    };
  },

  configureInitTransition: function(init) {
    if (typeof init === 'string') {
      return this.mapTransition(mixin({}, this.defaults.init, { to: init, active: true }));
    }
    else if (typeof init === 'object') {
      return this.mapTransition(mixin({}, this.defaults.init, init, { active: true }));
    }
    else {
      this.addState(this.defaults.init.from);
      return this.defaults.init;
    }
  },

  configureData: function(data) {
    if (typeof data === 'function')
      return data;
    else if (typeof data === 'object')
      return function() { return data; }
    else
      return function() { return {};  }
  },

  configureMethods: function(methods) {
    return methods || {};
  },

  configurePlugins: function(plugins, builtin) {
    plugins = plugins || [];
    var n, max, plugin;
    for(n = 0, max = plugins.length ; n < max ; n++) {
      plugin = plugins[n];
      if (typeof plugin === 'function')
        plugins[n] = plugin = plugin()
      if (plugin.configure)
        plugin.configure(this);
    }
    return plugins
  },

  configureTransitions: function(transitions) {
    var i, n, transition, from, to, wildcard = this.defaults.wildcard;
    for(n = 0 ; n < transitions.length ; n++) {
      transition = transitions[n];
      from  = Array.isArray(transition.from) ? transition.from : [transition.from || wildcard]
      to    = transition.to || wildcard;
      for(i = 0 ; i < from.length ; i++) {
        this.mapTransition({ name: transition.name, from: from[i], to: to });
      }
    }
  },

  transitionFor: function(state, transition) {
    var wildcard = this.defaults.wildcard;
    return this.map[state][transition] ||
           this.map[wildcard][transition];
  },

  transitionsFor: function(state) {
    var wildcard = this.defaults.wildcard;
    return Object.keys(this.map[state]).concat(Object.keys(this.map[wildcard]));
  },

  allStates: function() {
    return this.states;
  },

  allTransitions: function() {
    return this.transitions;
  }

});

//-------------------------------------------------------------------------------------------------

module.exports = Config;

//-------------------------------------------------------------------------------------------------


================================================
FILE: src/jsm.js
================================================

var mixin      = require('./util/mixin'),
    Exception  = require('./util/exception'),
    plugin     = require('./plugin'),
    UNOBSERVED = [ null, [] ];

//-------------------------------------------------------------------------------------------------

function JSM(context, config) {
  this.context   = context;
  this.config    = config;
  this.state     = config.init.from;
  this.observers = [context];
}

//-------------------------------------------------------------------------------------------------

mixin(JSM.prototype, {

  init: function(args) {
    mixin(this.context, this.config.data.apply(this.context, args));
    plugin.hook(this, 'init');
    if (this.config.init.active)
      return this.fire(this.config.init.name, []);
  },

  is: function(state) {
    return Array.isArray(state) ? (state.indexOf(this.state) >= 0) : (this.state === state);
  },

  isPending: function() {
    return this.pending;
  },

  can: function(transition) {
    return !this.isPending() && !!this.seek(transition);
  },

  cannot: function(transition) {
    return !this.can(transition);
  },

  allStates: function() {
    return this.config.allStates();
  },

  allTransitions: function() {
    return this.config.allTransitions();
  },

  transitions: function() {
    return this.config.transitionsFor(this.state);
  },

  seek: function(transition, args) {
    var wildcard = this.config.defaults.wildcard,
        entry    = this.config.transitionFor(this.state, transition),
        to       = entry && entry.to;
    if (typeof to === 'function')
      return to.apply(this.context, args);
    else if (to === wildcard)
      return this.state
    else
      return to
  },

  fire: function(transition, args) {
    return this.transit(transition, this.state, this.seek(transition, args), args);
  },

  transit: function(transition, from, to, args) {

    var lifecycle = this.config.lifecycle,
        changed   = this.config.options.observeUnchangedState || (from !== to);

    if (!to)
      return this.context.onInvalidTransition(transition, from, to);

    if (this.isPending())
      return this.context.onPendingTransition(transition, from, to);

    this.config.addState(to);  // might need to add this state if it's unknown (e.g. conditional transition or goto)

    this.beginTransit();

    args.unshift({             // this context will be passed to each lifecycle event observer
      transition: transition,
      from:       from,
      to:         to,
      fsm:        this.context
    });

    return this.observeEvents([
                this.observersForEvent(lifecycle.onBefore.transition),
                this.observersForEvent(lifecycle.onBefore[transition]),
      changed ? this.observersForEvent(lifecycle.onLeave.state) : UNOBSERVED,
      changed ? this.observersForEvent(lifecycle.onLeave[from]) : UNOBSERVED,
                this.observersForEvent(lifecycle.on.transition),
      changed ? [ 'doTransit', [ this ] ]                       : UNOBSERVED,
      changed ? this.observersForEvent(lifecycle.onEnter.state) : UNOBSERVED,
      changed ? this.observersForEvent(lifecycle.onEnter[to])   : UNOBSERVED,
      changed ? this.observersForEvent(lifecycle.on[to])        : UNOBSERVED,
                this.observersForEvent(lifecycle.onAfter.transition),
                this.observersForEvent(lifecycle.onAfter[transition]),
                this.observersForEvent(lifecycle.on[transition])
    ], args);
  },

  beginTransit: function()          { this.pending = true;                 },
  endTransit:   function(result)    { this.pending = false; return result; },
  failTransit:  function(result)    { this.pending = false; throw result;  },
  doTransit:    function(lifecycle) { this.state = lifecycle.to;           },

  observe: function(args) {
    if (args.length === 2) {
      var observer = {};
      observer[args[0]] = args[1];
      this.observers.push(observer);
    }
    else {
      this.observers.push(args[0]);
    }
  },

  observersForEvent: function(event) { // TODO: this could be cached
    var n = 0, max = this.observers.length, observer, result = [];
    for( ; n < max ; n++) {
      observer = this.observers[n];
      if (observer[event])
        result.push(observer);
    }
    return [ event, result, true ]
  },

  observeEvents: function(events, args, previousEvent, previousResult) {
    if (events.length === 0) {
      return this.endTransit(previousResult === undefined ? true : previousResult);
    }

    var event     = events[0][0],
        observers = events[0][1],
        pluggable = events[0][2];

    args[0].event = event;
    if (event && pluggable && event !== previousEvent)
      plugin.hook(this, 'lifecycle', args);

    if (observers.length === 0) {
      events.shift();
      return this.observeEvents(events, args, event, previousResult);
    }
    else {
      var observer = observers.shift(),
          result = observer[event].apply(observer, args);
      if (result && typeof result.then === 'function') {
        return result.then(this.observeEvents.bind(this, events, args, event))
                     .catch(this.failTransit.bind(this))
      }
      else if (result === false) {
        return this.endTransit(false);
      }
      else {
        return this.observeEvents(events, args, event, result);
      }
    }
  },

  onInvalidTransition: function(transition, from, to) {
    throw new Exception("transition is invalid in current state", transition, from, to, this.state);
  },

  onPendingTransition: function(transition, from, to) {
    throw new Exception("transition is invalid while previous transition is still in progress", transition, from, to, this.state);
  }

});

//-------------------------------------------------------------------------------------------------

module.exports = JSM;

//-------------------------------------------------------------------------------------------------


================================================
FILE: src/plugin/history.js
================================================
'use strict'

//-------------------------------------------------------------------------------------------------

var camelize = require('../util/camelize');

//-------------------------------------------------------------------------------------------------

module.exports = function(options) { options = options || {};

  var past       = camelize(options.name || options.past   || 'history'),
      future     = camelize(                options.future || 'future'),
      clear      = camelize.prepended('clear', past),
      back       = camelize.prepended(past,   'back'),
      forward    = camelize.prepended(past,   'forward'),
      canBack    = camelize.prepended('can',   back),
      canForward = camelize.prepended('can',   forward),
      max        = options.max;

  var plugin = {

    configure: function(config) {
      config.addTransitionLifecycleNames(back);
      config.addTransitionLifecycleNames(forward);
    },

    init: function(instance) {
      instance[past]   = [];
      instance[future] = [];
    },

    lifecycle: function(instance, lifecycle) {
      if (lifecycle.event === 'onEnterState') {
        instance[past].push(lifecycle.to);
        if (max && instance[past].length > max)
          instance[past].shift();
        if (lifecycle.transition !== back && lifecycle.transition !== forward)
          instance[future].length = 0;
      }
    },

    methods:    {},
    properties: {}

  }

  plugin.methods[clear] = function() {
    this[past].length = 0
    this[future].length = 0
  }

  plugin.properties[canBack] = {
    get: function() {
      return this[past].length > 1
    }
  }

  plugin.properties[canForward] = {
    get: function() {
      return this[future].length > 0
    }
  }

  plugin.methods[back] = function() {
    if (!this[canBack])
      throw Error('no history');
    var from = this[past].pop(),
        to   = this[past].pop();
    this[future].push(from);
    this._fsm.transit(back, from, to, []);
  }

  plugin.methods[forward] = function() {
    if (!this[canForward])
      throw Error('no history');
    var from = this.state,
        to = this[future].pop();
    this._fsm.transit(forward, from, to, []);
  }

  return plugin;

}


================================================
FILE: src/plugin/visualize.js
================================================
'use strict'

//-------------------------------------------------------------------------------------------------

var mixin = require('../util/mixin')

//-------------------------------------------------------------------------------------------------

function visualize(fsm, options) {
  return dotify(dotcfg(fsm, options));
}

//-------------------------------------------------------------------------------------------------

function dotcfg(fsm, options) {

  options = options || {}

  var config      = dotcfg.fetch(fsm),
      name        = options.name,
      rankdir     = dotcfg.rankdir(options.orientation),
      states      = dotcfg.states(config, options),
      transitions = dotcfg.transitions(config, options),
      result      = { }

  if (name)
    result.name = name

  if (rankdir)
    result.rankdir = rankdir

  if (states && states.length > 0)
    result.states = states

  if (transitions && transitions.length > 0)
    result.transitions = transitions

  return result
}

//-------------------------------------------------------------------------------------------------

dotcfg.fetch = function(fsm) {
  return (typeof fsm === 'function') ? fsm.prototype._fsm.config
                                     : fsm._fsm.config
}

dotcfg.rankdir = function(orientation) {
  if (orientation === 'horizontal')
    return 'LR';
  else if (orientation === 'vertical')
    return 'TB';
}

dotcfg.states = function(config, options) {
  var index, states = config.states;
  if (!options.init) { // if not showing init transition, then slice out the implied init :from state
    index  = states.indexOf(config.init.from);
    states = states.slice(0, index).concat(states.slice(index+1));
  }
  return states;
}

dotcfg.transitions = function(config, options) {
  var n, max, transition,
      init        = config.init,
      transitions = config.options.transitions || [], // easier to visualize using the ORIGINAL transition declarations rather than our run-time mapping
      output = [];
  if (options.init && init.active)
    dotcfg.transition(init.name, init.from, init.to, init.dot, config, options, output)
  for (n = 0, max = transitions.length ; n < max ; n++) {
    transition = config.options.transitions[n]
    dotcfg.transition(transition.name, transition.from, transition.to, transition.dot, config, options, output)
  }
  return output
}

dotcfg.transition = function(name, from, to, dot, config, options, output) {
  var n, max, wildcard = config.defaults.wildcard

  if (Array.isArray(from)) {
    for(n = 0, max = from.length ; n < max ; n++)
      dotcfg.transition(name, from[n], to, dot, config, options, output)
  }
  else if (from === wildcard || from === undefined) {
    for(n = 0, max = config.states.length ; n < max ; n++)
      dotcfg.transition(name, config.states[n], to, dot, config, options, output)
  }
  else if (to === wildcard || to === undefined) {
    dotcfg.transition(name, from, from, dot, config, options, output)
  }
  else if (typeof to === 'function') {
    // do nothing, can't display conditional transition
  }
  else {
    output.push(mixin({}, { from: from, to: to, label: pad(name) }, dot || {}))
  }

}

//-------------------------------------------------------------------------------------------------

function pad(name) {
  return " " + name + " "
}

function quote(name) {
  return "\"" + name + "\""
}

function dotify(dotcfg) {

  dotcfg = dotcfg || {};

  var name        = dotcfg.name || 'fsm',
      states      = dotcfg.states || [],
      transitions = dotcfg.transitions || [],
      rankdir     = dotcfg.rankdir,
      output      = [],
      n, max;

  output.push("digraph " + quote(name) + " {")
  if (rankdir)
    output.push("  rankdir=" + rankdir + ";")
  for(n = 0, max = states.length ; n < max ; n++)
    output.push(dotify.state(states[n]))
  for(n = 0, max = transitions.length ; n < max ; n++)
    output.push(dotify.edge(transitions[n]))
  output.push("}")
  return output.join("\n")

}

dotify.state = function(state) {
  return "  " + quote(state) + ";"
}

dotify.edge = function(edge) {
  return "  " + quote(edge.from) + " -> " + quote(edge.to) + dotify.edge.attr(edge) + ";"
}

dotify.edge.attr = function(edge) {
  var n, max, key, keys = Object.keys(edge).sort(), output = [];
  for(n = 0, max = keys.length ; n < max ; n++) {
    key = keys[n];
    if (key !== 'from' && key !== 'to')
      output.push(key + "=" + quote(edge[key]))
  }
  return output.length > 0 ? " [ " + output.join(" ; ") + " ]" : ""
}

//-------------------------------------------------------------------------------------------------

visualize.dotcfg = dotcfg;
visualize.dotify = dotify;

//-------------------------------------------------------------------------------------------------

module.exports = visualize;

//-------------------------------------------------------------------------------------------------


================================================
FILE: src/plugin.js
================================================
'use strict'

//-------------------------------------------------------------------------------------------------

var mixin = require('./util/mixin');

//-------------------------------------------------------------------------------------------------

module.exports = {

  build: function(target, config) {
    var n, max, plugin, plugins = config.plugins;
    for(n = 0, max = plugins.length ; n < max ; n++) {
      plugin = plugins[n];
      if (plugin.methods)
        mixin(target, plugin.methods);
      if (plugin.properties)
        Object.defineProperties(target, plugin.properties);
    }
  },

  hook: function(fsm, name, additional) {
    var n, max, method, plugin,
        plugins = fsm.config.plugins,
        args    = [fsm.context];

    if (additional)
      args = args.concat(additional)

    for(n = 0, max = plugins.length ; n < max ; n++) {
      plugin = plugins[n]
      method = plugins[n][name]
      if (method)
        method.apply(plugin, args);
    }
  }

}

//-------------------------------------------------------------------------------------------------


================================================
FILE: src/util/camelize.js
================================================
'use strict'

//-------------------------------------------------------------------------------------------------

function camelize(label) {

  if (label.length === 0)
    return label;

  var n, result, word, words = label.split(/[_-]/);

  // single word with first character already lowercase, return untouched
  if ((words.length === 1) && (words[0][0].toLowerCase() === words[0][0]))
    return label;

  result = words[0].toLowerCase();
  for(n = 1 ; n < words.length ; n++) {
    result = result + words[n].charAt(0).toUpperCase() + words[n].substring(1).toLowerCase();
  }

  return result;
}

//-------------------------------------------------------------------------------------------------

camelize.prepended = function(prepend, label) {
  label = camelize(label);
  return prepend + label[0].toUpperCase() + label.substring(1);
}

//-------------------------------------------------------------------------------------------------

module.exports = camelize;


================================================
FILE: src/util/exception.js
================================================
'use strict'

module.exports = function(message, transition, from, to, current) {
  this.message    = message;
  this.transition = transition;
  this.from       = from;
  this.to         = to;
  this.current    = current;
}


================================================
FILE: src/util/mixin.js
================================================
'use strict'

module.exports = function(target, sources) {
  var n, source, key;
  for(n = 1 ; n < arguments.length ; n++) {
    source = arguments[n];
    for(key in source) {
      if (source.hasOwnProperty(key))
        target[key] = source[key];
    }
  }
  return target;
}


================================================
FILE: test/basics.js
================================================
import test         from 'ava';
import StateMachine from '../src/app';

//-------------------------------------------------------------------------------------------------

test('version', t => {
  t.is(StateMachine.version, '3.0.1');
});

//-------------------------------------------------------------------------------------------------

test('state machine', t => {

  var fsm = new StateMachine({
    init: 'green',
    transitions: [
      { name: 'warn',  from: 'green',  to: 'yellow' },
      { name: 'panic', from: 'yellow', to: 'red'    },
      { name: 'calm',  from: 'red',    to: 'yellow' },
      { name: 'clear', from: 'yellow', to: 'green'  }
    ]
  })

  t.is(fsm.state, 'green')

  fsm.warn();  t.is(fsm.state, 'yellow')
  fsm.panic(); t.is(fsm.state, 'red')
  fsm.calm();  t.is(fsm.state, 'yellow')
  fsm.clear(); t.is(fsm.state, 'green')

});

//-----------------------------------------------------------------------------

test('state machine factory', t => {

  var Alarm = StateMachine.factory({
        init: 'green',
        transitions: [
          { name: 'warn',  from: 'green',  to: 'yellow' },
          { name: 'panic', from: 'yellow', to: 'red'    },
          { name: 'calm',  from: 'red',    to: 'yellow' },
          { name: 'clear', from: 'yellow', to: 'green'  }
        ]
      }),
      a = new Alarm(),
      b = new Alarm();

  t.is(a.state, 'green')
  t.is(b.state, 'green')

  a.warn();  t.is(a.state, 'yellow'); t.is(b.state, 'green')
  a.panic(); t.is(a.state, 'red');    t.is(b.state, 'green')
  a.calm();  t.is(a.state, 'yellow'); t.is(b.state, 'green')
  a.clear(); t.is(a.state, 'green');  t.is(b.state, 'green')

  b.warn();  t.is(a.state, 'green');  t.is(b.state, 'yellow')
  b.panic(); t.is(a.state, 'green');  t.is(b.state, 'red')
  b.calm();  t.is(a.state, 'green');  t.is(b.state, 'yellow')
  b.clear(); t.is(a.state, 'green');  t.is(b.state, 'green')

});

//-----------------------------------------------------------------------------

test('state machine - applied to existing object', t => {

  var obj = { name: 'alarm' }

  StateMachine.apply(obj, {
    init: 'green',
    transitions: [
      { name: 'warn',  from: 'green',  to: 'yellow' },
      { name: 'panic', from: 'yellow', to: 'red'    },
      { name: 'calm',  from: 'red',    to: 'yellow' },
      { name: 'clear', from: 'yellow', to: 'green'  }
    ]
  });

  t.is(obj.name,  'alarm');
  t.is(obj.state, 'green');

  obj.warn();  t.is(obj.state, 'yellow')
  obj.panic(); t.is(obj.state, 'red')
  obj.calm();  t.is(obj.state, 'yellow')
  obj.clear(); t.is(obj.state, 'green')

});

//-----------------------------------------------------------------------------

test('state machine factory - applied to existing class', t => {

  function Alarm(name) {
    this.name = name
    this._fsm(); // manual step needed to construct this FSM instance
  }

  StateMachine.factory(Alarm, {
    init: 'green',
    transitions: [
      { name: 'warn',  from: 'green',  to: 'yellow' },
      { name: 'panic', from: 'yellow', to: 'red'    },
      { name: 'calm',  from: 'red',    to: 'yellow' },
      { name: 'clear', from: 'yellow', to: 'green'  }
    ]
  });

  var a = new Alarm('A'),
      b = new Alarm('B');

  t.is(a.name, 'A')
  t.is(b.name, 'B')

  t.is(a.state, 'green')
  t.is(b.state, 'green')

  a.warn();  t.is(a.state, 'yellow'); t.is(b.state, 'green')
  a.panic(); t.is(a.state, 'red');    t.is(b.state, 'green')
  a.calm();  t.is(a.state, 'yellow'); t.is(b.state, 'green')
  a.clear(); t.is(a.state, 'green');  t.is(b.state, 'green')

  b.warn();  t.is(a.state, 'green'); t.is(b.state, 'yellow')
  b.panic(); t.is(a.state, 'green'); t.is(b.state, 'red')
  b.calm();  t.is(a.state, 'green'); t.is(b.state, 'yellow')
  b.clear(); t.is(a.state, 'green'); t.is(b.state, 'green')

});

//-----------------------------------------------------------------------------


================================================
FILE: test/construction.js
================================================
import test         from 'ava'
import StateMachine from '../src/app'

//-------------------------------------------------------------------------------------------------

test('singleton construction', t => {

  var fsm = new StateMachine({
    transitions: [
      { name: 'init',  from: 'none', to: 'A' },
      { name: 'step1', from: 'A',    to: 'B' },
      { name: 'step2', from: 'B',    to: 'C' }
    ]
  });

  t.is(fsm.state, 'none')

  t.deepEqual(fsm.allStates(),      [ 'none', 'A', 'B', 'C' ])
  t.deepEqual(fsm.allTransitions(), [ 'init', 'step1', 'step2' ])
  t.deepEqual(fsm.transitions(),    [ 'init' ])

})


//-------------------------------------------------------------------------------------------------

test('singleton construction - with init state', t => {

  var fsm = new StateMachine({
    init: 'A',
    transitions: [
      { name: 'step1', from: 'A', to: 'B' },
      { name: 'step2', from: 'B', to: 'C' }
    ]
  });

  t.is(fsm.state, 'A')

  t.deepEqual(fsm.allStates(),      [ 'none', 'A', 'B', 'C' ])
  t.deepEqual(fsm.allTransitions(), [ 'init', 'step1', 'step2' ])
  t.deepEqual(fsm.transitions(),    [ 'step1' ])

})

//-------------------------------------------------------------------------------------------------

test('singleton construction - with init state and transition', t => {

  var fsm = new StateMachine({
    init: { name: 'boot', to: 'A' },
    transitions: [
      { name: 'step1', from: 'A', to: 'B' },
      { name: 'step2', from: 'B', to: 'C' }
    ]
  });

  t.is(fsm.state, 'A')

  t.deepEqual(fsm.allStates(),      [ 'none', 'A', 'B', 'C' ])
  t.deepEqual(fsm.allTransitions(), [ 'boot', 'step1', 'step2' ])
  t.deepEqual(fsm.transitions(),    [ 'step1' ])

})

//-------------------------------------------------------------------------------------------------

test('singleton construction - with init state, transition, AND from state', t => {

  var fsm = new StateMachine({
    init: { name: 'boot', from: 'booting', to: 'A' },
    transitions: [
      { name: 'step1', from: 'A', to: 'B' },
      { name: 'step2', from: 'B', to: 'C' }
    ]
  });

  t.is(fsm.state, 'A')

  t.deepEqual(fsm.allStates(),      [ 'booting', 'A', 'B', 'C' ])
  t.deepEqual(fsm.allTransitions(), [ 'boot', 'step1', 'step2' ])
  t.deepEqual(fsm.transitions(),    [ 'step1' ])

})

//-------------------------------------------------------------------------------------------------

test('singleton construction - with custom data and methods', t => {

  var fsm = new StateMachine({
    init: 'A',
    transitions: [
      { name: 'step1', from: 'A', to: 'B' },
      { name: 'step2', from: 'B', to: 'C' }
    ],
    data: {
      value: 42
    },
    methods: {
      talk: function() {
        return this.state + ' - ' + this.value
      }
    }
  });

  t.is(fsm.state,  'A')
  t.is(fsm.value,  42)
  t.is(fsm.talk(), 'A - 42')

  fsm.step1()

  t.is(fsm.state,  'B')
  t.is(fsm.value,  42)
  t.is(fsm.talk(), 'B - 42')

  fsm.value = 99

  t.is(fsm.state,  'B')
  t.is(fsm.value,  99)
  t.is(fsm.talk(), 'B - 99')

})

//-------------------------------------------------------------------------------------------------

test('factory construction', t => {

  var MyClass = StateMachine.factory({
    init: 'A',
    transitions: [
      { name: 'step1', from: 'A', to: 'B' },
      { name: 'step2', from: 'B', to: 'C' }
    ]
  });

  var fsm1 = new MyClass(),
      fsm2 = new MyClass(),
      fsm3 = new MyClass();

  fsm2.step1()
  fsm3.step1()
  fsm3.step2()

  t.is(fsm1.state, 'A')
  t.is(fsm2.state, 'B')
  t.is(fsm3.state, 'C')

  t.deepEqual(fsm1.allStates(), [ 'none', 'A', 'B', 'C' ])
  t.deepEqual(fsm2.allStates(), [ 'none', 'A', 'B', 'C' ])
  t.deepEqual(fsm3.allStates(), [ 'none', 'A', 'B', 'C' ])

  t.deepEqual(fsm1.allTransitions(), [ 'init', 'step1', 'step2' ])
  t.deepEqual(fsm2.allTransitions(), [ 'init', 'step1', 'step2' ])
  t.deepEqual(fsm3.allTransitions(), [ 'init', 'step1', 'step2' ])

  t.deepEqual(fsm1.transitions(), [ 'step1' ])
  t.deepEqual(fsm2.transitions(), [ 'step2' ])
  t.deepEqual(fsm3.transitions(), [         ])

  t.is(fsm1.allStates, MyClass.prototype.allStates)
  t.is(fsm2.allStates, MyClass.prototype.allStates)
  t.is(fsm3.allStates, MyClass.prototype.allStates)

})

//-------------------------------------------------------------------------------------------------

test('factory construction - with custom data and methods', t => {

  var MyClass = StateMachine.factory({
    init: 'A',
    transitions: [
      { name: 'step1', from: 'A', to: 'B' },
      { name: 'step2', from: 'B', to: 'C' }
    ],
    data: function(value) {
      return {
        value: value
      }
    },
    methods: {
      talk: function() {
        return this.state + ' - ' + this.value
      }
    }
  });

  var fsm1 = new MyClass(1),
      fsm2 = new MyClass(2),
      fsm3 = new MyClass(3);

  t.is(fsm1.state, 'A')
  t.is(fsm2.state, 'A')
  t.is(fsm3.state, 'A')

  t.is(fsm1.talk(), 'A - 1')
  t.is(fsm2.talk(), 'A - 2')
  t.is(fsm3.talk(), 'A - 3')

  fsm2.step1()
  fsm3.step1()
  fsm3.step2()

  t.is(fsm1.state, 'A')
  t.is(fsm2.state, 'B')
  t.is(fsm3.state, 'C')

  t.is(fsm1.talk(), 'A - 1')
  t.is(fsm2.talk(), 'B - 2')
  t.is(fsm3.talk(), 'C - 3')

  t.deepEqual(fsm1.allStates(), [ 'none', 'A', 'B', 'C' ])
  t.deepEqual(fsm2.allStates(), [ 'none', 'A', 'B', 'C' ])
  t.deepEqual(fsm3.allStates(), [ 'none', 'A', 'B', 'C' ])

  t.deepEqual(fsm1.allTransitions(), [ 'init', 'step1', 'step2' ])
  t.deepEqual(fsm2.allTransitions(), [ 'init', 'step1', 'step2' ])
  t.deepEqual(fsm3.allTransitions(), [ 'init', 'step1', 'step2' ])

  t.deepEqual(fsm1.transitions(), [ 'step1' ])
  t.deepEqual(fsm2.transitions(), [ 'step2' ])
  t.deepEqual(fsm3.transitions(), [         ])

  t.is(fsm1.allStates, MyClass.prototype.allStates)
  t.is(fsm2.allStates, MyClass.prototype.allStates)
  t.is(fsm3.allStates, MyClass.prototype.allStates)

})

//-------------------------------------------------------------------------------------------------


================================================
FILE: test/defaults.js
================================================
import test         from 'ava';
import StateMachine from '../src/app';

//-------------------------------------------------------------------------------------------------

const defaults = JSON.stringify(StateMachine.defaults);

test.afterEach.always('restore defaults', t => {
  StateMachine.defaults = JSON.parse(defaults);
});

//-------------------------------------------------------------------------------------------------

test.serial('override global initialization defaults', t => {

  StateMachine.defaults.init = {
    name: 'boot',
    from: 'booting'
  }

  var fsm = new StateMachine({
    init: 'A',
    transitions: [
      { name: 'step1', from: 'A', to: 'B' },
      { name: 'step2', from: 'B', to: 'C' }
    ]
  });

  t.is(fsm.state, 'A');

  t.deepEqual(fsm.allStates(),      [ 'booting', 'A', 'B', 'C' ]);
  t.deepEqual(fsm.allTransitions(), [ 'boot', 'step1', 'step2' ]);
  t.deepEqual(fsm.transitions(),    [ 'step1' ]);

});

//-------------------------------------------------------------------------------------------------

test.serial('override global initialization defaults (again)', t => {

  StateMachine.defaults.init = {
    name: 'start',
    from: 'unknown'
  }

  var fsm = new StateMachine({
    init: 'A',
    transitions: [
      { name: 'step1', from: 'A', to: 'B' },
      { name: 'step2', from: 'B', to: 'C' }
    ]
  });

  t.is(fsm.state, 'A');

  t.deepEqual(fsm.allStates(),      [ 'unknown', 'A', 'B', 'C' ]);
  t.deepEqual(fsm.allTransitions(), [ 'start', 'step1', 'step2' ]);
  t.deepEqual(fsm.transitions(),    [ 'step1' ]);

});

//-------------------------------------------------------------------------------------------------


================================================
FILE: test/empty.js
================================================
import test         from 'ava'
import StateMachine from '../src/app'

//-------------------------------------------------------------------------------------------------

test('empty state machine', t => {

  var fsm = new StateMachine();

  t.is(fsm.state, 'none')

  t.deepEqual(fsm.allStates(),      [ 'none' ])
  t.deepEqual(fsm.allTransitions(), [ ])
  t.deepEqual(fsm.transitions(),    [ ])

})

//-------------------------------------------------------------------------------------------------

test('empty state machine - but caller forgot new keyword', t => {

  var fsm = StateMachine() // NOTE: missing 'new'

  t.is(fsm.state, 'none')

  t.deepEqual(fsm.allStates(),      [ 'none' ])
  t.deepEqual(fsm.allTransitions(), [ ])
  t.deepEqual(fsm.transitions(),    [ ])

})

//-------------------------------------------------------------------------------------------------

test('empty state machine - applied to existing object', t => {

  var fsm = {};

  StateMachine.apply(fsm)

  t.is(fsm.state, 'none')

  t.deepEqual(fsm.allStates(),      [ 'none' ])
  t.deepEqual(fsm.allTransitions(), [ ])
  t.deepEqual(fsm.transitions(),    [ ])

})

//-------------------------------------------------------------------------------------------------

test('empty state machine factory', t => {

  var FSM = StateMachine.factory(),
      fsm = new FSM();

  t.is(fsm.state, 'none')
  t.deepEqual(fsm.allStates(),      [ 'none' ])
  t.deepEqual(fsm.allTransitions(), [ ])
  t.deepEqual(fsm.transitions(),    [ ])

})

//-------------------------------------------------------------------------------------------------

test('empty state machine factory - applied to existing class', t => {

  var FSM = function() { this._fsm() };

  StateMachine.factory(FSM)

  var fsm = new FSM()

  t.is(fsm.state, 'none')
  t.deepEqual(fsm.allStates(),      [ 'none' ])
  t.deepEqual(fsm.allTransitions(), [ ])
  t.deepEqual(fsm.transitions(),    [ ])

})

//-------------------------------------------------------------------------------------------------


================================================
FILE: test/errors.js
================================================
import test         from 'ava'
import StateMachine from '../src/app'

//-------------------------------------------------------------------------------------------------

test('state cannot be modified directly', t => {

  var fsm = new StateMachine({
    transitions: [
      { name: 'step', from: 'none', to: 'complete' }
    ]
  })

  t.is(fsm.state, 'none')
  var error = t.throws(() => {
    fsm.state = 'other'
  })
  t.is(error.message, 'use transitions to change state')
  t.is(fsm.state, 'none')

})

//-------------------------------------------------------------------------------------------------

test('StateMachine.apply only allowed on objects', t => {

  var config = {
    transitions: [
      { name: 'step', from: 'none', to: 'complete' }
    ]
  };

  var error = t.throws(() => {
    StateMachine.apply(function() {}, config)
  })
  t.is(error.message, 'StateMachine can only be applied to objects')

  error = t.throws(() => {
    StateMachine.apply([], config)
  })
  t.is(error.message, 'StateMachine can only be applied to objects')

  error = t.throws(() => {
    StateMachine.apply(42, config)
  })
  t.is(error.message, 'StateMachine can only be applied to objects')

})

//-------------------------------------------------------------------------------------------------

test('invalid transition raises an exception', t => {

  var fsm = new StateMachine({
        transitions: [
          { name: 'step1', from: 'none', to: 'A' },
          { name: 'step2', from: 'A',    to: 'B' }
        ]
      });

  t.is(fsm.state,        'none')
  t.is(fsm.can('step1'),  true)
  t.is(fsm.can('step2'), false)

  const error = t.throws(() => {
    fsm.step2();
  })

  t.is(error.message,    'transition is invalid in current state')
  t.is(error.transition, 'step2')
  t.is(error.from,       'none')
  t.is(error.to,         undefined)
  t.is(error.current,    'none')

})

//-------------------------------------------------------------------------------------------------

test('invalid transition handler can be customized', t => {

  var fsm = new StateMachine({
        transitions: [
          { name: 'step1', from: 'none', to: 'A' },
          { name: 'step2', from: 'A',    to: 'B' }
        ],
        methods: {
          onInvalidTransition: function() { return 'custom error'; }
        }
      });

  t.is(fsm.state,        'none')
  t.is(fsm.can('step1'),  true)
  t.is(fsm.can('step2'), false)
  t.is(fsm.step2(),      'custom error')
  t.is(fsm.state,        'none')

})

//-------------------------------------------------------------------------------------------------

test('fire transition while existing transition is still in process raises an exception', t => {

  var fsm = new StateMachine({
        transitions: [
          { name: 'step', from:  'none', to: 'A' },
          { name: 'other', from: '*',    to: 'X' }
        ],
        methods: {
          onBeforeStep:  function() { this.other();                 },
          onBeforeOther: function() { t.fail('should never happen') },
          onEnterX:      function() { t.fail('should never happen') }
        }
      });

  t.is(fsm.state,        'none')
  t.is(fsm.can('step'),  true)
  t.is(fsm.can('other'), true)

  const error = t.throws(() => {
    fsm.step()
  })

  t.is(error.message, 'transition is invalid while previous transition is still in progress')
  t.is(error.transition, 'other')
  t.is(error.from,       'none')
  t.is(error.to,         'X')
  t.is(error.current,    'none')

  t.is(fsm.state, 'none', 'entire transition was cancelled by the exception')

})

//-------------------------------------------------------------------------------------------------

test('pending transition handler can be customized', t => {

  var error = "",
      fsm = new StateMachine({
        transitions: [
          { name: 'step', from:  'none', to: 'A' },
          { name: 'other', from: '*',    to: 'X' }
        ],
        methods: {
          onBeforeStep:        function() { error = this.other(); return false },
          onPendingTransition: function() { return 'custom error' },
          onBeforeOther:       function() { t.fail('should never happen') },
          onEnterX:            function() { t.fail('should never happen') }
        }
      });

  t.is(fsm.state,        'none')
  t.is(fsm.can('step'),  true)
  t.is(fsm.can('other'), true)
  t.is(fsm.step(),       false)
  t.is(fsm.state,        'none')
  t.is(error,            'custom error')

})

//-------------------------------------------------------------------------------------------------


================================================
FILE: test/goto.js
================================================
import test            from 'ava'
import StateMachine    from '../src/app'
import LifecycleLogger from './helpers/lifecycle_logger'

//-------------------------------------------------------------------------------------------------

function goto(state) {
  return state
}

//-------------------------------------------------------------------------------------------------

test('goto transition', t => {

  var logger = new LifecycleLogger(),
      fsm = new StateMachine({
        init: 'A',
        transitions: [
          { name: 'step', from: 'A', to: 'B'  },
          { name: 'step', from: 'B', to: 'C'  },
          { name: 'step', from: 'C', to: 'D'  },
          { name: 'goto', from: '*', to: goto }
        ],
        methods: {
          onBeforeTransition: logger,
          onBeforeInit:       logger,
          onBeforeStep:       logger,
          onBeforeGoto:       logger,
          onLeaveState:       logger,
          onLeaveNone:        logger,
          onLeaveA:           logger,
          onLeaveB:           logger,
          onLeaveC:           logger,
          onLeaveD:           logger,
          onTransition:       logger,
          onEnterState:       logger,
          onEnterNone:        logger,
          onEnterA:           logger,
          onEnterB:           logger,
          onEnterC:           logger,
          onEnterD:           logger,
          onAfterTransition:  logger,
          onAfterInit:        logger,
          onAfterStep:        logger,
          onAfterGoto:        logger
        }
      });

  t.is(fsm.state, 'A')
  t.deepEqual(fsm.allStates(), [ 'none', 'A', 'B', 'C', 'D' ])
  t.deepEqual(fsm.allTransitions(), [ 'init', 'step', 'goto' ])

  logger.clear()

  fsm.goto('C')

  t.is(fsm.state, 'C')
  t.deepEqual(logger.log, [
    { event: 'onBeforeTransition', transition: 'goto', from: 'A', to: 'C', current: 'A', args: [ 'C' ] },
    { event: 'onBeforeGoto',       transition: 'goto', from: 'A', to: 'C', current: 'A', args: [ 'C' ] },
    { event: 'onLeaveState',       transition: 'goto', from: 'A', to: 'C', current: 'A', args: [ 'C' ] },
    { event: 'onLeaveA',           transition: 'goto', from: 'A', to: 'C', current: 'A', args: [ 'C' ] },
    { event: 'onTransition',       transition: 'goto', from: 'A', to: 'C', current: 'A', args: [ 'C' ] },
    { event: 'onEnterState',       transition: 'goto', from: 'A', to: 'C', current: 'C', args: [ 'C' ] },
    { event: 'onEnterC',           transition: 'goto', from: 'A', to: 'C', current: 'C', args: [ 'C' ] },
    { event: 'onAfterTransition',  transition: 'goto', from: 'A', to: 'C', current: 'C', args: [ 'C' ] },
    { event: 'onAfterGoto',        transition: 'goto', from: 'A', to: 'C', current: 'C', args: [ 'C' ] }
  ])

})

//-------------------------------------------------------------------------------------------------

test('goto can have additional arguments', t => {

  var logger = new LifecycleLogger(),
      fsm = new StateMachine({
        init: 'A',
        transitions: [
          { name: 'step', from: 'A', to: 'B'  },
          { name: 'step', from: 'B', to: 'C'  },
          { name: 'step', from: 'C', to: 'D'  },
          { name: 'goto', from: '*', to: goto }
        ],
        methods: {
          onStep: logger,
          onGoto: logger
        }
      });

  t.is(fsm.state, 'A')
  t.deepEqual(fsm.allStates(), [ 'none', 'A', 'B', 'C', 'D' ])
  t.deepEqual(fsm.allTransitions(), [ 'init', 'step', 'goto' ])

  logger.clear()

  fsm.goto('C', 'with', 4, 'additional', 'arguments')

  t.is(fsm.state, 'C')
  t.deepEqual(logger.log, [
    { event: 'onGoto', transition: 'goto', from: 'A', to: 'C', current: 'C', args: [ 'C', 'with', 4, 'additional', 'arguments' ] }
  ])

})

//-------------------------------------------------------------------------------------------------

test('goto can go to an unknown state', t => {

  var fsm = new StateMachine({
    init: 'A',
    transitions: [
      { name: 'step', from: 'A', to: 'B'  },
      { name: 'step', from: 'B', to: 'C'  },
      { name: 'step', from: 'C', to: 'D'  },
      { name: 'goto', from: '*', to: goto }
    ]
  })

  t.is(fsm.state, 'A')
  t.deepEqual(fsm.allStates(), [ 'none', 'A', 'B', 'C', 'D' ]);

  fsm.goto('B')
  t.is(fsm.state, 'B')
  t.deepEqual(fsm.allStates(), [ 'none', 'A', 'B', 'C', 'D' ]);

  fsm.goto('X')
  t.is(fsm.state, 'X')
  t.deepEqual(fsm.allStates(), [ 'none', 'A', 'B', 'C', 'D', 'X' ]);
  
})

//-------------------------------------------------------------------------------------------------

test('goto can be configured with a custom name', t => {

  var logger = new LifecycleLogger(),
      fsm = new StateMachine({
        init: 'A',
        transitions: [
          { name: 'step', from: 'A', to: 'B'  },
          { name: 'step', from: 'B', to: 'C'  },
          { name: 'step', from: 'C', to: 'D'  },
          { name: 'jump', from: '*', to: goto }
        ],
        methods: {
          onBeforeTransition: logger,
          onBeforeInit:       logger,
          onBeforeStep:       logger,
          onBeforeJump:       logger,
          onLeaveState:       logger,
          onLeaveNone:        logger,
          onLeaveA:           logger,
          onLeaveB:           logger,
          onLeaveC:           logger,
          onLeaveD:           logger,
          onTransition:       logger,
          onEnterState:       logger,
          onEnterNone:        logger,
          onEnterA:           logger,
          onEnterB:           logger,
          onEnterC:           logger,
          onEnterD:           logger,
          onAfterTransition:  logger,
          onAfterInit:        logger,
          onAfterStep:        logger,
          onAfterJump:        logger
        }
      });

  t.is(fsm.state, 'A')
  t.deepEqual(fsm.allStates(), [ 'none', 'A', 'B', 'C', 'D' ])
  t.deepEqual(fsm.allTransitions(), [ 'init', 'step', 'jump' ])
  t.is(fsm.goto, undefined)

  logger.clear()

  fsm.jump('C')

  t.is(fsm.state, 'C')
  t.deepEqual(logger.log, [
    { event: 'onBeforeTransition', transition: 'jump', from: 'A', to: 'C', current: 'A', args: [ 'C' ] },
    { event: 'onBeforeJump',       transition: 'jump', from: 'A', to: 'C', current: 'A', args: [ 'C' ] },
    { event: 'onLeaveState',       transition: 'jump', from: 'A', to: 'C', current: 'A', args: [ 'C' ] },
    { event: 'onLeaveA',           transition: 'jump', from: 'A', to: 'C', current: 'A', args: [ 'C' ] },
    { event: 'onTransition',       transition: 'jump', from: 'A', to: 'C', current: 'A', args: [ 'C' ] },
    { event: 'onEnterState',       transition: 'jump', from: 'A', to: 'C', current: 'C', args: [ 'C' ] },
    { event: 'onEnterC',           transition: 'jump', from: 'A', to: 'C', current: 'C', args: [ 'C' ] },
    { event: 'onAfterTransition',  transition: 'jump', from: 'A', to: 'C', current: 'C', args: [ 'C' ] },
    { event: 'onAfterJump',        transition: 'jump', from: 'A', to: 'C', current: 'C', args: [ 'C' ] }
  ])

})

//-------------------------------------------------------------------------------------------------


================================================
FILE: test/helpers/lifecycle_logger.js
================================================

module.exports = function() {

  'use strict'

  var entries = [],
      logger = function(lifecycle) {
        var entry = {
              event:      lifecycle.event,
              transition: lifecycle.transition,
              from:       lifecycle.from,
              to:         lifecycle.to,
              current:    lifecycle.fsm.state
            }
        if (arguments.length > 1)
          entry.args = [].slice.call(arguments, 1);
        entries.push(entry);
      };

  logger.clear = function() {
    entries.length = 0;
  }

  logger.log = entries;

  return logger;
}


================================================
FILE: test/introspection.js
================================================
import test         from 'ava';
import StateMachine from '../src/app';

//-----------------------------------------------------------------------------

test('is', t => {

  var fsm = new StateMachine({
    init: 'green',
    transitions: [
      { name: 'warn',  from: 'green',  to: 'yellow' },
      { name: 'panic', from: 'yellow', to: 'red'    },
      { name: 'calm',  from: 'red',    to: 'yellow' },
      { name: 'clear', from: 'yellow', to: 'green'  }
  ]});

  t.is(fsm.state, 'green')

  t.is(fsm.is('green'),           true)
  t.is(fsm.is('yellow'),          false)
  t.is(fsm.is(['green',  'red']), true,   'current state should match when included in array')
  t.is(fsm.is(['yellow', 'red']), false,  'current state should NOT match when not included in array')

  fsm.warn()

  t.is(fsm.state, 'yellow')
  t.is(fsm.is('green'),           false)
  t.is(fsm.is('yellow'),          true)
  t.is(fsm.is(['green',  'red']), false, 'current state should NOT match when not included in array')
  t.is(fsm.is(['yellow', 'red']), true,  'current state should match when included in array')

});

//-----------------------------------------------------------------------------

test('can & cannot', t => {

  var fsm = new StateMachine({
    init: 'green',
    transitions: [
      { name: 'warn',  from: 'green',  to: 'yellow' },
      { name: 'panic', from: 'yellow', to: 'red'    },
      { name: 'calm',  from: 'red',    to: 'yellow' },
    ]
  });

  t.is(fsm.state, 'green')
  t.is(fsm.can('warn'),     true)
  t.is(fsm.can('panic'),    false)
  t.is(fsm.can('calm'),     false)
  t.is(fsm.cannot('warn'),  false)
  t.is(fsm.cannot('panic'), true)
  t.is(fsm.cannot('calm'),  true)

  fsm.warn();
  t.is(fsm.state, 'yellow')
  t.is(fsm.can('warn'),     false)
  t.is(fsm.can('panic'),    true)
  t.is(fsm.can('calm'),     false)
  t.is(fsm.cannot('warn'),  true)
  t.is(fsm.cannot('panic'), false)
  t.is(fsm.cannot('calm'),  true)

  fsm.panic();
  t.is(fsm.state, 'red')
  t.is(fsm.can('warn'),     false)
  t.is(fsm.can('panic'),    false)
  t.is(fsm.can('calm'),     true)
  t.is(fsm.cannot('warn'),  true)
  t.is(fsm.cannot('panic'), true)
  t.is(fsm.cannot('calm'),  false)

  t.is(fsm.can('jibber'),    false, "unknown event should not crash")
  t.is(fsm.cannot('jabber'), true,  "unknown event should not crash")

});

//-----------------------------------------------------------------------------

test('can is always false during lifecycle events', t => {

  t.plan(81);

  var fsm = new StateMachine({
    init: 'green',
    transitions: [
      { name: 'warn',  from: 'green',  to: 'yellow' },
      { name: 'panic', from: 'yellow', to: 'red'    },
      { name: 'calm',  from: 'red',    to: 'yellow' },
    ],
    methods: {
      assertTransitionsNotAllowed: function() {
        t.false(this.can('warn'))
        t.false(this.can('panic'))
        t.false(this.can('calm'))
      },
      onBeforeTransition: function() { this.assertTransitionsNotAllowed(); },
      onBeforeWarn:       function() { this.assertTransitionsNotAllowed(); },
      onBeforePanic:      function() { this.assertTransitionsNotAllowed(); },
      onBeforeCalm:       function() { this.assertTransitionsNotAllowed(); },
      onLeaveState:       function() { this.assertTransitionsNotAllowed(); },
      onLeaveNone:        function() { this.assertTransitionsNotAllowed(); },
      onLeaveGreen:       function() { this.assertTransitionsNotAllowed(); },
      onLeaveYellow:      function() { this.assertTransitionsNotAllowed(); },
      onLeaveRed:         function() { this.assertTransitionsNotAllowed(); },
      onTransition:       function() { this.assertTransitionsNotAllowed(); },
      onEnterState:       function() { this.assertTransitionsNotAllowed(); },
      onEnterNone:        function() { this.assertTransitionsNotAllowed(); },
      onEnterGreen:       function() { this.assertTransitionsNotAllowed(); },
      onEnterYellow:      function() { this.assertTransitionsNotAllowed(); },
      onEnterRed:         function() { this.assertTransitionsNotAllowed(); },
      onAfterTransition:  function() { this.assertTransitionsNotAllowed(); },
      onAfterInit:        function() { this.assertTransitionsNotAllowed(); },
      onAfterWarn:        function() { this.assertTransitionsNotAllowed(); },
      onAfterPanic:       function() { this.assertTransitionsNotAllowed(); },
      onAfterCalm:        function() { this.assertTransitionsNotAllowed(); }
    }
  });

  t.is(fsm.state, 'green')
  fsm.warn()
  t.is(fsm.state, 'yellow')
  fsm.panic()
  t.is(fsm.state, 'red')

});

//-----------------------------------------------------
Download .txt
gitextract_31ic2yb8/

├── .ackrc
├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── RELEASE_NOTES.md
├── bin/
│   ├── examples
│   └── minify
├── dist/
│   ├── state-machine-history.js
│   ├── state-machine-visualize.js
│   └── state-machine.js
├── docs/
│   ├── async-transitions.md
│   ├── contributing.md
│   ├── data-and-methods.md
│   ├── error-handling.md
│   ├── initialization.md
│   ├── lifecycle-events.md
│   ├── state-history.md
│   ├── state-machine-factory.md
│   ├── states-and-transitions.md
│   ├── upgrading-from-v2.md
│   └── visualization.md
├── examples/
│   ├── atm.dot
│   ├── atm.js
│   ├── demo/
│   │   ├── demo.css
│   │   └── demo.js
│   ├── horizontal_door.dot
│   ├── horizontal_door.js
│   ├── matter.dot
│   ├── matter.js
│   ├── vertical_door.dot
│   ├── vertical_door.js
│   ├── wizard.dot
│   └── wizard.js
├── index.html
├── lib/
│   ├── history.js
│   ├── state-machine.js
│   └── visualize.js
├── package.json
├── src/
│   ├── app.js
│   ├── config.js
│   ├── jsm.js
│   ├── plugin/
│   │   ├── history.js
│   │   └── visualize.js
│   ├── plugin.js
│   └── util/
│       ├── camelize.js
│       ├── exception.js
│       └── mixin.js
├── test/
│   ├── basics.js
│   ├── construction.js
│   ├── defaults.js
│   ├── empty.js
│   ├── errors.js
│   ├── goto.js
│   ├── helpers/
│   │   └── lifecycle_logger.js
│   ├── introspection.js
│   ├── issues.js
│   ├── lifecycle.js
│   ├── observers.js
│   ├── plugin/
│   │   ├── history.js
│   │   └── visualize.js
│   ├── plugins.js
│   ├── transitions.js
│   ├── util/
│   │   ├── camelize.js
│   │   └── mixin.js
│   └── wildcards.js
└── webpack.config.js
Download .txt
SYMBOL INDEX (50 symbols across 15 files)

FILE: dist/state-machine-history.js
  function __webpack_require__ (line 16) | function __webpack_require__(moduleId) {
  function camelize (line 88) | function camelize(label) {

FILE: dist/state-machine-visualize.js
  function __webpack_require__ (line 16) | function __webpack_require__(moduleId) {
  function visualize (line 112) | function visualize(fsm, options) {
  function dotcfg (line 118) | function dotcfg(fsm, options) {
  function pad (line 206) | function pad(name) {
  function quote (line 210) | function quote(name) {
  function dotify (line 214) | function dotify(dotcfg) {

FILE: dist/state-machine.js
  function __webpack_require__ (line 16) | function __webpack_require__(moduleId) {
  function camelize (line 155) | function camelize(label) {
  function Config (line 200) | function Config(options, StateMachine) {
  function JSM (line 366) | function JSM(context, config) {
  function StateMachine (line 586) | function StateMachine(options) {
  function factory (line 590) | function factory() {
  function apply (line 608) | function apply(instance, options) {
  function build (line 615) | function build(target, config) {

FILE: lib/history.js
  function __webpack_require__ (line 16) | function __webpack_require__(moduleId) {
  function camelize (line 88) | function camelize(label) {

FILE: lib/state-machine.js
  function __webpack_require__ (line 16) | function __webpack_require__(moduleId) {
  function camelize (line 155) | function camelize(label) {
  function Config (line 200) | function Config(options, StateMachine) {
  function JSM (line 366) | function JSM(context, config) {
  function StateMachine (line 586) | function StateMachine(options) {
  function factory (line 590) | function factory() {
  function apply (line 608) | function apply(instance, options) {
  function build (line 615) | function build(target, config) {

FILE: lib/visualize.js
  function __webpack_require__ (line 16) | function __webpack_require__(moduleId) {
  function visualize (line 112) | function visualize(fsm, options) {
  function dotcfg (line 118) | function dotcfg(fsm, options) {
  function pad (line 206) | function pad(name) {
  function quote (line 210) | function quote(name) {
  function dotify (line 214) | function dotify(dotcfg) {

FILE: src/app.js
  function StateMachine (line 40) | function StateMachine(options) {
  function factory (line 44) | function factory() {
  function apply (line 62) | function apply(instance, options) {
  function build (line 69) | function build(target, config) {

FILE: src/config.js
  function Config (line 10) | function Config(options, StateMachine) {

FILE: src/jsm.js
  function JSM (line 9) | function JSM(context, config) {

FILE: src/plugin/visualize.js
  function visualize (line 9) | function visualize(fsm, options) {
  function dotcfg (line 15) | function dotcfg(fsm, options) {
  function pad (line 103) | function pad(name) {
  function quote (line 107) | function quote(name) {
  function dotify (line 111) | function dotify(dotcfg) {

FILE: src/util/camelize.js
  function camelize (line 5) | function camelize(label) {

FILE: test/basics.js
  function Alarm (line 94) | function Alarm(name) {

FILE: test/goto.js
  function goto (line 7) | function goto(state) {

FILE: test/lifecycle.js
  function done (line 513) | function done(answer) {
  function done (line 574) | function done(answer) {
  function done (line 719) | function done() {

FILE: test/plugin/history.js
  function FSM (line 443) | function FSM(name) {
Condensed preview — 67 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (324K chars).
[
  {
    "path": ".ackrc",
    "chars": 74,
    "preview": "--ignore-dir=coverage\n--ignore-dir=node_modules\n--ignore-dir=.nyc_output\n\n"
  },
  {
    "path": ".gitignore",
    "chars": 40,
    "preview": "node_modules\ncoverage\n.nyc_output\n*.swp\n"
  },
  {
    "path": ".travis.yml",
    "chars": 53,
    "preview": "language: node_js\nnode_js:\n - '4'\n - '6'\nsudo: false\n"
  },
  {
    "path": "LICENSE",
    "chars": 1110,
    "preview": "Copyright (c) 2012, 2013, 2014, 2015, 2016, 2017, 2018, Jake Gordon and contributors\n\nPermission is hereby granted, free"
  },
  {
    "path": "README.md",
    "chars": 4200,
    "preview": "# Javascript State Machine\n\n[![NPM version](https://badge.fury.io/js/javascript-state-machine.svg)](https://badge.fury.i"
  },
  {
    "path": "RELEASE_NOTES.md",
    "chars": 5226,
    "preview": "Version 3.1.0 (July 12th 2018)\n------------------------------\n\n * Changed back to MIT license\n\nVersion 3.0.1 (June 10th "
  },
  {
    "path": "bin/examples",
    "chars": 2087,
    "preview": "#!/usr/bin/env node\n\n//================================================================================================="
  },
  {
    "path": "bin/minify",
    "chars": 1298,
    "preview": "#!/usr/bin/env node\n\n//================================================================================================="
  },
  {
    "path": "dist/state-machine-history.js",
    "chars": 6336,
    "preview": "(function webpackUniversalModuleDefinition(root, factory) {\n\tif(typeof exports === 'object' && typeof module === 'object"
  },
  {
    "path": "dist/state-machine-visualize.js",
    "chars": 8340,
    "preview": "(function webpackUniversalModuleDefinition(root, factory) {\n\tif(typeof exports === 'object' && typeof module === 'object"
  },
  {
    "path": "dist/state-machine.js",
    "chars": 20517,
    "preview": "(function webpackUniversalModuleDefinition(root, factory) {\n\tif(typeof exports === 'object' && typeof module === 'object"
  },
  {
    "path": "docs/async-transitions.md",
    "chars": 1937,
    "preview": "# Asynchronous Transitions\n\n> You should be familiar with the state machine [Lifecycle Events](lifecycle-events.md) befo"
  },
  {
    "path": "docs/contributing.md",
    "chars": 1908,
    "preview": "# Contributing\n\nThe `javascript-state-machine` library is built using:\n\n  * [Webpack 2](https://webpack.js.org/concepts/"
  },
  {
    "path": "docs/data-and-methods.md",
    "chars": 1556,
    "preview": "# Data and Methods\n\nIn addition to [States](states-and-transitions.md) and [Transitions](states-and-transitions.md), a s"
  },
  {
    "path": "docs/error-handling.md",
    "chars": 1667,
    "preview": "# Error Handling\n\n## Invalid Transitions\n\nBy default, if you try to fire a transition that is not allowed in the current"
  },
  {
    "path": "docs/initialization.md",
    "chars": 1797,
    "preview": "# Initialization Options\n\n## Explicit Init Transition\n\nBy default, if you don't specify an initial state, the state mach"
  },
  {
    "path": "docs/lifecycle-events.md",
    "chars": 5123,
    "preview": "# Lifecycle Events\n\nIn order to track or perform an action when a transition occurs, five\ngeneral-purpose lifecycle even"
  },
  {
    "path": "docs/state-history.md",
    "chars": 2913,
    "preview": "# Remembering State History\n\nBy default, a state machine only tracks its current state. If you wish to track the state h"
  },
  {
    "path": "docs/state-machine-factory.md",
    "chars": 3099,
    "preview": "# State Machine Factory\n\nMost examples in this documentation construct a single state machine instance, for example:\n\n``"
  },
  {
    "path": "docs/states-and-transitions.md",
    "chars": 3882,
    "preview": "# States and Transitions\n\n![matter state machine](../examples/matter.png)\n\nA state machine consists of a set of **states"
  },
  {
    "path": "docs/upgrading-from-v2.md",
    "chars": 11861,
    "preview": "# Upgrading from Version 2.x\n\nVersion 3.0 is a significant rewrite from earlier versions in order to support more\nadvanc"
  },
  {
    "path": "docs/visualization.md",
    "chars": 6787,
    "preview": "# Visualization\n\nIt can be very helpful to visualize your state machine as a directed graph. This is possible\nwith the o"
  },
  {
    "path": "examples/atm.dot",
    "chars": 1164,
    "preview": "digraph \"ATM\" {\n  \"ready\";\n  \"pin\";\n  \"action\";\n  \"return-card\";\n  \"deposit-account\";\n  \"deposit-amount\";\n  \"confirm-dep"
  },
  {
    "path": "examples/atm.js",
    "chars": 1685,
    "preview": "var StateMachine = require('../src/app'),\n    visualize    = require('../src/plugin/visualize');\n\nvar ATM = StateMachine"
  },
  {
    "path": "examples/demo/demo.css",
    "chars": 440,
    "preview": "#demo { width: 400px; margin: 0 auto; text-align: center; }\n\n#controls { text-align: center; }\n\n#demo #notes   { margin-"
  },
  {
    "path": "examples/demo/demo.js",
    "chars": 2496,
    "preview": "Demo = function() {\n\n  var output = document.getElementById('output'),\n      demo   = document.getElementById('demo'),\n "
  },
  {
    "path": "examples/horizontal_door.dot",
    "chars": 226,
    "preview": "digraph \"door\" {\n  rankdir=LR;\n  \"closed\";\n  \"open\";\n  \"closed\" -> \"open\" [ color=\"blue\" ; headport=\"n\" ; label=\" open \""
  },
  {
    "path": "examples/horizontal_door.js",
    "chars": 515,
    "preview": "var StateMachine = require('../src/app'),\n    visualize    = require('../src/plugin/visualize');\n\nvar Door = StateMachin"
  },
  {
    "path": "examples/matter.dot",
    "chars": 304,
    "preview": "digraph \"matter\" {\n  rankdir=LR;\n  \"solid\";\n  \"liquid\";\n  \"gas\";\n  \"solid\" -> \"liquid\" [ headport=\"nw\" ; label=\" melt \" "
  },
  {
    "path": "examples/matter.js",
    "chars": 633,
    "preview": "var StateMachine = require('../src/app'),\n    visualize    = require('../src/plugin/visualize');\n\nvar Matter = StateMach"
  },
  {
    "path": "examples/vertical_door.dot",
    "chars": 122,
    "preview": "digraph \"fsm\" {\n  \"closed\";\n  \"open\";\n  \"closed\" -> \"open\" [ label=\" open \" ];\n  \"open\" -> \"closed\" [ label=\" close \" ];"
  },
  {
    "path": "examples/vertical_door.js",
    "chars": 361,
    "preview": "var StateMachine = require('../src/app'),\n    visualize    = require('../src/plugin/visualize');\n\nvar Door = StateMachin"
  },
  {
    "path": "examples/wizard.dot",
    "chars": 447,
    "preview": "digraph \"wizard\" {\n  rankdir=LR;\n  \"A\";\n  \"B\";\n  \"C\";\n  \"D\";\n  \"A\" -> \"B\" [ headport=\"w\" ; label=\" step \" ; tailport=\"ne"
  },
  {
    "path": "examples/wizard.js",
    "chars": 694,
    "preview": "var StateMachine = require('../src/app'),\n    visualize    = require('../src/plugin/visualize');\n\nvar Wizard = StateMach"
  },
  {
    "path": "index.html",
    "chars": 938,
    "preview": "<!DOCTYPE html> \n<html>\n<head>\n  <title>Javascript Finite State Machine</title>\n  <meta http-equiv=\"Content-Type\" conten"
  },
  {
    "path": "lib/history.js",
    "chars": 6336,
    "preview": "(function webpackUniversalModuleDefinition(root, factory) {\n\tif(typeof exports === 'object' && typeof module === 'object"
  },
  {
    "path": "lib/state-machine.js",
    "chars": 20517,
    "preview": "(function webpackUniversalModuleDefinition(root, factory) {\n\tif(typeof exports === 'object' && typeof module === 'object"
  },
  {
    "path": "lib/visualize.js",
    "chars": 8340,
    "preview": "(function webpackUniversalModuleDefinition(root, factory) {\n\tif(typeof exports === 'object' && typeof module === 'object"
  },
  {
    "path": "package.json",
    "chars": 1255,
    "preview": "{\n  \"name\": \"javascript-state-machine\",\n  \"description\": \"A finite state machine library\",\n  \"homepage\": \"https://github"
  },
  {
    "path": "src/app.js",
    "chars": 3598,
    "preview": "'use strict'\n\n//-----------------------------------------------------------------------------------------------\n\nvar mix"
  },
  {
    "path": "src/config.js",
    "chars": 4896,
    "preview": "'use strict'\n\n//-------------------------------------------------------------------------------------------------\n\nvar m"
  },
  {
    "path": "src/jsm.js",
    "chars": 5928,
    "preview": "\nvar mixin      = require('./util/mixin'),\n    Exception  = require('./util/exception'),\n    plugin     = require('./plu"
  },
  {
    "path": "src/plugin/history.js",
    "chars": 2212,
    "preview": "'use strict'\n\n//-------------------------------------------------------------------------------------------------\n\nvar c"
  },
  {
    "path": "src/plugin/visualize.js",
    "chars": 4902,
    "preview": "'use strict'\n\n//-------------------------------------------------------------------------------------------------\n\nvar m"
  },
  {
    "path": "src/plugin.js",
    "chars": 1093,
    "preview": "'use strict'\n\n//-------------------------------------------------------------------------------------------------\n\nvar m"
  },
  {
    "path": "src/util/camelize.js",
    "chars": 974,
    "preview": "'use strict'\n\n//-------------------------------------------------------------------------------------------------\n\nfunct"
  },
  {
    "path": "src/util/exception.js",
    "chars": 224,
    "preview": "'use strict'\n\nmodule.exports = function(message, transition, from, to, current) {\n  this.message    = message;\n  this.tr"
  },
  {
    "path": "src/util/mixin.js",
    "chars": 279,
    "preview": "'use strict'\n\nmodule.exports = function(target, sources) {\n  var n, source, key;\n  for(n = 1 ; n < arguments.length ; n+"
  },
  {
    "path": "test/basics.js",
    "chars": 3893,
    "preview": "import test         from 'ava';\nimport StateMachine from '../src/app';\n\n//----------------------------------------------"
  },
  {
    "path": "test/construction.js",
    "chars": 6036,
    "preview": "import test         from 'ava'\nimport StateMachine from '../src/app'\n\n//------------------------------------------------"
  },
  {
    "path": "test/defaults.js",
    "chars": 1686,
    "preview": "import test         from 'ava';\nimport StateMachine from '../src/app';\n\n//----------------------------------------------"
  },
  {
    "path": "test/empty.js",
    "chars": 2049,
    "preview": "import test         from 'ava'\nimport StateMachine from '../src/app'\n\n//------------------------------------------------"
  },
  {
    "path": "test/errors.js",
    "chars": 4587,
    "preview": "import test         from 'ava'\nimport StateMachine from '../src/app'\n\n//------------------------------------------------"
  },
  {
    "path": "test/goto.js",
    "chars": 7091,
    "preview": "import test            from 'ava'\nimport StateMachine    from '../src/app'\nimport LifecycleLogger from './helpers/lifecy"
  },
  {
    "path": "test/helpers/lifecycle_logger.js",
    "chars": 588,
    "preview": "\nmodule.exports = function() {\n\n  'use strict'\n\n  var entries = [],\n      logger = function(lifecycle) {\n        var ent"
  },
  {
    "path": "test/introspection.js",
    "chars": 6806,
    "preview": "import test         from 'ava';\nimport StateMachine from '../src/app';\n\n//----------------------------------------------"
  },
  {
    "path": "test/issues.js",
    "chars": 2767,
    "preview": "import test            from 'ava'\nimport StateMachine    from '../src/app'\nimport LifecycleLogger from './helpers/lifecy"
  },
  {
    "path": "test/lifecycle.js",
    "chars": 47057,
    "preview": "import test            from 'ava'\nimport StateMachine    from '../src/app'\nimport LifecycleLogger from './helpers/lifecy"
  },
  {
    "path": "test/observers.js",
    "chars": 8580,
    "preview": "import test            from 'ava'\nimport StateMachine    from '../src/app'\nimport LifecycleLogger from './helpers/lifecy"
  },
  {
    "path": "test/plugin/history.js",
    "chars": 15500,
    "preview": "import test                from 'ava'\nimport StateMachine        from '../../src/app'\nimport StateMachineHistory from '."
  },
  {
    "path": "test/plugin/visualize.js",
    "chars": 14099,
    "preview": "import test         from 'ava'\nimport StateMachine from '../../src/app'\nimport visualize    from '../../src/plugin/visua"
  },
  {
    "path": "test/plugins.js",
    "chars": 3966,
    "preview": "import test            from 'ava'\nimport StateMachine    from '../src/app'\nimport LifecycleLogger from './helpers/lifecy"
  },
  {
    "path": "test/transitions.js",
    "chars": 12027,
    "preview": "import test            from 'ava'\nimport StateMachine    from '../src/app'\nimport LifecycleLogger from './helpers/lifecy"
  },
  {
    "path": "test/util/camelize.js",
    "chars": 672,
    "preview": "import test     from 'ava';\nimport camelize from '../../src/util/camelize';\n\ntest('camelize', t => {\n  t.is(camelize(\"\")"
  },
  {
    "path": "test/util/mixin.js",
    "chars": 1935,
    "preview": "import test from 'ava';\nimport mixin from '../../src/util/mixin';\n\n//---------------------------------------------------"
  },
  {
    "path": "test/wildcards.js",
    "chars": 6894,
    "preview": "import test         from 'ava'\nimport StateMachine from '../src/app'\n\n//------------------------------------------------"
  },
  {
    "path": "webpack.config.js",
    "chars": 944,
    "preview": "module.exports = function(env) {\n\n  'use strict'\n\n  const webpack   = require('webpack'),\n        glob      = require('g"
  }
]

About this extraction

This page contains the full source code of the jakesgordon/javascript-state-machine GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 67 files (302.3 KB), approximately 79.8k tokens, and a symbol index with 50 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!