UNPKG

typeorm

Version:

Data-Mapper ORM for TypeScript and ES2021+. Supports MySQL/MariaDB, PostgreSQL, MS SQL Server, Oracle, SAP HANA, SQLite, MongoDB databases.

261 lines (259 loc) • 9.16 kB
"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(connection, clientType) { this.connection = connection; this.clientType = clientType; this.redis = this.loadRedis(); } // ------------------------------------------------------------------------- // Public Methods // ------------------------------------------------------------------------- /** * Creates a connection with given cache provider. */ async connect() { const cacheOptions = this.connection.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.connection.options.cache === "object" && this.connection.options.cache.ignoreErrors) { this.client.on("error", (err) => { this.connection.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. */ async synchronize(queryRunner) { } /** * Get data from cache. * Returns cache result if found. * Returns undefined if result is not cached. */ 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. */ isExpired(savedCache) { return savedCache.time + savedCache.duration < Date.now(); } /** * Stores given query result in the cache. */ 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. */ 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. */ async remove(identifiers, queryRunner) { await Promise.all(identifiers.map((identifier) => { return this.deleteKey(identifier); })); } // ------------------------------------------------------------------------- // Protected Methods // ------------------------------------------------------------------------- /** * Removes a single key from redis database. */ 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