sjdb
Version:
The simple lightweight (2.75Kb) with 0 depencies embedded/persistant JSON database to Node.js, made for little projects.
391 lines (335 loc) • 18.5 kB
JavaScript
// ==ClosureCompiler==
// @output_file_name default.js
// @compilation_level SIMPLE_OPTIMIZATIONS
// ==/ClosureCompiler==
var fs = require('fs')
var readline = require('readline')
class JDatabase {
constructor(dirpath, path) {
this.dir = {
path: dirpath
}
this.file = {
path: path
}
this.db = {
isCompressed: false,
isCompacted: false
}
this.core = {
secureMod: false,
handleEx: false,
log: {
active: false,
dir: (this.dir.path + this.file.path + '/log').toString()
},
sjdbfy: (data) => {
if ( this.db.isCompacted ) { return JSON.stringify(data) }
else { return JSON.stringify(data, null, 4) }
},
compress: () => {
let dbData = fs.readFileSync(this.dir.path + '/' + this.file.path + '.json')
dbData = JSON.parse(dbData)
dbData = JSON.stringify(dbData)
fs.writeFileSync(this.dir.path + '/' + this.file.path + '.json', dbData)
},
fancify: () => {
let dbData = fs.readFileSync(this.dir.path + '/' + this.file.path + '.json')
dbData = JSON.parse(dbData)
dbData = JSON.stringify(dbData, null, 4)
fs.writeFileSync(this.dir.path + '/' + this.file.path + '.json', dbData)
}
// Autosaves
}
// Avoid crashes
if ( this.core.handleEx == true ) {
process.on(`uncaughtException`, (err) => { this.log.write( `An exeption occured, SJDB handle every exeptions for data security, if you want that SJDB stop handling exeptions, set 'db.core.handleEx = false', error: \n${err}`, 'danger') })
}
this.tables = { }
this.create = (force) => {
let dbPath = this.dir.path + '/' + this.file.name
let dirExists = true
let fileExists = true
let toWrite
// I don't know why i got a bug when dir root and file path are the same :|
if ( this.dir.path.substring(2, this.length) == this.file.path ) { this.file.path += (Math.random().toString()).substring(2, 3) }
// If true, disable any protections.
if ( force != true ) {
// Check for dir
try {
fs.accessSync(this.dir.path)
} catch (e) {
dirExists = false
fs.mkdirSync(this.dir.path)
this.log.log('Creating directory..', 'info')
}
// Check for file
try {
fs.accessSync(this.file.path)
} catch (e) {
fileExists = false
}
if ( dirExists && this.core.secureMod == true ) { this.log.log(this.dir.path + ' directory already exists! For security reasons, to overwrite your databases, please delete them manualy (/!\\ or give true as argument /!\\).', 'danger'); return }
if ( fileExists ) { this.log.log(this.file.path + ' database already exists! For security reasons, to overwrite your database, please delete it manualy (/!\\or give true as argument /!\\).', 'danger'); return }
fs.writeFileSync( this.dir.path + '/' + this.file.path + '.json', this.core.sjdbfy( {} ))
if ( this.core.log.active == true ) {
fs.writeFileSync( this.dir.path + '/' + this.file.path + '_log.log', '')
fs.writeFileSync( this.dir.path + '/' + this.file.path + '_log.html', '')
}
}
else {
fs.writeFileSync( this.dir.path + '/' + this.file.path + '.json', this.core.sjdbfy( {} ))
if ( this.core.log.active == true ) {
fs.writeFileSync( this.dir.path + '/' + this.file.path + '_log.log', '')
fs.writeFileSync( this.dir.path + '/' + this.file.path + '_log.html', '')
}
}
}
this.table = (tableName) => {
let dbPath = this.dir.path + '/' + this.file.path + '.json'
this.tables[tableName] = {
name: tableName,
create: (template) => {
if ( template == undefined ) template = {}
let data = fs.readFileSync(dbPath, 'utf8')
let content = JSON.parse(data)
if ( content[tableName] ) { this.log.write('table "' + tableName + '" already exists! For security reasons, to overwrite it, please delete this table manualy.', 'danger' ); return }
content[tableName] = template
fs.writeFileSync(dbPath, this.core.sjdbfy(content))
},
put: (key, value) => {
let data = fs.readFileSync(dbPath, 'utf8')
let content = JSON.parse(data)
if ( content[tableName][key] ) { this.log.log('key "' + key + '" already exists! For security reasons, to overwrite it, please delete this key manualy.', 'danger' ); return }
content[tableName][key] = value
fs.writeFileSync(dbPath, this.core.sjdbfy(content))
this.log.log(`Put ${value} as ${key} in ${dbPath}`)
},
get: (key, fancy) => {
let data = fs.readFileSync(dbPath, 'utf8')
let content = JSON.parse(data)
if ( !key ) { return content[tableName] }
if ( !content[tableName][key] ) { this.log.log('key "' + key + '" doesn\' exists!', 'danger' ); return }
if ( !fancy ){ return (content[tableName][key]) }
else { return this.core.sjdbfy( content[tableName][key] ) }
},
update: (key, value) => {
let data = fs.readFileSync(dbPath, 'utf8')
let content = JSON.parse(data)
if ( !content[tableName][key] ) { this.log.log('key "' + key + '" doesn\' exists!', 'danger' ); return }
if ( key == undefined ) { content[tableName] = value }
else { content[tableName][key] = value }
fs.writeFileSync(dbPath, this.core.sjdbfy(content))
},
delete: (key) => {
let data = fs.readFileSync(dbPath, 'utf8')
let content = JSON.parse(data)
if (!key) {
if (!content[tableName]) { this.log.log('table ' + tableName + ' doesn\'t exists!', 'danger'); return }
else {
delete content[tableName]
fs.writeFileSync(dbPath, this.core.sjdbfy(content))
this.log.log(`Table ${tableName} deleted!`)
}
} else {
if ( !content[tableName][key] ) { this.log.log('key "' + key + '" doesn\' exists!', 'danger' ); return }
delete content[tableName][key]
fs.writeFileSync(dbPath, this.core.sjdbfy(content))
this.log.log(`Key ${key} of table ${tableName} was deleted!`)
}
}
}
return this.tables[tableName]
}
this.log = {
log: (data, type) => {
if ( !this.core.log.active ) { return }
if ( type == 'danger' ) { console.error(data) }
else { console.log('[SJDB]', data) }
},
write: (data, type) => {
if ( !this.core.log.active ) { return }
if (type == undefined) { type = 'log' }
let d = new Date();
let time = ( d.toLocaleDateString() + '|' + d.toLocaleTimeString() ).toString()
let typeStyle
let msgStyle
if (type == 'info') { typeStyle = 'color: blue;'; msgStyle = 'color: white' }
if (type == 'log') { typeStyle = 'color: #888888;'; msgStyle = 'color: white;' }
if (type == 'danger') { typeStyle = 'color: red;'; msgStyle='font-weight: 600; color: red;'}
if (type == 'success') { typeStyle = 'color: green;'; msgStyle='font-weight: 100; color: #2ecc71;'}
fs.appendFile(this.dir.path + this.file.path + '/log' + '.log', time + ' [' + type + ']' + ' >> ' + data + '\n')
fs.appendFile(this.dir.path + this.file.path + '/log' + '.html', '<span style="color:grey;">' + time + '</span>' + '<span style="' + typeStyle + '">' + ' [' + type + ']' + '</span>' + ' >> ' + '<span style="' + msgStyle + '">' + data + '</span>' + '<br/>')
if (type == 'danger') { console.log('[SJDB]', data) }
else { console.log('[SJDB]', data) }
},
open: () => {
var exec = require('child_process').exec;
let os = process.platform
if (os == 'win32' || os == 'win64') {
var cmd = 'cd database & log.html';
exec(cmd, function(error, stdout, stderr) {
if (error) console.log('[SJDB]', error)
});
} else if( os == 'linux') {
var cmd = 'cd database & xdg-open log.html';
exec(cmd, function(error, stdout, stderr) {
this.log.write(stdout)
});
}
},
clear: () => {
fs.writeFileSync(this.dir.path + this.file.path + '/log' + '.log', '')
fs.writeFileSync(this.dir.path + this.file.path + '/log' + '.html', '<link href="https://fonts.googleapis.com/css?family=Inconsolata" rel="stylesheet"> <style>html {font-family: "Inconsolata", monospace; background: black; color: white;}</style>')
}
}
this.QueryCLI = (indentifier) => {
if (indentifier == undefined) indentifier = '-/'
const querystring = require('querystring');
console.log('[SJDB]', '< SJDB Query CLI started >')
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
rl.on('line', (input) => {
if ( input.substring(0, indentifier.length).toString() == indentifier) {
let querystr = input.substring(indentifier.length, input.length)
let parsed = querystring.parse(querystr, '.')
let queryArr = []
for ( var query in parsed ) {
queryArr.push(query)
}
// Decision three
if ( queryArr[0] == 'db' ) {
if ( queryArr[1] == 'create()' ) {
this.create()
} else if ( queryArr[1].substring(0, 5) == 'table' ) {
var table = ''
var targetQuery = queryArr[1].substring(6, queryArr[1].lenght)
// Get table name inside func
for ( var char in targetQuery ) {
if ( targetQuery[char] != "'" && targetQuery[char] != ')' && targetQuery[char] != ' ' ) table += targetQuery[char]
}
if ( queryArr[2].substring(0, 3) == 'get' ) {
targetQuery = queryArr[2].substring(4, queryArr[2].lenght )
var toGet = ''
for ( var char in targetQuery ) {
if ( targetQuery[char] != "'" && targetQuery[char] != ")" ) {
toGet += targetQuery[char]
}
}
this.log.write(this.table(`${table}`).get(`${toGet}`), 'info')
} else if ( queryArr[2].substring(0, 3) == 'put' ) {
targetQuery = queryArr[2].substring(4, queryArr[2].lenght )
var toPut = {}
parsed = querystring.parse(targetQuery, ',')
let i = 0
for ( var val in parsed ) { toPut[i] = val; i++ }
toPut[1] = toPut[1].toString().slice(1, -1)
toPut[0] = toPut[0].replace(/[\"\']+/g, '')
toPut[1] = toPut[1].replace(/[\"\']+/g, '')
this.table(table).put(toPut[0], toPut[1] )
} else if ( queryArr[2].substring(0, 6) == 'update' ) {
targetQuery = queryArr[2].substring(7, queryArr[2].lenght )
var toUpd = {}
parsed = querystring.parse(targetQuery, ',')
let i = 0
for ( var val in parsed ) { toUpd[i] = val; i++ }
toUpd[1] = toUpd[1].toString().slice(1, -1)
this.table(table).update(toUpd[0], toUpd[1])
} else if ( queryArr[2].substring(0, 8) == 'create()' ) {
this.table(`${table}`).create()
} else if ( queryArr[2].substring(0, 6) == 'delete' ) {
if ( queryArr[2] == 'delete()' ) {
this.table(table).delete()
} else {
let keyDel = queryArr[2].substring(8, queryArr[2].lenght).toString()
keyDel = keyDel.slice(0, -2)
this.table(table).delete(keyDel)
}
} else { this.log.write( 'Invalid SJDB CLI command!', 'danger' ) }
} else if ( queryArr[1] == 'log' ) {
if ( queryArr[2] == 'open()' ) {
this.log.open()
} else if ( queryArr[2] == 'clear()' ) {
this.log.clear()
}
} else if (queryArr[1] == 'core') {
if (queryArr[2] == 'compress()') {
this.core.compress()
} else if (queryArr[2] == 'fancify()') {
this.core.fancify()
} else { this.log.write( 'Invalid SJDB CLI command!', 'danger' ) }
} else { this.log.write( 'Invalid SJDB CLI command!', 'danger' ) }
} else if ( queryArr[0] == 'help' ) {
console.log('[SJDB]', `< SJDB Query CLI help >
${indentifier}db
.create(force[true||false@default]): Create a new database. Overwrite if true gived as arg.
.table('tableName'): Select table as target.
.create(): Create new table as '.table()' argument.
.put('key', 'value'): Insert string or int as key into db, don't support objects. Use real editor.
.update('key', 'newValue'): Update key.
.get('key', 'value'): Get value from key.
.delete('key'): Remove key and he's value.
.get(): Get table.
.delete(): Delete table.
.log
.open(): Open logs in HTML file.
.clear(): Clear logs.
.core
.compress(): Improve database's speed by compressing data, use only for production.
.fancify(): Fancify database for humain readability, less faster, use this command only for reading.
.makeBenchmark(ops): Run a benchmark on a new db with arg Put, Update, Delete operations.
`)
} else if ( queryArr[0].substring(0, 13) == 'makeBenchmark' ) {
let ops = queryArr[0].substring(14, this.length)
ops = ops.substring(0, ops.length - 1)
makeBenchmark(ops)
} else { this.log.write( 'Invalid SJDB CLI command!', 'danger' ) }
}
});
}
}
}
function makeBenchmark(ops) {
if ( ops == undefined ) { console.error('Need number of operations in argument.'); return; }
if ( process.platform != 'linux' ) { console.warn(`Starting benchmark for ${ops} ops, keep in mind that your OS can slow down nodejs process, for this reason, you should benchmark on a real server.`) }
let benchDB = new JDatabase('./~BENCHMARK~', Math.random().toString().substring(14))
benchDB.create()
benchDB.table('Benchmark').create()
console.time('Put')
benchDB.table('Benchmark').create()
for ( var i = 0; i < ops; i++ ) {
benchDB.table('Benchmark').put(i, i)
}
var a = console.timeEnd('Put')
// Update
console.time('Update')
for ( var i = 0; i < ops; i++ ) {
benchDB.table('Benchmark').update(i, 'klj')
}
console.timeEnd('Update')
// Delete
console.time('Delete')
for ( var i = 0; i < ops; i++ ) {
benchDB.table('Benchmark').delete(i.toString())
}
if( fs.existsSync(benchDB.dir.path) ) {
fs.readdirSync(benchDB.dir.path).forEach(function(file,index){
var curPath = benchDB.dir.path + "/" + file;
if(fs.lstatSync(curPath).isDirectory()) { // recurse
deleteFolderRecursive(curPath);
} else { // delete file
fs.unlinkSync(curPath);
}
});
fs.rmdirSync(benchDB.dir.path);
}
console.timeEnd('Delete')
console.log('[SJDB]', 'When there is more than 1 000 keys in the same file, we recommand you to split a big database into smallers databases.')
}
module.exports = {
JDatabase: JDatabase,
makeBenchmark: makeBenchmark
}