redis-collections
Version:
Collection based views for Redis
158 lines (142 loc) • 6.34 kB
JavaScript
const debug = require('debug')('redis-collections')
class Store {
constructor(redisClient) {
this.redisClient = redisClient;
this.isIterableCommand = {'sscan': 1, 'zscan': 1};
}
injectInto(object) {
const store = this
object.promise = function (commands) {
return store.promise(commands)
}
}
promise(command) {
if (command == null) throw new Error("Missing command")
if (arguments.length > 1) throw new Error("Use array or object for multiple commands")
debug("promise: ", command)
if (this._isCommand(command)) {
if (this._getCommand(command) === 'iterate') return this._promiseIteration(command)
else return this._promiseList([command.splice(1)]).then(list => list[0])
}
else if (typeof command !== 'object') return Promise.resolve(command)
else return this._promiseStructure(command)
}
_isCommand(object) {
return Array.isArray(object) && object.length && object[0] === 'redis'
}
_getCommand(object) {
return object[1]
}
_promiseList(commands) {
debug("promiseList: ", commands)
const redisClient = this.redisClient
return new Promise(function (resolve, reject) {
redisClient.multi(commands).exec(function (err, data) {
if (err) {
console.error("exception at running " + JSON.stringify(commands, null, 2), err)
return reject(err)
}
else return resolve(data)
})
})
}
_promiseStructure(structure) {
if (structure == null) throw new Error("Missing command")
if (arguments.length > 1) throw new Error("Expect a single command or command structure")
debug("promiseStructure: ", structure)
const commandsWithPath = []
this._findCommands(commandsWithPath, [], structure)
const cloneStructure = this._cloneStructure(structure)
if (commandsWithPath.length == 0) return Promise.resolve(cloneStructure)
const commandList = commandsWithPath.map(c => c[c.length - 1].slice(1))
return this._promiseList(commandList).then((results) => {
for (let i = 0, listLen = commandsWithPath.length; i < listLen; i++) {
let location = cloneStructure
const path = commandsWithPath[i]
for (let p = 0, pathLen = path.length - 2; p < pathLen; p++) location = location[path[p]]
location[path[path.length - 2]] = results[i]
}
return cloneStructure
})
}
_findCommands(commands, path, structure) {
if (this._isCommand(commands)) throw new Error("Not a structure.")
else if (Array.isArray(structure)) {
for (let k = 0; k < structure.length; k++) {
const value = structure[k]
if (this._isCommand(value)) {
if (this._getCommand(value) == 'iterate') throw new Error("Cannot iterate in a structure.")
commands.push(path.concat([k, value]))
}
else this._findCommands(commands, path.concat(k), value)
}
}
else if (typeof structure === 'object') {
for (const k in structure) {
const value = structure[k]
if (this._isCommand(value)) {
if (this._getCommand(value) == 'iterate') throw new Error("Cannot iterate in a structure.")
commands.push(path.concat([k, value]))
}
else this._findCommands(commands, path.concat(k), value)
}
}
}
_cloneStructure(structure) {
if (this._isCommand(structure)) return null
else if (Array.isArray(structure)) {
const clone = []
for (let k = 0, len = structure.length; k < len; k++)
clone.push(this._cloneStructure(structure[k]))
return clone
}
else if (typeof structure === 'object') {
const clone = {}
for (const k in structure) clone[k] = this._cloneStructure(structure[k])
return clone
}
else return structure
}
_promiseIteration(command) {
debug("promiseIteration: ", command)
command = command.slice(2)
if (command.length < 2 || !this.isIterableCommand[command[0].toLowerCase()])
return Promise.reject(new Error("Not itererable command: " + command));
const scanType = command[0];
const key = command[1];
const value = command[command.length - 1];
const count = command[command.length - 2];
const isCount = count.toLowerCase && (count.toLowerCase() === 'count') && !isNaN(value);
const countValue = isCount ? +value : undefined;
const client = this.redisClient
const scanMethod = client[scanType]
const args = [key, '0'].concat(countValue ? ['COUNT', countValue] : [])
let results = []
return new Promise(function (resolve, reject) {
const repeat = function (fromCursor) {
args[1] = fromCursor
scanMethod.call(client, args, function (err, res) {
if (err) return reject(err)
const nextCursor = res[0]
const values = res[1]
if (values.length > 0) {
results = results.concat(values)
}
const done = nextCursor === '0'
if (done) {
const duplicated = {}
for (let i = 0; i < results.length; i++) {
const value = results[i]
if (!duplicated[value]) duplicated[value] = 1
else results.splice(i--, 1)
}
return resolve(results)
}
return repeat(nextCursor)
})
}
repeat('0')
})
}
}
module.exports = Store