UNPKG

mxtorie

Version:

Easy manage and use sql database

661 lines (624 loc) 30.2 kB
const mysql = require('mysql') const datatype = require('./datatype') class database { /** * @typedef {Array[databaseData]} databaseArray */ /** * @typedef {object} databaseData * @property {string} name * @property {datatype} type * @property {number} length */ /** * @typedef {object} mxtorieOptions * @property {string} host The address ip or the url of the database * @property {string} database The database name * @property {string} user The username * @property {string} pass The password * @property {number} port The database port * @property {string} charset The database charset * @property {boolean} multipleStatements If the database can have a multipleStatements */ /** * * @param {mxtorieOptions} options * @param {object[databaseArray]} data The database tables */ constructor(options, data) { if (!options.host) options.host = "127.0.0.1" if (typeof options.host !== 'string') throw new TypeError("The host must be a string") if (!options.database) throw new TypeError("The database name is undefined") if (typeof options.database !== 'string') throw new TypeError("The database name must be a string") if (!options.user) throw new TypeError("The user is undefined") if (typeof options.user !== 'string') throw new TypeError("The user must be a string") /*if (!options.pass) throw new TypeError("The pass is undefined") if (typeof options.pass !== 'string') throw new TypeError("The pass must be a string")*/ if (options.port) if (typeof options.port !== 'number') throw new TypeError("The port must be a number") if (options.charset) if (typeof options.charset !== 'string') throw new TypeError("The charset must be a string") if (options.multipleStatements) if (typeof options.multipleStatements !== 'boolean') throw new TypeError("The multipleStatements must be a boolean") /** * @private */ this.database = mysql.createConnection({ host: options.host, port: options.port || 3306, user: options.user, password: options.pass, database: options.database, charset: options.charset || "utf8mb4_unicode_ci", multipleStatements: options.multipleStatements || true }) /** * @private */ this.data = data /** * @private */ this.cache = new Map() /** * @private */ this.deleted = new Map() this.options = options //if(this.data) this.syncData().catch(e=>{throw new TypeError("Sync data critical error : "+e)}) } async connectToDb() { return new Promise(async (resolve, reject) => { this.database.connect(err => { if (err) return reject("Can't connect to the database. \n" + err) console.log("[BDD] • Connected") this.syncData().catch(e => { throw new TypeError("Sync data critical error : " + e) }) this.checkErrorEvent() this.checkError() return resolve(this.database) }) }) } /** * @private */ async ReconnectAfterError() { return new Promise(async (resolve, reject) => { console.log("Reconnection to BDD after error...") this.database = mysql.createConnection({ host: this.options.host, port: this.options.port || 3306, user: this.options.user, password: this.options.pass, database: this.options.database, charset: this.options.charset || "utf8mb4_unicode_ci", multipleStatements: this.options.multipleStatements || true }) this.database.connect(function (err) { if (err) return reject("Error during the reconnection to the database after a fatal error.") console.log("[BDD BOT] • Reconnected") return resolve(true) }) }) } /** * @private */ async checkErrorEvent() { this.database.on('error', async (error) => { if (error.fatal) { console.log("Fatal error with the database connection...") this.ReconnectAfterError().then(v => this.checkError()) } }) } /** * @private */ async checkError() { let x = setInterval(async () => { this.database.query(`SELECT * FROM ${Object.keys(this.data)[0]}`, async (err, results) => { if (err?.fatal) { console.log("Fatal error with the database connection...") clearInterval(x) this.ReconnectAfterError().then(v => this.checkErrorEvent()) } }) }, 30000) } /** * @private * @returns */ async syncData() { return new Promise(async (resolve, reject) => { //console.log("sync data") if (typeof this.data !== "object") return reject("Data must be an object") if (this.data.length < 1) return reject("Data length must be above 0") Object.keys(this.data).map(async key => { if (this.data[key].length < 1) return console.log('No data for the table : ' + key + '\nWe can\'t create it.') this.database.query(`CREATE TABLE IF NOT EXISTS ${key} (${this.data[key].map(v => `${v.name} ${v.type.toUpperCase()}${v.length ? `(${v.length})` : v.type.toUpperCase() === 'JSON' ? '' : '(50)'}`).join(', ')})`, async (error) => { if (error) return reject(`Error during the sync of the table ${key} ! Reason : ${error}`) //console.log('table created') this.data[key].map(v => { this.database.query(`ALTER table ${key} add column (${v.name} ${v.type.toUpperCase()}${v.length ? `(${v.length})` : v.type.toUpperCase() === 'JSON' ? '' : '(50)'})`, err => { if (err?.sqlMessage.includes("Duplicate column name")) { } else { if (err) return reject("Alter table error : " + err) } }) this.database.query(`ALTER table ${key} MODIFY ${v.name} ${v.type}${v.length ? `(${v.length})` : ''}`, err => { if (error) return console.log(error) }) }) }) }) setTimeout(() => { this.putInCache() return resolve(this) }, 3000) }) } /** * @private * @returns */ async putInCache() { if (typeof this.data !== "object") return console.error("Data must be an object") if (this.data.length < 1) return console.error("Data length must be above 0") await Object.keys(this.data).map(async key => { if (this.data[key].length < 1) return console.error("Key " + key + " is empty.") this.database.query(`SELECT * FROM ${key}`, async (error, res) => { if (error) return [console.error("Error during the sync in the local cache : " + error), this.cache.set(key, []), this.deleted.set(key, [])] if (res.length < 1) return [this.cache.set(key, []), this.deleted.set(key, [])] res.map(async r => { if (this.cache.has(key)) { let current = this.cache.get(key) current.push(r) this.cache.set(key, current) } else { this.cache.set(key, [r]) this.deleted.set(key, []) } }) }) }) return this } /** * Refresh the local cache of one or all tables * @param {string} table The table to refresh, undefined will refresh all tables * @returns */ async refreshLocalData(table) { if (table) { this.database.query(`SELECT * FROM ${table}`, async (error, res) => { if (error) return [console.error("Error during the sync in the local cache : " + error), this.cache.set(key, []), this.deleted.set(key, [])] if (res.length < 1) return [this.cache.set(table, []), this.deleted.set(table, [])] res.map(async r => { if (this.cache.has(table)) { let current = this.cache.get(table) current.push(r) this.cache.set(table, current) } else { this.cache.set(table, [r]) this.deleted.set(table, []) } }) }) } else { if (typeof this.data !== "object") return console.error("Data must be an object") if (this.data.length < 1) return console.error("Data length must be above 0") await Object.keys(this.data).map(async key => { if (this.data[key].length < 1) return console.error("Key " + key + " is empty.") this.database.query(`SELECT * FROM ${key}`, async (error, res) => { if (error) return [console.error("Error during the sync in the local cache : " + error), this.cache.set(key, []), this.deleted.set(key, [])] if (res.length < 1) return [this.cache.set(key, []), this.deleted.set(key, [])] res.map(async r => { if (this.cache.has(key)) { let current = this.cache.get(key) current.push(r) this.cache.set(key, current) } else { this.cache.set(key, [r]) this.deleted.set(key, []) } }) }) }) } return this } /** * Allow you to change a value in the cache of the database, this changement is local ! You need to do save() to really save it in the database. * @param {string} table The table name * @param {object} where The current value, to find where the program change the data. Example : set('test_table', [{message: 'hello'}], 'count', 5) will change the value to 5 of 'count' in the table 'test_table' where the value of 'message' is 'equal' * @param {string} key The key * @param {*} value The value */ async set(table, where, key, value) { if (!table || typeof table !== 'string') throw new TypeError("The table name must be a string") if (!key || typeof key !== 'string') throw new TypeError("The key must be a string") if (!value) throw new TypeError("The value is undefined") if (where && typeof where !== 'object') throw new TypeError("Where must be a object") if (!this.cache.has(table)) throw new TypeError("The table is not existing") if (!where || where.length < 1) { // console.log('sans where') let all = this.cache.get(table) if (!all.find(v => v[key] || v[key]===null)) throw new TypeError("The key '" + key + "' is not existing in the table '" + table + "'") let data = all.filter(v => v[key]) data.map(async v => v[key] = value) return this } else { try { // console.log("avec where") let all = this.cache.get(table) if (!all.find(v => v[key] || v[key]===null)) throw new TypeError("The key '" + key + "' is not existing in the table '" + table + "'") let data = all.filter(v => { let isOk = true where.map(w => { Object.keys(w).map(k => { if (v[key] && v[k] !== w[k]) isOk = false }) }) if (isOk) return true else return false }) data.map(async v => v[key] = value) return this } catch (err) { console.log(err) } } } /** * Get a values from the cache of the database * @param {string} table The table name * @param {object} where The current value, to find where the program change the data. Example : get('test_table', [{message: 'hello'}], 'count') will return the value 'count' where 'message' is equal to 'hello' * @param {string} key The key */ async get(table, where, key) { return new Promise(async (resolve, reject) => { if (!table || typeof table !== 'string') return reject("The table name must be a string") if (!key || typeof key !== 'string') return reject("The key must be a string") if (where && typeof where !== 'object') return reject("Where must be a object") if (!this.cache.has(table)) return reject("The table is not existing") if (!where || where.length < 1) { // console.log('sans where') let all = this.cache.get(table) if (!all.find(v => v[key])) return resolve(null) let data = all.filter(v => v[key]) let newdata = [] data.map(v => newdata.push(v[key])) return resolve(newdata) } else { let all = this.cache.get(table) if (!all.find(v => v[key])) return resolve(null) let data = all.filter(v => { let isOk = true where.map(w => { Object.keys(w).map(k => { if (v[key] && v[k] !== w[k]) isOk = false }) }) if (isOk) return true else return false }) let newdata = [] data.map(v => newdata.push(v[key])) return resolve(newdata) } }) } /** * Get a value from the cache of the database * @param {string} table The table name * @param {object} where The current value, to find where the program change the data. Example : get('test_table', [{message: 'hello'}], 'count') will return the value 'count' where 'message' is equal to 'hello' * @param {string} key The key */ async getOne(table, where, key) { return new Promise(async (resolve, reject) => { if (!table || typeof table !== 'string') return reject("The table name must be a string") if (!key || typeof key !== 'string') return reject("The key must be a string") if (where && typeof where !== 'object') return reject("Where must be a object") if (!this.cache.has(table)) return reject("The table is not existing") if (!where || where.length < 1) { // console.log('sans where') let all = this.cache.get(table) if (!all.find(v => v[key])) return resolve(null) let data = all.filter(v => v[key]) if (!data || data.length < 1) return resolve(null) return resolve(data[0][key]) } else { let all = this.cache.get(table) if (!all.find(v => v[key])) return resolve(null) let data = all.filter(v => { let isOk = true where.map(w => { Object.keys(w).map(k => { if (v[key] && v[k] !== w[k]) isOk = false }) }) if (isOk) return true else return false }) if (!data || data.length < 1) return resolve(null) return resolve(data[0][key]) } }) } /** * Get everything where the values corresponds in the cache of the database. * @param {string} table The table name * @param {object} where The current value, to find where the program change the data. Example : getAllWhere('test_table', [{message: 'hello'}]) will return every column where 'message' is equal to 'hello' * @returns */ async getAllWhere(table, where) { return new Promise(async (resolve, reject) => { if (!table || typeof table !== 'string') return reject("The table name must be a string") if (where && typeof where !== 'object') return reject("Where must be a object") if (!this.cache.has(table)) return reject("The table is not existing") if (!where || where.length < 1) return reject("The value of 'where' can't be undefined by using getAllWhere") let all = this.cache.get(table) let data = all.filter(v => { let isOk = true where.map(w => { Object.keys(w).map(k => { if (v[k] !== w[k]) isOk = false }) }) if (isOk) return true else return false }) return resolve(data) }) } /* /** * * @param {string} table The table name * @param {*} key The key * @param {*} defaultvalue The default value of the key, you can let this value null async addToTable(table, key, defaultvalue) { return new Promise(async (resolve, reject) => { if (!table || typeof table !== 'string') return reject("The table name must be a string") if (!this.cache.has(table)) return reject("The table is not existing") if (!key) return reject("The key is undefined") let all = this.cache.get(table) if (all.filter(v => v[key]).length > 0) return reject('The key "' + key + '" is already existing in the table "' + table + '"') all.map(v => v[key] = defaultvalue ? defaultvalue : null) return resolve(true) }) } /** * * @param {string} table The table name * @param {*} key The key * @param {*} defaultvalue The default value of the key, you can let this value null ** async removeFromTable(table, key, defaultvalue) { return new Promise(async (resolve, reject) => { if (!table || typeof table !== 'string') return reject("The table name must be a string") if (!this.cache.has(table)) return reject("The table is not existing") if (!key) return reject("The key is undefined") let all = this.cache.get(table) if (all.filter(v => v[key]).length < 1) return reject('The key "' + key + '" is already existing in the table "' + table + '"') let newdata = [] all.map(v => { delete v[key] newdata.push(v) }) return resolve(true) }) }*/ /** * Insert a new line in the cache of the database, this is Local ! You need to save() to really put them in the database. * @param {string} table The table name * @param {*} values The values to insert */ async insert(table, values) { if (!table || typeof table !== 'string') throw new TypeError("The table name must be a string") if (!this.cache.has(table)) throw new TypeError("The table is not existing") if (values && typeof values !== 'object') throw new TypeError("Values must be a object") if (!values || values.length < 1) throw new TypeError("The value of 'values' can't be undefined when you insert a data") let all = this.cache.get(table) Object.keys(values).map(async key => { if (!this.data[table]) throw new TypeError("Error, the table '" + table + "' has not defined when you declare my class.") //console.log(this.data[table].filter(v => v.name === key)) if (this.data[table].filter(v => v.name === key).length < 1) throw new TypeError("Error, the key '" + key + "' has not defined when you declare the table '" + table + "'.") }) let data = {} this.data[table].map(v => { data[v.name] = values[v.name] ? values[v.name] : null }) //console.log(data) data.new = true all.push(data) //console.log(all) this.cache.set(table, all) return this } /** * Delete a line from the cache of the datable, this changement is local ! You need to save() to really delete them from the database. * @param {string} table The table name * @param {object} where The current value, to find where the program change the data. Example : getAllWhere('test_table', [{message: 'hello'}]) will return every column where 'message' is equal to 'hello' */ async delete(table, where) { if (!table || typeof table !== 'string') throw new TypeError("The table name must be a string") if (where && typeof where !== 'object') throw new TypeError("Where must be a object") if (!this.cache.has(table)) throw new TypeError("The table is not existing") //if (!where || where.length < 1) return reject("The value of 'where' can't be undefined by using delete") let all = this.cache.get(table) let notouch = this.cache.get(table) if (!where || where.length < 1) { this.cache.set(table, []) this.deleted.set(table, all) return this } else { let data = all.filter(v => { let isOk = false where.map(w => { Object.keys(w).map(k => { if (v[k] !== w[k]) isOk = true }) }) if (isOk) return true else return false }) // console.log(data) let all2 = this.cache.get(table) // console.log('------ notouch') /*console.log(notouch) console.log('------------ deletable')*/ let deletable = notouch.filter(me => !data.includes(me)) // console.log(deletable) let currentdeleted = this.deleted.get(table) if (currentdeleted && currentdeleted.length > 0) deletable.map(v => currentdeleted.push(v)) else currentdeleted = deletable /* console.log('------- currentdeleted') console.log(currentdeleted) console.log('-------')*/ this.deleted.set(table, currentdeleted) // console.log(this.deleted) //console.log(this.deleted) all = data this.cache.set(table, all) // console.log('---------') // console.log(all) return this } } /** * Save a table in the database * @param {string} table The table name * @param {object} values The values to save * @param {object} where Where we update the values * @example save('test', ['message'], [{time: '1h'}]) */ async save(table, values, where) { return new Promise(async (resolve, reject) => { if (!table || typeof table !== 'string') return reject("The table name must be a string") if (where && typeof where !== 'object') return reject("Where must be a object") if (values && typeof values !== 'object') throw new TypeError("Values must be a object") if (!this.cache.has(table)) return reject("The table is not existing") let all = this.cache.get(table) let data = all.filter(v => { let isOk = true where.map(w => { Object.keys(w).map(k => { if (v[k] !== w[k]) isOk = false }) }) if (isOk) return true else return false }) /*console.log(data) console.log(values) console.log(data)*/ this.deleteData(table) setTimeout(() => { data.map(d => { // console.log(d[values[0]]) if (d.new) { delete d.new this.database.query(`INSERT INTO ${table} (${values.map(v => `${v}`).join(', ')}) VALUES (${values.map(v => `"${d[v]}"`)})`, async (error, res) => { if (error) return reject(error) else return resolve(true) }) } else { this.database.query(`UPDATE ${table} SET ${values.map(v => `${v} = "${d[v]}"`).join(' , ')} WHERE ${where.map(w => Object.keys(w).map(key => `${key} = "${w[key]}"`).join(' AND '))}`, async (error, res) => { if (error) return reject(error) else return resolve(true) }) } }) return resolve(true) }, 1500) }) } /** * Save in the database all deleted lines * @param {string} table The table name * @returns */ async saveDeleted(table) { return new Promise(async (resolve, reject) => { if (!table || typeof table !== 'string') return reject("The table name must be a string") if (!this.deleted.has(table)) return reject("The table '" + table + "' have no deleted data") let all = this.deleted.get(table) if (!all) return reject("Error, can't find the table in deleted data") this.deleteData(table) return resolve(true) }) } /** * @private * @param {string} table */ async deleteData(table) { let all = this.deleted.get(table) if (!all) return all.map(async v => { if (v.deleted) return let firstKey = Object.keys(v)[0] || null let secondKey = Object.keys(v)[1] || null let thridKey = Object.keys(v)[2] || null /* console.log(firstKey) console.log(v[firstKey]) console.log(secondKey) console.log(v[secondKey]) console.log(thridKey) console.log(v[thridKey])*/ this.database.query(`SELECT * FROM ${table} WHERE ${firstKey} = '${v[firstKey]}'${secondKey ? ` AND ${secondKey} = '${v[secondKey]}'` : ''}${thridKey ? ` AND ${thridKey} = '${v[thridKey]}'` : ''}`, async (error, res) => { if (error) return console.error(error) if (res.length < 1) return //console.log(`DELETE FROM ${table} WHERE ${firstKey} = '${v[firstKey]}'${secondKey ? ` AND ${secondKey} = '${v[secondKey]}'` : ''}${thridKey ? ` AND ${thridKey} = '${v[thridKey]}'` : ''}`) this.database.query(`DELETE FROM ${table} WHERE ${firstKey} = '${v[firstKey]}'${secondKey ? ` AND ${secondKey} = '${v[secondKey]}'` : ''}${thridKey ? ` AND ${thridKey} = '${v[thridKey]}'` : ''}`, async (error, res) => { if (error) return console.error(error) v.deleted = true }) }) }) } /** * Return true if the table exist or false if not * @param {string} table The table name * @returns {boolean} */ async hasTable(table) { return new Promise(async (resolve, reject) => { if (!table || typeof table !== 'string') return reject("The table name must be a string") if (this.cache.has(table)) return resolve(true) else return resolve(false) }) } /** * Return true if the table have something where or false if not * @param {table} table The table name * @param {object} where Where you search * @returns {boolean} */ async has(table, where) { return new Promise(async (resolve, reject) => { if (!table || typeof table !== 'string') return reject("The table name must be a string") if (!where || typeof where !== 'object') return reject("Where must be a object") if (where.length < 1) return reject("Can't resolve where when his length is 0") let all = this.cache.get(table) let data = all.filter(v => { let isOk = true where.map(w => { Object.keys(w).map(k => { if (v[k] !== w[k]) isOk = false }) }) if (isOk) return true else return false }) if (!data || data.length < 1) return resolve(false) return resolve(true) }) } async getAll(table) { return new Promise(async (resolve, reject) => { if (!table || typeof table !== 'string') return reject("The table name must be a string") let all = this.cache.get(table) if (!all || all.length < 1) return resolve([]) return resolve(all) }) } } exports.database = database