const levelup = require("levelup");
const leveldown = require("leveldown");
const { EventEmitter } = require("events");
const deasync = require("deasync");
const fs = require("fs");
const express = require("express");
/**
* The built in string object
*
* @typedef String
* @type {String}
* @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String|String}
*/
/**
* The built in number object
*
* @typedef Number
* @type {Number}
* @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number|Number}
*/
/**
* The built in boolean object
*
* @typedef Boolean
* @type {Boolean}
* @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean|Boolean}
*/
/**
* The built in array object
*
* @typedef Array
* @type {Object}
* @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array|Array}
*/
/**
* The built in plain object
*
* @typedef PlainObject
* @type {Object}
* @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object|PlainObject}
*/
/**
* The built in undefined object
*
* @typedef undefined
* @type {undefined}
* @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/undefined|undefined}
*/
/**
* The built in map object
*
* @typedef Map
* @type {Object}
* @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map|Map}
*/
/**
* The built in Class
*
* @typedef Class
* @type {Object}
* @see {@link https://developer.mozilla.org/tr/docs/Web/JavaScript/Reference/Classes|Class}
*/
/**
* The built in anything
*
* @typedef any
* @type {String|Map|Number|Array|PlainObject|Boolean|Class|undefined}
*/
function readMap(key, value) {
let object = this[key];
if(object instanceof Map) {
return {
"type": "map",
"veryverySecretSecretKey": "this is a key yey",
"data": Array.from(object.entries())
};
} else {
return value;
}
}
function getMap(key, value) {
if((typeof value == "object") && value.hasOwnProperty("veryverySecretSecretKey") && (value.veryverySecretSecretKey == "this is a key yey")) {
if(value.type === "map") {
return new Map(value.data);
}
}
return value;
}
class LevelDatabase extends EventEmitter {
/**
* This main class of module
*
* @class LevelDatabase
* @see {@link https://nodejs.org/api/events.html#events_class_eventemitter|EventEmitter}
* @type {Class}
* @param {String} dirName
* @param {Object} options
* @param {Boolean} options.server Is server feature enabled?
*/
constructor(dirName, options = {}) {
super();
/**
* The Level database created by {@link https://npmjs.com/~vweevers|vweevers}, {@link https://npmjs.com/~rvagg|rvagg} and {@link https://npmjs.com/~ralphtheninja|ralphtheninja}
* @see {@link https://npmjs.com/package/level|Level}
*/
this.database = levelup(leveldown(dirName));
/**
* The directory name with database
* @type {String}
*/
this.dirName = dirName;
if (options.hasOwnProperty("server") && (options.server == true)) {
let app = express();
app.listen(3000, function() {
console.log("Connected to the server!");
})
/**
* The express app
* @type {ExpressApp}
*/
this.app = app;
}
}
/**
* Convert data from {@link String} to specified type
* @param {String} data The data to convert from {@link String}
* @param {String} type The type to convert from {@link String}
* @returns {any}
*/
convertTo(data, type) {
if(type == "array") {
return JSON.parse(data);
} else if(type == "object") {
return JSON.parse(data, getMap);
} else if(type == "number") {
return Number(data);
} else if(type == "string") {
return data;
} else if(type == "boolean") {
return JSON.parse(data);
} else if(type == "map") {
return JSON.parse(data, getMap);
} else if(type == "undefined") {
return undefined;
}
}
/**
* Fetch type
* @param {any} data The data of you want to fetch type
* @returns {String} The type of the data
*/
fetchFullType(data) {
if(typeof data == "object") {
if(Array.isArray(data)) {
return "array";
} else if(data instanceof Map) {
return "map";
} else {
return "object";
}
} else {
return (typeof data);
}
}
/**
* Get the data from {@link LevelDatabase#database}
* @param {String} key The name of the data you want to get
* @returns {any} The data you want to get
*/
baseGet(key) {
if(typeof key != "string")
throw new TypeError("First argument (key)'s type must be string");
return deasync(function(all, callback) {
all.database.get("all", function(error, value) {
if(error) callback(error, null);
if(typeof value == "undefined") {
callback(null, undefined);
} else if(!all.convertTo(value.toString(), "object").hasOwnProperty(all.key)) {
callback(null, undefined);
} else {
callback(null, all.convertTo(JSON.stringify(all.convertTo(value.toString(), "object")[all.key].data, readMap), all.convertTo(value.toString(), "object")[all.key].type));
}
})
})({
"database": this.database,
"convertTo": this.convertTo,
"key": key
});
}
/**
* Get all data
* @returns {any} The data
*/
all() {
return deasync(function(all, callback) {
all.database.get("all", function(error, value) {
if(error) callback(error, null);
if(typeof value == "undefined") {
callback(null, undefined);
} else {
callback(null, JSON.parse(value.toString()));
}
})
})({
"database": this.database
});
}
/**
* Set the data to {@link LevelDatabase#database}
* @param {String} key The name of the data you want to set
* @param {any} value The data you want to set
* @returns {any} The data you want to set
*/
baseSet(key, value) {
if(typeof key != "string")
throw new TypeError("First argument (key)'s type must be string");
return deasync(function(all, callback) {
let data = (all.all() || {});
data[all.key.split(".")[0]] = {
"data": all.value,
"type": all.fetchFullType(all.value)
};
if(typeof all.app != "undefined") {
all.app.get(`/${all.dirName.split("/").pop()}`, function(req, res) {
res.send(JSON.parse(JSON.stringify(all.all()), function getMap(key, value) {
if((typeof value == "object") && value.hasOwnProperty("veryverySecretSecretKey") && (value.veryverySecretSecretKey == "this is a key yey")) {
if(value.type === "map") {
return value.data;
}
}
return value;
}));
})
}
all.database.put("all", JSON.stringify(data, readMap), function(error) {
if(error) callback(error, null);
callback(null, data);
})
})({
"dirName": this.dirName,
"app": this.app,
"database": this.database,
"value": value,
"key": key,
"all": this.all,
"fetchFullType": this.fetchFullType
});
}
/**
* Get the data from module
* @param {String} key The name of the data you want to get
* @returns {any} The data you want to get
*/
get(key) {
if(typeof key != "string")
throw new TypeError("First argument (key)'s type must be string");
if(key.split(".").length != 1) {
let data = this.baseGet(key.split(".")[0]);
let keys = key.split(".").slice(1);
let string = "data";
let isUndefined = false;
for(let i = 0; i < keys.length; i++) {
if(typeof eval(string) != "undefined") {
string += `["${keys[i]}"]`;
} else {
isUndefined = true;
break;
}
}
return (isUndefined ? undefined : eval(string));
} else {
return this.baseGet(key);
}
}
/**
* Set the data to module
* @param {String} key The name of the data you want to set
* @param {any} value The data you want to set
* @returns {any} The data you want to set
*/
set(key, value) {
if(typeof key != "string")
throw new TypeError("First argument (key)'s type must be string");
if(key.split(".").length != 1) {
let data = this.baseGet(key.split(".")[0])
let keys = key.split(".").slice(1);
let string = "data";
if(this.fetchFullType(data) != "object") {
data = {};
}
for(let i = 0; i < keys.length; i++) {
if(typeof eval(string) != "object") {
eval(`${string} = {}`);
}
string += `["${keys[i]}"]`;
}
eval(`${string} = value`);
return this.baseSet(key, data);
} else {
return this.baseSet(key, value);
}
}
/**
* Delete the data
* @param {String} key The name of the data
* @returns {Boolean} Is the data deleted?
*/
delete(key) {
if(typeof key != "string")
throw new TypeError("First argument (key)'s type must be string");
if(!this.has(key)) return false;
if(key.split(".").length != 1) {
let data = this.baseGet(key.split(".")[0])
let keys = key.split(".").slice(1);
let string = "data";
if(this.fetchFullType(data) != "object") {
data = {};
}
for(let i = 0; i < keys.length; i++) {
if(typeof eval(string) != "object") {
eval(`${string} = {}`);
}
string += `["${keys[i]}"]`;
}
eval(`delete ${string}`);
this.set(key.split(".")[0], data);
return true;
} else {
let data = this.all();
delete data[key];
return deasync(function(all, callback) {
if(typeof all.app != "undefined") {
all.app.get(`/${all.dirName.split("/").pop()}`, function(req, res) {
res.send(JSON.parse(JSON.stringify(data), function getMap(key, value) {
if((typeof value == "object") && value.hasOwnProperty("veryverySecretSecretKey") && (value.veryverySecretSecretKey == "this is a key yey")) {
if(value.type === "map") {
return value.data;
}
}
return value;
}));
})
}
all.database.put("all", JSON.stringify(data, readMap), function(error) {
if(error) callback(error, null);
callback(null, true);
})
})({
"dirName": this.dirName,
"app": this.app,
"database": this.database,
"key": key,
"fetchFullType": this.fetchFullType
});
}
}
/**
* Check if the data is here
* @param {String} key
*/
has(key) {
if(typeof key != "string")
throw new TypeError("First argument (key)'s type must be string");
return (this.get(key) ? true : false);
}
/**
* Push value to specified data
* @param {String} key The name of the data you want to push
* @param {any} value The data you want to push
* @returns {any} The array of the data
*/
push(key, value) {
if(typeof key != "string")
throw new TypeError("First argument (key)'s type must be string");
let data = (this.has(key) ? (Array.isArray(this.get(key)) ? this.get(key) : []) : []);
data.push(value);
return this.set(key, value);
}
}
module.exports = LevelDatabase;