Home Reference Source

src/Res.js

import HTTP from 'http-status-codes'
import assignDeep from 'assign-deep'
import { deleteUndefineds } from './utils'
import { isPlainObject } from 'lodash'

/**
 * A wrapper for the response object with helper methods and access to Autonym model data.
 */
export default class Res {
  /**
   * The status code for successful find, findOne, findOneAndUpdate, and findOneAndDelete calls.
   * @type {number}
   * @constant
   */
  static OK = HTTP.OK

  /**
   * The status code for successful create calls.
   * @type {number}
   * @constant
   */
  static CREATED = HTTP.CREATED

  /**
   * @param {http.ServerResponse} raw The raw ServerResponse object.
   * @param {Model} model The Autonym model instance.
   * @param {Meta} meta The meta object aggregated by policies during the request.
   * @example
   * const res = new AutonymRes(response, Post, meta)
   */
  constructor(raw, model, meta) {
    raw.autonym = this
    raw.autonymMeta = meta

    this._raw = raw
    this._model = model
    this._data = null
    this._status = null
    this._isPopulated = false
  }

  /**
   * Gets the raw response.
   * @returns {http.ServerResponse} The raw response.
   */
  getRaw() {
    return this._raw
  }

  /**
   * Gets the data currently set for the response body.
   * @returns {Record} The data.
   * @throws {ReferenceError} If the store method has not been called yet.
   */
  getData() {
    if (!this.isPopulated()) {
      throw new ReferenceError('Cannot get response data before store method has been called.')
    }
    return this._data
  }

  /**
   * Merges the currently set response data with the given data.
   * @param {Record} data The new properties to set.
   * @param {boolean} [replace] If true, replaces the data on the response instead of merging it.
   * @returns {void}
   * @throws {ReferenceError} If the store method has not been called yet.
   * @example
   * console.log(res.getData()) // { title: 'Hello World' }
   * res.setData({ name: 'Test' })
   * console.log(res.getData()) // { name: 'Test', title: 'Hello World' }
   * @example
   * console.log(res.getData()) // { title: 'Hello World' }
   * res.setData({ name: 'Test' }, true)
   * console.log(res.getData()) // { name: 'Test' }
   */
  setData(data, replace = false) {
    if (!this.isPopulated()) {
      throw new ReferenceError('Cannot set response data before store method has been called.')
    }

    if (replace) {
      this._data = data
      deleteUndefineds(this._data)
    } else if (Array.isArray(this._data)) {
      if (!Array.isArray(data)) {
        throw new TypeError('The data must be an array.')
      }

      this._data.forEach((record, i) => {
        assignDeep(record, data[i] || {})
        deleteUndefineds(record)
      })
    } else {
      if (!isPlainObject(data)) {
        throw new TypeError('The data must be a plain object.')
      }
      this._data = assignDeep(this._data, data)
      deleteUndefineds(this._data)
    }
  }

  _setErrorData(data) {
    this._data = data
  }

  /**
   * Gets the currently set status code.
   * @returns {number|null} The status code.
   */
  getStatus() {
    return this._status
  }

  /**
   * Sets the status code.
   * @param {number} status The status code.
   * @returns {void}
   */
  setStatus(status) {
    if (typeof status !== 'number') {
      throw new TypeError('The status must be a number.')
    }
    this._status = status
  }

  /**
   * Gets the model instance.
   * @returns {Model} The model.
   */
  getModel() {
    return this._model
  }

  /**
   * Gets the given header.
   * @param {string} header The header to find.
   * @returns {string|undefined} The header value.
   */
  getHeader(header) {
    return this.getRaw().get(header)
  }

  /**
   * Sets the given header.
   * @param {string} header The header to set.
   * @param {string} value The value to set to.
   * @returns {void}
   */
  setHeader(header, value) {
    if (typeof header !== 'string') {
      throw new TypeError('The header must be a string')
    }
    if (typeof value !== 'string') {
      throw new TypeError('The value must be a string')
    }
    return this.getRaw().set(header, value)
  }

  /**
   * Whether the store method has populated the response data yet.
   * @returns {boolean} True if the store method has been called.
   */
  isPopulated() {
    return this._isPopulated
  }

  /**
   * Whether the response has been sent to the client.
   * @returns {boolean} True if the response has been sent.
   */
  isSent() {
    return this.getRaw().headerSent
  }
}