UNPKG

typescript-fsm

Version:

finite state machine with async callbacks

122 lines (91 loc) 4.92 kB
# TypeScript State Machine (typescript-fsm) [![Build Status](https://app.travis-ci.com/eram/typescript-fsm.svg?branch=master)](https://app.travis-ci.com/github/eram/typescript-fsm) [![npm package](https://img.shields.io/npm/v/typescript-fsm.svg?logo=nodedotjs&color=00a400)](https://www.npmjs.com/package/typescript-fsm) <img src="https://img.shields.io/badge/winter-is%20coming-5593c8" alt="It's already here!" height="20"/> Finite state machines are useful for modeling complicated flows and keeping track of state. TS-FSM is a strongly typed finite state machine for TypeScript that is using promises for async operations. I'm using this state-machine as a simple replacement for Redux in some ReactJs based apps. Example [here](https://github.com/eram/tensorflow-stack-ts/blob/master/client/src/components/server-status-card/StatusCardModel.ts) ## Features - TypeScript native (compiles to ES6) - Only 1 KB (minified) and zero dependencies!!! - Hooks after state change - - async or sync callbacks - Promises are used for async transition completion - Generics for states and events types - Simple tabular state machine definition - Use with NodeJS or JS client ## Get it ```script git clone https://github.com/eram/typescript-fsm.git cd typescript-fsm npm install npm test ``` ## Use it ```script npm install typescript-fsm ``` ## Basic Example I'm modeling a "door" here. One can open the door, close it or break it. Each action is done async: when you open it goes into opening state and then resolved to opened state etc. Once broken, it reaches a final state. <img src="https://mermaid.ink/svg/c3RhdGVEaWFncmFtLXYyCiAgICBbKl0gLS0+IENsb3NlZAogICAgQ2xvc2VkIC0tPiBPcGVuaW5nIDogT3BlbgogICAgT3BlbmluZyAtLT4gT3BlbmVkIDogb3BlbkNvbXBsZXRlCiAgICBPcGVuZWQgLS0+IENsb3NpbmcgOiBDbG9zZQogICAgQ2xvc2luZyAtLT4gQ2xvc2VkIDogY2xvc2VDb21wbGV0ZQogICAgQ2xvc2VkIC0tPiBCcm9rZW4gOiBCcmVhawogICAgT3BlbmluZyAtLT4gQnJva2VuIDogQnJlYWsKICAgIE9wZW5lZCAtLT4gQnJva2VuIDogQnJlYWsKICAgIENsb3NpbmcgLS0+IEJyb2tlbiA6IEJyZWFrCiAgICBCcm9rZW4gLS0+IFsqXQo=?bgColor=!white" alt="Door state machine" height="500" /> Let's code it in Typescript! Note that the same code can be run in Javascript, just remove the generics. ```typescript import { t, StateMachine } from "typescript-fsm"; // these are the states and events for the door enum States { closing = 0, closed, opening, opened, broken }; enum Events { open = 100, openComplete, close, closeComplete, break }; // lets define the transitions that will govern the state-machine const transitions = [ /* fromState event toState callback */ t(States.closed, Events.open, States.opening, onOpen), t(States.opening, Events.openComplete, States.opened, justLog), t(States.opened, Events.close, States.closing, onClose), t(States.closing, Events.closeComplete, States.closed, justLog), t(States.closed, Events.break, States.broken, justLog), t(States.opened, Events.break, States.broken, justLog), t(States.opening, Events.break, States.broken, justLog), t(States.closing, Events.break, States.broken, justLog), ]; // initialize the state machine const door = new StateMachine<States, Events>( States.closed, // initial state transitions, // array of transitions ); // transition callbacks - async functions async function onOpen() { console.log("onOpen..."); return door.dispatch(Events.openComplete); } async function onClose() { console.log("onClose..."); return door.dispatch(Events.closeComplete); } // synchronous callbacks are also ok function justLog() { console.log(`${States[door.getState()]}`); } // we are ready for action - run a few state-machine steps... new Promise(async (resolve) => { // open the door and wait for it to be opened await door.dispatch(Events.open); door.getState(); // => States.opened // check if the door can be closed door.can(Events.close); // => true // break the door async door.dispatch(Events.break).then(() => { // did we get to a finite state? door.isFinal(); // => true }); // door is now broken. It cannot be closed... try { await door.dispatch(Events.close); assert("should not get here!"); } catch (e) { // we're good } // let the async complete setTimeout(resolve, 10); }); ``` ## Another example Check out [the test code](https://github.com/eram/typescript-fsm/blob/master/src/__test__/stateMachine.test.ts) - a class that implements a state machine with method binding, method params and more transitions. 100% coverage here! ## Beautiful :-) Comments and suggestions are [welcome](https://github.com/eram/typescript-fsm/issues/new).