lib_factory_action.js

/**
 * Dobo model's chainable methods. Only the keys listed here are allowed to be called in chainable methods.
 * The values are the only allowed arguments for the corresponding chainable methods.
 * @typedef {Object} TMethods
 * @type {Object}
 * @memberof DoboAction
 * @property {string[]} [createRecord=['body']]
 * @property {string[]} [updateRecord=['id', 'body']]
 * @property {string[]} [upsertRecord=['body']]
 * @property {string[]} [removeRecord=['id']]
 * @property {string[]} [getRecord=['id']]
 * @property {string[]} [findRecord=['filter']]
 * @property {string[]} [findOneRecord=['filter']]
 * @property {string[]} [findAllRecord=['filter']]
 * @property {string[]} [findAllRecords=['filter']] - Alias of `findAllRecord`
 * @property {string[]} [countRecord=['params']]
 * @property {string[]} [createAggregate=['params']]
 * @property {string[]} [createHistogram=['params']]
 * @property {string[]} [createAttachment=['id']]
 * @property {string[]} [findAttachment=['id']]
 * @property {string[]} [getAttachment=['id', 'field', 'file']]
 * @property {string[]} [listAttachment=['params']]
 * @property {string[]} [removeAttachment=['id', 'field', 'file']]
 * @property {string[]} [updateAttachment=['id']]
 */
const methods = {
  createRecord: ['body'],
  updateRecord: ['id', 'body'],
  upsertRecord: ['body'],
  removeRecord: ['id'],
  getRecord: ['id'],
  findRecord: ['filter'],
  findOneRecord: ['filter'],
  findAllRecord: ['filter'],
  findAllRecords: ['filter'],
  countRecord: ['params'],
  createAggregate: ['params'],
  createHistogram: ['params'],
  createAttachment: ['id'],
  findAttachment: ['id'],
  getAttachment: ['id', 'field', 'file'],
  listAttachment: ['params'],
  removeAttachment: ['id', 'field', 'file'],
  updateAttachment: ['id']
}

/**
 * Allowed options for Dobo model's chainable methods. Only the keys listed here are allowed to be called
 * in chainable methods. The values are the default values for the corresponding options.
 * @typedef {Object} TOptions
 * @type {Object}
 * @memberof DoboAction
 * @property {boolean} [truncateString=true]
 * @property {boolean} [noResult=false]
 * @property {boolean} [noBodySanitizer=false]
 * @property {boolean} [noResultSanitizer=false]
 * @property {boolean} [noValidation=false]
 * @property {boolean} [dataOnly=true]
 * @property {boolean} [noHook=false]
 * @property {boolean} [noModelHook=false]
 * @property {boolean} [noDynHook=false]
 * @property {Array} [extFields=[]]
 * @property {Array} [fields=[]]
 * @property {boolean} [noFlash=false]
 * @property {Array} [hidden=[]]
 * @property {Array} [refs=[]]
 * @property {Array} [types=[]]
 * @property {string|undefined} [type=undefined]
 * @property {string|undefined} [group=undefined]
 * @property {Array} [aggregates=[]]
 * @property {string|undefined} [field=undefined]
 * @property {string|undefined} [queryHandler=undefined]
 * @property {boolean} [count=true]
 * @property {boolean} [noCache=true]
 * @property {boolean} [partial=true]
 * @property {boolean} [mimeType=true]
 * @property {boolean} [fullPath=true]
 * @property {boolean} [stats=true]
 * @property {boolean} [dirOnly=true]
 * @property {string|undefined} [setField=undefined]
 * @property {string|undefined} [setFile=undefined]
 * @property {string|undefined} [source=undefined]
 * @property {boolean} [uriEncoded=true]
 */
const options = {
  truncateString: true,
  noResult: false,
  noBodySanitizer: false,
  noResultSanitizer: false,
  noValidation: false,
  dataOnly: true,
  noHook: false,
  noModelHook: false,
  noDynHook: false,
  extFields: [],
  fields: [],
  noFlash: false,
  hidden: [],
  refs: [],
  types: [],
  type: undefined,
  group: undefined,
  aggregates: [],
  field: undefined,
  queryHandler: undefined,
  count: true,
  noCache: true,
  partial: true,
  // attachment
  mimeType: true,
  fullPath: true,
  stats: true,
  dirOnly: true,
  setField: undefined,
  setFile: undefined,
  source: undefined,
  uriEncoded: true
}

/**
 * Action factory function
 *
 * @async
 * @returns {Promise<void>}
 */
async function actionFactory () {
  const { Tools } = this.app.baseClass

  /**
   * Action class enables you to call model's method with all parameters and options in chainable methods
   * in addition of the normal call.
   *
   * Action class is created by calling model method without any parameter.
   * The resulted class instance can be used to call model's method with all parameters and options
   * in chainable methods.
   *
   * Action class can also be created by calling `model.action()` method, with or without parameters.
   * Please see the example below:
   *
   * You always need to call the `run()` method as the last step. The `run()` method will return the result
   * of the model's method call.
   *
   * @class
   * @example
   * // method 1:
   * const action = await model.getRecord() // Important: no parameter passed
   * const result = action.id('rec-id').noHook().dataOnly(false).run()
   * // method 2:
   * const result = await model.action().getRecord('rec-id').noHook().dataOnly(false).run()
   * // method 3:
   * const result = await model.action('getRecord').id('rec-id').noHook().dataOnly(false).run()
   * // method 4:
   * const result = await model.action('getRecord', 'rec-id').noHook().dataOnly(false).run()
   * // Instead of chaining options as methods, you can also pass options object as the parameter to the run() method
   * const result = await model.action('getRecord', 'rec-id').run({ noHook: true, dataOnly: false })
   */
  class DoboAction extends Tools {
    constructor (model, name, ...args) {
      super(model.plugin)
      this.model = model
      this.name = name
      this._options = {}
      if (name) this._setArgs(name, args)
      // create methods
      for (const method in methods) {
        this[method] = (...args) => {
          this.name = method
          this._setArgs(method, args)
          this._options = {}
          return this
        }
      }
      // create options builder methods
      for (const option in options) {
        this[option] = (value) => {
          this._options[option] = value ?? options[option]
          return this
        }
      }
    }

    /**
     * Internal method to set the arguments to the instance properties based on the method name
     * and the arguments passed.
     * @internal
     * @method
     * @param {string} method - The name of the method being called
     * @param {Array} args - The arguments passed to the method
     * @returns {void}
     */
    _setArgs = (method, args) => {
      if (args.length === 0) return
      for (const idx in methods[method]) {
        this['_' + methods[method][idx]] = args[idx]
      }
    }

    /**
     * Set the ID for the corresponding record. You need this for `updateRecord` or `removeRecord`
     * operations. This method is chainable.
     * @method
     * @param {string|number} value - The ID value to set
     * @returns {DoboAction} The current instance for chaining
     */
    id = value => {
      this._id = value
      return this
    }

    /**
     * Set the body for the corresponding record. You need this for `createRecord`, `updateRecord`
     * or `upsertRecord` operations. This method is chainable.
     * @method
     * @param {object} value - The body value to set
     * @returns {DoboAction} The current instance for chaining
     */
    body = value => {
      this._body = value
      return this
    }

    /**
     * Set the filter for the corresponding record. You optionally need this for `findRecord`,
     * `findOneRecord` or `findAllRecord` operations. This method is chainable.
     * @method
     * @param {object} value - The filter value to set
     * @returns {DoboAction} The current instance for chaining
     */
    filter = value => {
      this._filter = value
      return this
    }

    field = value => {
      this._field = value
      return this
    }

    file = value => {
      this._file = value
      return this
    }

    /**
     * Set the options for the corresponding method. This method is chainable.
     * @method
     * @param {DoboAction.TOptions} value - The options to set
     * @returns {DoboAction} The current instance for chaining
     */
    options = (value = {}) => {
      for (const k in value) {
        this._options[k] = value
      }
      return this
    }

    /**
     * Execute the corresponding method with the set arguments and options. This method is chainable.
     * @async
     * @method
     * @param {DoboAction.TOptions} value - If set, it will override the options set by the `options()` method. This is useful if you want to set options at the last step.
     * @returns {Promise<*>} The result of the method execution
     */
    run = async (value = {}) => {
      this.options(value)
      const args = methods[this.name].map(item => this['_' + item])
      args.push(this._options)
      return await this.model[this.name](...args)
    }

    /**
     * Dispose the instance and clean up resources.
     * @async
     * @method
     * @returns {Promise<void>}
     */
    dispose = async () => {
      await super.dispose()
      this.model = null
      this._options = null
    }
  }

  this.app.baseClass.DoboAction = DoboAction
}

export default actionFactory