Source

config/redis.js

import { env } from "@config/env";
import { logger } from "@config/logger";
import Redis from "ioredis";
let redisClient = null;
let isConnected = false;
/**
 * Establish connection to Redis for caching and pub/sub use cases.
 * @category Database
 */
export function connectToRedis() {
    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;
    }
}
/**
 * Wire Redis connection lifecycle event logging.
 * @category Database
 * @param {Redis} client Active Redis client instance.
 */
function setupRedisEventListeners(client) {
    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 it exists.
 * @category Database
 * @returns {Promise<void>} Resolves when the client quits.
 */
export async function closeRedisConnection() {
    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 current Redis client status.
 * @category Database
 * @returns {object} Connection metadata.
 */
export function getRedisStatus() {
    return {
        isConnected,
        status: redisClient?.status,
    };
}
/**
 * Convenience wrappers around common Redis commands.
 * @category Database
 */
export const redisUtils = {
    /**
     * Set a string value with optional TTL.
     * @param {string} key Redis key to set.
     * @param {string} value Serialized payload.
     * @param {number} [ttlSeconds] Optional TTL in seconds.
     * @returns {Promise<void>} Resolves when the value is stored.
     */
    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);
        }
    },
    /**
     * Retrieve a string value from Redis.
     * @param {string} key Redis key to fetch.
     * @returns {Promise<string | null>} Stored value 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 delete.
     * @returns {Promise<number>} Number of keys removed.
     */
    async del(key) {
        if (!redisClient) {
            throw new Error("Redis client not connected");
        }
        return await redisClient.del(key);
    },
    /**
     * Check whether a key exists in Redis.
     * @param {string} key Redis key to test.
     * @returns {Promise<boolean>} True if 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>} Number of 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 per message.
     * @param {string} channel Channel to subscribe to.
     * @param {Function} callback Handler executed when messages arrive.
     * @returns {Promise<void>} Resolves once subscription completes.
     */
    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);
            }
        });
    },
};