// eslint-disable-next-line n/no-deprecated-api
const TRedisClient = process.binding('synode_redis').TSMRedisClient
const ubVersionNum = process.version.slice(1).split('.').map(v => parseInt(v, 10)).reduce((acum, v) => acum * 1000 + v)
/**
* @module @unitybase/redis
* @example
const redis = require('@unitybase/redis')
const redisConn = redis.getDefaultRedisConnection()
// Set key `key1` to hold the string value `val1` with 3 second expire
redisConn.commands('set', 'key1', 'val1', 'EX', 3)
// get time-to-live (TTL0 of key1
const TTL = redisConn.commands('TTL', 'key1')
console.log('key1 will live', TTL, 'sec')
// get value
let val = redisConn.commands('get', 'key1')
console.log('Value for key1 is', val)
console.log('Waiting for 3 second...')
sleep(3100)
// now value is expired
val = redisConn.commands('get', 'key1')
console.log('Expired value for key1 is', val) // val is null
*/
class RedisClient {
/**
* Creates a lazy connection to the redis server.
* Actual TCP connection established on first command.
*
* Automatically reconnect and repeating a command in case of connection lost.
* If reconnect fails in reconnectTimeout - throws.
*
* @param {object} connSettings
* @param {string} [connSettings.host='127.0.0.1']
* @param {string} [connSettings.port='6379']
* @param {number} [connSettings.dbIndex=0] Active database index. See https://redis.io/commands/select/
* @param {number} [connSettings.reconnectTimeout=30000]
* @param {string} [connSettings.password=''] For UB server > 5.24.23
* @param {string} [connSettings.username=''] For UB server > 5.24.23
*/
constructor (connSettings) {
this._connSettings = Object.assign({
host: '127.0.0.1',
port: '6379',
dbIndex: 0,
reconnectTimeout: 30000,
password: '',
username: ''
}, connSettings)
this._native = new TRedisClient()
if (ubVersionNum <= 5024023) {
this._native.initialize(this._connSettings.host, this._connSettings.port, this._connSettings.dbIndex)
} else {
this._native.initialize(this._connSettings.host, this._connSettings.port, this._connSettings.dbIndex,
'ub', this._connSettings.password, this._connSettings.username)
}
}
/**
* Send a series of commands and return a redis response.
* Depending on command response can be a string, integer, array of <string|array<string>>
*
* For blocking operations with timeout reached returns null.
* Throw in case of protocol level error or connection lost and reconnect fails in connSettings.reconnectTimeout ms.
*
* @param {...string|number} args
* @returns {null | string | number | Array<string | Array<string>>}
*/
commands (...args) {
let resp
try {
resp = this._native.commands(...args)
return resp
} catch (e) {
console.warn(`Reconnecting to redis on ${this._connSettings.host}:${this._connSettings.port} with timeout ${this._connSettings.reconnectTimeout}...`)
this._native.reconnect(this._connSettings.reconnectTimeout)
console.log('Reconnected')
}
console.log('Repeat redis command after reconnect')
resp = this._native.commands(...args) // repeat a command. Throws if fails again
}
/**
* PRIVATE method to send a raw command to redis. Reconnect is not supported
*
* Raw command example: '*2\r\n$7\r\nhgetall\r\n$8\r\ntestHash\r\n`
*
* @private
* @param {string} cmd
* @returns {null | string | number | Array<string | Array<string>>}
*/
rawCommand (cmd) {
return this._native.rawCommand(cmd)
}
get ioError () {
return this._native.ioError
}
get ioErrorText () {
return this._native.ioErrorText
}
}
let defaultClient
/**
* Return per-thread instance of redis client connected to server using configuration from `ubConfig.application.redis`.
* To be used inside server-side thread
*
* @example
const redis = require('@unitybase/redis')
const redisConn = redis.getDefaultRedisConnection()
console.log(redisConn.commands('PING')) // PONG
* @returns {RedisClient}
*/
function getDefaultRedisConnection () {
if (!defaultClient) {
// eslint-disable-next-line no-undef
defaultClient = new RedisClient(App.serverConfig.redis)
}
return defaultClient
}
module.exports = {
getDefaultRedisConnection,
RedisClient
}