typeorm
Version:
Data-Mapper ORM for TypeScript and ES2021+. Supports MySQL/MariaDB, PostgreSQL, MS SQL Server, Oracle, SAP HANA, SQLite, MongoDB databases.
240 lines (238 loc) • 8.44 kB
JavaScript
"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;
/**
* Whether the Redis client uses Promise-based API (v5+) or callback-based API (v3/v4).
* Detected at runtime after connection.
*/
this.isPromiseBasedApi = false;
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 API style by checking if methods return Promises
this.detectPromiseBasedApi();
}
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.isPromiseBasedApi) {
await this.client.quit();
this.client = undefined;
return;
}
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.isPromiseBasedApi) {
return this.client.get(key).then((result) => {
return result ? JSON.parse(result) : undefined;
});
}
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.isPromiseBasedApi) {
await this.client.set(key, value, {
PX: duration,
});
return;
}
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.isPromiseBasedApi) {
await this.client.flushDb();
return;
}
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.isPromiseBasedApi) {
await this.client.del(key);
return;
}
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 whether the Redis client uses Promise-based API (v5+) or callback-based API (v3/v4)
* by checking the return type of a method call at runtime.
*/
detectPromiseBasedApi() {
// Call ping() and check if it returns a Promise
// This works because v5+ returns Promises directly, while v4 with legacyMode uses callbacks
const result = this.client.ping();
this.isPromiseBasedApi = !!result?.then;
if (this.isPromiseBasedApi) {
result.catch(() => { });
}
}
}
exports.RedisQueryResultCache = RedisQueryResultCache;
//# sourceMappingURL=RedisQueryResultCache.js.map