import type { Action } from 'redux' import type { IsUnknownOrNonInferrable, IfMaybeUndefined, IfVoid, IsAny, } from './tsHelpers' import isPlainObject from './isPlainObject' /** * An action with a string type and an associated payload. This is the * type of action returned by `createAction()` action creators. * * @template P The type of the action's payload. * @template T the type used for the action type. * @template M The type of the action's meta (optional) * @template E The type of the action's error (optional) * * @public */ export type PayloadAction< P = void, T extends string = string, M = never, E = never > = { payload: P type: T } & ([M] extends [never] ? {} : { meta: M }) & ([E] extends [never] ? {} : { error: E }) /** * A "prepare" method to be used as the second parameter of `createAction`. * Takes any number of arguments and returns a Flux Standard Action without * type (will be added later) that *must* contain a payload (might be undefined). * * @public */ export type PrepareAction

= | ((...args: any[]) => { payload: P }) | ((...args: any[]) => { payload: P; meta: any }) | ((...args: any[]) => { payload: P; error: any }) | ((...args: any[]) => { payload: P; meta: any; error: any }) /** * Internal version of `ActionCreatorWithPreparedPayload`. Not to be used externally. * * @internal */ export type _ActionCreatorWithPreparedPayload< PA extends PrepareAction | void, T extends string = string > = PA extends PrepareAction ? ActionCreatorWithPreparedPayload< Parameters, P, T, ReturnType extends { error: infer E } ? E : never, ReturnType extends { meta: infer M } ? M : never > : void /** * Basic type for all action creators. * * @inheritdoc {redux#ActionCreator} */ export interface BaseActionCreator { type: T match: (action: Action) => action is PayloadAction } /** * An action creator that takes multiple arguments that are passed * to a `PrepareAction` method to create the final Action. * @typeParam Args arguments for the action creator function * @typeParam P `payload` type * @typeParam T `type` name * @typeParam E optional `error` type * @typeParam M optional `meta` type * * @inheritdoc {redux#ActionCreator} * * @public */ export interface ActionCreatorWithPreparedPayload< Args extends unknown[], P, T extends string = string, E = never, M = never > extends BaseActionCreator { /** * Calling this {@link redux#ActionCreator} with `Args` will return * an Action with a payload of type `P` and (depending on the `PrepareAction` * method used) a `meta`- and `error` property of types `M` and `E` respectively. */ (...args: Args): PayloadAction } /** * An action creator of type `T` that takes an optional payload of type `P`. * * @inheritdoc {redux#ActionCreator} * * @public */ export interface ActionCreatorWithOptionalPayload extends BaseActionCreator { /** * Calling this {@link redux#ActionCreator} with an argument will * return a {@link PayloadAction} of type `T` with a payload of `P`. * Calling it without an argument will return a PayloadAction with a payload of `undefined`. */ (payload?: P): PayloadAction } /** * An action creator of type `T` that takes no payload. * * @inheritdoc {redux#ActionCreator} * * @public */ export interface ActionCreatorWithoutPayload extends BaseActionCreator { /** * Calling this {@link redux#ActionCreator} will * return a {@link PayloadAction} of type `T` with a payload of `undefined` */ (noArgument: void): PayloadAction } /** * An action creator of type `T` that requires a payload of type P. * * @inheritdoc {redux#ActionCreator} * * @public */ export interface ActionCreatorWithPayload extends BaseActionCreator { /** * Calling this {@link redux#ActionCreator} with an argument will * return a {@link PayloadAction} of type `T` with a payload of `P` */ (payload: P): PayloadAction } /** * An action creator of type `T` whose `payload` type could not be inferred. Accepts everything as `payload`. * * @inheritdoc {redux#ActionCreator} * * @public */ export interface ActionCreatorWithNonInferrablePayload< T extends string = string > extends BaseActionCreator { /** * Calling this {@link redux#ActionCreator} with an argument will * return a {@link PayloadAction} of type `T` with a payload * of exactly the type of the argument. */ (payload: PT): PayloadAction } /** * An action creator that produces actions with a `payload` attribute. * * @typeParam P the `payload` type * @typeParam T the `type` of the resulting action * @typeParam PA if the resulting action is preprocessed by a `prepare` method, the signature of said method. * * @public */ export type PayloadActionCreator< P = void, T extends string = string, PA extends PrepareAction

| void = void > = IfPrepareActionMethodProvided< PA, _ActionCreatorWithPreparedPayload, // else IsAny< P, ActionCreatorWithPayload, IsUnknownOrNonInferrable< P, ActionCreatorWithNonInferrablePayload, // else IfVoid< P, ActionCreatorWithoutPayload, // else IfMaybeUndefined< P, ActionCreatorWithOptionalPayload, // else ActionCreatorWithPayload > > > > > /** * A utility function to create an action creator for the given action type * string. The action creator accepts a single argument, which will be included * in the action object as a field called payload. The action creator function * will also have its toString() overriden so that it returns the action type, * allowing it to be used in reducer logic that is looking for that action type. * * @param type The action type to use for created actions. * @param prepare (optional) a method that takes any number of arguments and returns { payload } or { payload, meta }. * If this is given, the resulting action creator will pass its arguments to this method to calculate payload & meta. * * @public */ export function createAction

( type: T ): PayloadActionCreator /** * A utility function to create an action creator for the given action type * string. The action creator accepts a single argument, which will be included * in the action object as a field called payload. The action creator function * will also have its toString() overriden so that it returns the action type, * allowing it to be used in reducer logic that is looking for that action type. * * @param type The action type to use for created actions. * @param prepare (optional) a method that takes any number of arguments and returns { payload } or { payload, meta }. * If this is given, the resulting action creator will pass its arguments to this method to calculate payload & meta. * * @public */ export function createAction< PA extends PrepareAction, T extends string = string >( type: T, prepareAction: PA ): PayloadActionCreator['payload'], T, PA> export function createAction(type: string, prepareAction?: Function): any { function actionCreator(...args: any[]) { if (prepareAction) { let prepared = prepareAction(...args) if (!prepared) { throw new Error('prepareAction did not return an object') } return { type, payload: prepared.payload, ...('meta' in prepared && { meta: prepared.meta }), ...('error' in prepared && { error: prepared.error }), } } return { type, payload: args[0] } } actionCreator.toString = () => `${type}` actionCreator.type = type actionCreator.match = (action: Action): action is PayloadAction => action.type === type return actionCreator } export function isFSA(action: unknown): action is { type: string payload?: unknown error?: unknown meta?: unknown } { return ( isPlainObject(action) && typeof (action as any).type === 'string' && Object.keys(action).every(isValidKey) ) } function isValidKey(key: string) { return ['type', 'payload', 'error', 'meta'].indexOf(key) > -1 } /** * Returns the action type of the actions created by the passed * `createAction()`-generated action creator (arbitrary action creators * are not supported). * * @param action The action creator whose action type to get. * @returns The action type used by the action creator. * * @public */ export function getType( actionCreator: PayloadActionCreator ): T { return `${actionCreator}` as T } // helper types for more readable typings type IfPrepareActionMethodProvided< PA extends PrepareAction | void, True, False > = PA extends (...args: any[]) => any ? True : False