Skip to content

Instantly share code, notes, and snippets.

@cevr
Last active November 14, 2021 15:25
Show Gist options
  • Save cevr/8f984684fd24be94ec5c4d1c8a6f263c to your computer and use it in GitHub Desktop.
Save cevr/8f984684fd24be94ec5c4d1c8a6f263c to your computer and use it in GitHub Desktop.
xstate fp utils POC
import * as x from './xstate-fp';
export enum EditableTextState {
idle = 'idle',
editing = 'editing',
}
export enum EditableTextEvent {
mouseenter = 'mouseenter',
mouseleave = 'mouseleave',
blur = 'blur',
focus = 'focus',
edit = 'edit',
change = 'change',
save = 'save',
cancel = 'cancel',
}
interface EditableTextMachineContext {
initialValue: string;
value: string;
actionsAreVisible: boolean;
}
const resetValue = x.assign<EditableTextMachineContext>({
value: (context) => context.initialValue,
});
const showActions = x.assign<EditableTextMachineContext>({
actionsAreVisible: true,
});
const hideActions = x.assign<EditableTextMachineContext>({
actionsAreVisible: false,
});
const setValue = x.assign({ value: (_, event) => event.value });
export const EditableTextMachine = x.createMachine<EditableTextMachineContext>(
x.on(EditableTextEvent.mouseenter, showActions),
x.on(EditableTextEvent.mouseleave, hideActions),
x.states(
x.state(EditableTextState.idle, x.on(EditableTextEvent.edit, EditableTextState.editing)),
x.state(
EditableTextState.editing,
x.on(EditableTextEvent.focus, hideActions),
x.on(EditableTextEvent.blur, EditableTextState.idle, resetValue),
x.on(EditableTextEvent.change, setValue),
x.on(
EditableTextEvent.save,
EditableTextState.idle,
x.choose(
x.action('onSave'),
x.guard((context) => Boolean(context.value)),
resetValue,
),
),
x.on(EditableTextEvent.cancel, EditableTextState.idle, resetValue),
),
),
);
import * as yup from 'yup';
import * as x from './xstate-fp';
type FormElement = HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement;
type ValidationSchema = yup.ObjectSchema<yup.Shape<object | undefined, Record<string, any>>>;
interface FormMachineContext {
values: Record<string, any>;
errors: Record<string, string> & { submit?: string };
refs: Record<string, React.RefObject<FormElement>>;
validationSchema: ValidationSchema;
}
type FormMachineError = {
[inputName: string]: string;
};
enum FormMachineEventTypes {
BLUR = 'BLUR',
CHANGE = 'CHANGE',
SUBMIT = 'SUBMIT',
}
const hasErrors = x.guard((context) => Object.values(context.errors).some(Boolean));
const setValue = x.assign((context, event) => ({
values: {
...context.values,
[event.name]: event.value,
},
}));
const setErrors = x.assign({
errors: (context, event) => ({
...context.errors,
...event.data,
}),
});
const resetError = x.assign({
errors: (context, event) => {
// if the input doesnt have an error, no need to reset it
if (!context.errors[event.name]) return context.errors;
return {
...context.errors,
[event.name]: undefined,
};
},
});
const resetErrors = x.assign({ errors: {} });
const focusInput = x.effect((context, event): void => {
// this will look at the first input and focus it for some pleasant UX
const [firstKey] = Object.keys(event.data);
context.refs[firstKey as Extract<keyof typeof context.values, string>].current?.focus();
});
const setSubmitError = x.assign({
errors: (context, event: any) => ({
...context.errors,
submit: event.data.message,
}),
});
const onChangeActions = x.composeActions(resetError, setValue);
const createFormMachine = () =>
x.createMachine<FormMachineContext>(
x.states(
x.state(
'idle',
x.states(x.state('noError'), x.state('error'), x.state('submitError')),
x.on(
FormMachineEventTypes.CHANGE,
x.transition('idle.error', onChangeActions, hasErrors),
x.transition('idle.noError', onChangeActions),
),
x.on(FormMachineEventTypes.BLUR, 'validatingField'),
x.on(FormMachineEventTypes.SUBMIT, 'validating'),
),
x.state(
'validatingField',
x.invoke(
(context, event) => {
const field = event.name;
return new Promise((resolve, reject) =>
yup
.reach(context.validationSchema, field)
.validate(event.value)
.then(
() => resolve(),
(error: yup.ValidationError) => reject({ [field]: error.message }),
),
);
},
x.on('done', 'idle.noError'),
x.on('error', 'idle.error', setErrors),
),
),
x.state(
'validating',
x.entry(resetErrors),
x.invoke(
(context) =>
new Promise((resolve, reject) =>
// when validating the whole form, make sure we don't abort early to receive full list of errors
context.validationSchema
.validate(context.values, {
abortEarly: false,
})
.then(
() => resolve(),
(error: yup.ValidationError) => reject(yupToFormErrors(error)),
),
),
x.on('done', 'submitting'),
x.on('error', 'idle.error', setErrors, focusInput),
),
),
x.state(
'submitting',
x.invoke(
'onSubmit',
x.on('done', 'submitted', x.action('afterSubmit')),
x.on('error', 'idle.submitError', setSubmitError),
),
),
x.final('submitted'),
),
);
// yup errors are kind of weird
// create a simple object like so { [inputName]: inputError }
function yupToFormErrors(yupError: yup.ValidationError): FormMachineError {
if (yupError.inner) {
if (yupError.inner.length === 0) {
return { [yupError.path]: yupError.message };
}
return Object.fromEntries(yupError.inner.map((err) => [err.path, err.message]));
}
return {};
}
export { FormMachineEventTypes, FormElement, ValidationSchema, FormMachineContext };
export default createFormMachine;
import * as xstate from 'xstate';
type StateNodeConfig<
TContext = any,
TStateSchema extends xstate.StateSchema<any> = any,
TEvent extends xstate.EventObject = any
> = xstate.StateNodeConfig<TContext, TStateSchema['states'][keyof TStateSchema['states']], TEvent> & {
isInitial?: boolean;
};
type StateTuple<
TContext = any,
TStateSchema extends xstate.StateSchema<any> = any,
TEvent extends xstate.EventObject = any
> = [string, StateNodeConfig<TContext, TStateSchema, TEvent>];
type StatesTuple<
TContext = any,
TStateSchema extends xstate.StateSchema<any> = any,
TEvent extends xstate.EventObject = any
> = [
StateNodeConfigStatesTuple<TContext, TStateSchema, TEvent>,
(
| StateNodeConfigInitialTuple<TContext, TStateSchema, TEvent>
| StateNodeConfigTypeTuple<TContext, TStateSchema, TEvent>
),
];
type StateNodeConfigInitialTuple<
TContext = any,
TStateSchema extends xstate.StateSchema<any> = any,
TEvent extends xstate.EventObject = any
> = ['initial', xstate.StateNodeConfig<TContext, TStateSchema, TEvent>['initial']];
type StateNodeConfigTypeTuple<
TContext = any,
TStateSchema extends xstate.StateSchema<any> = any,
TEvent extends xstate.EventObject = any
> = ['type', xstate.StateNodeConfig<TContext, TStateSchema, TEvent>['type']];
type StateNodeConfigContextTuple<
TContext = any,
TStateSchema extends xstate.StateSchema<any> = any,
TEvent extends xstate.EventObject = any
> = ['context', xstate.StateNodeConfig<TContext, TStateSchema, TEvent>['context']];
type StateNodeConfigHistoryTuple<
TContext = any,
TStateSchema extends xstate.StateSchema<any> = any,
TEvent extends xstate.EventObject = any
> = ['history', xstate.StateNodeConfig<TContext, TStateSchema, TEvent>['history']];
type StateNodeConfigStatesTuple<
TContext = any,
TStateSchema extends xstate.StateSchema<any> = any,
TEvent extends xstate.EventObject = any
> = ['states', xstate.StateNodeConfig<TContext, TStateSchema, TEvent>['states']];
type StateNodeConfigInvokeTuple<
TContext = any,
TStateSchema extends xstate.StateSchema<any> = any,
TEvent extends xstate.EventObject = any
> = ['invoke', xstate.StateNodeConfig<TContext, TStateSchema, TEvent>['invoke']];
type StateNodeConfigOnTuple<
TContext = any,
TStateSchema extends xstate.StateSchema<any> = any,
TEvent extends xstate.EventObject = any
> = ['on', xstate.StateNodeConfig<TContext, TStateSchema, TEvent>['on']];
type StateNodeConfigEntryTuple<
TContext = any,
TStateSchema extends xstate.StateSchema<any> = any,
TEvent extends xstate.EventObject = any
> = ['entry', xstate.StateNodeConfig<TContext, TStateSchema, TEvent>['entry']];
type StateNodeConfigExitTuple<
TContext = any,
TStateSchema extends xstate.StateSchema<any> = any,
TEvent extends xstate.EventObject = any
> = ['exit', xstate.StateNodeConfig<TContext, TStateSchema, TEvent>['exit']];
type StateNodeConfigOnDoneTuple<
TContext = any,
TStateSchema extends xstate.StateSchema<any> = any,
TEvent extends xstate.EventObject = any
> = ['onDone', xstate.StateNodeConfig<TContext, TStateSchema, TEvent>['onDone']];
type StateNodeConfigAfterTuple<
TContext = any,
TStateSchema extends xstate.StateSchema<any> = any,
TEvent extends xstate.EventObject = any
> = ['after', xstate.StateNodeConfig<TContext, TStateSchema, TEvent>['after']];
type StateNodeConfigAlwaysTuple<
TContext = any,
TStateSchema extends xstate.StateSchema<any> = any,
TEvent extends xstate.EventObject = any
> = ['always', xstate.StateNodeConfig<TContext, TStateSchema, TEvent>['always']];
type StateNodeConfigActivitiesTuple<
TContext = any,
TStateSchema extends xstate.StateSchema<any> = any,
TEvent extends xstate.EventObject = any
> = ['activities', xstate.StateNodeConfig<TContext, TStateSchema, TEvent>['activities']];
type StateNodeConfigMetaTuple<
TContext = any,
TStateSchema extends xstate.StateSchema<any> = any,
TEvent extends xstate.EventObject = any
> = ['meta', xstate.StateNodeConfig<TContext, TStateSchema, TEvent>['meta']];
type StateNodeConfigDataTuple<
TContext = any,
TStateSchema extends xstate.StateSchema<any> = any,
TEvent extends xstate.EventObject = any
> = ['data', xstate.StateNodeConfig<TContext, TStateSchema, TEvent>['data']];
type StateNodeConfigIdTuple<
TContext = any,
TStateSchema extends xstate.StateSchema<any> = any,
TEvent extends xstate.EventObject = any
> = ['id', xstate.StateNodeConfig<TContext, TStateSchema, TEvent>['id']];
type StateNodeConfigTuple<
TContext = any,
TStateSchema extends xstate.StateSchema<any> = any,
TEvent extends xstate.EventObject = any
> =
| StateNodeConfigInitialTuple<TContext, TStateSchema, TEvent>
| StateNodeConfigTypeTuple<TContext, TStateSchema, TEvent>
| StateNodeConfigContextTuple<TContext, TStateSchema, TEvent>
| StateNodeConfigHistoryTuple<TContext, TStateSchema, TEvent>
| StateNodeConfigStatesTuple<TContext, TStateSchema, TEvent>
| StateNodeConfigInvokeTuple<TContext, TStateSchema, TEvent>
| StateNodeConfigOnTuple<TContext, TStateSchema, TEvent>
| StateNodeConfigEntryTuple<TContext, TStateSchema, TEvent>
| StateNodeConfigExitTuple<TContext, TStateSchema, TEvent>
| StateNodeConfigOnDoneTuple<TContext, TStateSchema, TEvent>
| StateNodeConfigAfterTuple<TContext, TStateSchema, TEvent>
| StateNodeConfigAlwaysTuple<TContext, TStateSchema, TEvent>
| StateNodeConfigMetaTuple<TContext, TStateSchema, TEvent>
| StateNodeConfigDataTuple<TContext, TStateSchema, TEvent>
| StateNodeConfigIdTuple<TContext, TStateSchema, TEvent>
| StateNodeConfigActivitiesTuple<TContext, TStateSchema, TEvent>;
type TransitionTuple<TContext = any, TEvent extends xstate.EventObject = any> =
| ActionTuple<TContext, TEvent>
| GuardActionTuple<TContext, TEvent>;
type ActionTuple<TContext = any, TEvent extends xstate.EventObject = any> =
| BaseActionTuple<TContext, TEvent>
| AssignActionTuple<TContext, TEvent>
| EffectActionTuple<TContext, TEvent>
| ChooseActionTuple<TContext, TEvent>
| ComposedActionTuple<TContext, TEvent>
| KeyActionTuple;
type ActionFunctionWithCleanup<TContext = any, TEvent extends xstate.EventObject = any> = (
context: TContext,
event: TEvent,
meta: xstate.ActionMeta<TContext, TEvent>,
) => xstate.DisposeActivityFunction | void;
type KeyActionTuple = ['actions', string];
type ComposedActionTuple<TContext = any, TEvent extends xstate.EventObject = any> = [
'actions',
xstate.Action<TContext, TEvent>[],
];
type BaseActionTuple<TContext = any, TEvent extends xstate.EventObject = any> = [
'actions',
xstate.Action<TContext, TEvent>,
];
type EffectActionTuple<TContext = any, TEvent extends xstate.EventObject = any> = [
'actions',
xstate.ActionFunction<TContext, TEvent> | ActionFunctionWithCleanup<TContext, TEvent>,
];
type AssignActionTuple<TContext = any, TEvent extends xstate.EventObject = any> = [
'actions',
xstate.AssignAction<TContext, TEvent>,
];
type GuardActionTuple<TContext = any, TEvent extends xstate.EventObject = any> = [
'cond',
xstate.Condition<TContext, TEvent>,
];
type ChooseActionTuple<TContext = any, TEvent extends xstate.EventObject = any> = [
'actions',
xstate.ChooseAction<TContext, TEvent>,
];
type Delay<TContext = any, TEvent extends xstate.EventObject = any> =
| number
| ((context: TContext, event: TEvent) => number);
type DelayedTransitionTuple<TContext = any, TEvent extends xstate.EventObject = any> = [
Delay<TContext, TEvent>,
xstate.TransitionConfig<TContext, TEvent>[],
];
function last<T>(array: T[]): T {
return array[array.length - 1];
}
function extractStates<
TContext = any,
TStateSchema extends xstate.StateSchema<any> = any,
TEvent extends xstate.EventObject = any
>(args: (StateNodeConfigTuple<TContext, TStateSchema, TEvent> | StatesTuple<TContext, TStateSchema, TEvent>)[]) {
const states = args.find(([maybeArray]) => Array.isArray(maybeArray)) as
| StatesTuple<TContext, TStateSchema, TEvent>
| undefined;
return [...args.filter(([maybeArray]) => !Array.isArray(maybeArray)), ...(states ?? [])];
}
function extractConfig<
TContext = any,
TStateSchema extends xstate.StateSchema<any> = any,
TEvent extends xstate.EventObject = any
>(
args: (StateNodeConfigTuple<TContext, TStateSchema, TEvent> | StatesTuple<TContext, TStateSchema, TEvent>)[],
): xstate.StateNodeConfig<TContext, TStateSchema, TEvent> {
const nextArgs = extractEvents(extractStates(args));
return Object.fromEntries(nextArgs);
}
function extractEvents<
TContext = any,
TStateSchema extends xstate.StateSchema<any> = any,
TEvent extends xstate.EventObject = any
>(
args: (StateNodeConfigTuple<TContext, TStateSchema, TEvent> | StatesTuple<TContext, TStateSchema, TEvent>)[],
): (StateNodeConfigTuple<TContext, TStateSchema, TEvent> | StatesTuple<TContext, TStateSchema, TEvent>)[] {
const events = args.filter(([key]) => key === 'on') as StateNodeConfigOnTuple<TContext, TStateSchema, TEvent>[];
const nextArgs = args.filter(([key]) => key !== 'on');
if (events.length) {
const { done, ...reducedEvents } = events.reduce(
(events, [_key, event]) => ({ ...events, ...event }),
{} as Record<string, xstate.TransitionConfig<TContext, TEvent>[]>,
);
if (done) {
// we destructure done because it's a reserved event
nextArgs.push(['onDone', done] as StateNodeConfigOnDoneTuple<TContext, TStateSchema, TEvent>);
}
nextArgs.push(['on', reducedEvents as any]);
}
return nextArgs;
}
function extractActions<TContext = any, TEvent extends xstate.EventObject = any>(
args: TransitionTuple<TContext, TEvent>[] | (string | TransitionTuple<TContext, TEvent>)[],
) {
return args
.filter(([key]) => key === 'actions')
.map(([_key, actions]) => actions)
.flat() as xstate.Action<TContext, TEvent>[];
}
function extractGuards<TContext = any, TEvent extends xstate.EventObject = any>(
args: TransitionTuple<TContext, TEvent>[] | (string | TransitionTuple<TContext, TEvent>)[],
) {
return args.filter(([key]) => key === 'cond').map(([_key, guards]) => guards);
}
function extractTransitions<TContext = any, TEvent extends xstate.EventObject = any>(
args: (string | xstate.TransitionConfig<TContext, TEvent> | TransitionTuple<TContext, TEvent>)[],
): xstate.TransitionConfig<TContext, TEvent>[] {
return args.reduce((transitions, maybeTransitionTuple) => {
if (typeof maybeTransitionTuple === 'object' && !Array.isArray(maybeTransitionTuple)) {
// if its an object, then its a transition object made from the transition function
transitions.push(maybeTransitionTuple);
return transitions;
}
let currentTransition = last(transitions);
// if the currentTransition has cond defined, the next args will describe a new transition
// until the next the end or until another cond is defined
if (!currentTransition || currentTransition.cond) {
currentTransition = {};
transitions.push(currentTransition);
}
if (typeof maybeTransitionTuple === 'string') {
// if it's a string, then we'll treat it as a transition target
currentTransition.target = maybeTransitionTuple;
}
if (Array.isArray(maybeTransitionTuple)) {
// if its an array then it's a tuple
const [type, config] = maybeTransitionTuple;
if (type === 'actions') {
const actions = currentTransition.actions
? ([...currentTransition.actions, config] as xstate.Action<TContext, TEvent>[])
: ([config] as xstate.Action<TContext, TEvent>[]);
currentTransition.actions = actions;
}
if (type === 'cond') {
currentTransition.cond = config as xstate.Guard<TContext, TEvent>;
}
}
return transitions;
}, [] as xstate.TransitionConfig<TContext, TEvent>[]);
}
export function states<
TContext = any,
TStateSchema extends xstate.StateSchema<any> = any,
TEvent extends xstate.EventObject = any
>(...args: StateTuple<TContext, TStateSchema, TEvent>[]): StatesTuple<TContext, TStateSchema, TEvent> {
const maybeInitialStateTuple = args.find(([_key, stateConfig]) => stateConfig.isInitial);
let initialTuple: ['initial', keyof TStateSchema['states']];
if (maybeInitialStateTuple) {
const [stateName, config] = maybeInitialStateTuple;
initialTuple = ['initial', stateName as keyof TStateSchema['states']];
delete config.isInitial;
} else {
const [[firstStateKey]] = args;
initialTuple = ['initial', firstStateKey as keyof TStateSchema['states']];
}
const states = Object.fromEntries(args) as xstate.StatesConfig<TContext, TStateSchema, TEvent>;
return [['states', states], initialTuple];
}
export function parallel<
TContext = any,
TStateSchema extends xstate.StateSchema<any> = any,
TEvent extends xstate.EventObject = any
>(
...args: [string, xstate.StateNodeConfig<TContext, TStateSchema, TEvent>][]
): StatesTuple<TContext, TStateSchema, TEvent> {
const states = Object.fromEntries(args) as xstate.StatesConfig<TContext, TStateSchema, TEvent>;
return [
['states', states],
['type', 'parallel'],
];
}
export function initial<
TContext = any,
TStateSchema extends xstate.StateSchema<any> = any,
TEvent extends xstate.EventObject = any
>(
stateName: string,
...args: (StateNodeConfigTuple<TContext, TStateSchema, TEvent> | StatesTuple<TContext, TStateSchema, TEvent>)[]
): StateTuple<TContext, TStateSchema, TEvent> {
const [, stateConfig] = state<TContext, TStateSchema, TEvent>(stateName, ...args);
return [stateName, { ...stateConfig, isInitial: true }];
}
export function state<
TContext = any,
TStateSchema extends xstate.StateSchema<any> = any,
TEvent extends xstate.EventObject = any
>(
stateName: string,
...args: (StateNodeConfigTuple<TContext, TStateSchema, TEvent> | StatesTuple<TContext, TStateSchema, TEvent>)[]
): StateTuple<TContext, TStateSchema, TEvent> {
return [
stateName,
extractConfig<TContext, TStateSchema, TEvent>(args) as xstate.StateNodeConfig<
TContext,
TStateSchema['states'][keyof TStateSchema['states']],
TEvent
>,
];
}
export function final<TContext = any, TEvent extends xstate.EventObject = any>(
stateName: string,
): [string, xstate.FinalStateNodeConfig<TContext, TEvent>] {
return [stateName, { type: 'final' }];
}
export function on<
TContext = any,
TStateSchema extends xstate.StateSchema<any> = any,
TEvent extends xstate.EventObject = any
>(
event: string,
...args: (string | xstate.TransitionConfig<TContext, TEvent> | TransitionTuple<TContext, TEvent>)[]
): StateNodeConfigOnTuple<TContext, TStateSchema, TEvent> {
const eventTuple = [event, extractTransitions<TContext, TEvent>(args)];
return ['on', Object.fromEntries([eventTuple])];
}
export function assign<TContext = any, TEvent extends xstate.EventObject = any>(
assignment: xstate.Assigner<TContext, TEvent> | xstate.PropertyAssigner<TContext, TEvent>,
): AssignActionTuple<TContext, TEvent> {
return ['actions', xstate.assign<TContext, TEvent>(assignment)];
}
export function action(act: string): KeyActionTuple {
return ['actions', act];
}
export function effect<TContext = any, TEvent extends xstate.EventObject = any>(
effect: xstate.ActionFunction<TContext, TEvent> | ActionFunctionWithCleanup<TContext, TEvent>,
): EffectActionTuple<TContext, TEvent> {
return ['actions', effect];
}
export function guard<TContext = any, TEvent extends xstate.EventObject = any>(
cond: xstate.Condition<TContext, TEvent>,
): GuardActionTuple<TContext, TEvent> {
return ['cond', cond];
}
export function transition<TContext = any, TEvent extends xstate.EventObject = any>(
...args:
| [string, ...TransitionTuple<TContext, TEvent>[]]
| [TransitionTuple<TContext, TEvent>, ...TransitionTuple<TContext, TEvent>[]]
): xstate.TransitionConfig<TContext, TEvent> {
const actions = extractActions<TContext, TEvent>(args);
const cond = last(extractGuards<TContext, TEvent>(args)) as xstate.Condition<TContext, TEvent> | undefined;
return {
target: args.find((arg) => typeof arg === 'string') as string,
actions: actions.length ? actions : undefined,
cond,
};
}
export function invoke<
TContext = any,
TStateSchema extends xstate.StateSchema<any> = any,
TEvent extends xstate.EventObject = any
>(
src: xstate.InvokeConfig<TContext, TEvent>['src'],
...args: StateNodeConfigOnTuple[]
): StateNodeConfigInvokeTuple<TContext, TStateSchema, TEvent> {
const on = args
.filter(([key]) => key === 'on')
.reduce(
(events, [_key, event]) => ({ ...events, ...event }),
{} as {
done: xstate.TransitionConfig<TContext, xstate.DoneInvokeEvent<any>>[];
error: xstate.TransitionConfig<TContext, xstate.DoneInvokeEvent<any>>[];
},
);
return [
'invoke',
{
src,
onDone: on.done,
onError: on.error,
},
];
}
export function always<
TContext = any,
TStateSchema extends xstate.StateSchema<any> = any,
TEvent extends xstate.EventObject = any
>(...args: TransitionTuple<TContext, TEvent>[]): StateNodeConfigAlwaysTuple<TContext, TStateSchema, TEvent> {
return ['always', extractTransitions<TContext, TEvent>(args)];
}
export function choose<TContext = any, TEvent extends xstate.EventObject = any>(
...choices: TransitionTuple<TContext, TEvent>[]
): ChooseActionTuple<TContext, TEvent> {
return [
'actions',
xstate.actions.choose<TContext, TEvent>(
// we can leverage the extractTransitions function because we disallow strings at the type level
extractTransitions<TContext, TEvent>(choices) as xstate.ChooseConditon<TContext, TEvent>[],
),
];
}
export function choice<TContext = any, TEvent extends xstate.EventObject = any>(
...args: [ActionTuple<TContext, TEvent>, ...TransitionTuple<TContext, TEvent>[]] | [ActionTuple<TContext, TEvent>]
): xstate.ChooseConditon<TContext, TEvent> {
const actions = extractActions<TContext, TEvent>(args);
const cond = last(extractGuards<TContext, TEvent>(args)) as xstate.Condition<TContext, TEvent> | undefined;
return {
cond,
actions,
};
}
export function id<
TContext = any,
TStateSchema extends xstate.StateSchema<any> = any,
TEvent extends xstate.EventObject = any
>(name: string): StateNodeConfigIdTuple<TContext, TStateSchema, TEvent> {
return ['id', name];
}
export function entry<
TContext = any,
TStateSchema extends xstate.StateSchema<any> = any,
TEvent extends xstate.EventObject = any
>(...actions: BaseActionTuple<TContext, TEvent>[]): StateNodeConfigEntryTuple<TContext, TStateSchema, TEvent> {
return ['entry', extractActions<TContext, TEvent>(actions)];
}
export function exit<
TContext = any,
TStateSchema extends xstate.StateSchema<any> = any,
TEvent extends xstate.EventObject = any
>(...actions: BaseActionTuple<TContext, TEvent>[]): StateNodeConfigExitTuple<TContext, TStateSchema, TEvent> {
return ['exit', extractActions<TContext, TEvent>(actions)];
}
export function meta<
TContext = any,
TStateSchema extends xstate.StateSchema<any> = any,
TEvent extends xstate.EventObject = any
>(
meta: TStateSchema extends {
meta: infer D;
}
? D
: any,
): StateNodeConfigMetaTuple<TContext, TStateSchema, TEvent> {
return ['meta', meta];
}
export function data<
TContext = any,
TStateSchema extends xstate.StateSchema<any> = any,
TEvent extends xstate.EventObject = any
>(
data: xstate.Mapper<TContext, TEvent, any> | xstate.PropertyMapper<TContext, TEvent, any>,
): StateNodeConfigDataTuple<TContext, TStateSchema, TEvent> {
return ['data', data];
}
export function history<
TContext = any,
TStateSchema extends xstate.StateSchema<any> = any,
TEvent extends xstate.EventObject = any
>(history: 'none' | 'shallow' | 'deep'): StateNodeConfigHistoryTuple<TContext, TStateSchema, TEvent> {
return ['history', history === 'none' ? false : history];
}
export function context<
TContext = any,
TStateSchema extends xstate.StateSchema<any> = any,
TEvent extends xstate.EventObject = any
>(context: TContext | (() => TContext)): StateNodeConfigTuple<TContext, TStateSchema, TEvent> {
return ['context', context];
}
export function activities<
TContext = any,
TStateSchema extends xstate.StateSchema<any> = any,
TEvent extends xstate.EventObject = any
>(...args: EffectActionTuple<TContext, TEvent>[]): StateNodeConfigActivitiesTuple<TContext, TStateSchema, TEvent> {
const activities: xstate.ActivityDefinition<TContext, TEvent>[] = args.map(([, activity]) => {
const name = activity.name || activity.toString();
return {
id: name,
type: name,
exec: activity,
};
});
return ['activities', activities];
}
export function after<TContext = any, TEvent extends xstate.EventObject = any>(
...args: DelayedTransitionTuple<TContext, TEvent>[]
) {
return ['after', Object.fromEntries(args)];
}
export function delay<TContext = any, TEvent extends xstate.EventObject = any>(
delay: Delay<TContext, TEvent>,
...args: (string | xstate.TransitionConfig<TContext, TEvent> | TransitionTuple<TContext, TEvent>)[]
): DelayedTransitionTuple<TContext, TEvent> {
return [delay, extractTransitions<TContext, TEvent>(args)];
}
export function composeActions<TContext = any, TEvent extends xstate.EventObject = any>(
...args: ActionTuple<TContext, TEvent>[]
): ActionTuple<TContext, TEvent> {
const actions = extractActions<TContext, TEvent>(args);
return ['actions', actions];
}
export function createMachine<
TContext = any,
TStateSchema extends xstate.StateSchema<any> = any,
TEvent extends xstate.EventObject = any
>(...args: (StateNodeConfigTuple<TContext, TStateSchema, TEvent> | StatesTuple<TContext, TStateSchema, TEvent>)[]) {
const config = extractConfig<TContext, TStateSchema, TEvent>(args) as xstate.MachineConfig<
TContext,
TStateSchema,
TEvent
>;
return xstate.Machine<TContext, TStateSchema, TEvent>(config);
}
@cy-98
Copy link

cy-98 commented Nov 14, 2021

Awesome! Hope that your code can be a package!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment