import { Action, Event, EventObject, SingleOrArray, SendAction, SendActionOptions, CancelAction, ActionObject, ActionType, Assigner, PropertyAssigner, AssignAction, ActionFunction, ActionFunctionMap, ActivityActionObject, ActionTypes, ActivityDefinition, SpecialTargets, RaiseAction, RaiseActionObject, DoneEvent, ErrorPlatformEvent, DoneEventObject, SendExpr, SendActionObject, PureAction, LogExpr, LogAction, LogActionObject, DelayFunctionMap, SCXML, ExprWithMeta, ChooseCondition, ChooseAction, AnyEventObject, Expr, StopAction, StopActionObject, Cast, EventFrom, AnyActorRef} from './types';import * as actionTypes from './actionTypes';import { getEventType, isFunction, isString, toEventObject, toSCXMLEvent, partition, flatten, updateContext, warn, toGuard, evaluateGuard, toArray, isArray} from './utils';import { State } from './State';import { StateNode } from './StateNode';import { IS_PRODUCTION } from './environment';
export { actionTypes };
export const initEvent = toSCXMLEvent({ type: actionTypes.init });
export function getActionFunction<TContext, TEvent extends EventObject>( actionType: ActionType, actionFunctionMap?: ActionFunctionMap<TContext, TEvent>): | ActionObject<TContext, TEvent> | ActionFunction<TContext, TEvent> | undefined { return actionFunctionMap ? actionFunctionMap[actionType] || undefined : undefined;}
export function toActionObject<TContext, TEvent extends EventObject>( action: Action<TContext, TEvent>, actionFunctionMap?: ActionFunctionMap<TContext, TEvent>): ActionObject<TContext, TEvent> { let actionObject: ActionObject<TContext, TEvent>;
if (isString(action) || typeof action === 'number') { const exec = getActionFunction(action, actionFunctionMap); if (isFunction(exec)) { actionObject = { type: action, exec }; } else if (exec) { actionObject = exec; } else { actionObject = { type: action, exec: undefined }; } } else if (isFunction(action)) { actionObject = { type: action.name || action.toString(), exec: action }; } else { const exec = getActionFunction(action.type, actionFunctionMap); if (isFunction(exec)) { actionObject = { ...action, exec }; } else if (exec) { const actionType = exec.type || action.type;
actionObject = { ...exec, ...action, type: actionType } as ActionObject<TContext, TEvent>; } else { actionObject = action as ActionObject<TContext, TEvent>; } } return actionObject;}
export const toActionObjects = <TContext, TEvent extends EventObject>( action?: SingleOrArray<Action<TContext, TEvent>> | undefined, actionFunctionMap?: ActionFunctionMap<TContext, TEvent>): Array<ActionObject<TContext, TEvent>> => { if (!action) { return []; }
const actions = isArray(action) ? action : [action];
return actions.map((subAction) => toActionObject(subAction, actionFunctionMap) );};
export function toActivityDefinition<TContext, TEvent extends EventObject>( action: string | ActivityDefinition<TContext, TEvent>): ActivityDefinition<TContext, TEvent> { const actionObject = toActionObject(action);
return { id: isString(action) ? action : actionObject.id, ...actionObject, type: actionObject.type };}
export function raise<TContext, TEvent extends EventObject>( event: Event<TEvent>): RaiseAction<TEvent> | SendAction<TContext, AnyEventObject, TEvent> { if (!isString(event)) { return send(event, { to: SpecialTargets.Internal }); } return { type: actionTypes.raise, event };}
export function resolveRaise<TEvent extends EventObject>( action: RaiseAction<TEvent>): RaiseActionObject<TEvent> { return { type: actionTypes.raise, _event: toSCXMLEvent(action.event) };}
export function send< TContext, TEvent extends EventObject, TSentEvent extends EventObject = AnyEventObject>( event: Event<TSentEvent> | SendExpr<TContext, TEvent, TSentEvent>, options?: SendActionOptions<TContext, TEvent>): SendAction<TContext, TEvent, TSentEvent> { return { to: options ? options.to : undefined, type: actionTypes.send, event: isFunction(event) ? event : toEventObject<TSentEvent>(event), delay: options ? options.delay : undefined, id: options && options.id !== undefined ? options.id : isFunction(event) ? event.name : (getEventType<TSentEvent>(event) as string) };}
export function resolveSend< TContext, TEvent extends EventObject, TSentEvent extends EventObject>( action: SendAction<TContext, TEvent, TSentEvent>, ctx: TContext, _event: SCXML.Event<TEvent>, delaysMap?: DelayFunctionMap<TContext, TEvent>): SendActionObject<TContext, TEvent, TSentEvent> { const meta = { _event };
const resolvedEvent = toSCXMLEvent( isFunction(action.event) ? action.event(ctx, _event.data, meta) : action.event );
let resolvedDelay: number | undefined; if (isString(action.delay)) { const configDelay = delaysMap && delaysMap[action.delay]; resolvedDelay = isFunction(configDelay) ? configDelay(ctx, _event.data, meta) : configDelay; } else { resolvedDelay = isFunction(action.delay) ? action.delay(ctx, _event.data, meta) : action.delay; }
const resolvedTarget = isFunction(action.to) ? action.to(ctx, _event.data, meta) : action.to;
return { ...action, to: resolvedTarget, _event: resolvedEvent, event: resolvedEvent.data, delay: resolvedDelay };}
export function sendParent< TContext, TEvent extends EventObject, TSentEvent extends EventObject = AnyEventObject>( event: Event<TSentEvent> | SendExpr<TContext, TEvent, TSentEvent>, options?: SendActionOptions<TContext, TEvent>): SendAction<TContext, TEvent, TSentEvent> { return send<TContext, TEvent, TSentEvent>(event, { ...options, to: SpecialTargets.Parent });}
type InferEvent<E extends EventObject> = { [T in E['type']]: { type: T } & Extract<E, { type: T }>;}[E['type']];
export function sendTo< TContext, TEvent extends EventObject, TActor extends AnyActorRef>( actor: (ctx: TContext) => TActor, event: | EventFrom<TActor> | SendExpr< TContext, TEvent, InferEvent<Cast<EventFrom<TActor>, EventObject>> >, options?: SendActionOptions<TContext, TEvent>): SendAction<TContext, TEvent, any> { return send<TContext, TEvent, any>(event, { ...options, to: actor });}
export function sendUpdate<TContext, TEvent extends EventObject>(): SendAction< TContext, TEvent, { type: ActionTypes.Update }> { return sendParent<TContext, TEvent, { type: ActionTypes.Update }>( actionTypes.update );}
export function respond< TContext, TEvent extends EventObject, TSentEvent extends EventObject = AnyEventObject>( event: Event<TEvent> | SendExpr<TContext, TEvent, TSentEvent>, options?: SendActionOptions<TContext, TEvent>) { return send<TContext, TEvent>(event, { ...options, to: (_, __, { _event }) => { return _event.origin!; } });}
const defaultLogExpr = <TContext, TEvent extends EventObject>( context: TContext, event: TEvent) => ({ context, event});
export function log<TContext, TEvent extends EventObject>( expr: string | LogExpr<TContext, TEvent> = defaultLogExpr, label?: string): LogAction<TContext, TEvent> { return { type: actionTypes.log, label, expr };}
export const resolveLog = <TContext, TEvent extends EventObject>( action: LogAction<TContext, TEvent>, ctx: TContext, _event: SCXML.Event<TEvent>): LogActionObject<TContext, TEvent> => ({ ...action, value: isString(action.expr) ? action.expr : action.expr(ctx, _event.data, { _event })});
export const cancel = (sendId: string | number): CancelAction => { return { type: actionTypes.cancel, sendId };};
export function start<TContext, TEvent extends EventObject>( activity: string | ActivityDefinition<TContext, TEvent>): ActivityActionObject<TContext, TEvent> { const activityDef = toActivityDefinition(activity);
return { type: ActionTypes.Start, activity: activityDef, exec: undefined };}
export function stop<TContext, TEvent extends EventObject>( actorRef: | string | ActivityDefinition<TContext, TEvent> | Expr<TContext, TEvent, string | { id: string }>): StopAction<TContext, TEvent> { const activity = isFunction(actorRef) ? actorRef : toActivityDefinition(actorRef);
return { type: ActionTypes.Stop, activity, exec: undefined };}
export function resolveStop<TContext, TEvent extends EventObject>( action: StopAction<TContext, TEvent>, context: TContext, _event: SCXML.Event<TEvent>): StopActionObject { const actorRefOrString = isFunction(action.activity) ? action.activity(context, _event.data) : action.activity; const resolvedActorRef = typeof actorRefOrString === 'string' ? { id: actorRefOrString } : actorRefOrString;
const actionObject = { type: ActionTypes.Stop as const, activity: resolvedActorRef };
return actionObject;}
export const assign = <TContext, TEvent extends EventObject = EventObject>( assignment: Assigner<TContext, TEvent> | PropertyAssigner<TContext, TEvent>): AssignAction<TContext, TEvent> => { return { type: actionTypes.assign, assignment };};
export function isActionObject<TContext, TEvent extends EventObject>( action: Action<TContext, TEvent>): action is ActionObject<TContext, TEvent> { return typeof action === 'object' && 'type' in action;}
export function after(delayRef: number | string, id?: string) { const idSuffix = id ? `#${id}` : ''; return `${ActionTypes.After}(${delayRef})${idSuffix}`;}
export function done(id: string, data?: any): DoneEventObject { const type = `${ActionTypes.DoneState}.${id}`; const eventObject = { type, data };
eventObject.toString = () => type;
return eventObject as DoneEvent;}
export function doneInvoke(id: string, data?: any): DoneEvent { const type = `${ActionTypes.DoneInvoke}.${id}`; const eventObject = { type, data };
eventObject.toString = () => type;
return eventObject as DoneEvent;}
export function error(id: string, data?: any): ErrorPlatformEvent & string { const type = `${ActionTypes.ErrorPlatform}.${id}`; const eventObject = { type, data };
eventObject.toString = () => type;
return eventObject as ErrorPlatformEvent & string;}
export function pure<TContext, TEvent extends EventObject>( getActions: ( context: TContext, event: TEvent ) => SingleOrArray<ActionObject<TContext, TEvent>> | undefined): PureAction<TContext, TEvent> { return { type: ActionTypes.Pure, get: getActions };}
export function forwardTo<TContext, TEvent extends EventObject>( target: Required<SendActionOptions<TContext, TEvent>>['to'], options?: SendActionOptions<TContext, TEvent>): SendAction<TContext, TEvent, AnyEventObject> { return send<TContext, TEvent>((_, event) => event, { ...options, to: target });}
export function escalate< TContext, TEvent extends EventObject, TErrorData = any>( errorData: TErrorData | ExprWithMeta<TContext, TEvent, TErrorData>, options?: SendActionOptions<TContext, TEvent>): SendAction<TContext, TEvent, AnyEventObject> { return sendParent<TContext, TEvent>( (context, event, meta) => { return { type: actionTypes.error, data: isFunction(errorData) ? errorData(context, event, meta) : errorData }; }, { ...options, to: SpecialTargets.Parent } );}
export function choose<TContext, TEvent extends EventObject>( conds: Array<ChooseCondition<TContext, TEvent>>): ChooseAction<TContext, TEvent> { return { type: ActionTypes.Choose, conds };}
export function resolveActions<TContext, TEvent extends EventObject>( machine: StateNode<TContext, any, TEvent, any, any, any>, currentState: State<TContext, TEvent> | undefined, currentContext: TContext, _event: SCXML.Event<TEvent>, actions: Array<ActionObject<TContext, TEvent>>, preserveActionOrder: boolean = false): [Array<ActionObject<TContext, TEvent>>, TContext] { const [assignActions, otherActions] = preserveActionOrder ? [[], actions] : partition( actions, (action): action is AssignAction<TContext, TEvent> => action.type === actionTypes.assign );
let updatedContext = assignActions.length ? updateContext(currentContext, _event, assignActions, currentState) : currentContext;
const preservedContexts: TContext[] | undefined = preserveActionOrder ? [currentContext] : undefined;
const resolvedActions = flatten( otherActions .map((actionObject) => { switch (actionObject.type) { case actionTypes.raise: return resolveRaise(actionObject as RaiseAction<TEvent>); case actionTypes.send: const sendAction = resolveSend( actionObject as SendAction<TContext, TEvent, AnyEventObject>, updatedContext, _event, machine.options.delays as any ) as ActionObject<TContext, TEvent>;
if (!IS_PRODUCTION) { warn( !isString(actionObject.delay) || typeof sendAction.delay === 'number', `No delay reference for delay expression '${actionObject.delay}' was found on machine '${machine.id}'` ); }
return sendAction; case actionTypes.log: return resolveLog( actionObject as LogAction<TContext, TEvent>, updatedContext, _event ); case actionTypes.choose: { const chooseAction = actionObject as ChooseAction<TContext, TEvent>; const matchedActions = chooseAction.conds.find((condition) => { const guard = toGuard( condition.cond, machine.options.guards as any ); return ( !guard || evaluateGuard( machine, guard, updatedContext, _event, currentState as any ) ); })?.actions;
if (!matchedActions) { return []; }
const [ resolvedActionsFromChoose, resolvedContextFromChoose ] = resolveActions( machine, currentState, updatedContext, _event, toActionObjects( toArray(matchedActions), machine.options.actions as any ), preserveActionOrder ); updatedContext = resolvedContextFromChoose; preservedContexts?.push(updatedContext); return resolvedActionsFromChoose; } case actionTypes.pure: { const matchedActions = (actionObject as PureAction< TContext, TEvent >).get(updatedContext, _event.data); if (!matchedActions) { return []; } const [resolvedActionsFromPure, resolvedContext] = resolveActions( machine, currentState, updatedContext, _event, toActionObjects( toArray(matchedActions), machine.options.actions as any ), preserveActionOrder ); updatedContext = resolvedContext; preservedContexts?.push(updatedContext); return resolvedActionsFromPure; } case actionTypes.stop: { return resolveStop( actionObject as StopAction<TContext, TEvent>, updatedContext, _event ); } case actionTypes.assign: { updatedContext = updateContext( updatedContext, _event, [actionObject as AssignAction<TContext, TEvent>], currentState ); preservedContexts?.push(updatedContext); break; } default: let resolvedActionObject = toActionObject( actionObject, machine.options.actions as any ); const { exec } = resolvedActionObject; if (exec && preservedContexts) { const contextIndex = preservedContexts.length - 1; resolvedActionObject = { ...resolvedActionObject, exec: (_ctx, ...args) => { exec(preservedContexts[contextIndex], ...args); } }; } return resolvedActionObject; } }) .filter((a): a is ActionObject<TContext, TEvent> => !!a) ); return [resolvedActions, updatedContext];}