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