UNPKG

@digicroz/node-backend-utils

Version:

Backend utilities for Node.js applications - Redis client wrappers and more utilities for TypeScript/JavaScript projects

485 lines (481 loc) 13.2 kB
'use strict'; var redis = require('redis'); // src/redis/redisBase.ts var RedisDatabase = class _RedisDatabase { constructor() { this.client = null; this.isConnecting = false; this.isConnected = false; this.initializationAttempted = false; this.redisUrl = null; } static getInstance() { if (!_RedisDatabase.instance) { _RedisDatabase.instance = new _RedisDatabase(); } return _RedisDatabase.instance; } async initialize(config) { if (this.initializationAttempted) { return; } this.initializationAttempted = true; this.redisUrl = config.url; try { await this.connect(); console.log("Redis initialized successfully at application startup"); } catch (error) { console.warn( "Redis initialization failed (non-critical), app will continue without Redis:", error ); } } getClient() { if (this.isConnected && this.client) { return this.client; } return null; } async connect() { if (this.isConnecting || this.isConnected && this.client) { return this.client; } if (!this.redisUrl) { throw new Error( "Redis URL is required. Call initialize() with config first." ); } this.isConnecting = true; try { this.client = redis.createClient({ url: this.redisUrl }); this.client.on("error", (err) => { console.warn("Redis Client Error (non-critical):", err); this.isConnected = false; }); this.client.on("connect", () => { console.log("Redis Client Connected"); this.isConnected = true; }); this.client.on("disconnect", () => { console.log("Redis Client Disconnected"); this.isConnected = false; }); await this.client.connect(); this.isConnected = true; this.isConnecting = false; return this.client; } catch (error) { console.warn("Failed to connect to Redis (non-critical):", error); this.isConnecting = false; this.isConnected = false; this.client = null; throw error; } } async disconnect() { try { if (this.client && this.isConnected) { await this.client.quit(); this.isConnected = false; this.client = null; } } catch (error) { console.warn("Error disconnecting from Redis (non-critical):", error); this.isConnected = false; this.client = null; } } isClientConnected() { return this.isConnected && this.client !== null; } async safeExecute(operation) { try { const client = this.getClient(); if (!client) { return null; } return await operation(client); } catch (error) { console.warn("Redis operation failed (non-critical):", error); return null; } } getStatus() { return { isConnected: this.isConnected, isConnecting: this.isConnecting, initializationAttempted: this.initializationAttempted }; } }; var redisBase = RedisDatabase.getInstance(); // src/redis/redisGenericClient.ts var RedisGenericClient = class { constructor(config) { this.keyPrefix = config.prefix.endsWith(":") ? config.prefix : `${config.prefix}:`; this.isEnabled = config.isEnabled; } addPrefix(key) { if (key.startsWith(this.keyPrefix)) { return key; } return `${this.keyPrefix}${key}`; } removePrefix(key) { if (key.startsWith(this.keyPrefix)) { return key.slice(this.keyPrefix.length); } return key; } isClientEnabled() { return this.isEnabled; } isClientConnected() { if (!this.isEnabled) { return false; } return redisBase.isClientConnected(); } getStatus() { return { ...redisBase.getStatus(), isEnabled: this.isEnabled, prefix: this.keyPrefix }; } async set(key, value) { if (!this.isEnabled) { return null; } const prefixedKey = this.addPrefix(key); return redisBase.safeExecute(async (client) => { return await client.set(prefixedKey, value); }); } async setObj(key, value) { if (!this.isEnabled) { return null; } try { const serializedValue = JSON.stringify(value); const prefixedKey = this.addPrefix(key); return redisBase.safeExecute(async (client) => { return await client.set(prefixedKey, serializedValue); }); } catch (error) { console.error("Error serializing object for Redis:", error); return null; } } async setEx(key, seconds, value) { if (!this.isEnabled) { return null; } const prefixedKey = this.addPrefix(key); return redisBase.safeExecute(async (client) => { return await client.setEx(prefixedKey, seconds, value); }); } async setObjEx(key, seconds, value) { if (!this.isEnabled) { return null; } try { const serializedValue = JSON.stringify(value); const prefixedKey = this.addPrefix(key); return redisBase.safeExecute(async (client) => { return await client.setEx(prefixedKey, seconds, serializedValue); }); } catch (error) { console.error("Error serializing object for Redis setEx:", error); return null; } } async get(key) { if (!this.isEnabled) { return null; } const prefixedKey = this.addPrefix(key); return redisBase.safeExecute(async (client) => { return await client.get(prefixedKey); }); } async getObj(key) { if (!this.isEnabled) { return null; } try { const prefixedKey = this.addPrefix(key); const serializedValue = await redisBase.safeExecute(async (client) => { return await client.get(prefixedKey); }); if (serializedValue === null) { return null; } return JSON.parse(serializedValue); } catch (error) { console.error("Error deserializing object from Redis:", error); return null; } } async del(key) { if (!this.isEnabled) { return null; } const prefixedKey = this.addPrefix(key); return redisBase.safeExecute(async (client) => { return await client.del(prefixedKey); }); } async delMultiple(keys) { if (!this.isEnabled) { return null; } const prefixedKeys = keys.map((key) => this.addPrefix(key)); return redisBase.safeExecute(async (client) => { return await client.del(prefixedKeys); }); } /** * Delete all keys matching a pattern (Production-safe using SCAN) * @param pattern - Pattern to match (e.g., "games:*" to delete all keys starting with "games:") * @param batchSize - Number of keys to scan per iteration (default: 100) * @returns Number of keys deleted, or null if client is disabled * * @example * ```typescript * // Delete all game-related keys * await cache.delByPattern("games:*"); * * // With type-safe patterns * await cache.delByPattern(CACHE_KEYS.games._pattern); * ``` */ async delByPattern(pattern, batchSize = 100) { if (!this.isEnabled) { return null; } const prefixedPattern = this.addPrefix(pattern); return redisBase.safeExecute(async (client) => { let totalDeleted = 0; const keysToDelete = []; for await (const key of client.scanIterator({ MATCH: prefixedPattern, COUNT: batchSize })) { if (typeof key === "string") { keysToDelete.push(key); } else if (Array.isArray(key)) { keysToDelete.push(...key); } if (keysToDelete.length >= batchSize) { const deleted = await client.del(keysToDelete); totalDeleted += deleted; keysToDelete.length = 0; } } if (keysToDelete.length > 0) { const deleted = await client.del(keysToDelete); totalDeleted += deleted; } return totalDeleted; }); } async exists(key) { if (!this.isEnabled) { return null; } const prefixedKey = this.addPrefix(key); return redisBase.safeExecute(async (client) => { return await client.exists(prefixedKey); }); } async expire(key, seconds) { if (!this.isEnabled) { return null; } const prefixedKey = this.addPrefix(key); return redisBase.safeExecute(async (client) => { return await client.expire(prefixedKey, seconds); }); } async ttl(key) { if (!this.isEnabled) { return null; } const prefixedKey = this.addPrefix(key); return redisBase.safeExecute(async (client) => { return await client.ttl(prefixedKey); }); } async incr(key) { if (!this.isEnabled) { return null; } const prefixedKey = this.addPrefix(key); return redisBase.safeExecute(async (client) => { return await client.incr(prefixedKey); }); } async incrBy(key, increment) { if (!this.isEnabled) { return null; } const prefixedKey = this.addPrefix(key); return redisBase.safeExecute(async (client) => { return await client.incrBy(prefixedKey, increment); }); } async decr(key) { if (!this.isEnabled) { return null; } const prefixedKey = this.addPrefix(key); return redisBase.safeExecute(async (client) => { return await client.decr(prefixedKey); }); } async keys(pattern) { if (!this.isEnabled) { return null; } const prefixedPattern = this.addPrefix(pattern); const result = await redisBase.safeExecute(async (client) => { return await client.keys(prefixedPattern); }); if (result) { return result.map((key) => this.removePrefix(key)); } return result; } async hSet(key, field, value) { if (!this.isEnabled) { return null; } const prefixedKey = this.addPrefix(key); return redisBase.safeExecute(async (client) => { return await client.hSet(prefixedKey, field, value); }); } async hGet(key, field) { if (!this.isEnabled) { return null; } const prefixedKey = this.addPrefix(key); return redisBase.safeExecute(async (client) => { return await client.hGet(prefixedKey, field); }); } async hGetAll(key) { if (!this.isEnabled) { return null; } const prefixedKey = this.addPrefix(key); return redisBase.safeExecute(async (client) => { return await client.hGetAll(prefixedKey); }); } async hDel(key, fields) { if (!this.isEnabled) { return null; } const prefixedKey = this.addPrefix(key); return redisBase.safeExecute(async (client) => { return await client.hDel(prefixedKey, fields); }); } async lPush(key, ...elements) { if (!this.isEnabled) { return null; } const prefixedKey = this.addPrefix(key); return redisBase.safeExecute(async (client) => { return await client.lPush(prefixedKey, elements); }); } async rPush(key, ...elements) { if (!this.isEnabled) { return null; } const prefixedKey = this.addPrefix(key); return redisBase.safeExecute(async (client) => { return await client.rPush(prefixedKey, elements); }); } async lPop(key) { if (!this.isEnabled) { return null; } const prefixedKey = this.addPrefix(key); return redisBase.safeExecute(async (client) => { return await client.lPop(prefixedKey); }); } async rPop(key) { if (!this.isEnabled) { return null; } const prefixedKey = this.addPrefix(key); return redisBase.safeExecute(async (client) => { return await client.rPop(prefixedKey); }); } async lRange(key, start, stop) { if (!this.isEnabled) { return null; } const prefixedKey = this.addPrefix(key); return redisBase.safeExecute(async (client) => { return await client.lRange(prefixedKey, start, stop); }); } async sAdd(key, ...members) { if (!this.isEnabled) { return null; } const prefixedKey = this.addPrefix(key); return redisBase.safeExecute(async (client) => { return await client.sAdd(prefixedKey, members); }); } async sMembers(key) { if (!this.isEnabled) { return null; } const prefixedKey = this.addPrefix(key); return redisBase.safeExecute(async (client) => { return await client.sMembers(prefixedKey); }); } async sRem(key, ...members) { if (!this.isEnabled) { return null; } const prefixedKey = this.addPrefix(key); return redisBase.safeExecute(async (client) => { return await client.sRem(prefixedKey, members); }); } async safeExecute(operation) { if (!this.isEnabled) { return null; } return redisBase.safeExecute(async (client) => { return await operation(client, this.addPrefix.bind(this)); }); } }; var createRedisGenericClient = ({ prefix, isEnabled = true }) => { return new RedisGenericClient({ prefix, isEnabled }); }; exports.RedisGenericClient = RedisGenericClient; exports.createRedisGenericClient = createRedisGenericClient; exports.redisBase = redisBase; //# sourceMappingURL=index.cjs.map //# sourceMappingURL=index.cjs.map