UNPKG

wio.db

Version:

Gözle okunabilir database modülü.

513 lines (440 loc) 13.1 kB
const DatabaseError = require("./Error"); const path = require("path"); const { existsSync, mkdirSync, writeFileSync, readFileSync, unlinkSync } = require("fs"); const { set, get, unset } = require("lodash"); const yaml = require("yaml"); /** * @type {JsonDatabase<V>} * @template V */ class JsonDatabase { /** * @param {import("./Types/IOptions").IOptions} options * @constructor */ constructor({ databasePath = "db.yml", maxDataSize = null } = {}) { if (maxDataSize !== null && typeof maxDataSize !== "number") { throw new DatabaseError("The maximum limit must be in number type!"); } if (maxDataSize !== null && maxDataSize < 1) { throw new DatabaseError("Inappropriate range for the limit!"); } let basePath = process.cwd(); if (databasePath.startsWith(basePath)) { databasePath = databasePath.replace(basePath, ""); } if (databasePath.startsWith(`.${path.sep}`)) { databasePath = databasePath.slice(1); } if (!databasePath.startsWith(path.sep)) { databasePath = path.sep + databasePath; } if (!databasePath.endsWith(".yml")) { if (databasePath.endsWith(path.sep)) { databasePath += "db.yml"; } else { databasePath += ".yml"; } } basePath = `${basePath}${databasePath}`; const dirNames = databasePath.split(path.sep).slice(1); const length = dirNames.length; if (length > 1) { dirNames.pop(); const firstResolvedDir = path.resolve(dirNames[0]); if (!existsSync(firstResolvedDir)) { mkdirSync(firstResolvedDir); } dirNames.splice(0, 1); let targetDirPath = firstResolvedDir; for (const dirName of dirNames) { const currentPath = `${targetDirPath}${path.sep}${dirName}`; if (!existsSync(currentPath)) { mkdirSync(currentPath); } targetDirPath = `${targetDirPath}${path.sep}${dirName}`; } } this.path = basePath; if (!existsSync(this.path)) { writeFileSync(this.path, ""); } /** * @type {number} */ this.maxDataSize = maxDataSize; this.size = 0; } /** * * @param {string} key Key * @param {V} value Value * @param {boolean} [autoWrite=true] Automatic write setting. * @example db.set("test",3); */ set(key, value, autoWrite = true) { if (key === "" || typeof key !== "string") { throw new DatabaseError("Unapproved key!"); } if ( // @ts-ignore value === "" || value === undefined || value === null ) { throw new DatabaseError("Unapproved value!"); } if (typeof autoWrite !== "boolean") { throw new DatabaseError("autoWrite parameter must be true or false!"); } if (typeof this.maxDataSize === "number" && this.size >= this.maxDataSize) { throw new DatabaseError("Data limit exceeded!"); } const yamlData = this.toJSON(); set(yamlData, key, value); if (autoWrite) writeFileSync(this.path, yaml.stringify(yamlData, { indent: 4 })); this.size++; return value; } /** * * @param {string} key Key * @param {V} [defaultValue=null] If there is no value, the default value to return. * @returns {V} * @example db.get("test"); */ get(key, defaultValue = null) { const yamlData = this.toJSON(); if (key === "" || typeof key !== "string") { throw new DatabaseError("Unapproved key!"); } const data = get(yamlData, key); return data === undefined ? defaultValue : data; } /** * * @param {string} key Key * @param {V} [defaultValue=null] If there is no value, the default value to return. * @returns {V} * @example db.get("test"); */ fetch(key, defaultValue) { return this.get(key, defaultValue); } /** * * @param {string} key Key * @returns {boolean} * @example db.exists("test"); */ exists(key) { const data = this.get(key); return data !== undefined && data !== null; } /** * * @param {string} key Key * @returns {boolean} * @example db.has("test"); */ has(key) { return this.exists(key); } /** * * @param {number} limit Limit * @returns {Array<Schema<V>>}>} * @example db.all(5); */ all(limit = 0) { const yamlData = yaml.parse(readFileSync(this.path, "utf-8")); if (typeof limit !== "number") { throw new DatabaseError("Must be of limit number type!"); } const arr = []; for (const key in yamlData) { arr.push({ ID: key, data: yamlData[key] }); } return limit > 0 ? arr.splice(0, limit) : arr; } /** * * @param {number} [limit] Limit * @returns {Array<Schema<V>>} * @example db.fetchAll(5); */ fetchAll(limit) { return this.all(limit); } /** * * @param {number} [limit] Limit * @returns {{[key:string]:V}} * @example db.toJSON(); */ toJSON(limit) { const allData = this.all(limit); /** * @type {{[key:string]:V}} */ const json = {}; for (const element of allData) { json[element.ID] = element.data; } return json; } /** * * @param {string} key Key * @param {boolean} autoWrite Automatic write setting. * @returns {void} * @example db.delete("test"); */ delete(key, autoWrite = true) { const yamlData = this.toJSON(); if (key === "" || typeof key !== "string") { throw new DatabaseError("Unapproved key!"); } if (typeof autoWrite !== "boolean") { throw new DatabaseError("autoWrite parameter must be true or false!"); } this.size--; unset(yamlData, key); if (autoWrite) writeFileSync(this.path, yaml.stringify(yamlData, { indent: 4 })); return; } /** * * @returns {void} * @example db.deleteAll(); */ deleteAll() { writeFileSync(this.path, ""); this.size = 0; return; } /** * @param {string} key Key * @returns {"string" | "number" | "bigint" | "boolean" | "symbol" | "array" | "undefined" | "object" | "function"} * @example db.type("test"); */ type(key) { const data = this.get(key); if (Array.isArray(data)) return "array"; else return typeof data; } /** * * @param {string} key Key * @param {boolean} multiple Whether to target multiple targets. * @param {(element,index,array) => boolean} callbackfn Value * @param {any} [thisArg] * @returns {any} * @example db.pull("test","hello"); */ pull(key, callbackfn, multiple = false, thisArg) { let data = this.get(key); if (!data) return false; if (!Array.isArray(data)) throw new DatabaseError(`${key} It is not a data string with an ID.`); if (typeof multiple !== "boolean") { throw new DatabaseError("multiple parameter must be true or false!"); } if (thisArg) callbackfn = callbackfn.bind(thisArg); const length = data.length; if (multiple) { const newArray = []; for (let i = 0; i < length; i++) { if (!callbackfn(data[i], i, data)) { newArray.push(data[i]); } } // @ts-ignore return this.set(key, newArray); } else { const index = data.findIndex(callbackfn); data.splice(index, 1); } return this.set(key, data); } /** * * @returns {V[]} Values[] * @example db.valueArray(); */ valueArray() { const all = this.all(); return all.map((element) => element.data); } /** * * @returns {string[]} ID[] * @example db.keyArray(); */ keyArray() { const all = this.all(); return all.map((element) => element.ID); } /** * * @param {string} key Key * @param {"+" | "-" | "*" | "/" | "%"} operator Operator * @param {number|string} value Value * @param {boolean} [goToNegative] Verinin -'lere düşüp düşmeyeceği. (default false) * @returns {any} * @example db.math("test","/",5,false); */ math(key, operator, value, goToNegative = false) { // @ts-ignore if (Array.isArray(value) || isNaN(value)) { throw new DatabaseError(`The type of value is not a number.`); } if (value <= 0) throw new DatabaseError(`Value cannot be less than 1.`); value = Number(value); if (typeof goToNegative !== "boolean") throw new DatabaseError(`The goToNegative parameter must be of boolean type.`); let data = this.get(key); if (!data) { // @ts-ignore return this.set(key, value); } // @ts-ignore if (Array.isArray(data) || isNaN(data)) throw new DatabaseError(`${key} ID data is not a number type data.`); // @ts-ignore data = Number(data); switch (operator) { case "+": // @ts-ignore data += value; break; case "-": // @ts-ignore data -= value; // @ts-ignore if (goToNegative === false && data < 1) data = 0; break; case "*": // @ts-ignore data *= value; break; case "/": // @ts-ignore data /= value; break; case "%": // @ts-ignore data %= value; break; } return this.set(key, data); } /** * * @param {string} key Key * @param {number} value Value * @returns {any} * @example db.add("test",5,false); */ add(key, value) { return this.math(key, "+", value); } /** * * @param {string} key Key * @param {number} value Value * @param {boolean} [goToNegative] Eksilere düşüp düşmeyeceği * @returns {any} * @example db.substr("test",2,false); */ substr(key, value, goToNegative) { return this.math(key, "-", value, goToNegative); } /** * * @param {string} key Key * @param {any} value Value * @returns {V} * @example db.push("test","succes"); */ push(key, value) { const data = this.get(key); if (!data) { // @ts-ignore return this.set(key, [value]); } if (Array.isArray(data)) { data.push(value); return this.set(key, data); } else { // @ts-ignore return this.set(key, [value]); } } /** * * @param {string} key Key * @returns {Array<Schema<V>>} * @example db.includes("te"); */ includes(key) { return this.filter((element) => element.ID.includes(key)); } /** * * @param {string} key Key * @returns {Array<Schema<V>>} * @example db.startsWith("te"); */ startsWith(key) { return this.filter((element) => element.ID.startsWith(key)); } /** * @param {(value:Schema<V>,index:number,array:Array<Schema<V>>) => boolean} callbackfn */ filter(callbackfn) { return this.all().filter(callbackfn); } /** * @param {(a:Schema<V>,b:Schema<V>) => number} callbackfn */ sort(callbackfn) { return this.all().sort(callbackfn); } /** * * @returns {void} */ destroy() { return unlinkSync(this.path); } /** * * @param {(element:{ID:string,data:V},provider:this) => boolean} callbackfn * @returns {number} */ findAndDelete(callbackfn) { let deletedSize = 0; const all = this.all(); for (const element of all) { if (callbackfn(element, this)) { this.delete(element.ID); deletedSize++; } } return deletedSize; } get info() { return { size: this.size, version: "4.0.21" }; } } module.exports = JsonDatabase; /** * @template T * @typedef {Object} Schema * @prop {string} ID * @prop {T} data */