championswimmer/vuex-module-decorators

View on GitHub
src/action.ts

Summary

Maintainability
A
3 hrs
Test Coverage
import { Action as Act, ActionContext, Module as Mod, Payload } from 'vuex'
import { getModule, VuexModule } from './vuexmodule'
import { addPropertiesToObject, getModuleName } from './helpers'
import { config } from './config'

/**
 * Parameters that can be passed to the @Action decorator
 */
export interface ActionDecoratorParams {
  commit?: string
  rawError?: boolean
  root?: boolean
}
function actionDecoratorFactory<T>(params?: ActionDecoratorParams): MethodDecorator {
  const { commit = undefined, rawError = !!config.rawError, root = false } = params || {}
  return function (target: Object, key: string | symbol, descriptor: TypedPropertyDescriptor<any>) {
    const module = target.constructor as Mod<T, any>
    if (!module.hasOwnProperty('actions')) {
      module.actions = Object.assign({}, module.actions)
    }
    const actionFunction: Function = descriptor.value
    const action: Act<typeof target, any> = async function (
      context: ActionContext<typeof target, any>,
      payload: Payload
    ) {
      try {
        let actionPayload = null

        if ((module as any)._genStatic) {
          const moduleName = getModuleName(module)
          const moduleAccessor = context.rootGetters[moduleName]
            ? context.rootGetters[moduleName]
            : getModule(module as typeof VuexModule)
          moduleAccessor.context = context
          actionPayload = await actionFunction.call(moduleAccessor, payload)
        } else {
          const thisObj = { context }
          addPropertiesToObject(thisObj, context.state)
          addPropertiesToObject(thisObj, context.getters)
          actionPayload = await actionFunction.call(thisObj, payload)
        }
        if (commit) {
          context.commit(commit, actionPayload)
        }
        return actionPayload
      } catch (e: any) {
        throw rawError
          ? e
          : new Error(
              'ERR_ACTION_ACCESS_UNDEFINED: Are you trying to access ' +
                'this.someMutation() or this.someGetter inside an @Action? \n' +
                'That works only in dynamic modules. \n' +
                'If not dynamic use this.context.commit("mutationName", payload) ' +
                'and this.context.getters["getterName"]' +
                '\n' +
                new Error(`Could not perform action ${key.toString()}`).stack +
                '\n' +
                e.stack
            )
      }
    }
    module.actions![key as string] = root ? { root, handler: action } : action
  }
}

export function Action<T, R>(
  target: T,
  key: string | symbol,
  descriptor: TypedPropertyDescriptor<(...args: any[]) => R>
): void
export function Action<T>(params: ActionDecoratorParams): MethodDecorator

/**
 * The @Action decorator turns an async function into an Vuex action
 *
 * @param targetOrParams the module class
 * @param key name of the action
 * @param descriptor the action function descriptor
 * @constructor
 */
export function Action<T, R>(
  targetOrParams: T | ActionDecoratorParams,
  key?: string | symbol,
  descriptor?: TypedPropertyDescriptor<(...args: any[]) => R>
) {
  if (!key && !descriptor) {
    /*
     * This is the case when `targetOrParams` is params.
     * i.e. when used as -
     * <pre>
        @Action({commit: 'incrCount'})
        async getCountDelta() {
          return 5
        }
     * </pre>
     */
    return actionDecoratorFactory(targetOrParams as ActionDecoratorParams)
  } else {
    /*
     * This is the case when @Action is called on action function
     * without any params
     * <pre>
     *   @Action
     *   async doSomething() {
     *    ...
     *   }
     * </pre>
     */
    actionDecoratorFactory()(targetOrParams, key!, descriptor!)
  }
}