Skip to content

Instantly share code, notes, and snippets.

@baetheus
Last active August 24, 2022 02:40
Show Gist options
  • Save baetheus/2e16ad4118b6fcd45e45756780ebb22a to your computer and use it in GitHub Desktop.
Save baetheus/2e16ad4118b6fcd45e45756780ebb22a to your computer and use it in GitHub Desktop.
Rehashing Flex Standard Action Creators
/**
* An action is the standard action that most
* applications deal with. It is typed, like TypedAction, but
* also has a payload associated with it.
*/
export type Action<P = void, T extends string = string> = {
readonly tag: T;
readonly payload: P;
};
/**
* Extract the type const from an Action
*/
export type ToType<A> = A extends Action<infer _, infer T> ? T : never;
/**
* Extract the type const from an Action
*/
export type ToPayload<A> = A extends Action<infer P, infer _> ? P : never;
/**
* An action matcher is a struct with a match function that takes
* an action and narrows it if its tag matches.
*/
export type ActionMatcher<P = void, T extends string = string> = {
readonly tag: T;
readonly match: (action: Action<unknown>) => action is Action<P, T>;
};
/**
* An action function takes a payload and constructs an action from a payload.
* If the payload is void then the ActionFunction takes no arguments. An
* ActionCreator can be contramapped to change the inputs that create
* the action payload.
*/
export type ActionCreator<
PS extends unknown[] = [],
P = void,
T extends string = string,
> = P extends void ? () => Action<P, T>
: (...prepare: PS) => Action<P, T>;
/**
* An action factory is a function that creates an action, seeding the type
* and has a match property that acts as a refinement for any action.
* In other words it is an ActionFunction and an ActionMatcher.
*/
export type ActionFactory<
PS extends unknown[] = [],
P = void,
T extends string = string,
> =
& ActionCreator<PS, P, T>
& ActionMatcher<P, T>;
/**
* Create an ActionFactory. This creates and merges an ActionCreator
* and an ActionMatcher
*/
export function createAction<P = void, T extends string = string>(
tag: T,
): ActionFactory<[P], P, T> {
const fn = ((payload) => ({ tag, payload })) as ActionCreator<[P], P, T>;
const match: ActionMatcher<P, T> = {
tag,
match: (action): action is Action<P, T> => action.tag === tag,
};
return Object.assign(fn, match);
}
/**
* Prepare an action by changing the arguments used to
* create the action. This is an implementation of
* contramap on the ActionFactory type.
*/
export function contramap<AS extends unknown[], B>(
fab: (...as: AS) => B,
): <T extends string = string>(
factory: ActionFactory<[B], B, T>,
) => ActionFactory<AS, B, T> {
return (factory) => {
const fn = ((...as: AS) => factory(fab(...as))) as ActionCreator<
AS,
B,
typeof factory.tag
>;
const match = { match: factory.match, tag: factory.tag };
return Object.assign(fn, match);
};
}
/**
* A success payload represents the successful
* result of an effect.
*/
export type SuccessPayload<P = void, S = void> = {
readonly payload: P;
readonly success: S;
};
/**
* A failure payload represents the failed
* result of an effect.
*/
export type FailurePayload<P = void, F = void> = {
readonly payload: P;
readonly failure: F;
};
/**
* A cancelled payload represents the
* result of a cancelled effect
*/
export type CancelPayload<P = void, R = void> = {
readonly payload: P;
readonly reason: R;
};
/**
* The action string consts to add to tags in an
* effect group
*/
const INITIAL = "initial";
type INITIAL = typeof INITIAL;
const SUCCESS = "success";
type SUCCESS = typeof SUCCESS;
const FAILURE = "failure";
type FAILURE = typeof FAILURE;
const CANCEL = "cancel";
type CANCEL = typeof CANCEL;
/**
* A grouping of action creators representing the
* initiating, success, failure, and cancellation
* of an effect.
*/
export type EffectActionFactory<
P = void,
S = void,
F = void,
R = void,
T extends string = string,
> = {
initial: ActionFactory<[P], P, `${T}/${INITIAL}`>;
success: ActionFactory<
[S, P],
SuccessPayload<P, S>,
`${T}/${SUCCESS}`
>;
failure: ActionFactory<
[F, P],
FailurePayload<P, F>,
`${T}/${FAILURE}`
>;
cancel: ActionFactory<
[R, P],
CancelPayload<P, R>,
`${T}/${CANCEL}`
>;
};
/**
* A package of action creators around an effect
* generally relating to asynchronous action
*/
export function createEffectActions<
P = void,
S = void,
F = void,
R = void,
T extends string = string,
>(tag: T): EffectActionFactory<P, S, F, R, T> {
return {
initial: createAction(`${tag}/${INITIAL}`),
success: contramap((success: S, payload: P) => ({ success, payload }))(
createAction(`${tag}/${SUCCESS}`),
),
failure: contramap((failure: F, payload: P) => ({ failure, payload }))(
createAction(`${tag}/${FAILURE}`),
),
cancel: contramap((reason: R, payload: P) => ({ reason, payload }))(
createAction(`${tag}/${CANCEL}`),
),
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment