mxtorie
Version:
Easy manage and use sql database
661 lines (624 loc) • 30.2 kB
JavaScript
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