UNPKG

node-state-machine

Version:
252 lines (197 loc) 7.49 kB
<p align="center"> <img src="https://raw.githubusercontent.com/Jense5/node-state-machine/master/assets/title.png" /> </p> <p align="center"> <img src="https://img.shields.io/npm/v/node-state-machine.svg"> <img src="https://travis-ci.org/Jense5/node-state-machine.svg?branch=master"> <img src="https://img.shields.io/npm/dt/node-state-machine.svg"> <img src="https://img.shields.io/npm/l/node-state-machine.svg"> </p> 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 ```js 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 ```js 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 ```js // 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 ```js // 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 ```js // 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 ```js // 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 ```js // 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 ``` ```js // 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