UNPKG

nestdb

Version:

The JavaScript Database for Node.js, nwjs, Electron, and the browser

156 lines (129 loc) 4.49 kB
/** * Way data is stored for this database * For a Node.js/Node Webkit database it's the file system * For a browser-side database it's localforage which chooses the best option depending on user browser (IndexedDB then WebSQL then localStorage) * * This version is the Node.js/Node Webkit version * It's essentially fs, mkdirp and crash safe write and read functions */ // Node.js core modules var fs = require('fs') , path = require('path') // Userland modules , mkdirp = require('mkdirp') , async = require('async') // Local variables , storage = {} ; /** * Read the file asynchronously, providing the data to a callback * @public */ storage.read = function (file, callback) { fs.readFile(file, { encoding: 'utf8' }, callback); }; /** * Append new data to the file asynchronously * @public */ storage.append = function (file, data, callback) { fs.appendFile(file, data, { encoding: 'utf8' }, callback); }; /** * Delete the file * @public */ storage.remove = function (file, callback) { fs.exists(file, function (exists) { if (!exists) { return callback(null); } fs.unlink(file, function (err) { return callback(err); }); }); }; /** * Flush data in OS buffer to storage if corresponding option is set * @param {String} options.filename * @param {Boolean} options.isDir Optional, defaults to false * @private */ function flushToStorage(options, callback) { var filename = options.filename , flags = options.isDir ? 'r' : 'r+'; // Windows can't fsync (FlushFileBuffers) directories. We can live with this as it cannot cause 100% dataloss // except in the very rare event of the first time database is loaded and a crash happens if (flags === 'r' && (process.platform === 'win32' || process.platform === 'win64')) { return callback(null); } fs.open(filename, flags, function (err, fd) { if (err) { return callback(err); } fs.fsync(fd, function (errFS) { fs.close(fd, function (errC) { if (errFS || errC) { var e = new Error('Failed to flush to storage'); e.errorOnFsync = errFS; e.errorOnClose = errC; return callback(e); } else { return callback(null); } }); }); }); } /** * Fully write or rewrite the datafile, immune to crashes during the write operation (data will not be lost) * @param {String} filename * @param {String} data * @param {Function} cb Optional callback, signature: err */ storage.write = function (filename, data, cb) { var callback = cb || function () {} , tempFilename = filename + '~'; async.waterfall([ async.apply(flushToStorage, { filename: path.dirname(filename), isDir: true }) , function (cb) { fs.exists(filename, function (exists) { if (exists) { flushToStorage({ filename: filename }, function (err) { return cb(err); }); } else { return cb(); } }); } , function (cb) { fs.writeFile(tempFilename, data, { encoding: 'utf8' }, function (err) { return cb(err); }); } , async.apply(flushToStorage, { filename: tempFilename }) , function (cb) { fs.rename(tempFilename, filename, function (err) { return cb(err); }); } , async.apply(flushToStorage, { filename: path.dirname(filename), isDir: true }) ], function (err) { return callback(err); }) }; /** * Ensure the datafile contains all the data, even if there was a crash during a full file write, * or else create a new empty datafile and any missing ancestor directories in its path * @param {String} filename * @param {Function} callback signature: err */ storage.init = function (filename, callback) { var tempFilename = filename + '~'; fs.exists(filename, function (filenameExists) { // Write was successful if (filenameExists) { return callback(null); } fs.exists(tempFilename, function (oldFilenameExists) { // Write failed, use old version if (oldFilenameExists) { return fs.rename(tempFilename, filename, function (err) { return callback(err); }); } // New database! // Ensure that all of its ancestor directories exist mkdirp(path.dirname(filename), function (err) { if (err) { return callback(err); } // And then create an empty datafile return fs.writeFile(filename, '', { encoding: 'utf8' }, function (err) { callback(err); }); }); }); }); }; // Interface module.exports = storage;