Code-Y/redux-fluent

View on GitHub
src/create-action/create-action.ts

Summary

Maintainability
A
25 mins
Test Coverage
// eslint-disable-next-line no-unused-vars
import { ErrorFSA, FSA } from 'flux-standard-action';
import { createAliasCreator } from './create-alias-creator';


export interface ReduxFluentAction<
  T extends string = string,
  P = void,
  M = void,
> extends FSA<T, P, M> {
  type: T;
}

export interface ReduxFluentActionError<
  T extends string = string,
  E extends Error = Error,
  M = void,
  > extends ErrorFSA<E, M> {
  type: T;
}

export type Formatter<T, R> = (rawPayload: any, rawMeta: any, T: T) => R;
export type RFA<T extends string = string, P = any, M = any> = ReduxFluentAction<T, P, M>;
export type RFAE<T extends string = string, P extends Error = Error, M = any> = ReduxFluentActionError<T, P, M>;

export interface ActionCreator<T extends string, P, M> {
  readonly type: T;
  alias(payloadCreator?: Formatter<T, P>, metaCreator?: Formatter<M, P>): ActionCreator<T, P, M>;
  <E extends Error = Error, RM = any>(rawPayload?: E, rawMeta?: RM): RFAE<T, E, M>
  <RP = any, RM = any>(rawPayload?: RP, rawMeta?: RM): RFA<T, P, M>;
}

export function createAction<T extends string = string, P = void, M = void>(
  type: T,
  payloadCreator?: Formatter<T, P>,
  metaCreator?: Formatter<T, M>,
): ActionCreator<T, P, M> {
  const $payloadCreator = payloadCreator || ((p) => p);
  const $metaCreator = metaCreator || ((_, m) => m);

  function $action(rawPayload?: any, rawMeta?: any): RFA<T, P, M> {
    const payload: P = $payloadCreator(rawPayload, rawMeta, type);
    const meta: M = $metaCreator(rawPayload, rawMeta, type);

    const action: RFA<T, P, M> = { type };

    if (payload !== undefined) {
      action.error = payload instanceof Error;
      action.payload = payload;
    }

    if (meta !== undefined) {
      action.meta = meta;
    }

    return Object.freeze(action);
  }

  Object.defineProperties($action, {
    alias: { value: createAliasCreator<T, P, M>(type) },
    name: { configurable: true, value: `action('${type}')` },
    toString: { value: () => type },
    type: { enumerable: true, value: type },
  });

  // @ts-ignore TS2322 property `type` is defined in Object.defineProperties($action, ...)
  return $action;
}