Last active
August 24, 2022 02:40
-
-
Save baetheus/2e16ad4118b6fcd45e45756780ebb22a to your computer and use it in GitHub Desktop.
Rehashing Flex Standard Action Creators
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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