Created
May 8, 2022 17:57
-
-
Save jtmthf/6aad7638fb7069ea6a27e09dcfcc6f96 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
interface State<Value extends string, Context> { | |
value: Value; | |
context: Context; | |
} | |
const ASSIGN_TYPE = Symbol('Assign'); | |
type AssignAction<Context> = { | |
type: typeof ASSIGN_TYPE; | |
exec(context: Context): Context; | |
} | |
function assign<Context>(exec: AssignAction<Context>['exec']): AssignAction<Context> { | |
return { | |
type: ASSIGN_TYPE, | |
exec | |
} | |
} | |
type MaybeArray<T> = T | T[]; | |
function asArray<T>(value: MaybeArray<T> | undefined): T[] { | |
return Array.isArray(value) ? value : value === undefined ? [] : [value]; | |
} | |
type Action<Context> = ((context: Context) => void) | AssignAction<Context>; | |
interface Machine<StateValue extends string, Event extends string, Context> { | |
initialState: State<StateValue, Context>; | |
transition(currentState: State<StateValue, Context>, event: Event): State<StateValue, Context>; | |
} | |
interface MachineDefinition<StateValue extends string, Event extends string, Context> { | |
initialState: StateValue; | |
context: Context | |
states: Record<StateValue, { | |
entry?: MaybeArray<Action<Context>>; | |
exit?: MaybeArray<Action<Context>>; | |
on?: Record<Event, { | |
target?: StateValue; | |
actions?: MaybeArray<Action<Context>>; | |
}>; | |
}>; | |
} | |
function createMachine<StateValue extends string, Event extends string, Context>(definition: MachineDefinition<StateValue, Event, Context>): Machine<StateValue, Event, Context> { | |
return { | |
initialState: { | |
value: definition.initialState, | |
context: definition.context, | |
}, | |
transition(currentState, event) { | |
const currentStateDefinition = definition.states[currentState.value]; | |
const destinationTransition = currentStateDefinition?.on?.[event]; | |
if (!destinationTransition) { | |
return currentState; | |
} | |
const destinationState = destinationTransition.target ?? currentState.value; | |
const destinationStateDefinition = definition.states[destinationState]; | |
let context = currentState.context; | |
function execActions(actions: MaybeArray<Action<Context>> | undefined) { | |
asArray(actions).forEach(action => { | |
if (typeof action === 'function') { | |
action(context); | |
} else if (action.type === ASSIGN_TYPE) { | |
context = action.exec(context); | |
} | |
}); | |
} | |
execActions(destinationTransition.actions); | |
execActions(currentStateDefinition.exit); | |
execActions(destinationStateDefinition.entry); | |
return { | |
value: destinationState, | |
context, | |
}; | |
} | |
} | |
} | |
const machine = createMachine({ | |
initialState: 'counting', | |
context: 0, | |
states: { | |
counting: { | |
on: { | |
INC: { | |
actions: [ | |
assign<number>((context) => context + 1), | |
((context) => console.log('count:', context)) | |
], | |
}, | |
DEC: { | |
actions: [ | |
assign<number>((context) => context - 1), | |
((context) => console.log('count:', context)) | |
] | |
} | |
} | |
}, | |
} | |
}); | |
let state = machine.initialState; | |
state = machine.transition(state, 'INC'); | |
state = machine.transition(state, 'INC'); | |
state = machine.transition(state, 'DEC'); | |
state = machine.transition(state, 'DEC'); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment