const getNextState = require('./getNextState')
const { getFirstState, validateStateMachine } = require('./_internal')
/**
* Creates a state machine - which is managed inside a closure - that will only transition if name of the transition you pass into it is among those registered for the current state.
* You pass in a state machine Object and you'll have a function returned to you that can repeatedly receive a transition name and always return to you the next state (if it even changes at all).
*
* The current state will always match one of the keys in the state machine object.
* Associated with those keys are each Objects that hold the possible transitions that can move that state to another one.
*
* So each sub-object in the state machine holds transition names as keys and _states-to-transition-to_ as values.
* The curent state can only change if a given transition's name is among those registered for the current state.
* Only registered transition names can produce changes to the current state.
*
* If you use this with Redux then you'll probably want to make the transition names match the names of Redux Action Types.
* That way your state machine can be used in a reducer (or a piece of Redux middleware) to alter a single prop on your Redux store (representing that machine's current state).
*
* @function
* @name createMachine
* @sig {k: v} -> String -> (String -> String)
* @throws {TypeError} If the state machine is missing or not an object
* @throws {TypeError} If the state machine has any values which are not objects
* @throws {TypeError} If the state machine's "sub-objects" has values that are not keys on the root of the object
* @throws {Error} If the initialState is not one of the state values on the state machine
* @param {Object<string, Object<string, string>>} stateMachine An Object of (only) Objects. Inside each of those Objects all values are Strings which correspond to keys on the outer Object.
* @param {string} initialState The state at which the machine should be initialized (will default to the first key name in the state machine)
* @returns {function} A getNextState() function that is ready to receive your transition name and return the next state (which may or not be different).
* @example
* const authMachine = {
* initial: {
* ATTEMPT_LOGIN: 'inProgress'
* },
* inProgress: {
* LOGIN_ERROR: 'error',
* LOGOUT_ERROR: 'error',
* LOGIN_SUCCESSFUL: 'loggedIn',
* LOGOUT_SUCCESSFUL: 'loggedOut'
* },
* loggedIn: {
* ATTEMPT_LOGOUT: 'inProgress'
* },
* loggedOut: {
* ATTEMPT_LOGIN: 'inProgress'
* },
* error: {
* ATTEMPT_LOGIN: 'inProgress',
* CLEAR_ERROR: 'loggedOut'
* }
* }
*
* const getNextAuthState = createMachine(authMachine, 'initial')
*
* getNextAuthState('LOGIN_SUCCESSFUL')
* // initial - Can't transition there yet
* getNextAuthState('CLEAR_ERROR')
* // initial - Also doesn't affect state
* getNextAuthState('ATTEMPT_LOGIN')
* // inProgress - Now we have a login attempt, which changes state
* getNextAuthState('LOGIN_SUCCESSFUL')
* // loggedIn - Advances from that pending state to logged-in
* getNextAuthState('ATTEMPT_LOGOUT')
* // inProgress - Attempting to log out
* getNextAuthState('LOGOUT_ERROR')
* // error - Now we're in an error state
* getNextAuthState('CLEAR_ERROR')
* // loggedOut - Finishes logout process
*/
function createMachine(stateMachine, initialState) {
validateStateMachine(stateMachine)
let currentState = getFirstState(stateMachine, initialState)
/**
* A simple function that retrieves the current state or optionally advances to the next state determined by a given transition value
*
* @function
* @name nextState
* @sig String -> String
* @param {string} [transition] One of the transition names registered for the current state
* @returns {string} The current state (one of the root-level keys from the state machine
*/
return function nextState(transition) {
currentState = getNextState(currentState, transition, stateMachine)
return currentState
}
}
module.exports = createMachine