@onurege3467/zerohelper
Version:
ZeroHelper is a versatile JavaScript library offering helper functions, validation, logging, database utilities and migration system for developers. It supports MongoDB, MySQL, SQLite, Redis, and PostgreSQL with increment/decrement operations.
343 lines (293 loc) • 10.7 kB
JavaScript
// redis.js - Redis Database Adapter (v4.x Uyumlu)
const { createClient } = require('redis');
class RedisDatabase {
constructor(config = {}) {
this.config = {
host: config.host || '127.0.0.1',
port: config.port || 6379,
password: config.password,
db: config.db || 0,
connectTimeout: config.connectTimeout || 5000,
commandTimeout: config.commandTimeout || 5000,
};
this.keyPrefix = config.keyPrefix || 'app:';
this.client = null;
this.isConnecting = false; // Eşzamanlı bağlantı girişimlerini önlemek için
}
async connect() {
if (this.client && this.client.isReady) {
return this.client;
}
// Eğer zaten bağlantı girişimi yapılıyorsa, bekle
if (this.isConnecting) {
while (this.isConnecting) {
await new Promise(resolve => setTimeout(resolve, 100));
}
return this.client;
}
this.isConnecting = true;
try {
this.client = createClient({
socket: {
host: this.config.host,
port: this.config.port,
connectTimeout: this.config.connectTimeout,
},
password: this.config.password,
database: this.config.db,
});
this.client.on('error', (err) => {
console.error('Redis Error:', err.message);
});
//this.client.on('connect', () => console.log('Redis Connected'));
//this.client.on('ready', () => console.log('Redis Ready'));
this.client.on('end', () => {
console.log('Redis Connection Ended');
this.client = null;
});
// Timeout ile bağlantı kontrolü
await Promise.race([
this.client.connect(),
new Promise((_, reject) =>
setTimeout(() => reject(new Error('Redis connection timeout')), this.config.connectTimeout)
),
]);
console.log('Redis bağlantısı başarılı');
} catch (err) {
this.client = null;
throw new Error(`Redis bağlantısı başarısız: ${err.message}`);
} finally {
this.isConnecting = false;
}
return this.client;
}
_getKey(table, id) {
return `${this.keyPrefix}${table}:${id}`;
}
_getTableKey(table) {
return `${this.keyPrefix}${table}:*`;
}
async select(table, where = {}) {
try {
const client = await this.connect();
const pattern = this._getTableKey(table);
const keys = await client.keys(pattern);
if (!keys.length) return [];
const values = await client.mGet(keys);
return values
.map(v => {
try {
return v ? JSON.parse(v) : null;
} catch (parseErr) {
console.error('JSON parse error:', parseErr);
return null;
}
})
.filter(Boolean)
.filter(item => Object.entries(where).every(([k, val]) => item[k] === val));
} catch (err) {
console.error('Select error:', err);
throw err;
}
}
async selectOne(table, where = {}) {
const results = await this.select(table, where);
return results.length ? results[0] : null;
}
async insert(table, data) {
try {
const client = await this.connect();
const insertData = { ...data };
if (!insertData.id) {
insertData.id = Date.now().toString() + Math.random().toString(36).slice(2, 9);
}
const key = this._getKey(table, insertData.id);
await client.set(key, JSON.stringify(insertData));
return insertData;
} catch (err) {
console.error('Insert error:', err);
throw err;
}
}
async update(table, data, where) {
try {
const existing = await this.select(table, where);
if (!existing.length) return [];
const client = await this.connect();
const updated = [];
for (const item of existing) {
const merged = { ...item, ...data };
await client.set(this._getKey(table, item.id), JSON.stringify(merged));
updated.push(merged);
}
return updated;
} catch (err) {
console.error('Update error:', err);
throw err;
}
}
async updateOne(table, data, where) {
const results = await this.update(table, data, where);
return results.length ? results[0] : null;
}
async set(table, data, where) {
try {
const existing = await this.selectOne(table, where);
if (existing) {
return await this.updateOne(table, data, where);
} else {
return await this.insert(table, { ...data, ...where });
}
} catch (err) {
console.error('Set error:', err);
throw err;
}
}
async delete(table, where) {
try {
const existing = await this.select(table, where);
if (!existing.length) return [];
const client = await this.connect();
const keys = existing.map(item => this._getKey(table, item.id));
await client.del(keys);
return existing;
} catch (err) {
console.error('Delete error:', err);
throw err;
}
}
async deleteOne(table, where) {
const results = await this.delete(table, where);
return results.length ? results[0] : null;
}
async bulkInsert(table, dataArray) {
if (!Array.isArray(dataArray) || !dataArray.length) {
return [];
}
try {
const results = [];
for (const data of dataArray) {
results.push(await this.insert(table, data));
}
return results;
} catch (err) {
console.error('Bulk insert error:', err);
throw err;
}
}
async ensureTable(table) {
// Redis'te tablo kavramı yoktur, her zaman true döner
return true;
}
async close() {
if (this.client) {
try {
await this.client.quit();
} catch (err) {
console.error('Redis close error:', err);
} finally {
this.client = null;
}
}
}
async ping() {
try {
const client = await this.connect();
return await client.ping();
} catch (err) {
console.error('Ping error:', err);
throw err;
}
}
async flushTable(table) {
try {
const client = await this.connect();
const pattern = this._getTableKey(table);
const keys = await client.keys(pattern);
if (keys.length > 0) {
await client.del(keys);
}
return keys.length;
} catch (err) {
console.error('Flush table error:', err);
throw err;
}
}
// Yardımcı metodlar
async getStats(table) {
try {
const client = await this.connect();
const pattern = this._getTableKey(table);
const keys = await client.keys(pattern);
return {
table,
keyCount: keys.length,
pattern
};
} catch (err) {
console.error('Get stats error:', err);
throw err;
}
}
async exists(table, where) {
const result = await this.selectOne(table, where);
return result !== null;
}
/**
* Numerik alanları artırır (increment).
* Redis için hash field'ları üzerinde HINCRBY kullanır.
* @param {string} table - Verinin güncelleneceği tablo adı.
* @param {object} increments - Artırılacak alanlar ve miktarları.
* @param {object} where - Güncelleme koşulları.
* @returns {Promise<number>} Etkilenen kayıt sayısı.
*/
async increment(table, increments, where = {}) {
try {
const client = await this.connect();
// Önce mevcut kayıtları bul
const existingRecords = await this.select(table, where);
let affectedCount = 0;
for (const record of existingRecords) {
const key = this._getRecordKey(table, record._id);
// Her increment field için HINCRBY kullan
for (const [field, value] of Object.entries(increments)) {
await client.hIncrBy(key, field, value);
}
affectedCount++;
}
return affectedCount;
} catch (err) {
console.error('Increment error:', err);
throw err;
}
}
/**
* Numerik alanları azaltır (decrement).
* Redis için hash field'ları üzerinde HINCRBY ile negatif değer kullanır.
* @param {string} table - Verinin güncelleneceği tablo adı.
* @param {object} decrements - Azaltılacak alanlar ve miktarları.
* @param {object} where - Güncelleme koşulları.
* @returns {Promise<number>} Etkilenen kayıt sayısı.
*/
async decrement(table, decrements, where = {}) {
try {
const client = await this.connect();
// Önce mevcut kayıtları bul
const existingRecords = await this.select(table, where);
let affectedCount = 0;
for (const record of existingRecords) {
const key = this._getRecordKey(table, record._id);
// Her decrement field için HINCRBY ile negatif değer kullan
for (const [field, value] of Object.entries(decrements)) {
await client.hIncrBy(key, field, -value);
}
affectedCount++;
}
return affectedCount;
} catch (err) {
console.error('Decrement error:', err);
throw err;
}
}
}
module.exports = RedisDatabase;