A fluent JavaScript State Machine with full TypeScript support
Install package
npm i @2toad/fluent-state
import { fluentState } from '@2toad/fluent-state';
// or
const { fluentState } = require('@2toad/fluent-state');
fluentState
.from('vegetable').to('diced').or('pickled')
.from('diced').to('salad').or('trash');
fluentState
.when('diced').do(() => console.log('diced'));
fluentState.transition('diced');
// or
fluentState.next();
The current state
Adds a state
// Add the 'vegetable' state
fluentState.from('vegetable');
Adds a transition to a state
fluentState
.from('vegetable') // Add the 'vegetable' state
.to('diced'); // add the 'diced' state, with a transtion from 'vegetable'
Adds a transition to a state
fluentState
.from('vegetable') // Add the 'vegetable' state
.to('diced') // add the 'diced' state, with a transtion from 'vegetable'
.or('pickled') // add the 'pickled' state, with a transtion from 'vegetable'
.or('discarded'); // add the 'discarded' state, with a transtion from 'vegetable'
Explicitly set the state without triggering a transition
fluentState.setState('diced');
NOTE: the state is initially set to the first state you add via
from()
, and it is implicitly set when you transition to a new state viatransition()
ornext()
Returns true if the state exists
fluentState.has('vegetable');
Removes a state (and all of its transitions)
fluentState.remove('vegetable');
Removes all states
fluentState.clear();
- Transitions to another state.
- If multiple states are specified, a state is chosen at random.
- Returns
true
upon success.
// Transition to the 'diced' state
fluentState.transition('diced');
// Transition to the 'diced' or 'discarded' state (selected at random)
fluentState.transition('diced', 'discarded');
- If the current state contains a single transition, that state is transitioned to.
- If the current state contains multiple transitions, a transition is selected at random.
- With the option to exclude specified states from the random selection.
- Returns
true
upon success.
fluentState.next();
// A random state, excluding 'pickled' and 'discarded'
fluentState.next('pickled', 'discarded');
You can add callbacks to any state
Specifies the state you want to add a callback to
fluentState.when('diced');
Adds a callback
fluentState
.when('diced')
.do((previousState, fluentState) => {
console.log(`Transitioned from "${previousState.name}"`);
});
Adds another callback
fluentState
.when('diced')
.do(() => console.log('Transitioned to "diced"'))
.and((previousState, fluentState) => {
console.log(`Transitioned from "${previousState.name}"`);
});
And of course it's all chainable
fluentState
.when('diced').do(() => console.log('Transitioned to "diced"'))
.when('pickled').do(() => console.log('Transitioned to "pickled"'));
You can hook into the state machine lifecycle via the observe
method.
fluentState.observe(Lifecycle.BeforeTransition, (currentState, newState) => {
// You can prevent the transition by returning false from this event
return false;
});
// Chainable
fluentState
.observe(Lifecycle.FailedTransition, () => console.log('Transition failed'))
.observe(Lifecycle.FailedTransition, () => console.log('Multiple hooks allowed on each event'))
.observe(Lifecycle.AfterTransition, () => console.log('Transition complete'));
Order: BeforeTransition -> FailedTransition -> AfterTransition
-
BeforeTransition
(currentState: State, nextState: string): boolean
-
FailedTransition
(currentState: State, targetState: string): void
-
AfterTransition
(previousState: State, currentState: State): void
Fluent State is a non-hierarchial state machine. For more information on its architecture and how it operates, please refer to the State Machine documentation.
So you want to contribute to the Fluent State project? Fantastic! Please read the Contribute doc to get started.