fn-machine
Advanced tools
Comparing version 0.0.10 to 0.0.11
{ | ||
"name": "fn-machine", | ||
"version": "0.0.10", | ||
"version": "0.0.11", | ||
"description": "a tiny functional state machine", | ||
"main": "index", | ||
"module":"index", | ||
"directories": { | ||
@@ -7,0 +8,0 @@ "example": "example" |
@@ -1,8 +0,74 @@ | ||
### fn-machine | ||
# fn-machine | ||
A tiny, functional, state machine utility | ||
#### install | ||
`npm install fn-machine` | ||
### install | ||
`npm install --save fn-machine` | ||
#### usage | ||
see [example](https://github.com/jrobinson01/fn-machine/blob/master/example/index.html) | ||
### usage | ||
fn-machine consists of 3 functions. The first two are used to define a machine: | ||
`machine([State], 'initialState', initialContextObj, stateChangeCallback, loggerFn)` | ||
`state('name', transitionsObj, enterFunction, exitFunction)` | ||
The third function is what would traditionally be called a `send()` function. This function is returned whenever `machine(...)` is called. | ||
#### Setting up a machine | ||
```javascript | ||
// import the setup functions | ||
import {machine, state} from 'fn-machine'; | ||
// initial context object | ||
const initialContext = { | ||
loading: false, | ||
users: [] | ||
} | ||
function loadUsers() { | ||
// simulate a network request | ||
setTimeout(() => { | ||
// once the request completes, we can call `myMachine` (the 'send' function). | ||
myMachine('loaded', {users:['foo', 'bar']}) | ||
}, 1000); | ||
} | ||
// initialize a machine | ||
const myMachine = machine([ | ||
state('initial', { | ||
// each method on this object represents a transition for this particular state. | ||
loadData: (detail, context) => { | ||
// a transition method should return the new state, as well as the optional context. | ||
// here we return {state:'loadingData'} to signify we want the state to now be 'loadingData', and | ||
// that the context.loading property should be true. | ||
return { | ||
state:'loadingData', | ||
context: {...context, ...{loading: true}} | ||
} | ||
} | ||
}), | ||
state('loadingData', { | ||
loaded: (detail, context) => { | ||
return { | ||
state: 'loadedData', | ||
context: {...context, ...detail, ...{loading: false}} | ||
} | ||
} | ||
}, context => {// call loadUsers when this state is entered | ||
loadUsers(); | ||
}), | ||
state('loadedData', {}) // 'loaded' is an empty state. There are no transitions. | ||
], 'initial', initialContext, newState => { | ||
console.log('myMachine state changed:', newState.state, newState.context); | ||
}, console.log);// pass an optional logger function | ||
``` | ||
As you can see in the `loadUsers()` function above, we invoke the third function provided by fn-machine, which is the send function. The send function takes a string as the first parameter, which is the name of a transition we'd like to invoke, and optionally a `detail` object, which might contain some data we want the machine to work with. | ||
#### More examples | ||
There is an [example](https://github.com/jrobinson01/fn-machine/blob/master/example/index.html) in this repo, or you can play around with this [codepen](https://codepen.io/johnrobinson/pen/rNBPodV?editors=1001) that shows a basic integration with [LitElement](https://github.com/Polymer/lit-element). | ||
#### Contributing | ||
Yes! PR's are welcome. Tests are written in mocha. Run with `npm run test` or `yarn test`. Typechecking is provided by typescript via JSDoc annotations. |
@@ -10,5 +10,6 @@ /** @typedef {import('./fn-state').CurrentState} CurrentState */ | ||
* @param {function(CurrentState)=} changeCb | ||
* @param {function(any)=} loggerFn | ||
* @return {function(string, Object=):CurrentState?} | ||
*/ | ||
export default function machine(states, initialState, initialContext, changeCb = function(state){}) { | ||
export default function machine(states, initialState, initialContext, changeCb = function(){}, loggerFn = function(){}) { | ||
// store current state (name) and context | ||
@@ -20,4 +21,6 @@ let current = initialState; | ||
return function send(event, detail) { | ||
loggerFn(`sent '${event}'`); | ||
// if no event, return the current state | ||
if (!event) { | ||
loggerFn(`no event. returning currentState`); | ||
return currentState; | ||
@@ -35,5 +38,10 @@ } | ||
if (transition) { | ||
const newState = states.find(s => s.name === next.state); | ||
if (!newState) { | ||
// throw if next state is undefined | ||
// throw new Error(`the transition '${event}' of current state '${current}', returned a non-existant desired state '${next.state}'.`); | ||
throw `the transition '${event}' of current state '${current}', returned a non-existant desired state '${next.state}'.`; | ||
} | ||
// if the current state has an exit function, run it. | ||
active.exit && active.exit(); | ||
const newState = states.find(s => s.name === next.state); | ||
// if the new state has an enter function, run it as well. | ||
@@ -46,3 +54,6 @@ newState.enter && newState.enter(next.context || context); | ||
// call callback with the latest state. | ||
loggerFn(`state changed to '${next.state}'`); | ||
changeCb(next); | ||
} else { | ||
loggerFn(`state '${current}' does not handle event '${event}'.`); | ||
} | ||
@@ -52,2 +63,3 @@ | ||
} | ||
loggerFn('could not find active state'); | ||
return currentState; | ||
@@ -54,0 +66,0 @@ |
9834
179
75