UNPKG

@onurege3467/zerohelper

Version:

ZeroHelper is a versatile high-performance utility library and database framework for Node.js, fully written in TypeScript.

177 lines (176 loc) 6.65 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.RedisDatabase = void 0; const IDatabase_1 = require("./IDatabase"); const redis_1 = require("redis"); class RedisDatabase extends IDatabase_1.IDatabase { constructor(config) { super(); this.client = null; this._queue = []; this._isReady = false; this._connectionPromise = null; this.config = config; this.keyPrefix = config.keyPrefix || 'app:'; this._ensureConnection(); } async _ensureConnection() { if (this._connectionPromise) return this._connectionPromise; this._connectionPromise = (async () => { try { this.client = (0, redis_1.createClient)({ url: this.config.url, socket: { host: this.config.host || '127.0.0.1', port: this.config.port || 6379, connectTimeout: 5000, reconnectStrategy: false }, password: this.config.password, database: Number(this.config.database) || 0, }); await this.client.connect(); this._isReady = true; this._flushQueue(); } catch (error) { this._flushQueueWithError(error); } })(); return this._connectionPromise; } _flushQueue() { while (this._queue.length > 0) { const item = this._queue.shift(); if (item) item.operation().then(item.resolve).catch(item.reject); } } _flushQueueWithError(error) { while (this._queue.length > 0) { const item = this._queue.shift(); if (item) item.reject(error); } } async _execute(op, table, fn) { const operation = async () => { const start = Date.now(); const res = await fn(); this.recordMetric(op, table, Date.now() - start); return res; }; if (this._isReady) return operation(); return new Promise((resolve, reject) => { this._queue.push({ operation, resolve, reject }); }); } _getKey(table, id) { return `${this.keyPrefix}${table}:${id}`; } _getTableKey(table) { return `${this.keyPrefix}${table}:*`; } async select(table, where = {}) { return this._execute('select', table, async () => { const keys = await this.client.keys(this._getTableKey(table)); if (!keys.length) return []; const vals = await this.client.mGet(keys); return vals.map(v => v ? JSON.parse(v) : null).filter(Boolean) .filter(item => Object.entries(where).every(([k, v]) => String(item[k]) === String(v))); }); } async selectOne(table, where = {}) { const res = await this.select(table, where); return res[0] || null; } async insert(table, data) { await this.runHooks('beforeInsert', table, data); return this._execute('insert', table, async () => { const d = { ...data }; if (!d._id && !d.id) d._id = Date.now().toString() + Math.random().toString(36).slice(2, 9); const id = String(d._id || d.id); await this.client.set(this._getKey(table, id), JSON.stringify(d)); await this.runHooks('afterInsert', table, d); return d._id || d.id; }); } async update(table, data, where) { await this.runHooks('beforeUpdate', table, { data, where }); return this._execute('update', table, async () => { const existing = await this.select(table, where); for (const item of existing) { const merged = { ...item, ...data }; await this.client.set(this._getKey(table, item._id || item.id), JSON.stringify(merged)); } return existing.length; }); } async delete(table, where) { await this.runHooks('beforeDelete', table, where); return this._execute('delete', table, async () => { const existing = await this.select(table, where); if (existing.length) { const keys = existing.map(i => this._getKey(table, String(i._id || i.id))); await this.client.del(keys); } return existing.length; }); } async set(table, data, where) { const ex = await this.selectOne(table, where); return ex ? this.update(table, data, where) : this.insert(table, { ...data, ...where }); } async bulkInsert(table, dataArray) { for (const d of dataArray) await this.insert(table, d); return dataArray.length; } /** * Atomic Increment using Lua for Redis */ async increment(table, incs, where = {}) { return this._execute('increment', table, async () => { const recs = await this.select(table, where); for (const r of recs) { const id = String(r._id || r.id); const key = this._getKey(table, id); // Redis'te JSON sakladığımız için her alanı ayrı artırmak yerine // objeyi okuyup, güncelleyip tekrar yazmalıyız. // Bunu Lua script ile Redis tarafında atomik yapalım. const lua = ` local val = redis.call('get', KEYS[1]) if not val then return 0 end local data = cjson.decode(val) local incs = cjson.decode(ARGV[1]) for k, v in pairs(incs) do data[k] = (tonumber(data[k]) or 0) + v end redis.call('set', KEYS[1], cjson.encode(data)) return 1 `; await this.client.eval(lua, { keys: [key], arguments: [JSON.stringify(incs)] }); } return recs.length; }); } async decrement(table, decs, where = {}) { const incs = {}; for (const k in decs) incs[k] = -decs[k]; return this.increment(table, incs, where); } async close() { if (this.client) { await this.client.quit(); this.client = null; this._isReady = false; this._connectionPromise = null; } } } exports.RedisDatabase = RedisDatabase; exports.default = RedisDatabase;