UNPKG

sardines-built-in-services

Version:
224 lines (197 loc) 6.37 kB
/* * Created on 2020-02-18 * Author: Robin <robin@naturewake.com> */ import * as redis from 'redis' // import { ReplyError } from 'redis' // import { promisify } from 'util' import { StorageBase } from './base' // Server settings export interface RedisServerSettings { host?: string port?: number password?: string path?: string url?: string string_numbers?: boolean return_buffers?: boolean detect_buffers?: boolean enable_offline_queue?: boolean retry_unfulfilled_commands?: boolean family?: string disable_resubscribing?: boolean retry_strategy?: any db?: number [key:string]: any } const defaultServerSettings:RedisServerSettings = { host: '127.0.0.1', port: 6379, return_buffers: false, detect_buffers: false, family: 'IPv4', retry_strategy: null, db: 0 } const mergeDefaultServerSettings = (serverSettings: RedisServerSettings):RedisServerSettings => { const newSettings = Object.assign({}, defaultServerSettings, serverSettings) const redundentKeys = ['host', 'port'] if (typeof serverSettings.host === 'undefined') { for (const key of redundentKeys) { delete newSettings[key] } } return newSettings } // Operation options export enum RedisDataType { string = 'string', number = 'number', boolean = 'boolean', object = 'object' } export interface RedisOperationOptions { dataType?: RedisDataType expire?: number } export const defaultRedisOperationOptions = { dataType: RedisDataType.string, expire: -1 } const mergeDefaultRedisOperationOptions = (options?: RedisOperationOptions): RedisOperationOptions => { const newOptions = Object.assign({}, defaultRedisOperationOptions, options) return newOptions } // the class export class RedisCache extends StorageBase{ protected serverSettings: any protected client: any protected max_reconnect_retry_time: number = 1000 * 60 * 60 protected max_reconnect_attempts: number = 10 protected max_reconnect_interval: number = 5000 constructor(serverSettings: RedisServerSettings) { super() this.serverSettings = mergeDefaultServerSettings(serverSettings) } get connected() { return this.client && this.client.connected } async connect(serverSettings: RedisServerSettings|null = null): Promise<any> { if (this.connected) return if (serverSettings) { this.serverSettings = mergeDefaultServerSettings(serverSettings) } // Set default retry_strategy if (!this.serverSettings.retry_strategy && this.serverSettings.retry_strategy !== false) { const that = this this.serverSettings.retry_strategy = function(options:any) { if (options.error && options.error.code === "ECONNREFUSED") { // End reconnecting on a specific error and flush all commands with // a individual error return new Error("The server refused the connection"); } if (options.total_retry_time > that.max_reconnect_retry_time) { // End reconnecting after a specific timeout and flush all commands // with a individual error return new Error("Retry time exhausted"); } if (options.attempt > that.max_reconnect_attempts) { // End reconnecting with built in error return undefined; } // reconnect after return Math.min(options.attempt * 100, that.max_reconnect_interval); } } // connect to redis server this.client = redis.createClient(this.serverSettings) // When connecting to a Redis server that requires authentication, the AUTH command must be sent as the first command after connecting. return new Promise((resolve, reject) => { if (this.serverSettings.password) { this.client.auth(this.serverSettings.password, (err: any) => { if (err) { console.error('redis auth error:', err) reject(err) } }) } if (typeof this.serverSettings.db !== 'undefined') { try { this.client.select(this.serverSettings.db, (err:any) => { if (err) { console.error('redis select db error:', err) reject(err) } }) } catch (e) { reject(e) } } this.client.on('error', (err:any) => { console.error('redis connection error:', err) reject(err) }) this.client.on('ready', () => { resolve(this.client.connected) }) }) } async get(key:string, options?: RedisOperationOptions): Promise<any> { const theOptions = mergeDefaultRedisOperationOptions(options) return new Promise((resolve, reject) => { let handler = this.client.get if (theOptions.dataType === RedisDataType.object) { handler = this.client.hgetall } const args = [key, (err:any, res:any) => { if (err) reject(err) switch(theOptions.dataType) { case RedisDataType.number: resolve((res === null) ? Number.NaN : Number(res)) break case RedisDataType.boolean: resolve((res === 'true'?true:false)) break default: resolve(res) break } }] handler.apply(this.client, args) }) } async del(key: string): Promise<any> { return new Promise((resolve, reject) => { this.client.del(key, (err:any, res:any) => { if (err) reject(err) resolve(res) }) }) } async set(key:string, obj: any, options?: RedisOperationOptions): Promise<any> { const theOptions = mergeDefaultRedisOperationOptions(options) return new Promise((resolve, reject) => { const args: any[] = [key] let handler = this.client.set if (theOptions.dataType === RedisDataType.object || (typeof obj === 'object' && Object.keys(obj).length > 0)) { for (let k of Object.keys(obj)) { args.push(k) args.push(obj[k]) } handler = this.client.hmset } else { args.push(obj) } if (typeof theOptions.expire !== 'undefined' && theOptions.expire > 0) { args.push('EX') args.push(theOptions.expire) } args.push((err: any, res: any) => { if (err) reject(err) resolve(res) }) handler.apply(this.client, args) }) } }