node-state-machine

0.1.4 • Public • Published

Powerful finite state machine module that is very easy to use. From basic finite state machines to acceptance, locks and middleware. Install the module with npm.

$ npm install --save node-state-machine

Basics

import { Machine } from 'node-state-machine';
 
// We will start with a basic example. Lets create a machine
// with three states: 'A', 'B' and 'C'. The first state will
// always be the initial state. The machine will take care of
// duplicate states itself. So new Machine('A', 'B', 'C', 'B')
// only has three states.
let machine = new Machine('A','B','C');
 
// Now lets make some simple transitions. For example, this first
// one says "if we process 'b' and are in state 'A', go to state
// 'B'". The last one uses the '*' wildcard. It is called when
// there is no other transition found.
machine.from('A').to('B').on('b');
machine.from('B').to('C').on('c');
machine.from('C').to('C').on('a');
machine.from('C').to('A').on('*');
 
// Finalize the machine. From now on we can't add transitions
// anymore and we can start using the machine!
machine.finalize();
 
machine.current(); // 'A'
machine.process('b'); // machine.current() == 'B';
machine.process('c'); // machine.current() == 'C';
machine.process('d'); // machine.current() == 'A';
machine.current(); // 'A'

Transitions

import { Machine } from 'node-state-machine';
 
// We will create the exact same machine as the example before.
let machine = new Machine('A', 'B', 'C');
 
// With the same transitions...
machine.from('A').to('B').on('b');
machine.from('B').to('C').on('c');
 
// However, we can define more complex triggers for certain
// transitions if needed. Note that we define the same trigger
// here as the .on('a') like in the example above! However, we can
// write whatever we want in this lambda function.
machine.from('C').to('C').when(v => v == a);
 
// We can use .default() instead of .on('*'). Use whichever you like!
machine.from('C').to('A').default();
 
// Finalize the machine. From now on we can't add transitions
// and can start using the machine!
machine.finalize();
 
machine.current(); // 'A'
machine.process('b'); // machine.current() == 'B';
machine.process('c'); // machine.current() == 'C';
machine.process('d'); // machine.current() == 'A';
machine.current(); // 'A'

Callbacks

// Again, we will use the same machine.
import { Machine } from 'node-state-machine';
let machine = new Machine('A', 'B', 'C');
machine.from('A').to('B').on('b');
machine.from('B').to('C').on('c');
machine.from('C').to('C').on('a');
machine.from('C').to('A').on('*');
machine.finalize();
 
// But now, we use the callback function of the machine!
// Note that we can define (or change) it after the machine
// is finalized! This is because it doesn't change anything
// about the internal state of the machine.
machine.onChange((oldState, newState, trigger) => {
  console.log(`Went from ${oldState} to ${newState} via ${trigger}!`);
});
 
// We can also chain the process functions.
machine.process('b').process('c').process('d');
 
// The logs are now as follows:
// Went from A to B via b!
// Went from B to C via c!
// Went from C to A via d!

Acceptance

// Again, we will use the same machine.
import { Machine } from 'node-state-machine';
let machine = new Machine('A', 'B', 'C');
machine.from('A').to('B').on('b');
machine.from('B').to('C').on('c');
machine.from('C').to('C').on('a');
machine.from('C').to('A').on('*');
 
// But now we define accepting states! These are states
// that define whether or not the input is accepted.
machine.accepts('A', 'C');
 
machine.finalize();
machine.process('b').process('c').process('d');
 
// Now accept is true because the current state belongs
// to one of the accepting states!
let accept = machine.accepted();

Locks & Async

// Same code as above...
machine.finalize();
 
// When the machine is locked, it won't change its internal
// state! This can be used when waiting for async functions.
// After these transitions, the current state is 'A'.
machine.process('b');
machine.process('c');
machine.lock();
machine.process('d');
machine.unlock();
machine.process('b')

Middlewares

// Again, we will use the same machine.
import { Machine } from 'node-state-machine';
let machine = new Machine('A', 'B', 'C');
 
// The use of middlewares is very useful in some cases. A middleware
// is a function that handles the trigger of a transition. For
// example, every time we go from 'B' to 'C', we would like to add
// the trigger value to a global value.
let output = '';
let printMiddleware = (v) => { output += v; }
 
// We create the same machine as above, and append the middleware to
// the transition from 'B' to 'C'.
machine.from('A').to('B').on('b');
machine.from('B').to('C').middleware(printMiddleware).on('c');
machine.from('C').to('C').on('a');
machine.from('C').to('A').on('*');
 
machine.finalize();
machine.process('b');
machine.process('c');
machine.process('d');
machine.process('b');
machine.process('c');
 
// Now output == 'cc' !
// Note: You can also use multiple middlewares: .middlewares(m1, m2, m3).on('a');

Extra

// A little script that shows some extra functions that you can use.
 
// Process all the values at once. Returns true or false on whether or not the
// machine accepts the input. This function does the same as this code snippet:
// machine.process('a').process('b').process('c');
// let accept = machine.accepted();
let accept = machine.bulkProcess(['a', 'b', 'c']);
 
// Sets all default transitions to the same state. ex A > A.
machine.setDefaults();
 
// Check if the created machine is deterministic for the given alphabet.
machine.isDeterministic(['a', 'b']);
 
// Checking the finalized state.
machine.finalized(); // false;
machine.finalize();
machine.finalized(); // true;
 
// Checking the locked state.
machine.lock();
machine.locked(); // true;
machine.unlock();
machine.locked(); // false;
 
// Returns the current state of the machine.
machine.current();
// Returns the initial state of the machine.
machine.initial();
// Returns the number of states of the machine.
machine.numberOfStates();
 
// Resets the machine to its initial state.
machine.reset();

Development

Feel free to contribute some cool features. The following ideas will also be implemented in the future, but feel free to do it yourself if you want to. I will also add eslint and add all the development processes to gulp. To contribute, clone the repo and run npm install. Make sure gulp is installed globally.

$ npm install         # Install dependencies
$ npm install -g gulp # Make sure you have gulp
$ gulp                # This will run flow & babel
$ npm test            # To run the tests

// Future Code Features
 
// Return a new machine which is the minimal DFA of this machine. This
// depends on isDeterministic(alphabet: Array<string>).
machine.toMinimal(alphabet: Array<string>);
 
// Build a machine based on a regex.
machine.fromRegex(regex: string);

License

MIT - Jensen Bernard

Dependencies (0)

    Dev Dependencies (11)

    Package Sidebar

    Install

    npm i node-state-machine

    Weekly Downloads

    0

    Version

    0.1.4

    License

    MIT

    Last publish

    Collaborators

    • jense5