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.

818 lines 30.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.DB = void 0; const sql_formatter_1 = require("sql-formatter"); const AbstractDB_1 = require("./Abstracts/AbstractDB"); const Proxy_1 = require("./Handlers/Proxy"); const State_1 = require("./Handlers/State"); const tools_1 = require("../tools"); const Pool_1 = require("./Pool"); /** * 'DB' Class is a component of the database system * @param {string?} table table name * @example * new DB('users').findMany().then(results => console.log(results)) */ class DB extends AbstractDB_1.AbstractDB { constructor(table) { super(); this._initialDB(); if (table) this.table(table); return new Proxy(this, Proxy_1.proxyHandler); } /** * The 'instance' method is used get instance. * @override * @static * @returns {DB} instance of the DB */ static get instance() { return new this(); } /** * The 'query' method is used to execute sql statement * * @param {string} sql * @param {Record<string,any>} parameters * @returns {promise<any[]>} */ async query(sql, parameters = {}) { if (Object.keys(parameters).length) { let bindSql = sql; for (const key in parameters) { const parameter = parameters[key]; if (parameter === null) { bindSql = bindSql.replace(`:${key}`, this.$constants("NULL")); continue; } if (parameter === true || parameter === false) { bindSql = bindSql.replace(`:${key}`, `'${parameter === true ? 1 : 0}'`); continue; } bindSql = bindSql.replace(`:${key}`, Array.isArray(parameter) ? `(${parameter.map((p) => `'${this.escape(p)}'`).join(",")})` : `'${this.escape(parameter)}'`); } return await this.rawQuery(bindSql); } return await this.rawQuery(sql); } /** * The 'query' method is used to execute sql statement * * @param {string} sql * @param {Record<string,any>} parameters * @returns {promise<any[]>} */ static async query(sql, parameters = {}) { return await new this().query(sql, parameters); } /** * The 'from' method is used to define the from table name. * @param {string} table table name * @returns {this} this */ static from(table) { return new this().from(table); } /** * The 'table' method is used to define the table name. * @param {string} table table name * @returns {DB} DB */ static table(table) { return new this().table(table); } /** * The 'alias' method is used to set the table name. * * @param {string} sql raw sql from make a new alias for this table * @param {string} alias alias name * @returns {DB} DB */ static alias(sql, alias) { return new this().alias(sql, alias); } /** * The 'jsonObject' method is used to specify select data to JSON objects. * @param {string} object table name * @param {string} alias * @returns {string} string */ jsonObject(object, alias) { if (!Object.keys(object).length) throw new Error("The method 'jsonObject' is not supported for empty object"); let maping = []; for (const [key, value] of Object.entries(object)) { if (/\./.test(value)) { const [table, c] = value.split("."); maping = [...maping, `'${key}'`, `\`${table}\`.\`${c}\``]; continue; } maping = [ ...maping, `'${key}'`, `\`${this.getTableName()}\`.\`${value}\``, ]; } return `${this.$constants("JSON_OBJECT")}(${maping.join(" , ")}) ${this.$constants("AS")} \`${alias}\``; } /** * The 'jsonObject' method is used to specify select data to JSON objects. * @static * @param {string} object table name * @param {string} alias * @returns {string} string */ static jsonObject(object, alias) { return new this().jsonObject(object, alias); } /** * The 'JSONObject' method is used to specify select data to JSON objects. * @param {string} object table name * @param {string} alias * @returns {string} string */ JSONObject(object, alias) { return this.jsonObject(object, alias); } /** * The 'JSONObject' method is used to specify select data to JSON objects. * @static * @param {string} object table name * @param {string} alias * @returns {string} string */ static JSONObject(object, alias) { return new this().jsonObject(object, alias); } /** * The 'constants' method is used to return constants with key or none in 'DB' or 'Model'. * @param {string} key * @returns {string | object} string || object */ constants(key) { return this.$constants(key); } /** * The 'constants' method is used to return constants with key or none in 'DB' or 'Model'. * @static * @param {string} key * @returns {string | object} string || object */ static constants(key) { return new this().constants(key); } /** * cases query * @param {arrayObject} cases array object {when , then } * @param {string?} final else condition * @returns {string} string */ caseUpdate(cases, final) { if (!cases.length) return []; let query = []; for (const c of cases) { if (c.when == null) throw new Error(`can't find when condition`); if (c.then == null) throw new Error(`can't find then condition`); query = [ ...query, `${this.$constants("WHEN")} ${c.when} ${this.$constants("THEN")} ${c.then}`, ]; } return [ this.$constants("RAW"), this.$constants("CASE"), query.join(" "), final == null ? "" : `ELSE ${final}`, this.$constants("END"), ].join(" "); } /** * select by cases * @static * @param {arrayObject} cases array object {when , then } * @param {string?} final else condition * @returns {this} */ static caseUpdate(cases, final) { return new this().caseUpdate(cases, final); } /** * The 'generateUUID' methid is used to generate a universal unique identifier. * @returns {string} string */ generateUUID() { return this.$utils.generateUUID(); } /** * The 'generateUUID' methid is used to generate a universal unique identifier. * @static * @returns {string} string */ static generateUUID() { return new this().generateUUID(); } /** * The 'snakeCase' methid is used to covert value to snakeCase pattern. * @returns {string} string */ snakeCase(value) { return this.$utils.snakeCase(value); } /** * The 'snakeCase' methid is used to covert value to snake_case pattern. * @returns {string} string */ static snakeCase(value) { return new this().$utils.snakeCase(value); } /** * The 'camelCase' methid is used to covert value to camelCase pattern. * @returns {string} string */ camelCase(value) { return this.$utils.camelCase(value); } /** * The 'camelCase' methid is used to covert value to camelCase pattern. * @returns {string} string */ static camelCase(value) { return new this().$utils.camelCase(value); } /** * The 'escape' methid is used to escaping SQL injections. * @returns {string} string */ escape(value) { return this.$utils.escape(value, true); } /** * The 'escape' methid is used to escaping SQL injections. * @returns {string} string */ static escape(value) { return new this().escape(value); } /** * The 'escapeXSS' methid is used to escaping XSS characters. * @returns {string} string */ escapeXSS(value) { return this.$utils.escapeXSS(value); } /** * The 'escapeXSS' methid is used to escaping XSS characters. * @returns {string} string */ static escapeXSS(value) { return new this().escapeXSS(value); } /** * The 'raw' methid is used to allow for raw sql queries to some method in 'DB' or 'Model'. * @param {string} sql * @returns {string} string */ raw(sql) { return `${this.$constants("RAW")}${sql}`; } /** * The 'raw' methid is used to allow for raw sql queries to some method in 'DB' or 'Model'. * @static * @param {string} sql * @returns {string} string */ static raw(sql) { return `${new this().raw(sql)}`; } /** * The 'freeze' methid is used to freeze the column without any pattern. * * @param {string} column * @returns {string} string */ freeze(column) { return `${this.$constants("FREEZE")}${column}`; } /** * The 'freeze' methid is used to freeze the column without any pattern. * * @static * @param {string} column * @returns {string} string */ static freeze(column) { return new this().freeze(column); } /** * The 'getConnection' method is used to get a pool connection. * @param {Object} options options for connection database with credentials * @property {string} option.host * @property {number} option.port * @property {string} option.database * @property {string} option.username * @property {string} option.password * @returns {Connection} */ async getConnection(options) { if (options == null) { const pool = await this.$pool.get(); return await pool.newConnection(); } const { host, port, database, username: user, password, ...others } = options; const pool = new Pool_1.PoolConnection({ host, port, database, user, password, ...others, }); return pool.createNewConnected(); } /** * The 'getConnection' method is used to get a pool connection. * @param {Object} options options for connection database with credentials * @property {string} option.host * @property {number} option.port * @property {string} option.database * @property {string} option.username * @property {string} option.password * @returns {Connection} */ static async getConnection(options) { return new this().getConnection(options); } /** * The 'beginTransaction' is a method used to initiate a database transaction within your application's code. * * A database transaction is a way to group multiple database operations (such as inserts, updates, or deletes) into a single unit of work. * * Transactions are typically used when you want to ensure that a series of database operations either all succeed or all fail together, * ensuring data integrity. * @returns {ConnectionTransaction} object - Connection for the transaction * @type {object} connection * @property {function} connection.query - execute query sql then release connection to pool * @property {function} connection.startTransaction - start transaction of query * @property {function} connection.commit - commit transaction of query * @property {function} connection.rollback - rollback transaction of query */ async beginTransaction() { const pool = new Pool_1.PoolConnection().createNewConnected(); return await pool.connection(); } /** * The 'beginTransaction' is a method used to initiate a database transaction within your application's code. * * A database transaction is a way to group multiple database operations (such as inserts, updates, or deletes) into a single unit of work. * * Transactions are typically used when you want to ensure that a series of database operations either all succeed or all fail together, * ensuring data integrity. * @static * @returns {ConnectionTransaction} object - Connection for the transaction * @type {object} connection * @property {function} connection.query - execute query sql then release connection to pool * @property {function} connection.startTransaction - start transaction of query * @property {function} connection.commit - commit transaction of query * @property {function} connection.rollback - rollback transaction of query */ static async beginTransaction() { return await new this().beginTransaction(); } /** * The 'removeProperties' method is used to removed some properties. * * @param {Array | Record} data * @param {string[]} propertiesToRemoves * @returns {Array | Record} this */ removeProperties(data, propertiesToRemoves) { const setNestedProperty = (obj, path, value) => { const segments = path.split("."); let currentObj = obj; for (let i = 0; i < segments.length - 1; i++) { const segment = segments[i]; if (!currentObj.hasOwnProperty(segment)) { currentObj[segment] = {}; } currentObj = currentObj[segment]; } const lastSegment = segments[segments.length - 1]; currentObj[lastSegment] = value; }; const remove = (obj, propertiesToRemoves) => { const temp = JSON.parse(JSON.stringify(obj)); for (const property of propertiesToRemoves) { if (property == null) continue; const properties = property.split("."); let current = temp; let afterProp = ""; const props = []; for (let i = 0; i < properties.length - 1; i++) { const prop = properties[i]; if (current[prop] == null) continue; props.push(prop); if (typeof current[prop] === "object" && current[prop] != null) { current = current[prop]; afterProp = prop; continue; } delete current[prop]; afterProp = prop; } const lastProp = properties[properties.length - 1]; if (Array.isArray(current)) { setNestedProperty(temp, props.join("."), this.removeProperties(current, [afterProp, lastProp])); continue; } if (current[lastProp] == null) continue; delete current[lastProp]; } return temp; }; if (Array.isArray(data)) { return data.map((obj) => remove(obj, propertiesToRemoves)); } return remove(data, propertiesToRemoves); } /** * The 'removeProperties' method is used to removed some properties. * * @param {Array | Record} data * @param {string[]} propertiesToRemoves * @returns {Array | Record} this */ static removeProperties(data, propertiesToRemoves) { return new this().removeProperties(data, propertiesToRemoves); } /** * * This 'cloneDB' method is used to clone current database to new database * @param {string} database clone current database to new database name * @returns {Promise<boolean>} */ async cloneDB(database) { const db = await this._queryStatement(`${this.$constants("SHOW_DATABASES")} ${this.$constants("LIKE")} '${database}'`); if (Object.values(db[0] ?? []).length) throw new Error(`This database : '${database}' is already exists`); const tables = await this.showTables(); const backup = await this._backup({ tables, database }); await this._queryStatement(`${this.$constants("CREATE_DATABASE_NOT_EXISTS")} \`${database}\``); const creating = async ({ table, values, }) => { try { await this._queryStatement(table); if (values != null && values !== "") await this._queryStatement(values); } catch (e) { } }; await Promise.all(backup.map((b) => creating({ table: b.table, values: b.values }))); return; } /** * * This 'cloneDB' method is used to clone current database to new database * @param {string} database clone current database to new database name * @returns {Promise<boolean>} */ static async cloneDB(database) { return new this().cloneDB(database); } /** * * This 'backup' method is used to backup database intro new database same server or to another server * @type {Object} backup * @property {string} backup.database clone current 'db' in connection to this database * @type {object?} backup.to * @property {string} backup.to.host * @property {number} backup.to.port * @property {string} backup.to.username * @property {string} backup.to.password * @returns {Promise<void>} */ async backup({ database, to }) { if (to != null && Object.keys(to).length) this.connection({ ...to, database }); return this.cloneDB(database); } /** * * This 'backup' method is used to backup database intro new database same server or to another server * @type {Object} backup * @property {string} backup.database clone current 'db' in connection to this database * @type {object?} backup.to * @property {string} backup.to.host * @property {number} backup.to.port * @property {string} backup.to.username * @property {string} backup.to.password * @returns {Promise<void>} */ static async backup({ database, to }) { return new this().backup({ database, to }); } /** * * This 'backupToFile' method is used to backup database intro new ${file}.sql * @type {Object} backup * @property {string} backup.database * @property {string} backup.filePath * @type {object?} backup.connection * @property {string} backup.connection.host * @property {number} backup.connection.port * @property {number} backup.connection.database * @property {string} backup.connection.username * @property {string} backup.connection.password * @returns {Promise<void>} */ async backupToFile({ filePath, database = `dump_${+new Date()}`, connection, }) { await this.$utils.wait(1000 * 3); const tables = await this.showTables(); const backup = (await this._backup({ tables, database })).map((b) => { return { table: [ `\n--`, `-- Table structure for table '${b.name}'`, `--\n`, `${(0, sql_formatter_1.format)(b.table, { language: "spark", tabWidth: 2, linesBetweenQueries: 1, })}`, ].join("\n"), values: b.values !== "" ? [ `\n--`, `-- Dumping data for table '${b.name}'`, `--\n`, `${b.values}`, ].join("\n") : "", }; }); if (connection != null && Object.keys(connection)?.length) this.connection(connection); let sql = [ `--`, `-- tspace-mysql SQL Dump`, `-- https://www.npmjs.com/package/tspace-mysql`, `--`, `-- Host: mysql-db`, `-- Generation Time: ${new Date()}\n`, `SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";`, `START TRANSACTION;`, `--`, `-- Database: '${database}'`, `--\n`, `${this.$constants("CREATE_DATABASE_NOT_EXISTS")} \`${database}\`;`, `USE \`${database}\`;`, `-- --------------------------------------------------------`, ]; for (const b of backup) { sql = [...sql, b.table]; if (b.values) { sql = [...sql, b.values]; } } tools_1.Tool.fs.writeFileSync(filePath, [...sql, "COMMIT;"].join("\n")); return; } /** * * This 'backupToFile' method is used to backup database intro new ${file}.sql * @type {Object} backup * @property {string} backup.database * @property {string} backup.filePath * @type {object?} backup.connection * @property {string} backup.connection.host * @property {number} backup.connection.port * @property {number} backup.connection.database * @property {string} backup.connection.username * @property {string} backup.connection.password * @returns {Promise<void>} */ static async backupToFile({ filePath, database, connection, }) { return new this().backupToFile({ filePath, database, connection }); } /** * * This 'backupSchemaToFile' method is used to backup database intro new ${file}.sql * @type {Object} backup * @property {string} backup.database * @property {string} backup.filePath * @type {object?} backup.connection * @property {string} backup.connection.host * @property {number} backup.connection.port * @property {number} backup.connection.database * @property {string} backup.connection.username * @property {string} backup.connection.password * @returns {Promise<void>} */ async backupSchemaToFile({ filePath, database = `dump_${+new Date()}`, connection, }) { if (connection != null && Object.keys(connection)?.length) this.connection(connection); await this.$utils.wait(1000 * 3); const tables = await this.showTables(); const backup = (await this._backup({ tables, database })).map((b) => { return { table: (0, sql_formatter_1.format)(b.table, { language: "spark", tabWidth: 2, linesBetweenQueries: 1, }) + "\n", }; }); let sql = [ `SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";`, `START TRANSACTION;`, `SET time_zone = "+00:00";`, `${this.$constants("CREATE_DATABASE_NOT_EXISTS")} \`${database}\`;`, `USE \`${database}\`;`, ]; for (const b of backup) sql = [...sql, b.table]; tools_1.Tool.fs.writeFileSync(filePath, [...sql, "COMMIT;"].join("\n")); return; } /** * * This 'backupSchemaToFile' method is used to backup database intro new ${file}.sql * * @type {Object} backup * @property {string} backup.database * @property {string} backup.filePath * @type {object?} backup.connection * @property {string} backup.connection.host * @property {number} backup.connection.port * @property {number} backup.connection.database * @property {string} backup.connection.username * @property {string} backup.connection.password * @returns {Promise<void>} */ static async backupSchemaToFile({ filePath, database, connection, }) { return new this().backupSchemaToFile({ filePath, database, connection }); } /** * * This 'backupTableToFile' method is used to backup database intro new ${file}.sql * * @type {Object} backup * @property {string} backup.database * @property {string} backup.filePath * @type {object?} backup.connection * @property {string} backup.connection.host * @property {number} backup.connection.port * @property {number} backup.connection.database * @property {string} backup.connection.username * @property {string} backup.connection.password * @returns {Promise<void>} */ async backupTableToFile({ filePath, table, connection, }) { if (connection != null && Object.keys(connection)?.length) this.connection(connection); /** * * wait for the connection to new db connected */ await this.$utils.wait(1000 * 5); const schemas = await this.showSchema(table); const createTableSQL = [ `${this.$constants("CREATE_TABLE_NOT_EXISTS")}`, `\`${table}\``, `(${schemas.join(",")})`, `${this.$constants("ENGINE")};`, ]; const values = await this.showValues(table); let valueSQL = []; if (values.length) { const columns = await this.showColumns(table); valueSQL = [ `${this.$constants("INSERT")}`, `\`${table}\``, `(${columns.map((column) => `\`${column}\``).join(",")})`, `${this.$constants("VALUES")} ${values.join(",")};`, ]; } const sql = [ (0, sql_formatter_1.format)(createTableSQL.join(" "), { language: "mysql", tabWidth: 2, linesBetweenQueries: 1, }) + "\n", valueSQL.join(" "), ]; tools_1.Tool.fs.writeFileSync(filePath, [...sql, "COMMIT;"].join("\n")); return; } /** * * This 'backupTableSchemaToFile' method is used to backup database intro new ${file}.sql * @type {Object} backup * @property {string} backup.table * @property {string} backup.filePath * @type {object?} backup.connection * @property {string} backup.connection.host * @property {number} backup.connection.port * @property {number} backup.connection.database * @property {string} backup.connection.username * @property {string} backup.connection.password * @returns {Promise<void>} */ static async backupTableToFile({ filePath, table, connection, }) { return new this().backupTableToFile({ filePath, table, connection }); } /** * * This 'backupTableSchemaToFile' method is used to backup database intro new ${file}.sql * @type {Object} backup * @property {string} backup.database * @property {string} backup.filePath * @type {object?} backup.connection * @property {string} backup.connection.host * @property {number} backup.connection.port * @property {number} backup.connection.database * @property {string} backup.connection.username * @property {string} backup.connection.password * @returns {Promise<void>} */ async backupTableSchemaToFile({ filePath, table, connection, }) { const schemas = await this.showSchema(table); const createTableSQL = [ `${this.$constants("CREATE_TABLE_NOT_EXISTS")}`, `\`${table}\``, `(${schemas.join(",")})`, `${this.$constants("ENGINE")};`, ]; const sql = [createTableSQL.join(" ")]; if (connection != null && Object.keys(connection)?.length) this.connection(connection); await this.$utils.wait(1000 * 5); tools_1.Tool.fs.writeFileSync(filePath, (0, sql_formatter_1.format)(sql.join("\n"), { language: "spark", tabWidth: 2, linesBetweenQueries: 1, })); return; } /** * * This 'backupTableSchemaToFile' method is used to backup database intro new ${file}.sql * @type {Object} backup * @property {string} backup.table * @property {string} backup.filePath * @type {object?} backup.connection * @property {string} backup.connection.host * @property {number} backup.connection.port * @property {number} backup.connection.database * @property {string} backup.connection.username * @property {string} backup.connection.password * @returns {Promise<void>} */ static async backupTableSchemaToFile({ filePath, table, connection, }) { return new this().backupTableSchemaToFile({ filePath, table, connection }); } async _backup({ tables, database, }) { const backup = []; for (const table of tables) { const schemas = await this.showSchema(table); const createTableSQL = [ `${this.$constants("CREATE_TABLE_NOT_EXISTS")}`, `\`${database}\`.\`${table}\``, `(${schemas.join(", ")})`, `${this.$constants("ENGINE")};`, ]; const values = await this.showValues(table); let valueSQL = []; if (values.length) { const columns = await this.showColumns(table); valueSQL = [ `${this.$constants("INSERT")}`, `\`${database}\`.\`${table}\``, `(${columns.map((column) => `\`${column}\``).join(", ")})`, `${this.$constants("VALUES")}`, `\n${values.join(",\n")};`, ]; } backup.push({ name: table == null ? "" : table, table: createTableSQL.join(" "), values: valueSQL.join(" "), }); } return backup; } _initialDB() { this.$state = new State_1.StateHandler("db"); return this; } } exports.DB = DB; exports.default = DB; //# sourceMappingURL=DB.js.map