@mjstahl/stated v6.2.0
Stated
Simply put state management for your JavaScript application. Pushdown automata (FSM with history) in less than 1KB.
Installation
$ npm install --save @mjstahl/stated// as a factory function
const stated = require('@mjstahl/stated')
// as a class expression
const { Stated } = require('@mjstahl/stated')API
stated(states: Object[, persistent: Boolean]) -> Stated
new Stated(states: Object[, persistent: Boolean]) -> Stated
To create an instance of Stated pass a 'states' object. A valid states object
must have an, at least, a single state. initial states can be set after
instantiation by assign a valid state to the initial property.
By default each Stated object is not persistent. If you wish to store each state change pass 'true' as the second argument to the constructor.
const h20 = stated({
initial: 'water',
water: {
// actions must reference existing states
FROZEN: 'ice',
BOILED: 'steam',
// Number, Array, Function, Object, etc
value: '60F'
},
ice: {
BOILED: 'steam',
WARMED: 'water',
value: '32F'
},
steam: {
COOLED: 'water',
FROZEN: 'ice',
value: {
temp: '212F'
}
}
})
h20.state //-> 'water'
h20.value //-> '60F'<stated>.to(action: String[, updateValue: Any]) -> Stated
Transition from the current state to a new state. If called with a second
argument. The value of the new state will be updated with the value. If value
is an Object, the current value and updateValue will be merged. If the
updateValue is not an Object, value will be replaced with updateValue.
h20.to(h20.actions.FROZEN)
h20.state //-> 'ice'
h20.value //-> '32F'
h20.to(h20.actions.BOILED, { state: 'gas' })
h20.value //-> { state: 'gas', temp: '212F' }<stated>.reset() -> Stated
Set the Stated object's state to initial state.
h20.reset()
h20.state //-> 'water'
h20.value //-> '60F'<stated>.actions -> Object
Return an object with actions as properties and associated values. Provided to avoid typos when traversing states. For example:
h20.to(h20.actions.BOILED)
h20.actions
//-> { 'FROZEN': 'FROZEN', 'COOLED': 'COOLED' }actions only includes actions related to the current state. So
attempting to call to with an invalid action will cause a runtime error
as that action does not exist on <stated>.actions.
For a state that does not have any actions all the states of the Stated
object will available from actions.
<stated>.state -> String
Return the name of the Stated's current state.
h20.state
//-> 'steam'<stated>.value -> Any
Returns the value of the current state if one exists; returns undefined
if not.
h20.to(h20.actions.WARMED)
h20.value
//-> '60F'<stated>.via -> Object
Return an object with actions as properties and the state they point to as values.
h20.to(h20.actions.FROZEN)
h20.via
//-> { BOILED: 'steam', WARMED: 'water' }Guards
Each state may have a canEnter and canLeave function that is executed
when attempting to leave one state and enter another. Each function takes
one argument, the Stated object, for use during its execution.
When a transition is occurring (via the on method), if there is a canLeave function on the current state, it is executed. If the canLeave function returns true and the future state has a canEnter function, the canEnter function is
executed. If the canEnter function of the future state returns true, the Stated object is transitioned successfully.
If either the canLeave or canEnter functions exist and return false, the transition does not occur.
.canLeave(self: Stated) -> Boolean
.canEnter(self: Stated) -> Boolean
Transition Functions
Each state can have an onLeave and onEnter function that is executed when
leaving one state, and having entered another. This are useful for performing
side-effects.
.onLeave(self: Stated) -> Void
Executed when a transition is to occur but before the current state has been replaced with the next state.
const h20 = stated({
initial: 'water',
water: {
FROZEN: 'ice',
value: '60F'
onLeave: () => console.log('Winter is Coming!!!')
},
ice: {
WARMED: 'water',
value: '32F'
},
})
h20.to(h20.actions.FROZEN)
//-> 'Winter is Coming!!!'.onEnter(self: Stated) -> Void
Executed after the state and values have been updated, history has recorded (if
persistent), and 'transition' event has been emitted.
const h20 = stated({
initial: 'water',
water: {
FROZEN: 'ice',
value: '60F'
onLeave: () => console.log('Winter is Coming!!!')
},
ice: {
WARMED: 'water',
value: '32F'
onEnter: () => console.log('Winter is Here!!!')
},
})
h20.to(h20.actions.FROZEN)
//-> 'Winter is Coming!!!'
//-> 'Winter is Here!!!'Events
Each stated object is also an EventEmitter. When the Stated object transitions
from one state to another all callbacks passed to the onTransition function are evaluated with the Stated object passed the only argument to the callback. onTransition returns a function that unsubscribes when executed.
<stated>.onTransition(callback: Function) -> unsubscribe: Function
const unbind = h20.onTransition(({ state, value }) => {
console.log(state) //-> 'water'
console.log(value) //-> '60F'
})
h20.reset()
// when you are finished listening
unbind()Persistence
Each Stated object is can be persistent and store each state change,
allowing the user to undo and redo states. Use can turn persistence
on and off with the persistent property. You can make Stated object
persistent by default by passing true as the second argument to the
constructor.
<stated>.persistent -> Boolean
new Stated({... states, true})
Toggle whether the Stated object will store each state change.
h20.persistant //-> false, Stated objects are not persistent by default
// To turn on persistence, simply set 'persistent' to 'true'
h20.persistent = true<stated>.undo() -> Stated
Return to a previous state. If the current state is the initial state and
undo is called, the Stated object will be returned without performing a
state transition (in the initial state).
h20.to(h20.actions.FROZEN)
h20.state //-> 'ice'
h20.undo()
h20.state //-> 'water'<stated>.redo() -> Stated
Re-apply a later state. If the current state is the top of the stack of states
and redo is called, the Stated object will be returned without performing a
state transition (the current state will remain equal to the state at the
top of the stack).
h20.to(h20.actions.FROZEN)
h20.state //-> 'ice'
h20.undo()
h20.state //-> 'water'
h20.redo()
h20.state //-> 'ice'7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago