awSQL/index.js
2024-11-22 04:36:31 +01:00

310 lines
10 KiB
JavaScript

const mysql = require("mysql");
const Select = require("./lib/Select");
const Insert = require("./lib/Insert");
const Delete = require("./lib/Delete");
const Update = require("./lib/Update");
const { CreateTable, Structure, AlterTable } = require("./lib/Tables");
class awSQL {
#instances = {};
#default;
/**
*
* @param {*} hostname
* @param {*} username
* @param {*} password
* @param {*} options
* @returns {Instance}
*/
createInstance = (hostname="localhost", username, password, options = {
charset:"utf8mb4",
defaultDatabase: false,
multipleStatements: false,
insecureAuth: false,
customIdentifier: false,
isDefault: false,
})=>{
const identifier = options.customIdentifier||`${username}@${hostname}`;
if (this.#instances[identifier]) throw new Error(`Can't create new instance with identifier "${identifier}": An instance with the same name already exists`);
const instance = new Instance(hostname, username, password, options.charset, options.defaultDatabase, options.multipleStatements, options.insecureAuth);
this.#instances[identifier] = instance;
if (options.createAsDefault) this.#default = identifier;
return instance;
}
/**
*
* @param {String} identifier
* @returns {Instance}
*/
getInstance = (identifier) => {
if (Object.keys(this.#instances).length===0) return undefined;
if (!identifier) return this.#default?this.#instances[this.#default]:this.#instances[Object.keys(this.#instances)[0]]; // If no identifier is set return default or first instance
return this.#instances[identifier];
}
listInstances = () => {
return Object.keys(this.#instances);
}
deleteInstance = (identifier) => {
if (!identifier) throw new Error("Can't delete Instance: No identifier set");
if (!this.#instances[identifier]) throw new Error(`Can't delete Instance '${identifier}': No Instance`);
this.#instances[identifier].destroy();
if (this.#default === identifier) this.#default = undefined;
delete this.#instances[identifier];
return true;
}
}
class Instance {
#user;
#password;
#host;
#insecureAuth;
#multipleStatements;
#charset;
#connection;
#selectedDatabase;
constructor(hostname="localhost", username, password, charset="utf8mb4", defaultDatabase=false, multipleStatements=false, insecureAuth=false){
this.#host = hostname;
this.#user = username;
this.#password = password;
this.#charset = charset;
this.#multipleStatements = multipleStatements;
this.#insecureAuth = insecureAuth;
this.#selectedDatabase = defaultDatabase||username;
}
/**
* Connects the instance
*
*/
connect = () => {
return new Promise((resolve, reject) => {
const connection = mysql.createConnection({
user: this.#user,
password: this.#password,
host: this.#host,
insecureAuth: this.#insecureAuth,
multipleStatements: this.#multipleStatements,
charset: this.#charset
});
this.#connection = connection;
connection.connect((err) =>{
if (err) throw err;
resolve(`Connected to ${this.#host} with user ${this.#user}`);
});
})
}
/**
* Destroys the instance
*/
destroy = () => {
if (this.#connection) this.#connection.end();
this.#connection = undefined;
return true;
}
/**
* Performs a raw query
* @param {String} queryString The sql query string to perform.
* @param {Array} values - An array holding all replacable ?-values from left to right.
* @returns {Any} - The individual result of your query
*/
queryRaw = (queryString, values) => {
return new Promise((resolve, reject) => {
if (!this.#connection) throw new Error("Querying failed: No connection");
this.#connection.query(queryString, values, (err, result) => {
if (err) throw err;
resolve(result);
})
})
}
/**
* Returns a list of database names the user has access to
* @param {Boolean} excludeSchema - Whether to exclude the default database 'information_schema'
* @returns {Array}
*/
getDatabases = async (excludeSchema=false) => {
let dbs = await this.queryRaw("SHOW DATABASES;");
if (excludeSchema) dbs = dbs.filter((db)=>db.Database!=="information_schema")
return dbs.map(db => db.Database);
}
/**
* Selects a default database for future queries
* @param {String} name - Name of the database
* @returns {this}
*/
selectDatabase = (name) => {
this.#selectedDatabase = name;
return this;
}
/**
* Returns a list of tables for the selected database
* - 'multipleStatements' must be active for this to work
* @param {String} [database] - Database to select. Can be empty as long as a default database was set with 'selectDatabase'
* @returns {Array}
*/
getTables = async (database) => {
if (!this.#multipleStatements) throw new Error("getTables: multipleStatements must be set to 'true' in instance options");
if (!this.#selectedDatabase && !database) throw new Error("getTables: No database selected");
const tables = (await this.queryRaw(`USE ${database||this.#selectedDatabase}; SHOW TABLES;`))[1];
return tables.map(table => table[`Tables_in_${database||this.#selectedDatabase}`]);
}
/**
* Prepares a new select query
* @param {String} from - Name of the table
* @param {...String} [columns] - Name of columns to select. Leave empty to select all
* @returns {Select}
*/
select = (from, ...columns) => {
return new Select(this, this.#selectedDatabase, from, columns);
}
/**
* Prepares a new query to insert data
* @param {String} into - Name of the table to insert into
* @returns {Insert}
*/
insert = (into) => {
return new Insert(this, this.#selectedDatabase, into);
}
/**
* Prepares a new delete query
* @param {String} from - Name of the table to delete from
* @returns {Delete}
*/
delete = (from) => {
return new Delete(this, this.#selectedDatabase, from);
}
/**
* Prepares a new update query
* @param {String} table - Name of the table to update data in
* @returns {Update}
*/
update = (table) => {
return new Update(this, this.#selectedDatabase, table);
}
/**
* Drops a whole database
* - Requires admin privileges
* @param {String} database - Name of the database to drop
* @returns
*/
dropDatabase = async (database) => {
return await this.queryRaw(`DROP DATABASE ${database};`);
}
/**
* Drops a whole table
* @param {String} table - Name of the table to drop
*/
dropTable = async (table) => {
if (!this.#selectedDatabase) throw new Error(`Can't drop table '${table}': Database not set`);
return await this.queryRaw(`DROP TABLE ${this.#selectedDatabase}.${table}`);
}
/**
* Creates a new database
* - Requires admin privileges
* @param {String} name - Name of the database to create
* @returns
*/
createDatabase = async (name) => {
return await this.queryRaw(`CREATE DATABASE ${name};`);
}
/**
* Prepares to create a new table
* @param {String} name - Name of the table to create
* @returns {CreateTable}
*/
createTable = (name) => {
return new CreateTable(this, this.#selectedDatabase, name);
}
/**
* Alters a table and updates to the new given structure.
*
* @param {*} name
*/
alterTable = (name) => {
return new AlterTable(this, this.#selectedDatabase, name);
}
/**
* Prepares to create a new table-structure
* @returns {Structure}
*/
createStructure = () => {
return new Structure();
}
/**
* Returns the structure object of a table
* @param {String} table - Name of table to get structure of
* @param {String} [database] - Name of the underlying database
* @returns {Structure}
*/
getStructure = async (table, database) => {
if (!this.#selectedDatabase && !database) throw new Error(`Can't get structure of table ${table}: Database not selected`);
return new Structure(await this.queryRaw(`DESCRIBE ${database||this.#selectedDatabase}.${table};`));
}
checkStructure = async (table, desiredStructure, database) => {
if (!this.#selectedDatabase && !database) throw new Error(`Can't get structure of table ${table}: Database not selected`);
const dbStruc = (await this.getStructure(table, database||this.#selectedDatabase)).get();
const result = {
errors: [],
passed: []
}
for (let col of dbStruc){
const desiredCol = desiredStructure.find((desCol) => desCol.Field === col.Field);
if (!desiredCol) {
result.errors.push(`${col.Field}: Missing completely`);
continue;
}
let breakOut = false;
for (let key in col){
if (col[key] !== desiredCol[key]){
result.errors.push(`${col.Field}.${key}: Required ${desiredCol[key]}, got ${col[key]}`);
breakOut=true;
}
}
if (!breakOut){
result.passed.push(`${col.Field}`);
}
}
return result;
}
/**
* Returns total amount of rows of a table
* @param {String} table - Table name
* @returns
*/
total = async (table) => {
return await new Select(this, this.#selectedDatabase, table).count(true).execute();
}
isConnected = () => {
if (this.#connection) return true;
return true;
}
}
module.exports = {awSQL: new awSQL(), Structure};