Skip to content

Instantly share code, notes, and snippets.

@jtmthf
Created May 8, 2022 17:57
Show Gist options
  • Save jtmthf/6aad7638fb7069ea6a27e09dcfcc6f96 to your computer and use it in GitHub Desktop.
Save jtmthf/6aad7638fb7069ea6a27e09dcfcc6f96 to your computer and use it in GitHub Desktop.
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