import { isAction } from 'redux' import type { IsUnknownOrNonInferrable, IfMaybeUndefined, IfVoid, IsAny, } from './tsHelpers' import { hasMatchFunction } from './tsHelpers' /** * 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: unknown) => 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() overridden so that it returns the 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() overridden so that it returns the 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: unknown): action is PayloadAction => isAction(action) && action.type === type return actionCreator } /** * Returns true if value is an RTK-like action creator, with a static type property and match method. */ export function isActionCreator( action: unknown, ): action is BaseActionCreator & Function { return ( typeof action === 'function' && 'type' in action && // hasMatchFunction only wants Matchers but I don't see the point in rewriting it hasMatchFunction(action as any) ) } /** * Returns true if value is an action with a string type and valid Flux Standard Action keys. */ export function isFSA(action: unknown): action is { type: string payload?: unknown error?: unknown meta?: unknown } { return isAction(action) && Object.keys(action).every(isValidKey) } function isValidKey(key: string) { return ['type', 'payload', 'error', 'meta'].indexOf(key) > -1 } // helper types for more readable typings type IfPrepareActionMethodProvided< PA extends PrepareAction | void, True, False, > = PA extends (...args: any[]) => any ? True : False