UNPKG

simple-json-db

Version:

A simple, no-frills, JSON storage engine for Node.JS

184 lines (168 loc) 5.7 kB
const fs = require("fs"); /** * Default configuration values. * @type {{asyncWrite: boolean, syncOnWrite: boolean, jsonSpaces: number}} */ const defaultOptions = { asyncWrite: false, syncOnWrite: true, jsonSpaces: 4, stringify: JSON.stringify, parse: JSON.parse }; /** * Validates the contents of a JSON file. * @param {string} fileContent * @returns {boolean} `true` if content is ok, throws error if not. */ let validateJSON = function(fileContent) { try { this.options.parse(fileContent); } catch (e) { console.error('Given filePath is not empty and its content is not valid JSON.'); throw e; } return true; }; /** * Main constructor, manages existing storage file and parses options against default ones. * @param {string} filePath The path of the file to use as storage. * @param {object} [options] Configuration options. * @param {boolean} [options.asyncWrite] Enables the storage to be asynchronously written to disk. Disabled by default (synchronous behaviour). * @param {boolean} [options.syncOnWrite] Makes the storage be written to disk after every modification. Enabled by default. * @param {boolean} [options.syncOnWrite] Makes the storage be written to disk after every modification. Enabled by default. * @param {number} [options.jsonSpaces] How many spaces to use for indentation in the output json files. Default = 4 * @constructor */ function JSONdb(filePath, options) { // Mandatory arguments check if (!filePath || !filePath.length) { throw new Error('Missing file path argument.'); } else { this.filePath = filePath; } // Options parsing if (options) { for (let key in defaultOptions) { if (!options.hasOwnProperty(key)) options[key] = defaultOptions[key]; } this.options = options; } else { this.options = defaultOptions; } // Storage initialization this.storage = {}; // File existence check let stats; try { stats = fs.statSync(filePath); } catch (err) { if (err.code === 'ENOENT') { /* File doesn't exist */ return; } else if (err.code === 'EACCES') { throw new Error(`Cannot access path "${filePath}".`); } else { // Other error throw new Error(`Error while checking for existence of path "${filePath}": ${err}`); } } /* File exists */ try { fs.accessSync(filePath, fs.constants.R_OK | fs.constants.W_OK); } catch (err) { throw new Error(`Cannot read & write on path "${filePath}". Check permissions!`); } if (stats.size > 0) { let data; try { data = fs.readFileSync(filePath); } catch (err) { throw err; // TODO: Do something meaningful } if (validateJSON.bind(this)(data)) this.storage = this.options.parse(data); } } /** * Creates or modifies a key in the database. * @param {string} key The key to create or alter. * @param {object} value Whatever to store in the key. You name it, just keep it JSON-friendly. */ JSONdb.prototype.set = function(key, value) { this.storage[key] = value; if (this.options && this.options.syncOnWrite) this.sync(); }; /** * Extracts the value of a key from the database. * @param {string} key The key to search for. * @returns {object|undefined} The value of the key or `undefined` if it doesn't exist. */ JSONdb.prototype.get = function(key) { return this.storage.hasOwnProperty(key) ? this.storage[key] : undefined; }; /** * Checks if a key is contained in the database. * @param {string} key The key to search for. * @returns {boolean} `True` if it exists, `false` if not. */ JSONdb.prototype.has = function(key) { return this.storage.hasOwnProperty(key); }; /** * Deletes a key from the database. * @param {string} key The key to delete. * @returns {boolean|undefined} `true` if the deletion succeeded, `false` if there was an error, or `undefined` if the key wasn't found. */ JSONdb.prototype.delete = function(key) { let retVal = this.storage.hasOwnProperty(key) ? delete this.storage[key] : undefined; if (this.options && this.options.syncOnWrite) this.sync(); return retVal; }; /** * Deletes all keys from the database. * @returns {object} The JSONdb instance itself. */ JSONdb.prototype.deleteAll = function() { for (var key in this.storage) { //noinspection JSUnfilteredForInLoop this.delete(key); } return this; }; /** * Writes the local storage object to disk. */ JSONdb.prototype.sync = function() { if (this.options && this.options.asyncWrite) { fs.writeFile(this.filePath, this.options.stringify(this.storage, null, this.options.jsonSpaces), (err) => { if (err) throw err; }); } else { try { fs.writeFileSync(this.filePath, this.options.stringify(this.storage, null, this.options.jsonSpaces)); } catch (err) { if (err.code === 'EACCES') { throw new Error(`Cannot access path "${this.filePath}".`); } else { throw new Error(`Error while writing to path "${this.filePath}": ${err}`); } } } }; /** * If no parameter is given, returns **a copy** of the local storage. If an object is given, it is used to replace the local storage. * @param {object} storage A JSON object to overwrite the local storage with. * @returns {object} Clone of the internal JSON storage. `Error` if a parameter was given and it was not a valid JSON object. */ JSONdb.prototype.JSON = function(storage) { if (storage) { try { JSON.parse(this.options.stringify(storage)); this.storage = storage; } catch (err) { throw new Error('Given parameter is not a valid JSON object.'); } } return JSON.parse(this.options.stringify(this.storage)); }; module.exports = JSONdb;