Source: src/index.js

import redis from 'redis'
import {
  Query,
  utils
} from 'js-data'
import {Adapter} from 'js-data-adapter'
import underscore from 'mout/string/underscore'
import guid from 'mout/random/guid'

function getPath (mapper) {
  if (mapper) {
    return mapper.table || underscore(mapper.name)
  }
}

const DEFAULTS = {
  /**
   * Redis host.
   *
   * @name RedisAdapter#host
   * @type {string}
   * @default "127.0.0.1"
   */
  host: '127.0.0.1',

  /**
   * Redis port.
   *
   * @name RedisAdapter#port
   * @type {number}
   * @default 6379
   */
  port: 6379
}

/**
 * RedisAdapter class.
 *
 * @example
 * // Use Container instead of DataStore on the server
 * import {Container} from 'js-data'
 * import RedisAdapter from 'js-data-redis'
 *
 * // Create a store to hold your Mappers
 * const store = new Container()
 *
 * // Create an instance of RedisAdapter with default settings
 * const adapter = new RedisAdapter()
 *
 * // Mappers in "store" will use the Redis adapter by default
 * store.registerAdapter('redis', adapter, { default: true })
 *
 * // Create a Mapper that maps to a "user" table
 * store.defineMapper('user')
 *
 * @class RedisAdapter
 * @extends Adapter
 * @param {Object} [opts] Configuration options.
 * @param {boolean} [opts.debug=false] See {@link Adapter#debug}.
 * @param {boolean} [opts.host="127.0.0.1"] See {@link RedisAdapter#host}.
 * @param {boolean} [opts.port=6379] See {@link RedisAdapter#port}.
 * @param {boolean} [opts.raw=false] See {@link Adapter#raw}.
 */
export function RedisAdapter (opts) {
  const self = this
  utils.classCallCheck(self, RedisAdapter)
  opts || (opts = {})
  utils.fillIn(opts, DEFAULTS)
  Adapter.call(self, opts)

  /**
   * The Redis client used by this adapter. Use this directly when you need to
   * write custom queries.
   *
   * @name RedisAdapter#client
   * @type {Object}
   */
  self.client = redis.createClient(opts.port, opts.host, opts)
}

// Setup prototype inheritance from Adapter
RedisAdapter.prototype = Object.create(Adapter.prototype, {
  constructor: {
    value: RedisAdapter,
    enumerable: false,
    writable: true,
    configurable: true
  }
})

Object.defineProperty(RedisAdapter, '__super__', {
  configurable: true,
  value: Adapter
})

/**
 * Alternative to ES6 class syntax for extending `RedisAdapter`.
 *
 * @example <caption>Using the ES2015 class syntax.</caption>
 * class MyRedisAdapter extends RedisAdapter {...}
 * const adapter = new MyRedisAdapter()
 *
 * @example <caption>Using {@link RedisAdapter.extend}.</caption>
 * var instanceProps = {...}
 * var classProps = {...}
 *
 * var MyRedisAdapter = RedisAdapter.extend(instanceProps, classProps)
 * var adapter = new MyRedisAdapter()
 *
 * @method RedisAdapter.extend
 * @static
 * @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 {Constructor} Subclass of `RedisAdapter`.
 */
RedisAdapter.extend = utils.extend

utils.addHiddenPropsToTarget(RedisAdapter.prototype, {
  getIds (mapper) {
    const self = this
    return new Promise(function (resolve, reject) {
      return self.client.SMEMBERS(getPath(mapper), function (err, ids) {
        if (err) {
          return reject(err)
        }
        return resolve(ids.filter(function (id) {
          return id !== 'undefined'
        }))
      })
    })
  },

  GET (path) {
    const self = this
    return new Promise(function (resolve, reject) {
      return self.client.GET(path, function (err, value) {
        return err ? reject(err) : resolve(utils.isUndefined(value) ? value : JSON.parse(value))
      })
    })
  },

  _count (mapper, query) {
    const self = this
    return self._findAll(mapper, query).then(function (result) {
      result[0] = result[0].length
      return result
    })
  },

  _create (mapper, props) {
    const self = this
    const idAttribute = mapper.idAttribute
    props || (props = {})
    props = utils.plainCopy(props)

    let id = utils.get(props, idAttribute)
    if (utils.isUndefined(id)) {
      id = guid()
      utils.set(props, idAttribute, id)
    }

    return new Promise(function (resolve, reject) {
      props = JSON.stringify(props)
      return self.client
        .multi()
        .set(`${getPath(mapper)}-${id}`, props)
        .SADD(getPath(mapper), id)
        .exec(function (err) {
          return err ? reject(err) : resolve([JSON.parse(props), {}])
        })
    })
  },

  _createMany (mapper, props) {
    const self = this
    const idAttribute = mapper.idAttribute
    props || (props = [])
    props = utils.plainCopy(props)

    const _path = getPath(mapper)
    return Promise.all(props.map(function (_props) {
      return new Promise(function (resolve, reject) {
        let id = utils.get(_props, idAttribute)
        if (utils.isUndefined(id)) {
          id = guid()
          utils.set(_props, idAttribute, id)
        }
        _props = JSON.stringify(_props)
        return self.client
          .multi()
          .set(`${_path}-${id}`, _props)
          .SADD(_path, id)
          .exec(function (err) {
            return err ? reject(err) : resolve(JSON.parse(_props))
          })
      })
    })).then(function (records) {
      return [records, {}]
    })
  },

  _destroy (mapper, id) {
    const self = this

    return new Promise(function (resolve, reject) {
      return self.client
        .multi()
        .DEL(`${getPath(mapper)}-${id}`)
        .SREM(getPath(mapper), id)
        .exec(function (err) {
          return err ? reject(err) : resolve([undefined, {}])
        })
    })
  },

  _destroyAll (mapper, query) {
    const self = this

    return self.findAll(mapper, query, { raw: false }).then(function (records) {
      const _path = getPath(mapper)
      const idAttribute = mapper.idAttribute
      return Promise.all(records.map(function (record) {
        return new Promise(function (resolve, reject) {
          const id = utils.get(record, idAttribute)
          return self.client
            .multi()
            .DEL(`${_path}-${id}`)
            .SREM(_path, id)
            .exec(function (err) {
              return err ? reject(err) : resolve()
            })
        })
      }))
    }).then(function () {
      return [undefined, {}]
    })
  },

  _find (mapper, id) {
    return this.GET(`${getPath(mapper)}-${id}`).then(function (record) {
      return [record, {}]
    })
  },

  _findAll (mapper, query) {
    const self = this
    query || (query = {})

    return self.getIds(mapper).then(function (ids) {
      const path = getPath(mapper)
      return Promise.all(ids.map(function (id) {
        return self.GET(`${path}-${id}`)
      }))
    }).then(function (_records) {
      const _query = new Query({
        index: {
          getAll () {
            return _records
          }
        }
      })
      return [_query.filter(query).run(), {}]
    })
  },

  _sum (mapper, field, query) {
    const self = this
    return self._findAll(mapper, query).then(function (result) {
      let sum = 0
      result[0].forEach(function (record) {
        sum += utils.get(record, field) || 0
      })
      result[0] = sum
      return result
    })
  },

  _updateHelper (mapper, records, props) {
    const self = this
    const idAttribute = mapper.idAttribute
    const _path = getPath(mapper)
    if (utils.isObject(records) && !utils.isArray(records)) {
      records = [records]
    }
    return Promise.all(records.map(function (record) {
      utils.deepMixIn(record, props)
      return new Promise(function (resolve, reject) {
        self.client.set(`${_path}-${record[idAttribute]}`, JSON.stringify(record), function (err) {
          return err ? reject(err) : resolve(record)
        })
      })
    }))
  },

  _update (mapper, id, props) {
    const self = this
    props || (props = {})

    return self.GET(`${getPath(mapper)}-${id}`).then(function (record) {
      if (!record) {
        throw new Error('Not Found')
      }
      return self._updateHelper(mapper, record, props)
    }).then(function (records) {
      return [records[0], {}]
    })
  },

  _updateAll (mapper, props, query) {
    const self = this
    props || (props = {})
    query || (query = {})

    return self.findAll(mapper, query, { raw: false }).then(function (records) {
      return self._updateHelper(mapper, records, props)
    }).then(function (records) {
      return [records, {}]
    })
  },

  _updateMany (mapper, records) {
    const self = this
    const idAttribute = mapper.idAttribute
    const _path = getPath(mapper)
    records || (records = [])

    return Promise.all(records.map(function (record) {
      return self.GET(`${_path}-${utils.get(record, idAttribute)}`).then(function (_record) {
        if (!_record) {
          return
        }
        return self._updateHelper(mapper, _record, record).then(function (records) {
          return records[0]
        })
      })
    })).then(function (records) {
      return [records.filter(function (record) {
        return record
      }), {}]
    })
  }
})

/**
 * Details of the current version of the `js-data-redis` module.
 *
 * @example
 * import {version} from 'js-data-redis'
 * console.log(version.full)
 *
 * @name module:js-data-redis.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.
 */
export const version = '<%= version %>'

/**
 * {@link RedisAdapter} class.
 *
 * @example
 * import {RedisAdapter} from 'js-data-redis'
 * const adapter = new RedisAdapter()
 *
 * @name module:js-data-redis.RedisAdapter
 * @see RedisAdapter
 * @type {Constructor}
 */

/**
 * Registered as `js-data-redis` in NPM.
 *
 * @example <caption>Install from NPM</caption>
 * npm i --save js-data-redis@beta js-data@beta redis
 *
 * @example <caption>Load via CommonJS</caption>
 * var RedisAdapter = require('js-data-redis').RedisAdapter
 * var adapter = new RedisAdapter()
 *
 * @example <caption>Load via ES2015 Modules</caption>
 * import {RedisAdapter} from 'js-data-redis'
 * const adapter = new RedisAdapter()
 *
 * @module js-data-redis
 */

export default RedisAdapter