"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.RedisQueryResultCache = void 0;
const PlatformTools_1 = require("../platform/PlatformTools");
const TypeORMError_1 = require("../error/TypeORMError");
/**
 * Caches query result into Redis database.
 */
class RedisQueryResultCache {
    // -------------------------------------------------------------------------
    // Constructor
    // -------------------------------------------------------------------------
    constructor(dataSource, clientType) {
        this.dataSource = dataSource;
        this.clientType = clientType;
        this.redis = this.loadRedis();
    }
    // -------------------------------------------------------------------------
    // Public Methods
    // -------------------------------------------------------------------------
    /**
     * Creates a connection with given cache provider.
     */
    async connect() {
        const cacheOptions = this.dataSource.options.cache;
        if (this.clientType === "redis") {
            const clientOptions = {
                ...cacheOptions?.options,
            };
            // Create initial client to test Redis version
            let tempClient = this.redis.createClient(clientOptions);
            const isRedis4Plus = typeof tempClient.connect === "function";
            if (isRedis4Plus) {
                // Redis 4+ detected, recreate with legacyMode for Redis 4.x
                // (Redis 5 will ignore legacyMode if not needed)
                clientOptions.legacyMode = true;
                tempClient = this.redis.createClient(clientOptions);
            }
            // Set as the main client
            this.client = tempClient;
            if (typeof this.dataSource.options.cache === "object" &&
                this.dataSource.options.cache.ignoreErrors) {
                this.client.on("error", (err) => {
                    this.dataSource.logger.log("warn", err);
                });
            }
            // Connect if Redis 4+
            if (typeof this.client.connect === "function") {
                await this.client.connect();
            }
            // Detect precise version after connection is established
            this.detectRedisVersion();
        }
        else if (this.clientType === "ioredis") {
            if (cacheOptions && cacheOptions.port) {
                if (cacheOptions.options) {
                    this.client = new this.redis(cacheOptions.port, cacheOptions.options);
                }
                else {
                    this.client = new this.redis(cacheOptions.port);
                }
            }
            else if (cacheOptions && cacheOptions.options) {
                this.client = new this.redis(cacheOptions.options);
            }
            else {
                this.client = new this.redis();
            }
        }
        else if (this.clientType === "ioredis/cluster") {
            if (cacheOptions &&
                cacheOptions.options &&
                Array.isArray(cacheOptions.options)) {
                this.client = new this.redis.Cluster(cacheOptions.options);
            }
            else if (cacheOptions &&
                cacheOptions.options &&
                cacheOptions.options.startupNodes) {
                this.client = new this.redis.Cluster(cacheOptions.options.startupNodes, cacheOptions.options.options);
            }
            else {
                throw new TypeORMError_1.TypeORMError(`options.startupNodes required for ${this.clientType}.`);
            }
        }
    }
    /**
     * Disconnects the connection
     */
    async disconnect() {
        if (this.isRedis5OrHigher()) {
            // Redis 5+ uses quit() that returns a Promise
            await this.client.quit();
            this.client = undefined;
            return;
        }
        // Redis 3/4 callback style
        return new Promise((ok, fail) => {
            this.client.quit((err, result) => {
                if (err)
                    return fail(err);
                ok();
                this.client = undefined;
            });
        });
    }
    /**
     * Creates table for storing cache if it does not exist yet.
     * @param queryRunner
     */
    async synchronize(queryRunner) { }
    /**
     * Get data from cache.
     * Returns cache result if found.
     * Returns undefined if result is not cached.
     * @param options
     * @param queryRunner
     */
    getFromCache(options, queryRunner) {
        const key = options.identifier || options.query;
        if (!key)
            return Promise.resolve(undefined);
        if (this.isRedis5OrHigher()) {
            // Redis 5+ Promise-based API
            return this.client.get(key).then((result) => {
                return result ? JSON.parse(result) : undefined;
            });
        }
        // Redis 3/4 callback-based API
        return new Promise((ok, fail) => {
            this.client.get(key, (err, result) => {
                if (err)
                    return fail(err);
                ok(result ? JSON.parse(result) : undefined);
            });
        });
    }
    /**
     * Checks if cache is expired or not.
     * @param savedCache
     */
    isExpired(savedCache) {
        return savedCache.time + savedCache.duration < Date.now();
    }
    /**
     * Stores given query result in the cache.
     * @param options
     * @param savedCache
     * @param queryRunner
     */
    async storeInCache(options, savedCache, queryRunner) {
        const key = options.identifier || options.query;
        if (!key)
            return;
        const value = JSON.stringify(options);
        const duration = options.duration;
        if (this.isRedis5OrHigher()) {
            // Redis 5+ Promise-based API with PX option
            await this.client.set(key, value, {
                PX: duration,
            });
            return;
        }
        // Redis 3/4 callback-based API
        return new Promise((ok, fail) => {
            this.client.set(key, value, "PX", duration, (err, result) => {
                if (err)
                    return fail(err);
                ok();
            });
        });
    }
    /**
     * Clears everything stored in the cache.
     * @param queryRunner
     */
    async clear(queryRunner) {
        if (this.isRedis5OrHigher()) {
            // Redis 5+ Promise-based API
            await this.client.flushDb();
            return;
        }
        // Redis 3/4 callback-based API
        return new Promise((ok, fail) => {
            this.client.flushdb((err, result) => {
                if (err)
                    return fail(err);
                ok();
            });
        });
    }
    /**
     * Removes all cached results by given identifiers from cache.
     * @param identifiers
     * @param queryRunner
     */
    async remove(identifiers, queryRunner) {
        await Promise.all(identifiers.map((identifier) => {
            return this.deleteKey(identifier);
        }));
    }
    // -------------------------------------------------------------------------
    // Protected Methods
    // -------------------------------------------------------------------------
    /**
     * Removes a single key from redis database.
     * @param key
     */
    async deleteKey(key) {
        if (this.isRedis5OrHigher()) {
            // Redis 5+ Promise-based API
            await this.client.del(key);
            return;
        }
        // Redis 3/4 callback-based API
        return new Promise((ok, fail) => {
            this.client.del(key, (err, result) => {
                if (err)
                    return fail(err);
                ok();
            });
        });
    }
    /**
     * Loads redis dependency.
     */
    loadRedis() {
        try {
            if (this.clientType === "ioredis/cluster") {
                return PlatformTools_1.PlatformTools.load("ioredis");
            }
            else {
                return PlatformTools_1.PlatformTools.load(this.clientType);
            }
        }
        catch {
            throw new TypeORMError_1.TypeORMError(`Cannot use cache because ${this.clientType} is not installed. Please run "npm i ${this.clientType}".`);
        }
    }
    /**
     * Detects the Redis package version by reading the installed package.json
     * and sets the appropriate API version (3 for callback-based, 5 for Promise-based).
     */
    detectRedisVersion() {
        if (this.clientType !== "redis")
            return;
        const version = PlatformTools_1.PlatformTools.readPackageVersion("redis");
        const major = parseInt(version.split(".")[0], 10);
        if (isNaN(major)) {
            throw new TypeORMError_1.TypeORMError(`Invalid Redis version format: ${version}`);
        }
        if (major <= 4) {
            // Redis 3/4 uses callback-based API
            this.redisMajorVersion = 3;
        }
        else {
            // Redis 5+ uses Promise-based API
            this.redisMajorVersion = 5;
        }
    }
    /**
     * Checks if Redis version is 5.x or higher
     */
    isRedis5OrHigher() {
        if (this.clientType !== "redis")
            return false;
        return (this.redisMajorVersion !== undefined && this.redisMajorVersion >= 5);
    }
}
exports.RedisQueryResultCache = RedisQueryResultCache;

//# sourceMappingURL=RedisQueryResultCache.js.map
