Source: /home/ubuntu/workspace/js-data-http/src/index.js

/* global fetch:true Headers:true Request:true */
const axios = require('axios')
import {utils} from 'js-data'
const {
  _,
  copy,
  deepMixIn,
  extend,
  fillIn,
  forOwn,
  isArray,
  isFunction,
  isNumber,
  isObject,
  isSorN,
  isString,
  resolve,
  reject,
  toJson
} = utils

let hasFetch = false

try {
  hasFetch = window && window.fetch
} catch (e) {}

function isValidString (value) {
  return (value != null && value !== '')
}
function join (items, separator) {
  separator || (separator = '')
  return items.filter(isValidString).join(separator)
}
function makePath (...args) {
  let result = join(args, '/')
  return result.replace(/([^:\/]|^)\/{2,}/g, '$1/')
}

function encode (val) {
  return encodeURIComponent(val)
    .replace(/%40/gi, '@')
    .replace(/%3A/gi, ':')
    .replace(/%24/g, '$')
    .replace(/%2C/gi, ',')
    .replace(/%20/g, '+')
    .replace(/%5B/gi, '[')
    .replace(/%5D/gi, ']')
}

function buildUrl (url, params) {
  if (!params) {
    return url
  }

  const parts = []

  forOwn(params, function (val, key) {
    if (val === null || typeof val === 'undefined') {
      return
    }
    if (!isArray(val)) {
      val = [val]
    }

    val.forEach(function (v) {
      if (window.toString.call(v) === '[object Date]') {
        v = v.toISOString()
      } else if (isObject(v)) {
        v = toJson(v)
      }
      parts.push(`${encode(key)}=${encode(v)}`)
    })
  })

  if (parts.length > 0) {
    url += (url.indexOf('?') === -1 ? '?' : '&') + parts.join('&')
  }

  return url
}

/**
 * DSHttpAdapter class.
 * @class DSHttpAdapter
 * @alias DSHttpAdapter
 *
 * @param {Object} [opts] Configuration options.
 * @param {string} [opts.basePath='']
 * @param {boolean} [opts.debug=false]
 * @param {boolean} [opts.forceTrailingSlash=false]
 * @param {Object} [opts.http=axios]
 * @param {Object} [opts.httpConfig={}]
 * @param {string} [opts.suffix='']
 * @param {boolean} [opts.useFetch=false]
 */
function DSHttpAdapter (opts) {
  const self = this

  // Default values for arguments
  opts || (opts = {})

  // Default and user-defined settings
  /**
   * @name DSHttpAdapter#basePath
   * @type {string}
   */
  self.basePath = opts.basePath === undefined ? '' : opts.basePath

  /**
   * @name DSHttpAdapter#debug
   * @type {boolean}
   * @default false
   */
  self.debug = opts.debug === undefined ? false : opts.debug

  /**
   * @name DSHttpAdapter#forceTrailingSlash
   * @type {boolean}
   * @default false
   */
  self.forceTrailingSlash = opts.forceTrailingSlash === undefined ? false : opts.forceTrailingSlash

  /**
   * @name DSHttpAdapter#http
   * @type {Function}
   */
  self.http = opts.http === undefined ? axios : opts.http

  /**
   * @name DSHttpAdapter#httpConfig
   * @type {Object}
   */
  self.httpConfig = opts.httpConfig === undefined ? {} : opts.httpConfig

  /**
   * @name DSHttpAdapter#suffix
   * @type {string}
   */
  self.suffix = opts.suffix === undefined ? '' : opts.suffix

  /**
   * @name DSHttpAdapter#useFetch
   * @type {boolean}
   * @default false
   */
  self.useFetch = opts.useFetch === undefined ? false : opts.useFetch
}

fillIn(DSHttpAdapter.prototype, {
  /**
   * Lifecycle hook called by {@link DSHttpAdapter#create}. If this method
   * returns a promise then {@link DSHttpAdapter#create} will wait for the
   * promise to resolve before continuing. If this method returns any other
   * value or the promise resolves with a value, then {@link DSHttpAdapter#create}
   * will resolve with that same value.
   *
   * @memberof DSHttpAdapter
   * @instance
   * @param {Object} Model The `Model` argument passed to {@link DSHttpAdapter#create}.
   * @param {Object} props The `props` argument passed to {@link DSHttpAdapter#create}.
   * @param {Object} opts The `opts` argument passed to {@link DSHttpAdapter#create}.
   * @param {Object} data The `data` value that {@link DSHttpAdapter#create} will return.
   */
  afterCreate () {},

  /**
   * Lifecycle hook called by {@link DSHttpAdapter#createMany}. If this method
   * returns a promise then {@link DSHttpAdapter#createMany} will wait for the
   * promise to resolve before continuing. If this method returns any other
   * value or the promise resolves with a value, then {@link DSHttpAdapter#createMany}
   * will resolve with that same value.
   *
   * @memberof DSHttpAdapter
   * @instance
   * @param {Object} Model The `Model` argument passed to {@link DSHttpAdapter#createMany}.
   * @param {Object} models The `models` argument passed to {@link DSHttpAdapter#createMany}.
   * @param {Object} opts The `opts` argument passed to {@link DSHttpAdapter#createMany}.
   * @param {Object} data The `data` value that {@link DSHttpAdapter#createMany} will return.
   */
  afterCreateMany () {},

  /**
   * Lifecycle hook called by {@link DSHttpAdapter#DEL}. If this method
   * returns a promise then {@link DSHttpAdapter#DEL} will wait for the
   * promise to resolve before continuing. If this method returns any other
   * value or the promise resolves with a value, then {@link DSHttpAdapter#DEL}
   * will resolve with that same value.
   *
   * @memberof DSHttpAdapter
   * @instance
   * @param {string} url The `url` argument passed to {@link DSHttpAdapter#DEL}.
   * @param {Object} config The `config` argument passed to {@link DSHttpAdapter#DEL}.
   * @param {Object} opts The `opts` argument passed to {@link DSHttpAdapter#DEL}.
   * @param {Object} response The `response` value that {@link DSHttpAdapter#DEL} will return.
   */
  afterDEL () {},

  /**
   * Lifecycle hook called by {@link DSHttpAdapter#destroy}. If this method
   * returns a promise then {@link DSHttpAdapter#destroy} will wait for the
   * promise to resolve before continuing. If this method returns any other
   * value or the promise resolves with a value, then {@link DSHttpAdapter#destroy}
   * will resolve with that same value.
   *
   * @memberof DSHttpAdapter
   * @instance
   * @param {Object} Model The `Model` argument passed to {@link DSHttpAdapter#destroy}.
   * @param {(string|number)} id The `id` argument passed to {@link DSHttpAdapter#destroy}.
   * @param {Object} opts The `opts` argument passed to {@link DSHttpAdapter#destroy}.
   * @param {Object} data The `data` value that {@link DSHttpAdapter#destroy} will return.
   */
  afterDestroy () {},

  /**
   * Lifecycle hook called by {@link DSHttpAdapter#destroyAll}. If this method
   * returns a promise then {@link DSHttpAdapter#destroyAll} will wait for the
   * promise to resolve before continuing. If this method returns any other
   * value or the promise resolves with a value, then {@link DSHttpAdapter#destroyAll}
   * will resolve with that same value.
   *
   * @memberof DSHttpAdapter
   * @instance
   * @param {Object} Model The `Model` argument passed to {@link DSHttpAdapter#destroyAll}.
   * @param {(string|number)} id The `id` argument passed to {@link DSHttpAdapter#destroyAll}.
   * @param {Object} opts The `opts` argument passed to {@link DSHttpAdapter#destroyAll}.
   * @param {Object} data The `data` value that {@link DSHttpAdapter#destroyAll} will return.
   */
  afterDestroyAll () {},

  /**
   * Lifecycle hook called by {@link DSHttpAdapter#find}. If this method
   * returns a promise then {@link DSHttpAdapter#find} will wait for the
   * promise to resolve before continuing. If this method returns any other
   * value or the promise resolves with a value, then {@link DSHttpAdapter#find}
   * will resolve with that same value.
   *
   * @memberof DSHttpAdapter
   * @instance
   * @param {Object} Model The `Model` argument passed to {@link DSHttpAdapter#find}.
   * @param {(string|number)} id The `id` argument passed to {@link DSHttpAdapter#find}.
   * @param {Object} opts The `opts` argument passed to {@link DSHttpAdapter#find}.
   * @param {Object} data The `data` value that {@link DSHttpAdapter#find} will return.
   */
  afterFind () {},

  /**
   * Lifecycle hook called by {@link DSHttpAdapter#findAll}. If this method
   * returns a promise then {@link DSHttpAdapter#findAll} will wait for the
   * promise to resolve before continuing. If this method returns any other
   * value or the promise resolves with a value, then {@link DSHttpAdapter#findAll}
   * will resolve with that same value.
   *
   * @memberof DSHttpAdapter
   * @instance
   * @param {Object} Model The `Model` argument passed to {@link DSHttpAdapter#findAll}.
   * @param {(string|number)} id The `id` argument passed to {@link DSHttpAdapter#findAll}.
   * @param {Object} opts The `opts` argument passed to {@link DSHttpAdapter#findAll}.
   * @param {Object} data The `data` value that {@link DSHttpAdapter#findAll} will return.
   */
  afterFindAll () {},

  /**
   * Lifecycle hook called by {@link DSHttpAdapter#GET}. If this method
   * returns a promise then {@link DSHttpAdapter#GET} will wait for the
   * promise to resolve before continuing. If this method returns any other
   * value or the promise resolves with a value, then {@link DSHttpAdapter#GET}
   * will resolve with that same value.
   *
   * @memberof DSHttpAdapter
   * @instance
   * @param {string} url The `url` argument passed to {@link DSHttpAdapter#GET}.
   * @param {Object} config The `config` argument passed to {@link DSHttpAdapter#GET}.
   * @param {Object} opts The `opts` argument passed to {@link DSHttpAdapter#GET}.
   * @param {Object} response The `response` value that {@link DSHttpAdapter#GET} will return.
   */
  afterGET () {},

  /**
   * Lifecycle hook called by {@link DSHttpAdapter#HTTP}. If this method
   * returns a promise then {@link DSHttpAdapter#HTTP} will wait for the
   * promise to resolve before continuing. If this method returns any other
   * value or the promise resolves with a value, then {@link DSHttpAdapter#HTTP}
   * will resolve with that same value.
   *
   * @memberof DSHttpAdapter
   * @instance
   * @param {Object} config The `config` argument passed to {@link DSHttpAdapter#HTTP}.
   * @param {Object} opts The `opts` argument passed to {@link DSHttpAdapter#HTTP}.
   * @param {Object} response The `response` value that {@link DSHttpAdapter#HTTP} will return.
   */
  afterHTTP () {},

  /**
   * Lifecycle hook called by {@link DSHttpAdapter#POST}. If this method
   * returns a promise then {@link DSHttpAdapter#POST} will wait for the
   * promise to resolve before continuing. If this method returns any other
   * value or the promise resolves with a value, then {@link DSHttpAdapter#POST}
   * will resolve with that same value.
   *
   * @memberof DSHttpAdapter
   * @instance
   * @param {string} url The `url` argument passed to {@link DSHttpAdapter#POST}.
   * @param {Object} data The `data` argument passed to {@link DSHttpAdapter#POST}.
   * @param {Object} config The `config` argument passed to {@link DSHttpAdapter#POST}.
   * @param {Object} opts The `opts` argument passed to {@link DSHttpAdapter#POST}.
   * @param {Object} response The `response` value that {@link DSHttpAdapter#POST} will return.
   */
  afterPOST () {},

  /**
   * Lifecycle hook called by {@link DSHttpAdapter#PUT}. If this method
   * returns a promise then {@link DSHttpAdapter#PUT} will wait for the
   * promise to resolve before continuing. If this method returns any other
   * value or the promise resolves with a value, then {@link DSHttpAdapter#PUT}
   * will resolve with that same value.
   *
   * @memberof DSHttpAdapter
   * @instance
   * @param {string} url The `url` argument passed to {@link DSHttpAdapter#PUT}.
   * @param {Object} data The `data` argument passed to {@link DSHttpAdapter#PUT}.
   * @param {Object} config The `config` argument passed to {@link DSHttpAdapter#PUT}.
   * @param {Object} opts The `opts` argument passed to {@link DSHttpAdapter#PUT}.
   * @param {Object} response The `response` value that {@link DSHttpAdapter#PUT} will return.
   */
  afterPUT () {},

  /**
   * Lifecycle hook called by {@link DSHttpAdapter#update}. If this method
   * returns a promise then {@link DSHttpAdapter#update} will wait for the
   * promise to resolve before continuing. If this method returns any other
   * value or the promise resolves with a value, then {@link DSHttpAdapter#update}
   * will resolve with that same value.
   *
   * @memberof DSHttpAdapter
   * @instance
   * @param {Object} Model The `Model` argument passed to {@link DSHttpAdapter#update}.
   * @param {(string|number)} id The `id` argument passed to {@link DSHttpAdapter#update}.
   * @param {Object} props The `props` argument passed to {@link DSHttpAdapter#update}.
   * @param {Object} opts The `opts` argument passed to {@link DSHttpAdapter#update}.
   * @param {Object} data The `data` value that {@link DSHttpAdapter#update} will return.
   */
  afterUpdate () {},

  /**
   * Lifecycle hook called by {@link DSHttpAdapter#updateAll}. If this method
   * returns a promise then {@link DSHttpAdapter#updateAll} will wait for the
   * promise to resolve before continuing. If this method returns any other
   * value or the promise resolves with a value, then {@link DSHttpAdapter#updateAll}
   * will resolve with that same value.
   *
   * @memberof DSHttpAdapter
   * @instance
   * @param {Object} Model The `Model` argument passed to {@link DSHttpAdapter#updateAll}.
   * @param {Object} props The `props` argument passed to {@link DSHttpAdapter#updateAll}.
   * @param {Object} query The `query` argument passed to {@link DSHttpAdapter#updateAll}.
   * @param {Object} opts The `opts` argument passed to {@link DSHttpAdapter#updateAll}.
   * @param {Object} data The `data` value that {@link DSHttpAdapter#updateAll} will return.
   */
  afterUpdateAll () {},

  /**
   * Lifecycle hook called by {@link DSHttpAdapter#updateMany}. If this method
   * returns a promise then {@link DSHttpAdapter#updateMany} will wait for the
   * promise to resolve before continuing. If this method returns any other
   * value or the promise resolves with a value, then {@link DSHttpAdapter#updateMany}
   * will resolve with that same value.
   *
   * @memberof DSHttpAdapter
   * @instance
   * @param {Object} Model The `Model` argument passed to {@link DSHttpAdapter#updateMany}.
   * @param {Object} models The `models` argument passed to {@link DSHttpAdapter#updateMany}.
   * @param {Object} opts The `opts` argument passed to {@link DSHttpAdapter#updateMany}.
   * @param {Object} data The `data` value that {@link DSHttpAdapter#updateMany} will return.
   */
  afterUpdateMany () {},

  /**
   * Lifecycle hook called by {@link DSHttpAdapter#create}. If this method
   * returns a promise then {@link DSHttpAdapter#create} will wait for the
   * promise to resolve before continuing.
   *
   * @memberof DSHttpAdapter
   * @instance
   * @param {Object} Model The `Model` argument passed to {@link DSHttpAdapter#create}.
   * @param {Object} props The `props` argument passed to {@link DSHttpAdapter#create}.
   * @param {Object} opts The `opts` argument passed to {@link DSHttpAdapter#create}.
   */
  beforeCreate () {},

  /**
   * Lifecycle hook called by {@link DSHttpAdapter#createMany}. If this method
   * returns a promise then {@link DSHttpAdapter#createMany} will wait for the
   * promise to resolve before continuing.
   *
   * @memberof DSHttpAdapter
   * @instance
   * @param {Object} Model The `Model` argument passed to {@link DSHttpAdapter#createMany}.
   * @param {Object} models The `models` argument passed to {@link DSHttpAdapter#createMany}.
   * @param {Object} opts The `opts` argument passed to {@link DSHttpAdapter#createMany}.
   */
  beforeCreateMany () {},

  /**
   * Lifecycle hook called by {@link DSHttpAdapter#DEL}. If this method
   * returns a promise then {@link DSHttpAdapter#DEL} will wait for the
   * promise to resolve before continuing. If this method returns any other
   * value or the promise resolves with a value, then {@link DSHttpAdapter#create}
   * will resolve with that same value, then the `config` argument passed to
   * {@link DSHttpAdapter#DEL} will be replaced by the value.
   *
   * @memberof DSHttpAdapter
   * @instance
   * @param {Object} url The `url` argument passed to {@link DSHttpAdapter#DEL}.
   * @param {Object} config The `config` argument passed to {@link DSHttpAdapter#DEL}.
   * @param {Object} opts The `opts` argument passed to {@link DSHttpAdapter#DEL}.
   */
  beforeDEL () {},

  /**
   * Lifecycle hook called by {@link DSHttpAdapter#destroy}. If this method
   * returns a promise then {@link DSHttpAdapter#destroy} will wait for the
   * promise to resolve before continuing.
   *
   * @memberof DSHttpAdapter
   * @instance
   * @param {Object} Model The `Model` argument passed to {@link DSHttpAdapter#destroy}.
   * @param {(string|number)} id The `id` argument passed to {@link DSHttpAdapter#destroy}.
   * @param {Object} opts The `opts` argument passed to {@link DSHttpAdapter#destroy}.
   */
  beforeDestroy () {},

  /**
   * Lifecycle hook called by {@link DSHttpAdapter#destroyAll}. If this method
   * returns a promise then {@link DSHttpAdapter#destroyAll} will wait for the
   * promise to resolve before continuing.
   *
   * @memberof DSHttpAdapter
   * @instance
   * @param {Object} Model The `Model` argument passed to {@link DSHttpAdapter#destroyAll}.
   * @param {Object} query The `query` argument passed to {@link DSHttpAdapter#destroyAll}.
   * @param {Object} opts The `opts` argument passed to {@link DSHttpAdapter#destroyAll}.
   */
  beforeDestroyAll () {},

  /**
   * Lifecycle hook called by {@link DSHttpAdapter#find}. If this method
   * returns a promise then {@link DSHttpAdapter#find} will wait for the
   * promise to resolve before continuing.
   *
   * @memberof DSHttpAdapter
   * @instance
   * @param {Object} Model The `Model` argument passed to {@link DSHttpAdapter#find}.
   * @param {(string|number)} id The `id` argument passed to {@link DSHttpAdapter#find}.
   * @param {Object} opts The `opts` argument passed to {@link DSHttpAdapter#find}.
   */
  beforeFind () {},

  /**
   * Lifecycle hook called by {@link DSHttpAdapter#findAll}. If this method
   * returns a promise then {@link DSHttpAdapter#findAll} will wait for the
   * promise to resolve before continuing.
   *
   * @memberof DSHttpAdapter
   * @instance
   * @param {Object} Model The `Model` argument passed to {@link DSHttpAdapter#findAll}.
   * @param {Object} query The `query` argument passed to {@link DSHttpAdapter#findAll}.
   * @param {Object} opts The `opts` argument passed to {@link DSHttpAdapter#findAll}.
   */
  beforeFindAll () {},

  /**
   * Lifecycle hook called by {@link DSHttpAdapter#GET}. If this method
   * returns a promise then {@link DSHttpAdapter#GET} will wait for the
   * promise to resolve before continuing. If this method returns any other
   * value or the promise resolves with a value, then {@link DSHttpAdapter#create}
   * will resolve with that same value, then the `config` argument passed to
   * {@link DSHttpAdapter#GET} will be replaced by the value.
   *
   * @memberof DSHttpAdapter
   * @instance
   * @param {Object} url The `url` argument passed to {@link DSHttpAdapter#GET}.
   * @param {Object} config The `config` argument passed to {@link DSHttpAdapter#GET}.
   * @param {Object} opts The `opts` argument passed to {@link DSHttpAdapter#GET}.
   */
  beforeGET () {},

  /**
   * Lifecycle hook called by {@link DSHttpAdapter#HTTP}. If this method
   * returns a promise then {@link DSHttpAdapter#HTTP} will wait for the
   * promise to resolve before continuing. If this method returns any other
   * value or the promise resolves with a value, then {@link DSHttpAdapter#create}
   * will resolve with that same value, then the `config` argument passed to
   * {@link DSHttpAdapter#HTTP} will be replaced by the value.
   *
   * @memberof DSHttpAdapter
   * @instance
   * @param {Object} config The `config` argument passed to {@link DSHttpAdapter#HTTP}.
   * @param {Object} opts The `opts` argument passed to {@link DSHttpAdapter#HTTP}.
   */
  beforeHTTP () {},

  /**
   * Lifecycle hook called by {@link DSHttpAdapter#POST}. If this method
   * returns a promise then {@link DSHttpAdapter#POST} will wait for the
   * promise to resolve before continuing. If this method returns any other
   * value or the promise resolves with a value, then {@link DSHttpAdapter#create}
   * will resolve with that same value, then the `config` argument passed to
   * {@link DSHttpAdapter#POST} will be replaced by the value.
   *
   * @memberof DSHttpAdapter
   * @instance
   * @param {Object} url The `url` argument passed to {@link DSHttpAdapter#POST}.
   * @param {Object} data The `data` argument passed to {@link DSHttpAdapter#POST}.
   * @param {Object} config The `config` argument passed to {@link DSHttpAdapter#POST}.
   * @param {Object} opts The `opts` argument passed to {@link DSHttpAdapter#POST}.
   */
  beforePOST () {},

  /**
   * Lifecycle hook called by {@link DSHttpAdapter#PUT}. If this method
   * returns a promise then {@link DSHttpAdapter#PUT} will wait for the
   * promise to resolve before continuing. If this method returns any other
   * value or the promise resolves with a value, then {@link DSHttpAdapter#create}
   * will resolve with that same value, then the `config` argument passed to
   * {@link DSHttpAdapter#PUT} will be replaced by the value.
   *
   * @memberof DSHttpAdapter
   * @instance
   * @param {Object} url The `url` argument passed to {@link DSHttpAdapter#PUT}.
   * @param {Object} data The `data` argument passed to {@link DSHttpAdapter#PUT}.
   * @param {Object} config The `config` argument passed to {@link DSHttpAdapter#PUT}.
   * @param {Object} opts The `opts` argument passed to {@link DSHttpAdapter#PUT}.
   */
  beforePUT () {},

  /**
   * Lifecycle hook called by {@link DSHttpAdapter#update}. If this method
   * returns a promise then {@link DSHttpAdapter#update} will wait for the
   * promise to resolve before continuing.
   *
   * @memberof DSHttpAdapter
   * @instance
   * @param {Object} Model The `Model` argument passed to {@link DSHttpAdapter#update}.
   * @param {(string|number)} id The `id` argument passed to {@link DSHttpAdapter#update}.
   * @param {Object} props The `props` argument passed to {@link DSHttpAdapter#update}.
   * @param {Object} opts The `opts` argument passed to {@link DSHttpAdapter#update}.
   */
  beforeUpdate () {},

  /**
   * Lifecycle hook called by {@link DSHttpAdapter#updateAll}. If this method
   * returns a promise then {@link DSHttpAdapter#updateAll} will wait for the
   * promise to resolve before continuing.
   *
   * @memberof DSHttpAdapter
   * @instance
   * @param {Object} Model The `Model` argument passed to {@link DSHttpAdapter#updateAll}.
   * @param {Object} props The `props` argument passed to {@link DSHttpAdapter#updateAll}.
   * @param {Object} query The `query` argument passed to {@link DSHttpAdapter#updateAll}.
   * @param {Object} opts The `opts` argument passed to {@link DSHttpAdapter#updateAll}.
   */
  beforeUpdateAll () {},

  /**
   * Lifecycle hook called by {@link DSHttpAdapter#updateMany}. If this method
   * returns a promise then {@link DSHttpAdapter#updateMany} will wait for the
   * promise to resolve before continuing.
   *
   * @memberof DSHttpAdapter
   * @instance
   * @param {Object} Model The `Model` argument passed to {@link DSHttpAdapter#updateMany}.
   * @param {Object} models The `models` argument passed to {@link DSHttpAdapter#updateMany}.
   * @param {Object} opts The `opts` argument passed to {@link DSHttpAdapter#updateMany}.
   */
  beforeUpdateMany () {},

  /**
   * Create a new the entity from the provided `props`.
   *
   * {@link DSHttpAdapter#beforeCreate} will be called before calling
   * {@link DSHttpAdapter#POST}.
   * {@link DSHttpAdapter#afterCreate} will be called after calling
   * {@link DSHttpAdapter#POST}.
   *
   * @memberof DSHttpAdapter
   * @instance
   * @param {Object} Model The Model.
   * @param {Object} props Properties to send as the payload.
   * @param {Object} [opts] Configuration options.
   * @param {string} [opts.params] TODO
   * @param {string} [opts.suffix={@link DSHttpAdapter#suffix}] TODO
   * @return {Promise}
   */
  create (Model, props, opts) {
    const self = this
    opts = opts ? copy(opts) : {}
    opts.params || (opts.params = {})
    opts.params = self.queryTransform(Model, opts.params, opts)
    opts.suffix || (opts.suffix = Model.suffix)
    opts.op = 'create'
    self.dbg(opts.op, Model, props, opts)
    return resolve(self.beforeCreate(Model, props, opts)).then(function () {
      return self.POST(
        self.getPath('create', Model, props, opts),
        self.serialize(Model, props, opts),
        opts
      )
    }).then(function (response) {
      return self.deserialize(Model, response, opts)
    }).then(function (data) {
      return resolve(self.afterCreate(Model, props, opts, data)).then(function (_data) {
        return _data || data
      })
    })
  },

  /**
   * Create multiple new entities in batch.
   *
   * {@link DSHttpAdapter#beforeCreateMany} will be called before calling
   * {@link DSHttpAdapter#POST}.
   * {@link DSHttpAdapter#afterCreateMany} will be called after calling
   * {@link DSHttpAdapter#POST}.
   *
   * @memberof DSHttpAdapter
   * @instance
   * @param {Object} Model The Model.
   * @param {Array} models Array of property objects to send as the payload.
   * @param {Object} [opts] Configuration options.
   * @param {string} [opts.params] TODO
   * @param {string} [opts.suffix={@link DSHttpAdapter#suffix}] TODO
   * @return {Promise}
   */
  createMany (Model, models, opts) {
    const self = this
    opts = opts ? copy(opts) : {}
    opts.params || (opts.params = {})
    opts.params = self.queryTransform(Model, opts.params, opts)
    opts.suffix || (opts.suffix = Model.suffix)
    opts.op = 'createMany'
    self.dbg(opts.op, Model, models, opts)
    return resolve(self.beforeCreateMany(Model, models, opts)).then(function () {
      return self.POST(
        self.getPath('createMany', Model, null, opts),
        self.serialize(Model, models, opts),
        opts
      )
    }).then(function (response) {
      return self.deserialize(Model, response, opts)
    }).then(function (data) {
      return resolve(self.afterCreateMany(Model, models, opts, data)).then(function (_data) {
        return _data || data
      })
    })
  },

  /**
   * Call {@link DSHttpAdapter#log} at the "debug" level.
   *
   * @memberof DSHttpAdapter
   * @instance
   * @param {...*} [args] Args passed to {@link DSHttpAdapter#log}.
   */
  dbg (...args) {
    this.log('debug', ...args)
  },

  /**
   * Make an Http request to `url` according to the configuration in `config`.
   *
   * {@link DSHttpAdapter#beforeDEL} will be called before calling
   * {@link DSHttpAdapter#HTTP}.
   * {@link DSHttpAdapter#afterDEL} will be called after calling
   * {@link DSHttpAdapter#HTTP}.
   *
   * @memberof DSHttpAdapter
   * @instance
   * @param {string} url Url for the request.
   * @param {Object} [config] Http configuration that will be passed to
   * {@link DSHttpAdapter#HTTP}.
   * @param {Object} [opts] Configuration options.
   * @return {Promise}
   */
  DEL (url, config, opts) {
    const self = this
    config || (config = {})
    config.url = url || config.url
    config.method = config.method || 'delete'
    return resolve(self.beforeDEL(url, config, opts)).then(function (_config) {
      config = _config || config
      return self.HTTP(config, opts)
    }).then(function (response) {
      return resolve(self.afterDEL(url, config, opts, response)).then(function (_response) {
        return _response || response
      })
    })
  },

  /**
   * Transform the server response object into the payload that will be returned
   * to JSData.
   *
   * @memberof DSHttpAdapter
   * @instance
   * @param {Object} Model The Model used for the operation.
   * @param {Object} response Response object from {@link DSHttpAdapter#HTTP}.
   * @param {Object} opts Configuration options.
   * @return {(Object|Array)} Deserialized data.
   */
  deserialize (Model, response, opts) {
    opts || (opts = {})
    if (isFunction(opts.deserialize)) {
      return opts.deserialize(Model, response, opts)
    }
    if (isFunction(Model.deserialize)) {
      return Model.deserialize(Model, response, opts)
    }
    if (opts.raw) {
      return response
    }
    return response ? ('data' in response ? response.data : response) : response
  },

  /**
   * Destroy the entity with the given primary key.
   *
   * {@link DSHttpAdapter#beforeDestroy} will be called before calling
   * {@link DSHttpAdapter#DEL}.
   * {@link DSHttpAdapter#afterDestroy} will be called after calling
   * {@link DSHttpAdapter#DEL}.
   *
   * @memberof DSHttpAdapter
   * @instance
   * @param {Object} Model The Model.
   * @param {(string|number)} id Primary key of the entity to destroy.
   * @param {Object} [opts] Configuration options.
   * @param {string} [opts.params] TODO
   * @param {string} [opts.suffix={@link DSHttpAdapter#suffix}] TODO
   * @return {Promise}
   */
  destroy (Model, id, opts) {
    const self = this
    opts = opts ? copy(opts) : {}
    opts.params || (opts.params = {})
    opts.params = self.queryTransform(Model, opts.params, opts)
    opts.suffix || (opts.suffix = Model.suffix)
    opts.op = 'destroy'
    self.dbg(opts.op, Model, id, opts)
    return resolve(self.beforeDestroy(Model, id, opts)).then(function () {
      return self.DEL(
        self.getPath('destroy', Model, id, opts),
        opts
      )
    }).then(function (response) {
      return self.deserialize(Model, response, opts)
    }).then(function (data) {
      return resolve(self.afterDestroy(Model, id, opts, data)).then(function (_data) {
        return _data || data
      })
    })
  },

  /**
   * Destroy the entities that match the selection `query`.
   *
   * {@link DSHttpAdapter#beforeDestroyAll} will be called before calling
   * {@link DSHttpAdapter#DEL}.
   * {@link DSHttpAdapter#afterDestroyAll} will be called after calling
   * {@link DSHttpAdapter#DEL}.
   *
   * @memberof DSHttpAdapter
   * @instance
   * @param {Object} Model The Model.
   * @param {Object} query Selection query.
   * @param {Object} [opts] Configuration options.
   * @param {string} [opts.params] TODO
   * @param {string} [opts.suffix={@link DSHttpAdapter#suffix}] TODO
   * @return {Promise}
   */
  destroyAll (Model, query, opts) {
    const self = this
    query || (query = {})
    opts = opts ? copy(opts) : {}
    opts.params || (opts.params = {})
    deepMixIn(opts.params, query)
    opts.params = self.queryTransform(Model, opts.params, opts)
    opts.suffix || (opts.suffix = Model.suffix)
    opts.op = 'destroyAll'
    self.dbg(opts.op, Model, query, opts)
    return resolve(self.beforeDestroyAll(Model, query, opts)).then(function () {
      return self.DEL(
        self.getPath('destroyAll', Model, null, opts),
        opts
      )
    }).then(function (response) {
      return self.deserialize(Model, response, opts)
    }).then(function (data) {
      return resolve(self.afterDestroyAll(Model, query, opts, data)).then(function (_data) {
        return _data || data
      })
    })
  },

  /**
   * Log an error.
   *
   * @memberof DSHttpAdapter
   * @instance
   * @param {...*} [args] Arguments to log.
   */
  error (...args) {
    if (console) {
      console[typeof console.error === 'function' ? 'error' : 'log'](...args)
    }
  },

  /**
   * Make an Http request using `window.fetch`.
   *
   * @memberof DSHttpAdapter
   * @instance
   * @param {Object} config Request configuration.
   * @param {Object} config.data Payload for the request.
   * @param {string} config.method Http method for the request.
   * @param {Object} config.headers Headers for the request.
   * @param {Object} config.params Querystring for the request.
   * @param {string} config.url Url for the request.
   * @param {Object} [opts] Configuration options.
   */
  fetch (config, opts) {
    const requestConfig = {
      method: config.method,
      // turn the plain headers object into the Fetch Headers object
      headers: new Headers(config.headers)
    }

    if (config.data) {
      requestConfig.body = toJson(config.data)
    }

    return fetch(new Request(buildUrl(config.url, config.params), requestConfig)).then(function (response) {
      response.config = {
        method: config.method,
        url: config.url
      }
      return response.json().then(function (data) {
        response.data = data
        return response
      })
    })
  },

  /**
   * Retrieve the entity with the given primary key.
   *
   * {@link DSHttpAdapter#beforeFind} will be called before calling
   * {@link DSHttpAdapter#GET}.
   * {@link DSHttpAdapter#afterFind} will be called after calling
   * {@link DSHttpAdapter#GET}.
   *
   * @memberof DSHttpAdapter
   * @instance
   * @param {Object} Model The Model.
   * @param {(string|number)} id Primary key of the entity to retrieve.
   * @param {Object} [opts] Configuration options.
   * @param {string} [opts.params] TODO
   * @param {string} [opts.suffix={@link DSHttpAdapter#suffix}] TODO
   * @return {Promise}
   */
  find (Model, id, opts) {
    const self = this
    opts = opts ? copy(opts) : {}
    opts.params || (opts.params = {})
    opts.params = self.queryTransform(Model, opts.params, opts)
    opts.suffix || (opts.suffix = Model.suffix)
    opts.op = 'find'
    self.dbg(opts.op, Model, id, opts)
    return resolve(self.beforeFind(Model, id, opts)).then(function () {
      return self.GET(
        self.getPath('find', Model, id, opts),
        opts
      )
    }).then(function (response) {
      return self.deserialize(Model, response, opts)
    }).then(function (data) {
      return resolve(self.afterFind(Model, id, opts, data)).then(function (_data) {
        return _data || data
      })
    })
  },

  /**
   * Retrieve the entities that match the selection `query`.
   *
   * {@link DSHttpAdapter#beforeFindAll} will be called before calling
   * {@link DSHttpAdapter#GET}.
   * {@link DSHttpAdapter#afterFindAll} will be called after calling
   * {@link DSHttpAdapter#GET}.
   *
   * @memberof DSHttpAdapter
   * @instance
   * @param {Object} Model The Model.
   * @param {Object} query Selection query.
   * @param {Object} [opts] Configuration options.
   * @param {string} [opts.params] TODO
   * @param {string} [opts.suffix={@link DSHttpAdapter#suffix}] TODO
   * @return {Promise}
   */
  findAll (Model, query, opts) {
    const self = this
    query || (query = {})
    opts = opts ? copy(opts) : {}
    opts.params || (opts.params = {})
    opts.suffix || (opts.suffix = Model.suffix)
    opts.op = 'findAll'
    self.dbg(opts.op, Model, query, opts)
    deepMixIn(opts.params, query)
    opts.params = self.queryTransform(Model, opts.params, opts)
    return resolve(self.beforeFindAll(Model, query, opts)).then(function () {
      return self.GET(
        self.getPath('findAll', Model, opts.params, opts),
        opts
      )
    }).then(function (response) {
      return self.deserialize(Model, response, opts)
    }).then(function (data) {
      return resolve(self.afterFindAll(Model, query, opts, data)).then(function (_data) {
        return _data || data
      })
    })
  },

  /**
   * { function_description }
   *
   * @memberof DSHttpAdapter
   * @instance
   * @param {string} url The url for the request.
   * @param {Object} config Request configuration options.
   * @param {Object} [opts] Configuration options.
   * @return {Promise}
   */
  GET (url, config, opts) {
    const self = this
    config || (config = {})
    config.url = url || config.url
    config.method = config.method || 'get'
    return resolve(self.beforeGET(url, config, opts)).then(function (_config) {
      config = _config || config
      return self.HTTP(config, opts)
    }).then(function (response) {
      return resolve(self.afterGET(url, config, opts, response)).then(function (_response) {
        return _response || response
      })
    })
  },

  /**
   * @memberof DSHttpAdapter
   * @instance
   * @param {*} Model { description }
   * @param {*} id { description }
   * @param {boolean} opts { description }
   * @return {string} Full path.
   */
  getEndpoint (Model, id, opts) {
    opts || (opts = {})
    opts.params || (opts.params = {})

    let item
    const parentKey = Model.parentKey
    const endpoint = opts.hasOwnProperty('endpoint') ? opts.endpoint : Model.endpoint
    let parentField = Model.parentField
    const parentDef = Model.parent ? Model.getResource(Model.parent) : undefined
    let parentId = opts.params[parentKey]

    if (parentId === false || !parentKey || !parentDef) {
      if (parentId === false) {
        delete opts.params[parentKey]
      }
      return endpoint
    } else {
      delete opts.params[parentKey]

      if (isString(id) || isNumber(id)) {
        item = Model.get(id)
      } else if (isObject(id)) {
        item = id
      }

      if (item) {
        parentId = parentId || item[parentKey] || (item[parentField] ? item[parentField][parentDef.idAttribute] : null)
      }

      if (parentId) {
        delete opts.endpoint
        const _opts = {}
        forOwn(opts, function (value, key) {
          _opts[key] = value
        })
        _(_opts, parentDef)
        return makePath(this.getEndpoint(parentDef, parentId, _opts, parentId, endpoint))
      } else {
        return endpoint
      }
    }
  },

  /**
   * @memberof DSHttpAdapter
   * @instance
   * @param {string} method TODO
   * @param {*} Model TODO
   * @param {(string|number)?} id TODO
   * @param {Object} opts Configuration options.
   */
  getPath (method, Model, id, opts) {
    const self = this
    opts || (opts = {})
    const args = [
      opts.basePath === undefined ? (Model.basePath === undefined ? self.basePath : Model.basePath) : opts.basePath,
      self.getEndpoint(Model, (isString(id) || isNumber(id) || method === 'create') ? id : null, opts)
    ]
    if (method === 'find' || method === 'update' || method === 'destroy') {
      args.push(id)
    }
    return makePath.apply(utils, args)
  },

  /**
   * Make an Http request.
   *
   * @memberof DSHttpAdapter
   * @instance
   * @param {Object} config Request configuration options.
   * @param {Object} [opts] Configuration options.
   * @return {Promise}
   */
  HTTP (config, opts) {
    const self = this
    const start = new Date()
    opts || (opts = {})
    config = copy(config)
    config = deepMixIn(config, self.httpConfig)
    if (self.forceTrailingSlash && config.url[config.url.length - 1] !== '/') {
      config.url += '/'
    }
    config.method = config.method.toUpperCase()
    const suffix = config.suffix || opts.suffix || self.suffix
    if (suffix && config.url.substr(config.url.length - suffix.length) !== suffix) {
      config.url += suffix
    }

    function logResponse (data) {
      const str = `${start.toUTCString()} - ${config.method.toUpperCase()} ${config.url} - ${data.status} ${(new Date().getTime() - start.getTime())}ms`
      if (data.status >= 200 && data.status < 300) {
        if (self.log) {
          self.dbg('debug', str, data)
        }
        return data
      } else {
        if (self.error) {
          self.error(`'FAILED: ${str}`, data)
        }
        return reject(data)
      }
    }

    if (!self.http) {
      throw new Error('You have not configured this adapter with an http library!')
    }

    return resolve(self.beforeHTTP(config, opts)).then(function (_config) {
      config = _config || config
      if (hasFetch && (self.useFetch || opts.useFetch || !self.http)) {
        return self.fetch(config, opts).then(logResponse, logResponse)
      }
      return self.http(config).then(logResponse, logResponse).catch(function (err) {
        return self.responseError(err, config, opts)
      })
    }).then(function (response) {
      return resolve(self.afterHTTP(config, opts, response)).then(function (_response) {
        return _response || response
      })
    })
  },

  /**
   * Log the provided arguments at the specified leve.
   *
   * @memberof DSHttpAdapter
   * @instance
   * @param {string} level Log level.
   * @param {...*} [args] Arguments to log.
   */
  log (level, ...args) {
    if (level && !args.length) {
      args.push(level)
      level = 'debug'
    }
    if (level === 'debug' && !this.debug) {
      return
    }
    const prefix = `${level.toUpperCase()}: (${this.name})`
    if (console[level]) {
      console[level](prefix, ...args)
    } else {
      console.log(prefix, ...args)
    }
  },

  /**
   * { function_description }
   *
   * @memberof DSHttpAdapter
   * @instance
   * @param {*} url { description }
   * @param {*} data { description }
   * @param {*} config { description }
   * @param {Object} [opts] Configuration options.
   * @return {Promise}
   */
  POST (url, data, config, opts) {
    const self = this
    config || (config = {})
    config.url = url || config.url
    config.data = data || config.data
    config.method = config.method || 'post'
    return resolve(self.beforePOST(url, data, config, opts)).then(function (_config) {
      config = _config || config
      return self.HTTP(config, opts)
    }).then(function (response) {
      return resolve(self.afterPOST(url, data, config, opts, response)).then(function (_response) {
        return _response || response
      })
    })
  },

  /**
   * { function_description }
   *
   * @memberof DSHttpAdapter
   * @instance
   * @param {*} url { description }
   * @param {*} data { description }
   * @param {*} config { description }
   * @param {Object} [opts] Configuration options.
   * @return {Promise}
   */
  PUT (url, data, config, opts) {
    const self = this
    config || (config = {})
    config.url = url || config.url
    config.data = data || config.data
    config.method = config.method || 'put'
    return resolve(self.beforePUT(url, data, config, opts)).then(function (_config) {
      config = _config || config
      return self.HTTP(config, opts)
    }).then(function (response) {
      return resolve(self.afterPUT(url, data, config, opts, response)).then(function (_response) {
        return _response || response
      })
    })
  },

  /**
   * { function_description }
   *
   * @memberof DSHttpAdapter
   * @instance
   * @param {*} Model { description }
   * @param {*} params { description }
   * @param {*} opts { description }
   * @return {*} Transformed params.
   */
  queryTransform (Model, params, opts) {
    opts || (opts = {})
    if (isFunction(opts.queryTransform)) {
      return opts.queryTransform(Model, params, opts)
    }
    if (isFunction(Model.queryTransform)) {
      return Model.queryTransform(Model, params, opts)
    }
    return params
  },

  /**
   * Error handler invoked when the promise returned by {@link DSHttpAdapter#http}
   * is rejected. Default implementation is to just return the error wrapped in
   * a rejected Promise, aka rethrow the error. {@link DSHttpAdapter#http} is
   * called by {@link DSHttpAdapter#HTTP}.
   *
   * @memberof DSHttpAdapter
   * @instance
   * @param {*} err The error that {@link DSHttpAdapter#http} rejected with.
   * @param {*} config The `config` argument that was passed to {@link DSHttpAdapter#HTTP}.
   * @param {*} opts The `opts` argument that was passed to {@link DSHttpAdapter#HTTP}.
   * @return {Promise}
   */
  responseError (err, config, opts) {
    return reject(err)
  },

  /**
   * { function_description }
   *
   * @memberof DSHttpAdapter
   * @instance
   * @param {*} Model { description }
   * @param {*} data { description }
   * @param {*} opts { description }
   * @return {*} Serialized data.
   */
  serialize (Model, data, opts) {
    opts || (opts = {})
    if (isFunction(opts.serialize)) {
      return opts.serialize(Model, data, opts)
    }
    if (isFunction(Model.serialize)) {
      return Model.serialize(Model, data, opts)
    }
    return data
  },

  /**
   * { function_description }
   *
   * @memberof DSHttpAdapter
   * @instance
   * @param {*} Model { description }
   * @param {*} id { description }
   * @param {*} props { description }
   * @param {Object} [opts] Configuration options.
   * @return {Promise}
   */
  update (Model, id, props, opts) {
    const self = this
    opts = opts ? copy(opts) : {}
    opts.params || (opts.params = {})
    opts.params = self.queryTransform(Model, opts.params, opts)
    opts.suffix || (opts.suffix = Model.suffix)
    opts.op = 'update'
    self.dbg(opts.op, Model, id, props, opts)
    return resolve(self.beforeUpdate(Model, id, props, opts)).then(function () {
      return self.PUT(
        self.getPath('update', Model, id, opts),
        self.serialize(Model, props, opts),
        opts
      )
    }).then(function (response) {
      return self.deserialize(Model, response, opts)
    }).then(function (data) {
      return resolve(self.afterUpdate(Model, id, props, opts, data)).then(function (_data) {
        return _data || data
      })
    })
  },

  /**
   * { function_description }
   *
   * @memberof DSHttpAdapter
   * @instance
   * @param {*} Model { description }
   * @param {*} props { description }
   * @param {*} query { description }
   * @param {Object} [opts] Configuration options.
   * @return {Promise}
   */
  updateAll (Model, props, query, opts) {
    const self = this
    query || (query = {})
    opts = opts ? copy(opts) : {}
    opts.params || (opts.params = {})
    deepMixIn(opts.params, query)
    opts.params = self.queryTransform(Model, opts.params, opts)
    opts.suffix || (opts.suffix = Model.suffix)
    opts.op = 'updateAll'
    self.dbg(opts.op, Model, props, query, opts)
    return resolve(self.beforeUpdateAll(Model, props, query, opts)).then(function () {
      return self.PUT(
        self.getPath('updateAll', Model, null, opts),
        self.serialize(Model, props, opts),
        opts
      )
    }).then(function (response) {
      return self.deserialize(Model, response, opts)
    }).then(function (data) {
      return resolve(self.afterUpdateAll(Model, props, query, opts, data)).then(function (_data) {
        return _data || data
      })
    })
  },

  /**
   * Update multiple entities in batch.
   *
   * {@link DSHttpAdapter#beforeUpdateMany} will be called before calling
   * {@link DSHttpAdapter#PUT}.
   * {@link DSHttpAdapter#afterUpdateMany} will be called after calling
   * {@link DSHttpAdapter#PUT}.
   *
   * @memberof DSHttpAdapter
   * @instance
   * @param {Object} Model The Model.
   * @param {Array} models Array of property objects to send as the payload.
   * @param {Object} [opts] Configuration options.
   * @param {string} [opts.params] TODO
   * @param {string} [opts.suffix={@link DSHttpAdapter#suffix}] TODO
   * @return {Promise}
   */
  updateMany (Model, models, opts) {
    const self = this
    opts = opts ? copy(opts) : {}
    opts.params || (opts.params = {})
    opts.params = self.queryTransform(Model, opts.params, opts)
    opts.suffix || (opts.suffix = Model.suffix)
    opts.op = 'updateMany'
    self.dbg(opts.op, Model, models, opts)
    return resolve(self.beforeUpdateMany(Model, models, opts)).then(function () {
      return self.PUT(
        self.getPath('updateMany', Model, null, opts),
        self.serialize(Model, models, opts),
        opts
      )
    }).then(function (response) {
      return self.deserialize(Model, response, opts)
    }).then(function (data) {
      return resolve(self.afterUpdateMany(Model, models, opts, data)).then(function (_data) {
        return _data || data
      })
    })
  }
})

/**
 * Add an Http actions to a Model.
 *
 * @name DSHttpAdapter.addAction
 * @method
 * @param {string} name Name of the new action.
 * @param {Object} [opts] Action configuration
 * @param {string} [opts.adapter]
 * @param {string} [opts.pathname]
 * @param {Function} [opts.request]
 * @param {Function} [opts.response]
 * @param {Function} [opts.responseError]
 * @return {Function} Decoration function, which should be passed the Model to
 * decorate when invoked.
 */
DSHttpAdapter.addAction = function (name, opts) {
  if (!name || !isString(name)) {
    throw new TypeError('action(name[, opts]): Expected: string, Found: ' + typeof name)
  }
  return function (Model) {
    if (Model[name]) {
      throw new Error('action(name[, opts]): ' + name + ' already exists on target!')
    }
    opts.request = opts.request || function (config) { return config }
    opts.response = opts.response || function (response) { return response }
    opts.responseError = opts.responseError || function (err) { return reject(err) }
    Model[name] = function (id, _opts) {
      if (isObject(id)) {
        _opts = id
      }
      _opts = _opts || {}
      let adapter = this.getAdapter(opts.adapter || this.defaultAdapter || 'http')
      let config = {}
      fillIn(config, opts)
      if (!_opts.hasOwnProperty('endpoint') && config.endpoint) {
        _opts.endpoint = config.endpoint
      }
      if (typeof _opts.getEndpoint === 'function') {
        config.url = _opts.getEndpoint(this, _opts)
      } else {
        let args = [
          _opts.basePath || this.basePath || adapter.defaults.basePath,
          adapter.getEndpoint(this, isSorN(id) ? id : null, _opts)
        ]
        if (isSorN(id)) {
          args.push(id)
        }
        args.push(opts.pathname || name)
        config.url = makePath.apply(null, args)
      }
      config.method = config.method || 'GET'
      config.modelName = this.name
      deepMixIn(config)(_opts)
      return resolve(config)
        .then(_opts.request || opts.request)
        .then(function (config) { return adapter.HTTP(config) })
        .then(function (data) {
          if (data && data.config) {
            data.config.modelName = this.name
          }
          return data
        })
        .then(_opts.response || opts.response, _opts.responseError || opts.responseError)
    }
    return Model
  }
}

/**
 * Add multiple Http actions to a Model. See {@link DSHttpAdapter.addAction} for
 * action configuration options.
 *
 * @name DSHttpAdapter.addActions
 * @method
 * @param {Object.<string, Object>} opts Object where the key is an action name
 * and the value is the configuration for the action.
 * @return {Function} Decoration function, which should be passed the Model to
 * decorate when invoked.
 */
DSHttpAdapter.addActions = function (opts) {
  opts || (opts = {})
  return function (Model) {
    forOwn(Model, function (value, key) {
      DSHttpAdapter.addAction(key, value)(Model)
    })
    return Model
  }
}

/**
 * Alternative to ES6 class syntax for extending `DSHttpAdapter`.
 *
 * __ES6__:
 * ```javascript
 * class MyHttpAdapter extends DSHttpAdapter {
 *   deserialize (Model, data, opts) {
 *     const data = super.deserialize(Model, data, opts)
 *     data.foo = 'bar'
 *     return data
 *   }
 * }
 * ```
 *
 * __ES5__:
 * ```javascript
 * var instanceProps = {
 *   // override deserialize
 *   deserialize: function (Model, data, opts) {
 *     var Ctor = this.constructor
 *     var superDeserialize = (Ctor.__super__ || Object.getPrototypeOf(Ctor)).deserialize
 *     // call the super deserialize
 *     var data = superDeserialize(Model, data, opts)
 *     data.foo = 'bar'
 *     return data
 *   },
 *   say: function () { return 'hi' }
 * }
 * var classProps = {
 *   yell: function () { return 'HI' }
 * }
 *
 * var MyHttpAdapter = DSHttpAdapter.extend(instanceProps, classProps)
 * var adapter = new MyHttpAdapter()
 * adapter.say() // "hi"
 * MyHttpAdapter.yell() // "HI"
 * ```
 *
 * @name DSHttpAdapter.extend
 * @method
 * @param {Object} [instanceProps] Properties that will be added to the
 * prototype of the subclass.
 * @param {Object} [classProps] Properties that will be added as static
 * properties to the subclass itself.
 * @return {Object} Subclass of `DSHttpAdapter`.
 */
DSHttpAdapter.extend = extend

/**
 * Details of the current version of the `js-data-http` module.
 *
 * @name DSHttpAdapter.version
 * @type {Object}
 * @property {string} version.full The full semver value.
 * @property {number} version.major The major version number.
 * @property {number} version.minor The minor version number.
 * @property {number} version.patch The patch version number.
 * @property {(string|boolean)} version.alpha The alpha version value,
 * otherwise `false` if the current version is not alpha.
 * @property {(string|boolean)} version.beta The beta version value,
 * otherwise `false` if the current version is not beta.
 */
DSHttpAdapter.version = {
  full: '<%= pkg.version %>',
  major: parseInt('<%= major %>', 10),
  minor: parseInt('<%= minor %>', 10),
  patch: parseInt('<%= patch %>', 10),
  alpha: '<%= alpha %>' !== 'false' ? '<%= alpha %>' : false,
  beta: '<%= beta %>' !== 'false' ? '<%= beta %>' : false
}

/**
 * Registered as `js-data-http` in NPM and Bower. The build of `js-data-http`
 * that works on Node.js is registered in NPM as `js-data-http-node`. The build
 * of `js-data-http` that does not bundle `axios` is registered in NPM and Bower
 * as `js-data-fetch`.
 *
 * __Script tag__:
 * ```javascript
 * window.DSHttpAdapter
 * ```
 * __CommonJS__:
 * ```javascript
 * var DSHttpAdapter = require('js-data-http')
 * ```
 * __ES6 Modules__:
 * ```javascript
 * import DSHttpAdapter from 'js-data-http'
 * ```
 * __AMD__:
 * ```javascript
 * define('myApp', ['js-data-http'], function (DSHttpAdapter) { ... })
 * ```
 *
 * @module js-data-http
 */

module.exports = DSHttpAdapter