@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
JavaScript
'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