UNPKG

mrsel.db

Version:

Morsel.db is an advanced json database module.

351 lines (328 loc) 11.6 kB
const fs = require('fs').promises; const path = require('path'); const color = require('color-console.js'); const errors = require('./errors'); class Database { constructor(file = 'morsel-database.json') { if (typeof file !== 'string') { throw errors.FILE_PATH_NOT_STRING; } this.filePath = path.resolve(file); this.data = {}; this.load(); } /** * Loads the database file and parses its content. * @throws {Error} If the file cannot be read or parsed. */ async load() { try { await fs.access(this.filePath); const fileData = await fs.readFile(this.filePath, 'utf8'); try { this.data = JSON.parse(fileData); } catch { throw errors.INVALID_JSON; } } catch (err) { if (err.code === 'ENOENT') { console.error(color.red('Error loading database:'), color.red(errors.FILE_NOT_FOUND.message)); } else { console.error(color.red('Error loading database:'), color.red(err.message)); } throw err; } } /** * Saves the current database content to the file. * @throws {Error} If the file cannot be written. */ async save() { try { await fs.writeFile(this.filePath, JSON.stringify(this.data, null, 2)); } catch (err) { console.error(color.red('Error saving database:'), color.red(err.message)); throw err; } } /** * Sets a value for a given key. * @param {string} key - The key to set. * @param {*} value - The value to set. * @throws {Error} If the key is not a string or value is null/undefined. */ async set(key, value) { if (typeof key !== 'string' || value === null || value === undefined) { console.error(color.red('Error setting value:'), color.red(errors.INVALID_VALUE.message)); throw errors.INVALID_VALUE; } this.data[key] = value; await this.save(); } /** * Pushes a value into an array at the specified key. * @param {string} key - The key for the array. * @param {*} value - The value to push. * @throws {Error} If the key is not a string or value is null/undefined. */ async push(key, value) { if (typeof key !== 'string' || value === null || value === undefined) { console.error(color.red('Error pushing value:'), color.red(errors.INVALID_VALUE.message)); throw errors.INVALID_VALUE; } if (!Array.isArray(this.data[key])) { this.data[key] = []; } this.data[key].push(value); await this.save(); } /** * Fetches a sub-value from an object stored at the given key. * @param {string} key - The key for the object. * @param {string} subKey - The sub-key for the value. * @returns {*} The value of the sub-key. * @throws {Error} If the key or sub-key is not a string or object is not found. */ objectFetch(key, subKey) { if (typeof key !== 'string' || typeof subKey !== 'string') { console.error(color.red('Error fetching sub-value:'), color.red(errors.INVALID_VALUE.message)); throw errors.INVALID_VALUE; } const object = this.data[key]; if (!object || typeof object !== 'object') { console.error(color.red('Error fetching sub-value:'), color.red(errors.OBJECT_NOT_FOUND.message)); throw errors.OBJECT_NOT_FOUND; } return object[subKey]; } /** * Fetches a value from an array at the specified key and index. * @param {string} key - The key for the array. * @param {number} index - The index of the value. * @returns {*} The value at the index. * @throws {Error} If the key is not a string or index is not an integer or array is not found. */ arrayFetch(key, index) { if (typeof key !== 'string' || !Number.isInteger(index)) { console.error(color.red('Error fetching array value:'), color.red(errors.INVALID_VALUE.message)); throw errors.INVALID_VALUE; } const array = this.data[key]; if (!Array.isArray(array)) { console.error(color.red('Error fetching array value:'), color.red(errors.ARRAY_NOT_FOUND.message)); throw errors.ARRAY_NOT_FOUND; } return array[index]; } /** * Fetches the value at the specified key. * @param {string} key - The key for the value. * @returns {*} The value associated with the key. * @throws {Error} If the key is not a string. */ fetch(key) { if (typeof key !== 'string') { console.error(color.red('Error fetching value:'), color.red(errors.INVALID_VALUE.message)); throw errors.INVALID_VALUE; } return this.data[key]; } /** * Removes a key from the database. * @param {string} key - The key to remove. * @throws {Error} If the key is not a string. */ async remove(key) { if (typeof key !== 'string') { console.error(color.red('Error removing key:'), color.red(errors.INVALID_VALUE.message)); throw errors.INVALID_VALUE; } delete this.data[key]; await this.save(); } /** * Deletes a value from an array stored at the given key. * @param {string} key - The key for the array. * @param {*} value - The value to delete. * @throws {Error} If the key is not a string or value is null/undefined. */ async delete(key, value) { if (typeof key !== 'string' || value === null || value === undefined) { console.error(color.red('Error deleting value:'), color.red(errors.INVALID_VALUE.message)); throw errors.INVALID_VALUE; } if (Array.isArray(this.data[key])) { this.data[key] = this.data[key].filter(item => item !== value); await this.save(); } } /** * Deletes a sub-key from an object stored at the given key. * @param {string} objectKey - The key for the object. * @param {string} key - The sub-key to delete. * @throws {Error} If the objectKey or key is not a string or object is not found. */ async deleteKey(objectKey, key) { if (typeof objectKey !== 'string' || typeof key !== 'string') { console.error(color.red('Error deleting sub-key:'), color.red(errors.INVALID_VALUE.message)); throw errors.INVALID_VALUE; } if (this.data[objectKey] && typeof this.data[objectKey] === 'object') { delete this.data[objectKey][key]; await this.save(); } else { console.error(color.red('Error deleting sub-key:'), color.red(errors.OBJECT_NOT_FOUND.message)); throw errors.OBJECT_NOT_FOUND; } } /** * Deletes all keys that start with the specified key. * @param {string} key - The prefix for the keys to delete. * @throws {Error} If the key is not a string. */ async deleteEach(key) { if (typeof key !== 'string') { console.error(color.red('Error deleting keys:'), color.red(errors.INVALID_VALUE.message)); throw errors.INVALID_VALUE; } for (let k in this.data) { if (k.startsWith(key)) { delete this.data[k]; } } await this.save(); } /** * Clears all data in the database. * @throws {Error} If there is an issue with saving the cleared data. */ async clear() { this.data = {}; await this.save(); } /** * Deletes the database file and clears the data. * @throws {Error} If the file cannot be deleted or data cannot be cleared. */ async destroy() { try { await fs.unlink(this.filePath); } catch (err) { if (err.code === 'ENOENT') { console.error(color.red('Error destroying database:'), color.red(errors.FILE_NOT_FOUND.message)); } else { console.error(color.red('Error destroying database:'), color.red(err.message)); } throw err; } await this.clear(); } /** * Checks if a key exists in the database. * @param {string} key - The key to check. * @returns {boolean} True if the key exists, false otherwise. * @throws {Error} If the key is not a string. */ has(key) { if (typeof key !== 'string') { console.error(color.red('Error checking key existence:'), color.red(errors.INVALID_VALUE.message)); throw errors.INVALID_VALUE; } return this.data.hasOwnProperty(key); } /** * Adds a number to the value stored at the given key. * @param {string} key - The key for the value. * @param {number} value - The value to add. * @throws {Error} If the key is not a string or value is not a number. */ async add(key, value) { if (typeof key !== 'string' || typeof value !== 'number') { console.error(color.red('Error adding value:'), color.red(errors.INVALID_VALUE.message)); throw errors.INVALID_VALUE; } if (typeof this.data[key] === 'number') { this.data[key] += value; await this.save(); } else { console.error(color.red('Error adding value:'), color.red(errors.INVALID_VALUE.message)); throw errors.INVALID_VALUE; } } /** * Subtracts a number from the value stored at the given key. * @param {string} key - The key for the value. * @param {number} value - The value to subtract. * @throws {Error} If the key is not a string or value is not a number. */ async subtract(key, value) { if (typeof key !== 'string' || typeof value !== 'number') { console.error(color.red('Error subtracting value:'), color.red(errors.INVALID_VALUE.message)); throw errors.INVALID_VALUE; } if (typeof this.data[key] === 'number') { this.data[key] -= value; await this.save(); } else { console.error(color.red('Error subtracting value:'), color.red(errors.INVALID_VALUE.message)); throw errors.INVALID_VALUE; } } /** * Multiplies the value stored at the given key by a number. * @param {string} key - The key for the value. * @param {number} value - The value to multiply by. * @throws {Error} If the key is not a string or value is not a number. */ async multiply(key, value) { if (typeof key !== 'string' || typeof value !== 'number') { console.error(color.red('Error multiplying value:'), color.red(errors.INVALID_VALUE.message)); throw errors.INVALID_VALUE; } if (typeof this.data[key] === 'number') { this.data[key] *= value; await this.save(); } else { console.error(color.red('Error multiplying value:'), color.red(errors.INVALID_VALUE.message)); throw errors.INVALID_VALUE; } } /** * Divides the value stored at the given key by a number. * @param {string} key - The key for the value. * @param {number} value - The value to divide by. * @throws {Error} If the key is not a string or value is not a number or division by zero. */ async divide(key, value) { if (typeof key !== 'string' || typeof value !== 'number') { console.error(color.red('Error dividing value:'), color.red(errors.INVALID_VALUE.message)); throw errors.INVALID_VALUE; } if (typeof this.data[key] === 'number') { if (value === 0) { console.error(color.red('Error dividing value:'), color.red(errors.DIVISION_BY_ZERO.message)); throw errors.DIVISION_BY_ZERO; } this.data[key] /= value; await this.save(); } else { console.error(color.red('Error dividing value:'), color.red(errors.INVALID_VALUE.message)); throw errors.INVALID_VALUE; } } /** * Gets all keys from the database. * @returns {Array<string>} The list of all keys. */ getAllKeys() { return Object.keys(this.data); } /** * Gets all values from the database. * @returns {Array<*>} The list of all values. */ getAllValues() { return Object.values(this.data); } } module.exports = Database;