Browser version works with local storage

pull/2/head
Louis Chatriot 9 years ago
parent d1e6d14b53
commit 0b564ba674
  1. 9
      browser-version/browser-specific/lib/storage.js
  2. 138
      browser-version/out/nedb.js
  3. 6
      browser-version/out/nedb.min.js
  4. 2
      lib/persistence.js

@ -78,19 +78,26 @@ function unlink (filename, callback) {
} }
// Nothing done, no directories will be used on the browser // Nothing to do, no directories will be used on the browser
function mkdirp (dir, callback) { function mkdirp (dir, callback) {
return callback(); return callback();
} }
// Nothing to do, no data corruption possible in the brower
function ensureDatafileIntegrity (filename, callback) {
return callback(null);
}
// Interface // Interface
module.exports.exists = exists; module.exports.exists = exists;
module.exports.rename = rename; module.exports.rename = rename;
module.exports.writeFile = writeFile; module.exports.writeFile = writeFile;
module.exports.crashSafeWriteFile = writeFile; // No need for a crash safe function in the browser
module.exports.appendFile = appendFile; module.exports.appendFile = appendFile;
module.exports.readFile = readFile; module.exports.readFile = readFile;
module.exports.unlink = unlink; module.exports.unlink = unlink;
module.exports.mkdirp = mkdirp; module.exports.mkdirp = mkdirp;
module.exports.ensureDatafileIntegrity = ensureDatafileIntegrity;

@ -1059,8 +1059,9 @@ var customUtils = require('./customUtils')
/** /**
* Create a new collection * Create a new collection
* @param {String} options.filename Optional, datastore will be in-memory only if not provided * @param {String} options.filename Optional, datastore will be in-memory only if not provided
* @param {Boolean} options.inMemoryOnly Optional, default to false * @param {Boolean} options.timestampData Optional, defaults to false. If set to true, createdAt and updatedAt will be created and populated automatically (if not specified by user)
* @param {Boolean} options.nodeWebkitAppName Optional, specify the name of your NW app if you want options.filename to be relative to the directory where * @param {Boolean} options.inMemoryOnly Optional, defaults to false
* @param {String} options.nodeWebkitAppName Optional, specify the name of your NW app if you want options.filename to be relative to the directory where
* Node Webkit stores application data such as cookies and local storage (the best place to store data in my opinion) * Node Webkit stores application data such as cookies and local storage (the best place to store data in my opinion)
* @param {Boolean} options.autoload Optional, defaults to false * @param {Boolean} options.autoload Optional, defaults to false
* @param {Function} options.onload Optional, if autoload is used this will be called after the load database with the error object as parameter. If you don't pass it the error will be thrown * @param {Function} options.onload Optional, if autoload is used this will be called after the load database with the error object as parameter. If you don't pass it the error will be thrown
@ -1079,6 +1080,7 @@ function Datastore (options) {
filename = options.filename; filename = options.filename;
this.inMemoryOnly = options.inMemoryOnly || false; this.inMemoryOnly = options.inMemoryOnly || false;
this.autoload = options.autoload || false; this.autoload = options.autoload || false;
this.timestampData = options.timestampData || false;
} }
// Determine whether in memory or persistent // Determine whether in memory or persistent
@ -1328,17 +1330,19 @@ Datastore.prototype.getCandidates = function (query) {
*/ */
Datastore.prototype._insert = function (newDoc, cb) { Datastore.prototype._insert = function (newDoc, cb) {
var callback = cb || function () {} var callback = cb || function () {}
, preparedDoc
; ;
try { try {
this._insertInCache(newDoc); preparedDoc = this.prepareDocumentForInsertion(newDoc)
this._insertInCache(preparedDoc);
} catch (e) { } catch (e) {
return callback(e); return callback(e);
} }
this.persistence.persistNewState(util.isArray(newDoc) ? newDoc : [newDoc], function (err) { this.persistence.persistNewState(util.isArray(preparedDoc) ? preparedDoc : [preparedDoc], function (err) {
if (err) { return callback(err); } if (err) { return callback(err); }
return callback(null, newDoc); return callback(null, model.deepCopy(preparedDoc));
}); });
}; };
@ -1356,6 +1360,7 @@ Datastore.prototype.createNewId = function () {
/** /**
* Prepare a document (or array of documents) to be inserted in a database * Prepare a document (or array of documents) to be inserted in a database
* Meaning adds _id and timestamps if necessary on a copy of newDoc to avoid any side effect on user input
* @api private * @api private
*/ */
Datastore.prototype.prepareDocumentForInsertion = function (newDoc) { Datastore.prototype.prepareDocumentForInsertion = function (newDoc) {
@ -1365,10 +1370,11 @@ Datastore.prototype.prepareDocumentForInsertion = function (newDoc) {
preparedDoc = []; preparedDoc = [];
newDoc.forEach(function (doc) { preparedDoc.push(self.prepareDocumentForInsertion(doc)); }); newDoc.forEach(function (doc) { preparedDoc.push(self.prepareDocumentForInsertion(doc)); });
} else { } else {
if (newDoc._id === undefined) {
newDoc._id = this.createNewId();
}
preparedDoc = model.deepCopy(newDoc); preparedDoc = model.deepCopy(newDoc);
if (preparedDoc._id === undefined) { preparedDoc._id = this.createNewId(); }
var now = new Date();
if (this.timestampData && preparedDoc.createdAt === undefined) { preparedDoc.createdAt = now; }
if (this.timestampData && preparedDoc.updatedAt === undefined) { preparedDoc.updatedAt = now; }
model.checkObject(preparedDoc); model.checkObject(preparedDoc);
} }
@ -1379,11 +1385,11 @@ Datastore.prototype.prepareDocumentForInsertion = function (newDoc) {
* If newDoc is an array of documents, this will insert all documents in the cache * If newDoc is an array of documents, this will insert all documents in the cache
* @api private * @api private
*/ */
Datastore.prototype._insertInCache = function (newDoc) { Datastore.prototype._insertInCache = function (preparedDoc) {
if (util.isArray(newDoc)) { if (util.isArray(preparedDoc)) {
this._insertMultipleDocsInCache(newDoc); this._insertMultipleDocsInCache(preparedDoc);
} else { } else {
this.addToIndexes(this.prepareDocumentForInsertion(newDoc)); this.addToIndexes(preparedDoc);
} }
}; };
@ -1392,10 +1398,8 @@ Datastore.prototype._insertInCache = function (newDoc) {
* inserts and throws the error * inserts and throws the error
* @api private * @api private
*/ */
Datastore.prototype._insertMultipleDocsInCache = function (newDocs) { Datastore.prototype._insertMultipleDocsInCache = function (preparedDocs) {
var i, failingI, error var i, failingI, error;
, preparedDocs = this.prepareDocumentForInsertion(newDocs)
;
for (i = 0; i < preparedDocs.length; i += 1) { for (i = 0; i < preparedDocs.length; i += 1) {
try { try {
@ -1588,6 +1592,7 @@ Datastore.prototype._update = function (query, updateQuery, options, cb) {
if (model.match(candidates[i], query) && (multi || numReplaced === 0)) { if (model.match(candidates[i], query) && (multi || numReplaced === 0)) {
numReplaced += 1; numReplaced += 1;
modifiedDoc = model.modify(candidates[i], updateQuery); modifiedDoc = model.modify(candidates[i], updateQuery);
if (self.timestampData) { modifiedDoc.updatedAt = new Date(); }
modifications.push({ oldDoc: candidates[i], newDoc: modifiedDoc }); modifications.push({ oldDoc: candidates[i], newDoc: modifiedDoc });
} }
} }
@ -1992,13 +1997,23 @@ function append (array, toAppend) {
* @return {Array of documents} * @return {Array of documents}
*/ */
Index.prototype.getMatching = function (value) { Index.prototype.getMatching = function (value) {
var res, self = this; var self = this;
if (!util.isArray(value)) { if (!util.isArray(value)) {
return this.tree.search(value); return this.tree.search(value);
} else { } else {
res = []; var _res = {}, res = [];
value.forEach(function (v) { append(res, self.getMatching(v)); });
value.forEach(function (v) {
self.getMatching(v).forEach(function (doc) {
_res[doc._id] = doc;
});
});
Object.keys(_res).forEach(function (_id) {
res.push(_res[_id]);
});
return res; return res;
} }
}; };
@ -2451,12 +2466,13 @@ function modify (obj, updateQuery) {
if (!modifierFunctions[m]) { throw "Unknown modifier " + m; } if (!modifierFunctions[m]) { throw "Unknown modifier " + m; }
try { // Can't rely on Object.keys throwing on non objects since ES6{
keys = Object.keys(updateQuery[m]); // Not 100% satisfying as non objects can be interpreted as objects but no false negatives so we can live with it
} catch (e) { if (typeof updateQuery[m] !== 'object') {
throw "Modifier " + m + "'s argument must be an object"; throw "Modifier " + m + "'s argument must be an object";
} }
keys = Object.keys(updateQuery[m]);
keys.forEach(function (k) { keys.forEach(function (k) {
modifierFunctions[m](newDoc, k, updateQuery[m][k]); modifierFunctions[m](newDoc, k, updateQuery[m][k]);
}); });
@ -2837,13 +2853,8 @@ function Persistence (options) {
this.filename = this.db.filename; this.filename = this.db.filename;
this.corruptAlertThreshold = options.corruptAlertThreshold !== undefined ? options.corruptAlertThreshold : 0.1; this.corruptAlertThreshold = options.corruptAlertThreshold !== undefined ? options.corruptAlertThreshold : 0.1;
if (!this.inMemoryOnly && this.filename) { if (!this.inMemoryOnly && this.filename && this.filename.charAt(this.filename.length - 1) === '~') {
if (this.filename.charAt(this.filename.length - 1) === '~') { throw "The datafile name can't end with a ~, which is reserved for crash safe backup files";
throw "The datafile name can't end with a ~, which is reserved for automatic backup files";
} else {
this.tempFilename = this.filename + '~';
this.oldFilename = this.filename + '~~';
}
} }
// After serialization and before deserialization hooks with some basic sanity checks // After serialization and before deserialization hooks with some basic sanity checks
@ -2874,8 +2885,6 @@ function Persistence (options) {
console.log("See https://github.com/rogerwang/node-webkit/issues/500"); console.log("See https://github.com/rogerwang/node-webkit/issues/500");
console.log("=================================================================="); console.log("==================================================================");
this.filename = Persistence.getNWAppFilename(options.nodeWebkitAppName, this.filename); this.filename = Persistence.getNWAppFilename(options.nodeWebkitAppName, this.filename);
this.tempFilename = Persistence.getNWAppFilename(options.nodeWebkitAppName, this.tempFilename);
this.oldFilename = Persistence.getNWAppFilename(options.nodeWebkitAppName, this.oldFilename);
} }
}; };
@ -2892,13 +2901,6 @@ Persistence.ensureDirectoryExists = function (dir, cb) {
}; };
Persistence.ensureFileDoesntExist = function (file, callback) {
storage.exists(file, function (exists) {
if (!exists) { return callback(null); }
storage.unlink(file, function (err) { return callback(err); });
});
};
/** /**
@ -2957,26 +2959,7 @@ Persistence.prototype.persistCachedDatabase = function (cb) {
} }
}); });
async.waterfall([ storage.crashSafeWriteFile(this.filename, toPersist, callback);
async.apply(Persistence.ensureFileDoesntExist, self.tempFilename)
, async.apply(Persistence.ensureFileDoesntExist, self.oldFilename)
, function (cb) {
storage.exists(self.filename, function (exists) {
if (exists) {
storage.rename(self.filename, self.oldFilename, function (err) { return cb(err); });
} else {
return cb();
}
});
}
, function (cb) {
storage.writeFile(self.tempFilename, toPersist, function (err) { return cb(err); });
}
, function (cb) {
storage.rename(self.tempFilename, self.filename, function (err) { return cb(err); });
}
, async.apply(Persistence.ensureFileDoesntExist, self.oldFilename)
], function (err) { if (err) { return callback(err); } else { return callback(null); } })
}; };
@ -3029,6 +3012,8 @@ Persistence.prototype.persistNewState = function (newDocs, cb) {
// In-memory only datastore // In-memory only datastore
if (self.inMemoryOnly) { return callback(null); } if (self.inMemoryOnly) { return callback(null); }
console.log('-------------------');
newDocs.forEach(function (doc) { newDocs.forEach(function (doc) {
toPersist += self.afterSerialization(model.serialize(doc)) + '\n'; toPersist += self.afterSerialization(model.serialize(doc)) + '\n';
}); });
@ -3088,30 +3073,6 @@ Persistence.prototype.treatRawData = function (rawData) {
}; };
/**
* Ensure that this.filename contains the most up-to-date version of the data
* Even if a loadDatabase crashed before
*/
Persistence.prototype.ensureDatafileIntegrity = function (callback) {
var self = this ;
storage.exists(self.filename, function (filenameExists) {
// Write was successful
if (filenameExists) { return callback(null); }
storage.exists(self.oldFilename, function (oldFilenameExists) {
// New database
if (!oldFilenameExists) {
return storage.writeFile(self.filename, '', 'utf8', function (err) { callback(err); });
}
// Write failed, use old version
storage.rename(self.oldFilename, self.filename, function (err) { return callback(err); });
});
});
};
/** /**
* Load the database * Load the database
* 1) Create all indexes * 1) Create all indexes
@ -3135,7 +3096,7 @@ Persistence.prototype.loadDatabase = function (cb) {
async.waterfall([ async.waterfall([
function (cb) { function (cb) {
Persistence.ensureDirectoryExists(path.dirname(self.filename), function (err) { Persistence.ensureDirectoryExists(path.dirname(self.filename), function (err) {
self.ensureDatafileIntegrity(function (exists) { storage.ensureDatafileIntegrity(self.filename, function (err) {
storage.readFile(self.filename, 'utf8', function (err, rawData) { storage.readFile(self.filename, 'utf8', function (err, rawData) {
if (err) { return cb(err); } if (err) { return cb(err); }
@ -3181,7 +3142,7 @@ module.exports = Persistence;
* For a Node.js/Node Webkit database it's the file system * For a Node.js/Node Webkit database it's the file system
* For a browser-side database it's localStorage when supported * For a browser-side database it's localStorage when supported
* *
* This version is the Node.js/Node Webkit version * This version is the browser version
*/ */
@ -3256,21 +3217,28 @@ function unlink (filename, callback) {
} }
// Nothing done, no directories will be used on the browser // Nothing to do, no directories will be used on the browser
function mkdirp (dir, callback) { function mkdirp (dir, callback) {
return callback(); return callback();
} }
// Nothing to do, no data corruption possible in the brower
function ensureDatafileIntegrity (filename, callback) {
return callback(null);
}
// Interface // Interface
module.exports.exists = exists; module.exports.exists = exists;
module.exports.rename = rename; module.exports.rename = rename;
module.exports.writeFile = writeFile; module.exports.writeFile = writeFile;
module.exports.crashSafeWriteFile = writeFile; // No need for a crash safe function in the browser
module.exports.appendFile = appendFile; module.exports.appendFile = appendFile;
module.exports.readFile = readFile; module.exports.readFile = readFile;
module.exports.unlink = unlink; module.exports.unlink = unlink;
module.exports.mkdirp = mkdirp; module.exports.mkdirp = mkdirp;
module.exports.ensureDatafileIntegrity = ensureDatafileIntegrity;
},{}],13:[function(require,module,exports){ },{}],13:[function(require,module,exports){

File diff suppressed because one or more lines are too long

@ -269,7 +269,7 @@ Persistence.prototype.loadDatabase = function (cb) {
async.waterfall([ async.waterfall([
function (cb) { function (cb) {
Persistence.ensureDirectoryExists(path.dirname(self.filename), function (err) { Persistence.ensureDirectoryExists(path.dirname(self.filename), function (err) {
storage.ensureDatafileIntegrity(self.filename, function (exists) { storage.ensureDatafileIntegrity(self.filename, function (err) {
storage.readFile(self.filename, 'utf8', function (err, rawData) { storage.readFile(self.filename, 'utf8', function (err, rawData) {
if (err) { return cb(err); } if (err) { return cb(err); }

Loading…
Cancel
Save