@appnest/typed-actions
Version:
A lightweight library to create and type check flux/redux actions in typescript
137 lines (124 loc) • 4.88 kB
JavaScript
/**
* 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 };