UNPKG

@onurege3467/zerohelper

Version:

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

283 lines (282 loc) 10.3 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.MySQLDatabase = void 0; const IDatabase_1 = require("./IDatabase"); const promise_1 = __importDefault(require("mysql2/promise")); class MySQLDatabase extends IDatabase_1.IDatabase { constructor(config) { super(); this.pool = null; this._queue = []; this._connected = false; this.config = config; this._connectionPromise = new Promise(async (resolve, reject) => { try { const connection = await promise_1.default.createConnection({ host: config.host || 'localhost', port: config.port || 3306, user: config.username, password: config.password, }); await connection.query(`CREATE DATABASE IF NOT EXISTS ${config.database} `); await connection.end(); this.pool = promise_1.default.createPool({ host: config.host || 'localhost', port: config.port || 3306, user: config.username, password: config.password, database: config.database, waitForConnections: true, connectionLimit: config.poolSize || 15, // Balanced limit queueLimit: 0, enableKeepAlive: true, keepAliveInitialDelay: 10000 }); this._connected = true; resolve(this.pool); this._processQueue(); } catch (error) { this._queue.forEach(q => q.reject(error)); this._queue = []; reject(error); } }); } async _execute(op, table, fn) { const start = Date.now(); const execute = async () => { const res = await fn(); this.recordMetric(op, table, Date.now() - start); return res; }; if (this._connected) return execute(); return new Promise((resolve, reject) => this._queue.push({ operation: execute, resolve, reject })); } async _processQueue() { if (!this._connected) return; while (this._queue.length > 0) { const item = this._queue.shift(); if (item) { try { item.resolve(await item.operation()); } catch (error) { item.reject(error); } } } } async query(sql, params = []) { const pool = await this._connectionPromise; let retries = 3; while (retries > 0) { try { const [rows] = await pool.execute(sql, params); return rows; } catch (error) { if ((error.code === 'ER_CON_COUNT_ERROR' || error.message.includes('Too many connections')) && retries > 1) { retries--; await new Promise(r => setTimeout(r, 1000)); // Wait 1s and retry continue; } throw error; } } } async _ensureMissingColumns(table, data) { const existingColumns = await this.query(`DESCRIBE ${table} `).catch(() => []); const columnNames = existingColumns.map(col => col.Field); for (const key of Object.keys(data)) { if (key !== '_id' && !columnNames.includes(key)) { await this.query(`ALTER TABLE ${table} ADD COLUMN ${key} TEXT`); } } } async ensureTable(table, data = {}) { const escapedTable = promise_1.default.escape(table); const tables = await this.query(`SHOW TABLES LIKE ${escapedTable}`); if (tables.length === 0) { const defs = Object.keys(data).map(k => ` ${k} TEXT`); const columnsPart = defs.length > 0 ? ', ' + defs.join(", ") : ''; await this.query(`CREATE TABLE ${table} (_id INT PRIMARY KEY AUTO_INCREMENT ${columnsPart}) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`); } else { await this._ensureMissingColumns(table, data); } } async insert(table, data) { await this.runHooks('beforeInsert', table, data); return this._execute('insert', table, async () => { await this.ensureTable(table, data); const keys = Object.keys(data); const sql = `INSERT INTO ${table} (${keys.map(k => ` ${k} `).join(",")}) VALUES (${keys.map(() => '?').join(",")})`; const result = await this.query(sql, Object.values(data).map(v => typeof v === 'object' ? JSON.stringify(v) : v)); const finalData = { _id: result.insertId, ...data }; await this.runHooks('afterInsert', table, finalData); return result.insertId; }); } async update(table, data, where) { await this.runHooks('beforeUpdate', table, { data, where }); return this._execute('update', table, async () => { await this.ensureTable(table, { ...data, ...where }); const set = Object.keys(data).map(k => ` ${k} = ?`).join(", "); const wKeys = Object.keys(where); const sql = `UPDATE ${table} SET ${set} WHERE ${wKeys.map(k => ` ${k} = ?`).join(" AND ")}`; const result = await this.query(sql, [...Object.values(data).map(v => typeof v === 'object' ? JSON.stringify(v) : v), ...Object.values(where).map(v => typeof v === 'object' ? JSON.stringify(v) : v)]); return result.affectedRows; }); } async delete(table, where) { await this.runHooks('beforeDelete', table, where); return this._execute('delete', table, async () => { await this.ensureTable(table, where); const keys = Object.keys(where); const whereClause = keys.length > 0 ? `WHERE ${keys.map(k => ` ${k} = ?`).join(" AND ")}` : ''; const sql = `DELETE FROM ${table} ${whereClause}`; const result = await this.query(sql, Object.values(where).map(v => typeof v === 'object' ? JSON.stringify(v) : v)); return result.affectedRows; }); } async select(table, where = null) { return this._execute('select', table, async () => { await this.ensureTable(table, where || {}); const keys = where ? Object.keys(where) : []; const sql = `SELECT * FROM ${table} ` + (keys.length ? ' WHERE ' + keys.map(k => ` ${k} = ?`).join(" AND ") : ''); const rows = await this.query(sql, keys.map(k => typeof where[k] === 'object' ? JSON.stringify(where[k]) : where[k])); return rows.map((row) => { const nr = {}; for (const k in row) { try { nr[k] = JSON.parse(row[k]); } catch { nr[k] = row[k]; } } return nr; }); }); } async selectOne(table, where = null) { const res = await this.select(table, where); return res[0] || null; } async set(table, data, where) { const existing = await this.selectOne(table, where); return existing ? this.update(table, data, where) : this.insert(table, { ...where, ...data }); } async bulkInsert(table, dataArray) { if (!dataArray.length) return 0; return this._execute('bulkInsert', table, async () => { await this.ensureTable(table, dataArray[0]); const keys = Object.keys(dataArray[0]); const placeholders = dataArray.map(() => `(${keys.map(() => '?').join(',')})`).join(','); const values = dataArray.flatMap(obj => keys.map(k => this._serializeValue(obj[k]))); const sql = `INSERT INTO ${table} (${keys.map(k => ` ${k} `).join(",")}) VALUES ${placeholders}`; const result = await this.query(sql, values); return result.affectedRows; }); } async increment(table, incs, where) { return this._execute('increment', table, async () => { await this.ensureTable(table, where); const set = Object.keys(incs).map(f => ` ${f} = ${f} + ?`).join(', '); const wKeys = Object.keys(where); const sql = `UPDATE ${table} SET ${set} WHERE ${wKeys.map(k => ` ${k} = ?`).join(" AND ")}`; const result = await this.query(sql, [...Object.values(incs), ...Object.values(where).map(v => typeof v === 'object' ? JSON.stringify(v) : v)]); return result.affectedRows; }); } 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.pool) await this.pool.end(); } _getColumnType(v) { if (v === null || v === undefined) return 'TEXT'; if (typeof v === 'boolean') return 'BOOLEAN'; if (typeof v === 'number') return Number.isInteger(v) ? 'INT' : 'DOUBLE'; if (v instanceof Date) return 'DATETIME'; if (typeof v === 'object') return 'JSON'; return 'TEXT'; } _serializeValue(v) { if (v instanceof Date) return v.toISOString().slice(0, 19).replace('T', ' '); return (typeof v === 'object' && v !== null) ? JSON.stringify(v) : v; } _buildWhereClause(where) { if (!where) return { whereClause: '', values: [] }; const keys = Object.keys(where); if (!keys.length) return { whereClause: '', values: [] }; return { whereClause: 'WHERE ' + keys.map(k => ` ${k} = ?`).join(' AND '), values: Object.values(where).map(v => this._serializeValue(v)) }; } } exports.MySQLDatabase = MySQLDatabase; exports.default = MySQLDatabase;