UNPKG

@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
"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;