import { env } from "@config/env";
import { getLogger } from "@utils/asyncLocalStorage";
import Redis from "ioredis";
let redisClient = null;
let isConnected = false;
/**
* Establish Redis connection for caching and pub/sub use cases.
* @category Database
*/
export function connectToRedis() {
const logger = getLogger();
if (isConnected && redisClient) {
logger.info("Redis already connected");
return;
}
try {
logger.info("Attempting to connect to Redis...");
redisClient = new Redis(env.REDIS_URI, {
db: env.REDIS_DB_INDEX,
});
setupRedisEventListeners(redisClient);
isConnected = true;
logger.info("Successfully connected to Redis");
}
catch (err) {
logger.error({
error: err instanceof Error ? err.message : "Unknown error",
}, "Failed to connect to Redis");
throw err;
}
}
/**
* Internal helper that wires Redis connection lifecycle logging.
* @category Database
* @param {Redis} client Connected Redis instance.
*/
function setupRedisEventListeners(client) {
const logger = getLogger();
client.on("connect", () => {
logger.info("Redis connection established");
});
client.on("ready", () => {
isConnected = true;
logger.info("Redis client ready");
});
client.on("error", (err) => {
logger.error({ error: err.message }, "Redis connection error");
});
client.on("close", () => {
isConnected = false;
logger.warn("Redis connection closed");
});
client.on("reconnecting", () => {
logger.info("Redis reconnecting...");
});
client.on("end", () => {
isConnected = false;
logger.warn("Redis connection ended");
});
}
/**
* Gracefully close the Redis connection if active.
* @category Database
* @returns {Promise<void>} Resolves when the connection quits.
*/
export async function closeRedisConnection() {
const logger = getLogger();
if (!redisClient || !isConnected) {
logger.info("Redis not connected, skipping close");
return;
}
try {
logger.info("Closing Redis connection...");
await redisClient.quit();
isConnected = false;
redisClient = null;
logger.info("Redis connection closed successfully");
}
catch (err) {
logger.error({
error: err instanceof Error ? err.message : "Unknown error",
}, "Error closing Redis connection");
}
}
/**
* Snapshot of the Redis client status.
* @category Database
* @returns {object} High-level connection flags.
*/
export function getRedisStatus() {
return {
isConnected,
status: redisClient?.status,
};
}
/**
* Convenience helpers for interacting with Redis commands.
* @category Database
*/
export const redisUtils = {
/**
* Write a value to Redis, optionally with TTL.
* @param {string} key Redis key to store.
* @param {string} value Serialized payload.
* @param {number} [ttlSeconds] Optional TTL in seconds.
* @returns {Promise<void>} Promise that resolves when the command completes.
*/
async set(key, value, ttlSeconds) {
if (!redisClient) {
throw new Error("Redis client not connected");
}
if (ttlSeconds) {
await redisClient.setex(key, ttlSeconds, value);
}
else {
await redisClient.set(key, value);
}
},
/**
* Fetch a value from Redis.
* @param {string} key Redis key to read.
* @returns {Promise<string | null>} Stored string or null when missing.
*/
async get(key) {
if (!redisClient) {
throw new Error("Redis client not connected");
}
return await redisClient.get(key);
},
/**
* Delete a Redis key.
* @param {string} key Redis key to remove.
* @returns {Promise<number>} Number of keys removed.
*/
async del(key) {
if (!redisClient) {
throw new Error("Redis client not connected");
}
return await redisClient.del(key);
},
/**
* Determine if a key exists in Redis.
* @param {string} key Redis key to check.
* @returns {Promise<boolean>} True when the key exists.
*/
async exists(key) {
if (!redisClient) {
throw new Error("Redis client not connected");
}
const result = await redisClient.exists(key);
return result === 1;
},
/**
* Publish a message to a channel.
* @param {string} channel Channel name.
* @param {string} message Payload to publish.
* @returns {Promise<number>} Subscribers that received the message.
*/
async publish(channel, message) {
if (!redisClient) {
throw new Error("Redis client not connected");
}
return await redisClient.publish(channel, message);
},
/**
* Subscribe to a channel and invoke a callback when messages arrive.
* @param {string} channel Channel to subscribe to.
* @param {Function} callback Handler executed per message.
* @returns {Promise<void>} Resolves once subscribed.
*/
async subscribe(channel, callback) {
if (!redisClient) {
throw new Error("Redis client not connected");
}
await redisClient.subscribe(channel);
redisClient.on("message", (receivedChannel, message) => {
if (receivedChannel === channel) {
callback(message);
}
});
},
};
Source