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