Skip to content

Instantly share code, notes, and snippets.

@Willmo36
Last active November 26, 2020 17:24
Show Gist options
  • Save Willmo36/5793ae4acc5aa8d4375f1ea70bb2c0a4 to your computer and use it in GitHub Desktop.
Save Willmo36/5793ae4acc5aa8d4375f1ea70bb2c0a4 to your computer and use it in GitHub Desktop.
TypeScript State Machines
/**
* Impl
*/
type TransitionHandler<
From extends Union,
States extends From,
Event extends Union
> = (from: From, event: Event) => States;
type Transition<
From extends Union,
States extends From,
Events extends Union
> = Events extends any
? Record<Events["type"], TransitionHandler<From, States, Events>>
: never;
type Union = { type: string };
type Transitions<
States extends Union,
States2 extends States,
Events extends Union
> = States extends any
? Record<States["type"], Transition<States, States2, Events>>
: never;
function makeStateMachine<State extends Union, Event extends Union>(
transitions: Transitions<State, State, Event>,
onDisallowedTransition: (current: State, event: Event) => void
): (s: State, e: Event) => State {
return (current, event) => {
const h = transitions[current.type]?.[event.type];
if (!h) {
onDisallowedTransition(current, event);
return current;
} else {
return h(current, event);
}
};
}
/**
* Usage
*/
type MyState = { type: "green" } | { type: "orange" } | { type: "red" };
type MyAction = { type: "change"} | { type: "reset" };
const green: MyState = {type: "green"};
const orange: MyState = {type: "orange"};
const red: MyState = {type: "red"};
/**
* `transition` is pretty much just a reducer
* which whitelists which actions can be run against the curent state
*/
const transition = makeStateMachine<MyState, MyAction>(
//this is our definition of whats allowed
{
//"When we are in the green state, use these handlers"
green: {
//The action name: reducer-like handler
//Hover over the parameters, notice they're the exact types
change: (_currentState, _action) => orange,
//no reset handler for green, disallow it!
},
orange: {
change: () => red,
reset: () => green
},
red: {
change: () => green,
reset: () => green
}
},
(current, event) => {
console.warn(`${current.type} is not permitted to handle ${event.type}`);
return current;
}
);
console.info("green.change", transition({type: "green"}, {type: "change"}))
console.info("green.reset", transition({type: "green"}, {type: "reset"}))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment