@onurege3467/zerohelper
Version:
ZeroHelper is a versatile high-performance utility library and database framework for Node.js, fully written in TypeScript.
215 lines (214 loc) • 7.61 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.CacheWrapper = void 0;
const IDatabase_1 = require("./IDatabase");
const lru_cache_1 = require("lru-cache");
const redis_1 = require("redis");
const telemetry_1 = require("./telemetry");
class CacheWrapper extends IDatabase_1.IDatabase {
constructor(databaseInstance, options = {}) {
super();
this.tableCaches = {};
this.redisClient = null;
this.redisAvailable = false;
this.ttl = 300;
this.keyPrefix = 'db_cache:';
this.cache = null;
this.db = databaseInstance;
this.cacheType = options.type || 'memory';
if (this.cacheType === 'redis') {
this._initRedisCache(options);
}
else {
this._initMemoryCache(options);
}
}
/**
* Redirect hooks registration to the underlying database instance.
*/
on(hook, fn) {
this.db.on(hook, fn);
}
_initMemoryCache(options) {
this.cache = new lru_cache_1.LRUCache({
max: options.max || 500,
ttl: options.ttl || 1000 * 60 * 5,
});
this.redisAvailable = false;
}
async _initRedisCache(options) {
const redisConfig = {
socket: {
host: options.host || '127.0.0.1',
port: options.port || 6379,
connectTimeout: options.connectTimeout || 5000,
},
password: options.password,
database: options.db || 0,
};
this.redisClient = (0, redis_1.createClient)(redisConfig);
this.ttl = (options.ttl || 300000) / 1000;
this.keyPrefix = options.keyPrefix || 'db_cache:';
this.redisClient.on('error', () => {
this.redisAvailable = false;
});
this.redisClient.on('ready', () => {
this.redisAvailable = true;
});
try {
await this.redisClient.connect();
this.redisAvailable = true;
}
catch (error) {
this._initMemoryCache(options);
}
}
_getCache(table) {
if (this.cacheType === 'redis' && this.redisAvailable && this.redisClient)
return this.redisClient;
if (!this.tableCaches[table]) {
this.tableCaches[table] = new lru_cache_1.LRUCache({
max: this.cache?.max || 500,
ttl: this.cache?.ttl || 300000,
});
}
return this.tableCaches[table];
}
_generateKey(table, where) {
const sortedWhere = where ? Object.keys(where).sort().reduce((acc, key) => {
acc[key] = where[key];
return acc;
}, {}) : {};
const key = `${table}:${JSON.stringify(sortedWhere)}`;
return this.cacheType === 'redis' ? `${this.keyPrefix}${key}` : key;
}
async _getCacheValue(cache, key, table) {
if (this.cacheType === 'redis' && this.redisAvailable && this.redisClient) {
try {
const value = await cache.get(key);
if (value) {
telemetry_1.telemetry.recordCacheHit();
return JSON.parse(value);
}
}
catch {
this.redisAvailable = false;
}
}
else if (cache instanceof lru_cache_1.LRUCache) {
const value = cache.get(key);
if (value) {
telemetry_1.telemetry.recordCacheHit();
return value;
}
}
telemetry_1.telemetry.recordCacheMiss();
return null;
}
async _setCacheValue(cache, key, value, table) {
if (this.cacheType === 'redis' && this.redisAvailable && this.redisClient) {
try {
await cache.setEx(key, Math.floor(this.ttl), JSON.stringify(value));
}
catch {
this.redisAvailable = false;
this._getCache(table).set(key, value);
}
}
else {
cache.set(key, value);
}
}
async _clearCache(table) {
if (this.cacheType === 'redis' && this.redisAvailable && this.redisClient) {
try {
const keys = await this.redisClient.keys(`${this.keyPrefix}${table}:*`);
if (keys.length)
await this.redisClient.del(keys);
}
catch {
this.redisAvailable = false;
}
}
if (this.tableCaches[table])
this.tableCaches[table].clear();
}
async select(table, where = null) {
const cache = this._getCache(table);
const key = this._generateKey(table, where);
let data = await this._getCacheValue(cache, key, table);
if (data !== null && data !== undefined)
return data;
const start = Date.now();
data = await this.db.select(table, where);
this.db.recordMetric?.('select', table, Date.now() - start);
if (data !== null && data !== undefined)
await this._setCacheValue(cache, key, data, table);
return data;
}
async selectOne(table, where = null) {
const cache = this._getCache(table);
const key = this._generateKey(table + '_one', where);
let data = await this._getCacheValue(cache, key, table);
if (data !== null && data !== undefined)
return data;
const start = Date.now();
data = await this.db.selectOne(table, where);
this.db.recordMetric?.('selectOne', table, Date.now() - start);
if (data !== null && data !== undefined)
await this._setCacheValue(cache, key, data, table);
return data;
}
async insert(table, data) {
const start = Date.now();
const result = await this.db.insert(table, data);
this.db.recordMetric?.('insert', table, Date.now() - start);
await this._clearCache(table);
return result;
}
async update(table, data, where) {
const start = Date.now();
const result = await this.db.update(table, data, where);
this.db.recordMetric?.('update', table, Date.now() - start);
if (result > 0)
await this._clearCache(table);
return result;
}
async set(table, data, where) {
const result = await this.db.set(table, data, where);
await this._clearCache(table);
return result;
}
async delete(table, where) {
const start = Date.now();
const result = await this.db.delete(table, where);
this.db.recordMetric?.('delete', table, Date.now() - start);
if (result > 0)
await this._clearCache(table);
return result;
}
async bulkInsert(table, dataArray) {
const result = await this.db.bulkInsert(table, dataArray);
await this._clearCache(table);
return result;
}
async increment(table, increments, where = {}) {
const result = await this.db.increment(table, increments, where);
await this._clearCache(table);
return result;
}
async decrement(table, decrements, where = {}) {
const result = await this.db.decrement(table, decrements, where);
await this._clearCache(table);
return result;
}
async close() {
if (this.redisClient) {
await this.redisClient.quit();
this.redisClient = null;
}
await this.db.close();
}
}
exports.CacheWrapper = CacheWrapper;
exports.default = CacheWrapper;