node-state-machine
v0.1.4
Published
State machine for NodeJS.
Downloads
5
Maintainers
Readme
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