@onurege3467/zerohelper 7.0.0 → 7.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,120 +1,233 @@
1
+ // cacheWrapper.js - Redis@4 ve Memory Cache Uyumlu
1
2
  const { LRUCache } = require('lru-cache');
3
+ const { createClient } = require('redis');
2
4
 
3
5
  class CacheWrapper {
4
6
  constructor(databaseInstance, options = {}) {
5
7
  this.db = databaseInstance;
8
+ this.cacheType = options.type || 'memory'; // 'memory' or 'redis'
9
+ this.tableCaches = {};
10
+
11
+ if (this.cacheType === 'redis') {
12
+ this._initRedisCache(options);
13
+ } else {
14
+ this._initMemoryCache(options);
15
+ }
16
+ }
17
+
18
+ _initMemoryCache(options) {
6
19
  this.cache = new LRUCache({
7
- max: options.max || 500, // Maximum number of items in cache
8
- ttl: options.ttl || 1000 * 60 * 5, // Time to live in ms (5 minutes)
9
- updateAgeOnGet: options.updateAgeOnGet !== undefined ? options.updateAgeOnGet : false, // Update item age on get
20
+ max: options.max || 500,
21
+ ttl: options.ttl || 1000 * 60 * 5,
22
+ updateAgeOnGet: options.updateAgeOnGet || false,
23
+ });
24
+ this.redisAvailable = false;
25
+ this.redisClient = null;
26
+ }
27
+
28
+ async _initRedisCache(options) {
29
+ const redisConfig = {
30
+ socket: {
31
+ host: options.host || '127.0.0.1',
32
+ port: options.port || 6379,
33
+ connectTimeout: options.connectTimeout || 5000,
34
+ },
35
+ password: options.password,
36
+ database: options.db || 0,
37
+ };
38
+
39
+ this.redisClient = createClient(redisConfig);
40
+ this.ttl = options.ttl || 300; // 5 dakika (saniye cinsinden)
41
+ this.keyPrefix = options.keyPrefix || 'db_cache:';
42
+ this.redisAvailable = false;
43
+
44
+ this.redisClient.on('error', (err) => {
45
+ console.warn('Redis Cache Error, memory cache fallback:', err.message);
46
+ this.redisAvailable = false;
47
+ });
48
+
49
+ this.redisClient.on('connect', () => {
50
+ console.log('Redis Cache Connected');
51
+ });
52
+
53
+ this.redisClient.on('ready', () => {
54
+ console.log('Redis Cache Ready');
55
+ this.redisAvailable = true;
10
56
  });
11
- this.tableCaches = {}; // Cache for each table
57
+
58
+ this.redisClient.on('end', () => {
59
+ console.log('Redis Cache Disconnected');
60
+ this.redisAvailable = false;
61
+ });
62
+
63
+ try {
64
+ await Promise.race([
65
+ this.redisClient.connect(),
66
+ new Promise((_, reject) =>
67
+ setTimeout(() => reject(new Error('Redis connection timeout')), 5000)
68
+ ),
69
+ ]);
70
+ this.redisAvailable = true;
71
+ } catch (error) {
72
+ console.warn('Redis bağlantısı başarısız, memory cache kullanılacak:', error.message);
73
+ this._initMemoryCache(options);
74
+ this.redisClient.removeAllListeners();
75
+ this.redisClient = null;
76
+ this.redisAvailable = false;
77
+ }
12
78
  }
13
79
 
14
80
  _getCache(table) {
81
+ if (this.cacheType === 'redis' && this.redisAvailable && this.redisClient) {
82
+ return this.redisClient;
83
+ }
84
+
85
+ // Memory fallback
15
86
  if (!this.tableCaches[table]) {
16
87
  this.tableCaches[table] = new LRUCache({
17
- max: this.cache.max,
18
- ttl: this.cache.ttl,
19
- updateAgeOnGet: this.cache.updateAgeOnGet,
88
+ max: this.cache ? this.cache.max : 500,
89
+ ttl: this.cache ? this.cache.ttl : 300000,
90
+ updateAgeOnGet: this.cache ? this.cache.updateAgeOnGet : false,
20
91
  });
21
92
  }
22
93
  return this.tableCaches[table];
23
94
  }
24
95
 
25
96
  _generateKey(table, where) {
26
- return `${table}:${JSON.stringify(where || {})}`;
97
+ const key = `${table}:${JSON.stringify(where || {})}`;
98
+ return this.cacheType === 'redis' ? `${this.keyPrefix}${key}` : key;
27
99
  }
28
100
 
29
- // Read operations
101
+ async _getCacheValue(cache, key) {
102
+ if (this.cacheType === 'redis' && this.redisAvailable && this.redisClient) {
103
+ try {
104
+ const value = await cache.get(key);
105
+ return value ? JSON.parse(value) : null;
106
+ } catch (err) {
107
+ console.warn('Redis get error, memory fallback:', err.message);
108
+ this.redisAvailable = false;
109
+ return this._getCache(key.split(':')[1]).get(key);
110
+ }
111
+ } else {
112
+ return cache.get(key);
113
+ }
114
+ }
115
+
116
+ async _setCacheValue(cache, key, value) {
117
+ if (this.cacheType === 'redis' && this.redisAvailable && this.redisClient) {
118
+ try {
119
+ await cache.setEx(key, this.ttl, JSON.stringify(value));
120
+ } catch (err) {
121
+ console.warn('Redis set error, memory fallback:', err.message);
122
+ this.redisAvailable = false;
123
+ this._getCache(key.split(':')[1]).set(key, value);
124
+ }
125
+ } else {
126
+ cache.set(key, value);
127
+ }
128
+ }
129
+
130
+ async _clearCache(table) {
131
+ const cache = this._getCache(table);
132
+ if (this.cacheType === 'redis' && this.redisAvailable && this.redisClient) {
133
+ try {
134
+ const keys = await this.redisClient.keys(`${this.keyPrefix}${table}:*`);
135
+ if (keys.length) await this.redisClient.del(keys);
136
+ } catch (err) {
137
+ console.warn('Redis clear error, memory fallback:', err.message);
138
+ this.redisAvailable = false;
139
+ cache.clear();
140
+ }
141
+ } else {
142
+ cache.clear();
143
+ }
144
+ }
145
+
146
+ // Read
30
147
  async select(table, where) {
31
148
  const cache = this._getCache(table);
32
149
  const key = this._generateKey(table, where);
33
- let data = cache.get(key);
34
-
35
- if (data) {
36
- // console.log(`Cache hit for select: ${key}`);
37
- return data;
38
- }
150
+ let data = await this._getCacheValue(cache, key);
151
+ if (data) return data;
39
152
 
40
- // console.log(`Cache miss for select: ${key}`);
41
153
  data = await this.db.select(table, where);
42
- cache.set(key, data);
154
+ await this._setCacheValue(cache, key, data);
43
155
  return data;
44
156
  }
45
157
 
46
158
  async selectOne(table, where) {
47
159
  const cache = this._getCache(table);
48
160
  const key = this._generateKey(table, where);
49
- let data = cache.get(key);
161
+ let data = await this._getCacheValue(cache, key);
162
+ if (data) return data;
50
163
 
51
- if (data) {
52
- // console.log(`Cache hit for selectOne: ${key}`);
53
- return data;
54
- }
55
-
56
- // console.log(`Cache miss for selectOne: ${key}`);
57
164
  data = await this.db.selectOne(table, where);
58
- cache.set(key, data);
165
+ await this._setCacheValue(cache, key, data);
59
166
  return data;
60
167
  }
61
168
 
62
- // Write operations (invalidate cache)
169
+ // Write (invalidate)
63
170
  async insert(table, data) {
64
- this._getCache(table).clear();
171
+ await this._clearCache(table);
65
172
  return this.db.insert(table, data);
66
173
  }
67
174
 
68
175
  async update(table, data, where) {
69
- this._getCache(table).clear();
176
+ await this._clearCache(table);
70
177
  return this.db.update(table, data, where);
71
178
  }
72
179
 
73
180
  async set(table, data, where) {
74
- this._getCache(table).clear();
181
+ await this._clearCache(table);
75
182
  return this.db.set(table, data, where);
76
183
  }
77
184
 
78
185
  async delete(table, where) {
79
- this._getCache(table).clear();
186
+ await this._clearCache(table);
80
187
  return this.db.delete(table, where);
81
188
  }
82
189
 
83
- async bulkInsert(table, dataArray) {
84
- this._getCache(table).clear();
85
- return this.db.bulkInsert(table, dataArray);
86
- }
87
-
88
190
  async deleteOne(table, where) {
89
- this._getCache(table).clear();
191
+ await this._clearCache(table);
90
192
  return this.db.deleteOne(table, where);
91
193
  }
92
194
 
93
195
  async updateOne(table, data, where) {
94
- this._getCache(table).clear();
196
+ await this._clearCache(table);
95
197
  return this.db.updateOne(table, data, where);
96
198
  }
97
199
 
98
- // Pass-through for other methods (e.g., close, ensureTable, query for MySQL/SQLite)
200
+ async bulkInsert(table, dataArray) {
201
+ await this._clearCache(table);
202
+ return this.db.bulkInsert(table, dataArray);
203
+ }
204
+
205
+ async clearAllCache() {
206
+ if (this.cacheType === 'redis' && this.redisAvailable && this.redisClient) {
207
+ try {
208
+ const keys = await this.redisClient.keys(`${this.keyPrefix}*`);
209
+ if (keys.length) await this.redisClient.del(keys);
210
+ } catch (err) {
211
+ console.warn('Redis clearAll error, memory fallback:', err.message);
212
+ this.redisAvailable = false;
213
+ }
214
+ }
215
+ Object.values(this.tableCaches).forEach(c => c.clear());
216
+ if (this.cache) this.cache.clear();
217
+ }
218
+
99
219
  async close() {
100
- // Clear all caches before closing
101
- Object.values(this.tableCaches).forEach(cache => cache.clear());
102
- this.cache.clear();
103
- return this.db.close();
104
- }
105
-
106
- // Dynamically pass through any other methods not explicitly defined here
107
- // This ensures compatibility with specific adapter methods (e.g., MySQL's connect, ping)
108
- // Note: This is a simplified approach. A more robust solution might involve checking
109
- // if the underlying db instance has the method before calling.
110
- get(target, prop) {
111
- if (typeof this[prop] !== 'undefined') {
112
- return this[prop];
113
- } else if (typeof this.db[prop] === 'function') {
114
- return this.db[prop].bind(this.db);
115
- } else {
116
- return this.db[prop];
220
+ await this.clearAllCache();
221
+ if (this.cacheType === 'redis' && this.redisClient) {
222
+ try {
223
+ this.redisClient.removeAllListeners();
224
+ if (this.redisAvailable) await this.redisClient.quit();
225
+ else await this.redisClient.disconnect();
226
+ } catch {}
227
+ this.redisClient = null;
228
+ this.redisAvailable = false;
117
229
  }
230
+ return this.db.close ? this.db.close() : null;
118
231
  }
119
232
  }
120
233
 
package/database/index.js CHANGED
@@ -1,9 +1,11 @@
1
+ // database-factory.js
1
2
  const IDatabase = require('./IDatabase');
2
3
  const MySQLDatabase = require('./mysql');
3
4
  const SQLiteDatabase = require('./sqlite');
4
5
  const MongoDBDatabase = require('./mongodb');
5
6
  const JsonDatabase = require('./json');
6
7
  const PostgreSQLDatabase = require('./pg');
8
+ const RedisDatabase = require('./redis'); // Yeni Redis adapter
7
9
  const CacheWrapper = require('./cacheWrapper');
8
10
 
9
11
  const adapters = {
@@ -12,6 +14,7 @@ const adapters = {
12
14
  mongodb: MongoDBDatabase,
13
15
  postgres: PostgreSQLDatabase,
14
16
  json: JsonDatabase,
17
+ redis: RedisDatabase, // Redis adapter eklendi
15
18
  };
16
19
 
17
20
  /**
@@ -19,7 +22,7 @@ const adapters = {
19
22
  * Bu bir "Fabrika Fonksiyonu"dur.
20
23
  *
21
24
  * @param {object} options - Yapılandırma seçenekleri.
22
- * @param {keyof adapters} options.adapter - Kullanılacak adaptör ('mysql', 'sqlite', vb.).
25
+ * @param {keyof adapters} options.adapter - Kullanılacak adaptör ('mysql', 'sqlite', 'redis', vb.).
23
26
  * @param {object} options.config - Seçilen adaptöre özel yapılandırma.
24
27
  * @returns {IDatabase} - IDatabase arayüzünü uygulayan bir örnek döndürür.
25
28
  */
@@ -62,4 +65,4 @@ function createDatabase(options) {
62
65
  module.exports = createDatabase;
63
66
 
64
67
  // Eğer hem fonksiyonu hem de tipleri export etmek isterseniz:
65
- // module.exports = { createDatabase };
68
+ // module.exports = { createDatabase };
package/database/mysql.js CHANGED
@@ -169,6 +169,25 @@ class MySQLDatabase extends IDatabase{
169
169
  return bestType;
170
170
  }
171
171
 
172
+ /**
173
+ * Eksik kolonları kontrol eder ve ekler
174
+ */
175
+ async _ensureMissingColumns(table, data) {
176
+ const existingColumns = await this.query(`DESCRIBE \`${table}\``).catch(() => null);
177
+ if (!existingColumns) throw new Error(`Table ${table} does not exist.`);
178
+
179
+ const existingColumnNames = existingColumns.map(col => col.Field);
180
+
181
+ for (const key of Object.keys(data)) {
182
+ if (!existingColumnNames.includes(key)) {
183
+ const columnType = this._getColumnType(data[key]);
184
+ const alterSQL = `ALTER TABLE \`${table}\` ADD COLUMN \`${key}\` ${columnType}`;
185
+ await this.query(alterSQL);
186
+ console.log(`Added missing column '${key}' to table '${table}' with type ${columnType}`);
187
+ }
188
+ }
189
+ }
190
+
172
191
  async _queueRequest(operation) {
173
192
  if (this._connected) {
174
193
  return operation();
@@ -231,10 +250,11 @@ class MySQLDatabase extends IDatabase{
231
250
  return this._queueRequest(async () => {
232
251
  const copy = { ...data };
233
252
  await this.ensureTable(table, copy);
234
- const existingColumns = await this.query(`DESCRIBE \`${table}\``).catch(() => null);
235
- if (!existingColumns) throw new Error(`Table ${table} does not exist.`);
253
+
254
+ // Eksik kolonları ekle
255
+ await this._ensureMissingColumns(table, copy);
236
256
 
237
- const existingNames = existingColumns.map(col => col.Field);
257
+ const existingColumns = await this.query(`DESCRIBE \`${table}\``);
238
258
  const primaryKeyColumn = existingColumns.find(col => col.Key === 'PRI' && col.Extra.includes('auto_increment'));
239
259
 
240
260
  const insertData = { ...copy };
@@ -243,13 +263,6 @@ class MySQLDatabase extends IDatabase{
243
263
  delete insertData[primaryKeyColumn.Field];
244
264
  }
245
265
 
246
- for (const key of Object.keys(insertData)) {
247
- if (!existingNames.includes(key)) {
248
- const columnType = this._getColumnType(insertData[key]);
249
- await this.query(`ALTER TABLE \`${table}\` ADD COLUMN \`${key}\` ${columnType}`);
250
- }
251
- }
252
-
253
266
  const keys = Object.keys(insertData);
254
267
  const placeholders = keys.map(() => "?").join(",");
255
268
  const values = Object.values(insertData).map(value => this._serializeValue(value));
@@ -263,22 +276,14 @@ class MySQLDatabase extends IDatabase{
263
276
  async update(table, data, where) {
264
277
  return this._queueRequest(async () => {
265
278
  await this.ensureTable(table, { ...data, ...where });
266
- const existingColumns = await this.query(`DESCRIBE \`${table}\``).catch(() => null);
267
- if (!existingColumns) throw new Error(`Table ${table} does not exist.`);
268
-
269
- const existingColumnNames = existingColumns.map(col => col.Field);
270
- for (const key of Object.keys(data)) {
271
- if (!existingColumnNames.includes(key)) {
272
- const columnType = this._getColumnType(data[key]);
273
- const alterSQL = `ALTER TABLE \`${table}\` ADD COLUMN \`${key}\` ${columnType}`;
274
- await this.query(alterSQL);
275
- console.log(`Added missing column '${key}' to table '${table}' with type ${columnType}`);
276
- }
277
- }
279
+
280
+ // Eksik kolonları ekle
281
+ await this._ensureMissingColumns(table, { ...data, ...where });
282
+
278
283
  const setString = Object.keys(data).map(k => `\`${k}\` = ?`).join(", ");
279
284
  const whereString = Object.keys(where).map(k => `\`${k}\` = ?`).join(" AND ");
280
285
  const sql = `UPDATE \`${table}\` SET ${setString} WHERE ${whereString}`;
281
- const result = await this.query(sql, [...Object.values(data), ...Object.values(where)]);
286
+ const result = await this.query(sql, [...Object.values(data).map(v => this._serializeValue(v)), ...Object.values(where).map(v => this._serializeValue(v))]);
282
287
  return result.affectedRows;
283
288
  });
284
289
  }
@@ -287,9 +292,13 @@ class MySQLDatabase extends IDatabase{
287
292
  return this._queueRequest(async () => {
288
293
  if (!where || Object.keys(where).length === 0) return 0;
289
294
  await this.ensureTable(table, { ...where });
295
+
296
+ // Eksik kolonları ekle (where koşulları için)
297
+ await this._ensureMissingColumns(table, where);
298
+
290
299
  const whereString = Object.keys(where).map(k => `\`${k}\` = ?`).join(" AND ");
291
300
  const sql = `DELETE FROM \`${table}\` WHERE ${whereString}`;
292
- const result = await this.query(sql, Object.values(where));
301
+ const result = await this.query(sql, Object.values(where).map(v => this._serializeValue(v)));
293
302
  return result.affectedRows;
294
303
  });
295
304
  }
@@ -297,13 +306,19 @@ class MySQLDatabase extends IDatabase{
297
306
  async select(table, where = null) {
298
307
  return this._queueRequest(async () => {
299
308
  await this.ensureTable(table, where || {});
309
+
310
+ // Eğer where koşulu varsa, eksik kolonları ekle
311
+ if (where && Object.keys(where).length > 0) {
312
+ await this._ensureMissingColumns(table, where);
313
+ }
314
+
300
315
  let sql = `SELECT * FROM \`${table}\``;
301
316
  let params = [];
302
317
 
303
318
  if (where && Object.keys(where).length > 0) {
304
319
  const whereString = Object.keys(where).map(k => `\`${k}\` = ?`).join(" AND ");
305
320
  sql += ` WHERE ${whereString}`;
306
- params = Object.values(where);
321
+ params = Object.values(where).map(v => this._serializeValue(v));
307
322
  }
308
323
 
309
324
  return (await this.query(sql, params)).map(row => {
@@ -319,18 +334,10 @@ class MySQLDatabase extends IDatabase{
319
334
  async set(table, data, where) {
320
335
  return this._queueRequest(async () => {
321
336
  await this.ensureTable(table, { ...data, ...where });
322
- const existingColumns = await this.query(`DESCRIBE \`${table}\``).catch(() => null);
323
- if (!existingColumns) throw new Error(`Table ${table} does not exist.`);
324
-
325
- const existingColumnNames = existingColumns.map(col => col.Field);
326
- for (const key of Object.keys(data)) {
327
- if (!existingColumnNames.includes(key)) {
328
- const columnType = this._getColumnType(data[key]);
329
- const alterSQL = `ALTER TABLE \`${table}\` ADD COLUMN \`${key}\` ${columnType}`;
330
- await this.query(alterSQL);
331
- console.log(`Added missing column '${key}' to table '${table}' with type ${columnType}`);
332
- }
333
- }
337
+
338
+ // Eksik kolonları ekle
339
+ await this._ensureMissingColumns(table, { ...data, ...where });
340
+
334
341
  const existing = await this.select(table, where);
335
342
  if (existing.length === 0) {
336
343
  return await this.insert(table, { ...where, ...data });
@@ -356,11 +363,16 @@ class MySQLDatabase extends IDatabase{
356
363
 
357
364
  async deleteOne(table, where) {
358
365
  return this._queueRequest(async () => {
366
+ await this.ensureTable(table, where);
367
+
368
+ // Eksik kolonları ekle (where koşulları için)
369
+ await this._ensureMissingColumns(table, where);
370
+
359
371
  const row = await this.selectOne(table, where);
360
372
  if (!row) return 0;
361
373
  const whereString = Object.keys(where).map(k => `\`${k}\` = ?`).join(" AND ");
362
374
  const sql = `DELETE FROM \`${table}\` WHERE ${whereString} LIMIT 1`;
363
- const result = await this.query(sql, Object.values(where));
375
+ const result = await this.query(sql, Object.values(where).map(v => this._serializeValue(v)));
364
376
  return result.affectedRows;
365
377
  });
366
378
  }
@@ -368,22 +380,14 @@ class MySQLDatabase extends IDatabase{
368
380
  async updateOne(table, data, where) {
369
381
  return this._queueRequest(async () => {
370
382
  await this.ensureTable(table, { ...data, ...where });
371
- const existingColumns = await this.query(`DESCRIBE \`${table}\``).catch(() => null);
372
- if (!existingColumns) throw new Error(`Table ${table} does not exist.`);
373
-
374
- const existingColumnNames = existingColumns.map(col => col.Field);
375
- for (const key of Object.keys(data)) {
376
- if (!existingColumnNames.includes(key)) {
377
- const columnType = this._getColumnType(data[key]);
378
- const alterSQL = `ALTER TABLE \`${table}\` ADD COLUMN \`${key}\` ${columnType}`;
379
- await this.query(alterSQL);
380
- console.log(`Added missing column '${key}' to table '${table}' with type ${columnType}`);
381
- }
382
- }
383
+
384
+ // Eksik kolonları ekle
385
+ await this._ensureMissingColumns(table, { ...data, ...where });
386
+
383
387
  const setString = Object.keys(data).map(k => `\`${k}\` = ?`).join(", ");
384
388
  const whereString = Object.keys(where).map(k => `\`${k}\` = ?`).join(" AND ");
385
389
  const sql = `UPDATE \`${table}\` SET ${setString} WHERE ${whereString} LIMIT 1`;
386
- const result = await this.query(sql, [...Object.values(data), ...Object.values(where)]);
390
+ const result = await this.query(sql, [...Object.values(data).map(v => this._serializeValue(v)), ...Object.values(where).map(v => this._serializeValue(v))]);
387
391
  return result.affectedRows;
388
392
  });
389
393
  }
@@ -395,18 +399,27 @@ class MySQLDatabase extends IDatabase{
395
399
 
396
400
  const existingColumns = await this.query(`DESCRIBE \`${table}\``);
397
401
  const existingColumnNames = existingColumns.map(col => col.Field);
398
- const keys = Object.keys(dataArray[0]);
402
+
403
+ // Tüm datalardan gelen tüm anahtarları topla
404
+ const allKeys = new Set();
405
+ dataArray.forEach(obj => {
406
+ Object.keys(obj).forEach(key => allKeys.add(key));
407
+ });
399
408
 
400
409
  // Eksik kolonları kontrol et ve ekle
401
- for (const key of keys) {
410
+ for (const key of allKeys) {
402
411
  if (!existingColumnNames.includes(key)) {
403
412
  // Tüm değerleri kontrol ederek en uygun türü belirle
404
- const columnValues = dataArray.map(obj => obj[key]).filter(val => val !== undefined && val !== null);
413
+ const columnValues = dataArray
414
+ .map(obj => obj[key])
415
+ .filter(val => val !== undefined && val !== null);
405
416
  const columnType = columnValues.length > 0 ? this._getBestColumnType(columnValues) : 'TEXT';
406
417
  await this.query(`ALTER TABLE \`${table}\` ADD COLUMN \`${key}\` ${columnType}`);
418
+ console.log(`Added missing column '${key}' to table '${table}' with type ${columnType}`);
407
419
  }
408
420
  }
409
421
 
422
+ const keys = Array.from(allKeys);
410
423
  const placeholders = dataArray.map(() => `(${keys.map(() => '?').join(',')})`).join(',');
411
424
  const values = dataArray.flatMap(obj => keys.map(k => this._serializeValue(obj[k])));
412
425
  const sql = `INSERT INTO \`${table}\` (${keys.map(k => `\`${k}\``).join(",")}) VALUES ${placeholders}`;