UNPKG

@appnest/typed-actions

Version:

A lightweight library to create and type check flux/redux actions in typescript

137 lines (124 loc) 4.88 kB
/** * All actions come with a "status" field. */ var ActionStatusKind; (function (ActionStatusKind) { ActionStatusKind["START"] = "START"; ActionStatusKind["SUCCESS"] = "SUCCESS"; ActionStatusKind["FAILURE"] = "FAILURE"; })(ActionStatusKind || (ActionStatusKind = {})); /** * Default action to use when the user doesn't specify an action status. */ const DEFAULT_ACTION_STATUS_KIND = ActionStatusKind.SUCCESS; /** * Creates an action object. * Payload and metadata is optional if their corresponding type is undefined. * @param id * @param status * @param payloadAndMeta */ function mkAction(id, status, ...payloadAndMeta) { return { type: `${id}/${status}`, id, status, error: status === ActionStatusKind.FAILURE, payload: payloadAndMeta[0], meta: payloadAndMeta[1] }; } function actionCreator(id, status = DEFAULT_ACTION_STATUS_KIND, defaultMeta) { // The action creator which is a function that makes an action const func = (...payloadAndMeta) => // TODO: Make it possible to type mkAction with both required and optional payload. // @ts-ignore mkAction(id, status, payloadAndMeta[0], payloadAndMeta[1] || defaultMeta); // Assign "id", "status" and optional "meta" to the function. return Object.assign(func, { id, status, meta: defaultMeta }); } /** * Checks if an action was made from a specific action creator using a type guard. * An action is created by an action creator if "id" and "status" of the action corresponds to the action creator. * @param action * @param actionCreator */ function isAction(action, actionCreator) { return action.id === actionCreator.id && action.status === actionCreator.status; } function asyncActionCreator(id, defaultMeta) { if (defaultMeta == null) { // If no metadata is specified we want to create an async action creator without metadata. // The base must be a function that takes metadata returns action creator with built in metadata. const func = (meta) => asyncActionCreator(id, meta); // Assign corresponding action creators to the function return Object.assign(func, { id, start: actionCreator(id, ActionStatusKind.START), success: actionCreator(id, ActionStatusKind.SUCCESS), failure: actionCreator(id, ActionStatusKind.FAILURE) }); } else { // If metadata is specified we want to create corresponding sub action creators with the default metadata. return { id, meta: defaultMeta, start: actionCreator(id, ActionStatusKind.START, defaultMeta), success: actionCreator(id, ActionStatusKind.SUCCESS, defaultMeta), failure: actionCreator(id, ActionStatusKind.FAILURE, defaultMeta) }; } } /** * Checks if the "action" has been made from a specific "actionCreator". * This function returns a discriminated union that can be used to switch/case the "status" field if that's your style of programming. * @param action * @param actionCreator */ function isAsyncAction(action, actionCreator) { return action.id === actionCreator.id; } /** * Used to keep track of how many action creators have been created in order to auto generate ids. */ let n = 0; /** * Creates a default async action creator where only the Success and Meta types are user defined. * If no id is given and id is auto-generated: `ACTION_${n}`. * If both "idOrNamespace" and "id" is given, the id will be `${namespace}/${id}`. * @param idOrNamespace * @param id */ function defaultAsyncActionCreator(idOrNamespace, id) { if (idOrNamespace == null) { // Auto generate an id if needed idOrNamespace = `ACTION_${n++}`; } else if (id != null) { // Concatenate namespace and id idOrNamespace = `${idOrNamespace}/${id}`; } return asyncActionCreator(idOrNamespace); } /** * Returns a curried function with a action dispatcher function. * The returned function will take an "actionCreator" and a async "handler" function that must return a successful value. * If the "handler" function throws an error a "failure" action will be dispatched an the error will be rethrown. * The function will always dispatch a "start" action. * @param dispatch */ const tryCatchDispatch = (dispatch) => async (actionCreator, handler) => { dispatch(actionCreator.start()); try { const data = await handler(); // @ts-ignore dispatch(actionCreator.success(data)); return data; } catch (e) { dispatch(actionCreator.failure(e)); throw e; } }; export { ActionStatusKind, DEFAULT_ACTION_STATUS_KIND, actionCreator, isAction, mkAction, asyncActionCreator, isAsyncAction, defaultAsyncActionCreator, tryCatchDispatch };