UNPKG

shelf.js

Version:

A modular, powerful wrapper library for persistent objects in the browser and Node.js

493 lines (393 loc) 12.3 kB
/** * # Shelf.JS * Copyright 2014 Stefano Balietti * GPL licenses. * * Persistent Client-Side Storage * --- */ (function(exports) { var version = '5.1'; var store, mainStorageType; mainStorageType = "volatile"; store = exports.store = function(key, value, options, type) { options = options || {}; type = (options.type && options.type in store.types) ? options.type : store.type; if (!type || !store.types[type]) { store.log('Cannot save/load value. Invalid storage type ' + 'selected: ' + type, 'ERR'); return; } store.log('Accessing ' + type + ' storage'); return store.types[type](key, value, options); }; // Adding functions and properties to store /////////////////////////////////////////// store.prefix = "__shelf__"; store.verbosity = 0; store.types = {}; //if Object.defineProperty works... try { Object.defineProperty(store, 'type', { set: function(type) { if ('undefined' === typeof store.types[type]) { store.log('Cannot set store.type to an invalid type: ' + type); return false; } mainStorageType = type; return type; }, get: function(){ return mainStorageType; }, configurable: false, enumerable: true }); } catch(e) { store.type = mainStorageType; // default: memory } store.addType = function(type, storage) { store.types[type] = storage; store[type] = function(key, value, options) { options = options || {}; options.type = type; return store(key, value, options); }; if (!store.type || store.type === "volatile") { store.type = type; } }; // TODO: create unit test store.onquotaerror = undefined; store.error = function() { console.log("shelf quota exceeded"); if ('function' === typeof store.onquotaerror) { store.onquotaerror(null); } }; store.log = function(text) { if (store.verbosity > 0) { console.log('Shelf v.' + version + ': ' + text); } }; store.isPersistent = function() { if (!store.types) return false; if (store.type === "volatile") return false; return true; }; //if Object.defineProperty works... try { Object.defineProperty(store, 'persistent', { set: function(){}, get: store.isPersistent, configurable: false }); } catch(e) { // safe case store.persistent = false; } store.decycle = function(o) { if (JSON && JSON.decycle && 'function' === typeof JSON.decycle) { o = JSON.decycle(o); } return o; }; store.retrocycle = function(o) { if (JSON && JSON.retrocycle && 'function' === typeof JSON.retrocycle) { o = JSON.retrocycle(o); } return o; }; store.stringify = function(o) { if (!JSON || !JSON.stringify || 'function' !== typeof JSON.stringify) { throw new Error('JSON.stringify not found. Received non-string' + 'value and could not serialize.'); } o = store.decycle(o); return JSON.stringify(o); }; store.parse = function(o) { if ('undefined' === typeof o) return undefined; if (JSON && JSON.parse && 'function' === typeof JSON.parse) { try { o = JSON.parse(o); } catch (e) { store.log('Error while parsing a value: ' + e, 'ERR'); store.log(o); } } o = store.retrocycle(o); return o; }; // ## In-memory storage // ### fallback to enable the API even if we can't persist data (function() { var memory = {}, timeout = {}; function copy(obj) { return store.parse(store.stringify(obj)); } store.addType("volatile", function(key, value, options) { if (!key) { return copy(memory); } if (value === undefined) { return copy(memory[key]); } if (timeout[key]) { clearTimeout(timeout[key]); delete timeout[key]; } if (value === null) { delete memory[key]; return null; } memory[key] = value; if (options.expires) { timeout[key] = setTimeout(function() { delete memory[key]; delete timeout[key]; }, options.expires); } return value; }); }()); }( 'undefined' !== typeof module && 'undefined' !== typeof module.exports ? module.exports : this )); /** * ## File System storage for Shelf.js * Copyright 2015 Stefano Balietti * * Available only in Node.JS */ (function(exports) { var store = exports.store; if (!store) { throw new Error('fs.shelf.js: shelf.js core not found.'); } var lock = false; var queue = []; function clearQueue() { if (isLocked()) { // console.log('cannot clear queue if lock is active'); return false; } // console.log('clearing queue'); for (var i=0; i< queue.length; i++) { queue[i].call(queue[i]); } } function locked() { lock = true; } function unlocked() { lock = false; } function isLocked() { return lock; } function addToQueue(cb) { queue.push(cb); } var counter = 0; store.filename = './shelf.out'; var fs = require('fs'), path = require('path'), util = require('util'); // https://github.com/jprichardson/node-fs-extra/blob/master/lib/copy.js //var copyFile = function(srcFile, destFile, cb) { // // var fdr, fdw; // // fdr = fs.createReadStream(srcFile, { // flags: 'r' // }); //// fs.flockSync(fdr, 'sh'); // // fdw = fs.createWriteStream(destFile, { // flags: 'w' // }); // //// fs.flockSync(fdw, 'ex'); // // fdr.on('end', function() { //// fs.flockSync(fdr, 'un'); // }); // // fdw.on('close', function() { //// fs.flockSync(fdw, 'un'); // if (cb) cb(null); // }); // // fdr.pipe(fdw); //}; //var overwrite = function (fileName, items) { //console.log('OW: ' + counter++); // //var file = fileName || store.filename; //if (!file) { // store.log('You must specify a valid file.', 'ERR'); // return false; //} // //var tmp_copy = path.dirname(file) + '/.' + path.basename(file); // ////console.log('files') ////console.log(file); ////console.log(fileName); ////console.log(tmp_copy) // //copyFile(file, tmp_copy, function(){ // var s = store.stringify(items); // // removing leading { and trailing } // s = s.substr(1, s = s.substr(0, s.legth-1)); //// console.log('SAVING') //// console.log(s) // fs.writeFile(file, s, 'utf-8', function(e) { // console.log('UNLINK ' + counter) // if (e) throw e; //// fs.unlinkSync(tmp_copy); // fs.unlink(tmp_copy, function (err) { // if (err) throw err; // }); // return true; // }); // //}); // //}; var BUF_LENGTH = 64 * 1024; var _buff = new Buffer(BUF_LENGTH); var copyFileSync = function(srcFile, destFile) { var bytesRead, fdr, fdw, pos; fdr = fs.openSync(srcFile, 'r'); fdw = fs.openSync(destFile, 'w'); bytesRead = 1; pos = 0; while (bytesRead > 0) { bytesRead = fs.readSync(fdr, _buff, 0, BUF_LENGTH, pos); fs.writeSync(fdw, _buff, 0, bytesRead); pos += bytesRead; } fs.closeSync(fdr); return fs.closeSync(fdw); }; var timeout = {}; var overwrite = function (fileName, items) { if (isLocked()) { addToQueue(this); return false; } locked(); // console.log('OW: ' + counter++); var file = fileName || store.filename; if (!file) { store.log('You must specify a valid file.', 'ERR'); return false; } var tmp_copy = path.dirname(file) + '/.' + path.basename(file); copyFileSync(file, tmp_copy); var s = store.stringify(items); // removing leading { and trailing } s = s.substr(1, s = s.substr(0, s.legth-1)); fs.writeFileSync(file, s, 'utf-8'); fs.unlinkSync(tmp_copy); // console.log('UNLINK ' + counter); unlocked(); clearQueue(); return true; }; if ('undefined' !== typeof fs.appendFileSync) { // node 0.8 var save = function (fileName, key, value) { var file = fileName || store.filename; if (!file) { store.log('You must specify a valid file.', 'ERR'); return false; } if (!key) return; var item = store.stringify(key) + ": " + store.stringify(value) + ",\n"; return fs.appendFileSync(file, item, 'utf-8'); }; } else { // node < 0.8 var save = function (fileName, key, value) { var file = fileName || store.filename; if (!file) { store.log('You must specify a valid file.', 'ERR'); return false; } if (!key) return; var item = store.stringify(key) + ": " + store.stringify(value) + ",\n"; var fd = fs.openSync(file, 'a', '0666'); fs.writeSync(fd, item, null, 'utf8'); fs.closeSync(fd); return true; }; } var load = function (fileName, key) { var file = fileName || store.filename; if (!file) { store.log('You must specify a valid file.', 'ERR'); return false; } var s = fs.readFileSync(file, 'utf-8'); // console.log('BEFORE removing end') // console.log(s) s = s.substr(0, s.length-2); // removing last ',' and /n // console.log('BEFORE PARSING') // console.log(s) var items = store.parse('{' + s + '}'); // console.log('PARSED') // console.log(items) return (key) ? items[key] : items; }; var deleteVariable = function (fileName, key) { var file = fileName || store.filename; var items = load(file); // console.log('dele') // console.log(items) // console.log(key) delete items[key]; overwrite(file, items); return null; }; store.addType("fs", function(key, value, options) { var filename = options.file || store.filename; if (!key) { return load(filename); } if (value === undefined) { return load(filename, key); } if (timeout[key]) { clearTimeout(timeout[key]); deleteVariable(filename, key); } if (value === null) { deleteVariable(filename, key); return null; } // save item save(filename, key, value); if (options.expires) { timeout[key] = setTimeout(function() { deleteVariable(filename, key); }, options.expires); } return value; }); }( ('undefined' !== typeof module && 'function' === typeof require) ? module.exports || module.parent.exports : {}) );