UNPKG

tspace-mysql

Version:

Tspace MySQL is a promise-based ORM for Node.js, designed with modern TypeScript and providing type safety for schema databases.

416 lines 16.9 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Pool = exports.loadOptionsEnvironment = exports.PoolConnection = void 0; const events_1 = require("events"); const mysql2_1 = require("mysql2"); const tools_1 = require("../tools"); const options_1 = __importStar(require("../options")); Object.defineProperty(exports, "loadOptionsEnvironment", { enumerable: true, get: function () { return options_1.loadOptionsEnvironment; } }); class PoolConnection extends events_1.EventEmitter { OPTIONS = this._loadOptions(); /** * * @Init a options connection pool */ constructor(options) { super(); if (options) { this.OPTIONS = new Map(Object.entries({ ...Object.fromEntries(this.OPTIONS), ...JSON.parse(JSON.stringify(options)) })); } } /** * * Get a connection to database * @return {Connection} Connection * @property {Function} Connection.query * @property {Function} Connection.connection */ connected() { const pool = (0, mysql2_1.createPool)(Object.fromEntries(this.OPTIONS)); /** * After the server is listening, verify that the pool has successfully connected to the database. * @param {Pool} pool * @returns {void} */ this._onPoolConnect(pool); pool.on('release', (connection) => { this.emit('release', connection); }); return { on: (event, data) => { return this.on(event, data); }, query: (sql) => { return new Promise((resolve, reject) => { const start = Date.now(); /** * * The pool does not need to be manually released, * because the mysql2 automatically handles the release when a query is successful. */ pool.query(sql, (err, results) => { if (err) return reject(err); this._detectEventQuery({ start, sql, results }); return resolve(results); }); }); }, connection: () => { return new Promise((resolve, reject) => { pool.getConnection((err, connection) => { if (err) return reject(err); const query = (sql) => { const start = Date.now(); return new Promise((ok, fail) => { connection.query(sql, (err, results) => { connection.release(); if (err) { return fail(err); } this._detectEventQuery({ start, sql, results }); return ok(results); }); }); }; return resolve({ on: (event, data) => this.on(event, data), query }); }); }); } }; } createNewConnected() { const pool = (0, mysql2_1.createPool)(Object.fromEntries(this.OPTIONS)); return { on: (event, data) => { return this.on(event, data); }, query: (sql) => { return new Promise((resolve, reject) => { const start = Date.now(); pool.query(sql, (err, results) => { if (err) return reject(err); this._detectEventQuery({ start, sql, results }); return resolve(results); }); }); }, connection: () => { let closeTransction = false; return new Promise((resolve, reject) => { pool.getConnection((err, connection) => { if (err) return reject(err); const query = (sql) => { const start = Date.now(); return new Promise((ok, fail) => { if (closeTransction) { return fail(new Error('The transaction has either been closed')); } connection.query(sql, (err, results) => { connection.release(); if (err) { return fail(err); } this._detectEventQuery({ start, sql, results }); return ok(results); }); }); }; const startTransaction = async () => { await query('START TRANSACTION') .catch(err => reject(err)); return; }; const commit = async () => { await query('COMMIT') .catch(err => reject(err)); return; }; const rollback = async () => { await query('ROLLBACK') .catch(err => reject(err)); // when rollback will end of transction await end(); return; }; const end = async () => { await new Promise(resolve => setTimeout(() => { // After commit the transaction, you can't perform any actions with this transaction. connection.destroy(); // After destroying the connection, it will be removed from the connection pool. pool.end(); closeTransction = true; return resolve(); }, 500)); return; }; return resolve({ on: (event, data) => this.on(event, data), query, startTransaction, commit, rollback, end }); }); }); } }; } _detectEventQuery({ start, sql, results }) { const duration = Date.now() - start; if (duration > 1000 * 10) { const maxLength = 8_000; if (sql.length > maxLength) { sql = `${sql.slice(0, maxLength)}.......`; } console.log(this._messageSlowQuery(duration, sql)); this.emit('slowQuery', { sql, results, execution: duration }); } this.emit('query', { sql, results, execution: duration }); this.emit(this._detectQueryType(sql), { sql, results, execution: duration }); } _detectQueryType(query) { const selectRegex = /^SELECT\b/i; const updateRegex = /^UPDATE\b/i; const insertRegex = /^INSERT\b/i; const deleteRegex = /^DELETE\b/i; if (selectRegex.test(query)) return 'select'; if (updateRegex.test(query)) return 'update'; if (insertRegex.test(query)) return 'insert'; if (deleteRegex.test(query)) return 'delete'; return ''; } _defaultOptions() { return new Map(Object.entries({ connectionLimit: Number(options_1.default.CONNECTION_LIMIT), dateStrings: Boolean(options_1.default.DATE_STRINGS), connectTimeout: Number(options_1.default.TIMEOUT), waitForConnections: Boolean(options_1.default.WAIT_FOR_CONNECTIONS), queueLimit: Number(options_1.default.QUEUE_LIMIT), charset: String(options_1.default.CHARSET), host: String(options_1.default.HOST), port: Number(options_1.default.PORT), database: String(options_1.default.DATABASE), user: String(options_1.default.USERNAME), password: String(options_1.default.PASSWORD), multipleStatements: Boolean(options_1.default.MULTIPLE_STATEMENTS), enableKeepAlive: Boolean(options_1.default.ENABLE_KEEP_ALIVE), keepAliveInitialDelay: Number(options_1.default.KEEP_ALIVE_DELAY) })); } _loadOptions() { try { /** * @source data * source db { * host = localhost * port = 3306 * database = npm * user = root * password = * connectionLimit = * dateStrings = * connectTimeout = * waitForConnections = * queueLimit = * charset = * } */ const dbOptionsPath = tools_1.Tool.path.join(tools_1.Tool.path.resolve(), 'db.tspace'); const dbOptionsExists = tools_1.Tool.fs.existsSync(dbOptionsPath); if (!dbOptionsExists) return this._defaultOptions(); const dbOptions = tools_1.Tool.fs.readFileSync(dbOptionsPath, 'utf8'); const options = this._convertStringToObject(dbOptions); if (options == null) return this._defaultOptions(); return new Map(Object.entries(options)); } catch (e) { return this._defaultOptions(); } } _convertStringToObject(str, target = 'db') { if (str.toLocaleLowerCase().includes('#ignore')) return null; str = str.trim(); const sources = str.split('\n\n'); if (!sources.length) return null; const lines = sources[0].split('\r\n'); if (!lines.length) return null; const sourceObj = {}; let targetKey = ''; for (const line of lines) { let [key, value] = line.split('='); const sourceKey = key.match(/source\s+(\w+)/); const sourceKeyClose = key.match(/}/g); if (sourceKey != null) { targetKey = sourceKey[1]; continue; } if (sourceKeyClose != null && sourceKeyClose.length) { targetKey = ''; continue; } if (key == null || value == null) continue; key = key.trim(); value = value.trim(); if (!sourceObj.hasOwnProperty(targetKey)) sourceObj[targetKey] = {}; sourceObj[targetKey][key] = value; } return this._covertKeyTypeToCorrectType(sourceObj[target]) || null; } _covertKeyTypeToCorrectType(data) { for (const [key, value] of Object.entries(data)) { if (value == null) continue; if (typeof value === 'string' && ['true', 'false'].some(v => value.toLowerCase() === v)) { data[key] = JSON.parse(value.toLowerCase()); continue; } if (/^[0-9]+$/.test(value)) data[key] = +value; } return data; } _onPoolConnect(pool) { const delay = 1000 * 6.5; setTimeout(() => { pool.getConnection((err, connection) => { if (err) { const message = this._messageError.bind(this); process.nextTick(() => { if (String(err.message).includes('Pool is close')) { return; } console.log(message(err.message == null || err.message === '' ? err.code : err.message)); if (options_1.default.CONNECTION_ERROR) return process.exit(); }); return; } this.emit('connected', connection); if (options_1.default.CONNECTION_SUCCESS) { connection.query(`SHOW VARIABLES LIKE 'version%'`, (err, results) => { connection.release(); if (err) return; const message = [ results.find(v => v?.Variable_name === 'version'), results.find(v => v?.Variable_name === 'version_comment') ].map(v => v?.Value).join(' - '); console.log(this._messageConnected.bind(this)(`${message}`)); }); } }); }, delay); return; } _messageConnected(message) { return ` \x1b[1m\x1b[32m Connection established to the database. Version : ${message ?? ''} \x1b[0m ------------------------------- \x1b[34m HOST : ${this.OPTIONS.get('host')} PORT : ${this.OPTIONS.get('port')} DATABASE : ${this.OPTIONS.get('database')} USERNAME : ${this.OPTIONS.get('user')} PASSWORD : ${this.OPTIONS.get('password')} \x1b[0m ------------------------------- `; } _messageError(message) { return ` \x1b[1m\x1b[31m Connection lost to database ! \x1b[0m ------------------------------- \x1b[33m HOST : ${this.OPTIONS.get('host')} PORT : ${this.OPTIONS.get('port')} DATABASE : ${this.OPTIONS.get('database')} USERNAME : ${this.OPTIONS.get('user')} PASSWORD : ${this.OPTIONS.get('password')} \x1b[0m ------------------------------- \x1b[1m\x1b[31mError Message : ${message ?? ''} \x1b[0m `; } _messageSlowQuery(duration, sql) { const message = `\n\x1b[1m\x1b[31mWARING:\x1b[0m \x1b[1m\x1b[29mSlow query detected: Execution time: ${duration} ms\x1b[0m \n\x1b[33m${sql};\x1b[0m`; return message; } } exports.PoolConnection = PoolConnection; /** * * Connection to database when service is started * * @returns {Connection} Connection * @property {Function} Connection.query * @property {Function} Connection.connection */ const pool = new PoolConnection().connected(); exports.Pool = pool; exports.default = pool; //# sourceMappingURL=Pool.js.map