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