|
|
@ -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){ |
|
|
|