UNPKG

redis-abstraction

Version:

A Redis client pool with abstraction to different Redis libraries.

157 lines (156 loc) 7.43 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.IORedisClientPool = void 0; const node_crypto_1 = __importDefault(require("node:crypto")); const node_fs_1 = __importDefault(require("node:fs")); function createInstance(c, args = []) { return new c(...args); } /** * A Redis Client Pool implementation using ioredis library * @template redisConnectionType Type of the ioredis connection (Redis or Cluster) */ class IORedisClientPool { constructor(redisConnectionCreator, idlePoolSize = 6, nodeFSModule = node_fs_1.default, nodeCryptoModule = node_crypto_1.default) { this.nodeFSModule = nodeFSModule; this.nodeCryptoModule = nodeCryptoModule; this.filenameToCommand = new Map(); this.totalConnectionCounter = 0; this.poolRedisClients = Array.from({ length: idlePoolSize }, (_) => redisConnectionCreator()); this.totalConnectionCounter += idlePoolSize; this.activeRedisClients = new Map(); this.redisConnectionCreator = redisConnectionCreator; this.idlePoolSize = idlePoolSize; } initialize() { // ioredis creates connection lazily on first command, so we don't need to do anything here. return Promise.resolve(); } generateUniqueToken(prefix) { return `${prefix}-${this.nodeCryptoModule.randomUUID()}`; } shutdown() { return __awaiter(this, void 0, void 0, function* () { const waitHandles = [...this.poolRedisClients, ...Array.from(this.activeRedisClients.values())] .map((_) => __awaiter(this, void 0, void 0, function* () { yield _.quit(); _.disconnect(); })); yield Promise.allSettled(waitHandles); this.poolRedisClients = []; this.activeRedisClients.clear(); this.totalConnectionCounter = 0; }); } acquire(token) { return __awaiter(this, void 0, void 0, function* () { if (!this.activeRedisClients.has(token)) { const availableClient = this.poolRedisClients.pop() || (() => { this.totalConnectionCounter += 1; return this.redisConnectionCreator(); })(); this.activeRedisClients.set(token, availableClient); } }); } release(token) { return __awaiter(this, void 0, void 0, function* () { const releasedClient = this.activeRedisClients.get(token); if (releasedClient == undefined) { return; } this.activeRedisClients.delete(token); if (this.poolRedisClients.length < this.idlePoolSize) { this.poolRedisClients.push(releasedClient); } else { yield releasedClient.quit(); releasedClient.disconnect(); } }); } run(token, commandArgs) { return __awaiter(this, void 0, void 0, function* () { const redisClient = this.activeRedisClients.get(token); if (redisClient == undefined) { throw new Error("Please acquire a client with proper token"); } return yield redisClient.call(commandArgs.shift(), ...commandArgs); }); } pipeline(token_1, commands_1) { return __awaiter(this, arguments, void 0, function* (token, commands, transaction = true) { var _a; const redisClient = this.activeRedisClients.get(token); if (redisClient == undefined) { throw new Error("Please acquire a client with proper token"); } const result = (_a = (transaction === true ? yield redisClient.multi(commands).exec() : yield redisClient.pipeline(commands).exec())) !== null && _a !== void 0 ? _a : []; return result.map(r => { let err = r[0]; if (err != null) { throw err; } return r[1]; }); }); } script(token, filePath, keys, args) { return __awaiter(this, void 0, void 0, function* () { const redisClient = this.activeRedisClients.get(token); if (redisClient == undefined) { throw new Error("Please acquire a client with proper token"); } let command = this.filenameToCommand.get(filePath); if (command == null || redisClient[command] == null) { const contents = yield this.nodeFSModule.promises.readFile(filePath, { encoding: "utf-8" }); command = this.MD5Hash(contents); redisClient.defineCommand(command, { lua: contents }); this.filenameToCommand.set(filePath, command); } return yield redisClient[command](keys.length, keys, args); }); } info() { const returnObj = { "Idle Size": this.idlePoolSize, "Current Active": this.activeRedisClients.size, "Pooled Connection": this.poolRedisClients.length, "Peak Connections": this.totalConnectionCounter }; this.totalConnectionCounter = 0; return returnObj; } MD5Hash(value) { return this.nodeCryptoModule.createHash('md5').update(value).digest('hex'); } static IORedisClientClusterFactory(connectionDetails, redisClass, clusterClass, parseURLFunction, instanceInjection = createInstance) { const distinctConnections = new Set(connectionDetails); if (distinctConnections.size === 0) { throw new Error("Incorrect or Invalid Connection details, cannot be empty"); } if (connectionDetails.length > distinctConnections.size || distinctConnections.size > 1) { const parsedRedisURl = parseURLFunction(connectionDetails[0]); //Assuming all have same password(they should have finally its a cluster) const awsElasticCacheOptions = { dnsLookup: (address, callback) => callback(null, address), redisOptions: { tls: connectionDetails[0].startsWith("rediss:") == true ? {} : undefined, password: parsedRedisURl.password, maxRedirections: 32 }, }; return instanceInjection(clusterClass, [Array.from(distinctConnections.values()), awsElasticCacheOptions]); } else { return instanceInjection(redisClass, [connectionDetails[0]]); } } [Symbol.asyncDispose]() { return this.shutdown(); } } exports.IORedisClientPool = IORedisClientPool;