Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

nedb

Package Overview
Dependencies
Maintainers
1
Versions
95
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

nedb - npm Package Compare versions

Comparing version 0.8.12 to 0.8.13

test_lac/loadAndCrash.test.js

17

lib/customUtils.js

@@ -1,2 +0,4 @@

var crypto = require('crypto');
var crypto = require('crypto')
, fs = require('fs')
;

@@ -19,2 +21,15 @@ /**

/**
* Callback signature: err
*/
function ensureFileDoesntExist (file, callback) {
fs.exists(file, function (exists) {
if (!exists) { return callback(null); }
fs.unlink(file, function (err) { return callback(err); });
});
}
module.exports.uid = uid;
module.exports.ensureFileDoesntExist = ensureFileDoesntExist;

2

lib/datastore.js

@@ -543,4 +543,4 @@ var customUtils = require('./customUtils')

};
module.exports = Datastore;

@@ -13,2 +13,3 @@ /**

, mkdirp = require('mkdirp')
, customUtils = require('./customUtils')
;

@@ -27,2 +28,11 @@

this.filename = this.db.filename;
if (!this.inMemoryOnly && this.filename) {
if (this.filename.charAt(this.filename.length - 1) === '~') {
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 + '~~';
}
}

@@ -90,9 +100,31 @@ // For NW apps, store data in the same directory where NW stores application data

, toPersist = ''
, self = this
;
if (this.inMemoryOnly) { return callback(null); }
this.db.getAllData().forEach(function (doc) {
toPersist += model.serialize(doc) + '\n';
});
fs.writeFile(this.filename, toPersist, function (err) { return callback(err); });
async.waterfall([
async.apply(customUtils.ensureFileDoesntExist, self.tempFilename)
, async.apply(customUtils.ensureFileDoesntExist, self.oldFilename)
, function (cb) {
fs.exists(self.filename, function (exists) {
if (exists) {
fs.rename(self.filename, self.oldFilename, function (err) { return cb(err); });
} else {
return cb();
}
});
}
, function (cb) {
fs.writeFile(self.tempFilename, toPersist, function (err) { return cb(err); });
}
, function (cb) {
fs.rename(self.tempFilename, self.filename, function (err) { return cb(err); });
}
, async.apply(customUtils.ensureFileDoesntExist, self.oldFilename)
], function (err) { if (err) { return callback(err); } else { return callback(null); } })
};

@@ -195,2 +227,26 @@

/**
* 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 ;
fs.exists(self.filename, function (filenameExists) {
// Write was successful
if (filenameExists) { return callback(null); }
fs.exists(self.oldFilename, function (oldFilenameExists) {
// New database
if (!oldFilenameExists) {
return fs.writeFile(self.filename, '', 'utf8', function (err) { callback(err); });
}
// Write failed, use old version
fs.rename(self.oldFilename, self.filename, function (err) { return callback(err); });
});
});
};
/**
* Load the database

@@ -215,5 +271,3 @@ * This means pulling data out of the data file or creating it if it doesn't exist

Persistence.ensureDirectoryExists(path.dirname(self.filename), function (err) {
fs.exists(self.filename, function (exists) {
if (!exists) { return fs.writeFile(self.filename, '', 'utf8', function (err) { cb(err); }); }
self.ensureDatafileIntegrity(function (exists) {
fs.readFile(self.filename, 'utf8', function (err, rawData) {

@@ -220,0 +274,0 @@ if (err) { return cb(err); }

{
"name": "nedb",
"version": "0.8.12",
"version": "0.8.13",
"author": {

@@ -5,0 +5,0 @@ "name": "tldr.io",

@@ -38,3 +38,3 @@ # NeDB (Node embedded database)

* `filename` (optional): path to the file where the data is persisted. If left blank, the datastore is automatically considered in-memory only.
* `filename` (optional): path to the file where the data is persisted. If left blank, the datastore is automatically considered in-memory only. It cannot end with a `~` which is used in the temporary files NeDB uses to perform crash-safe writes
* `inMemoryOnly` (optional, defaults to false): as the name implies.

@@ -41,0 +41,0 @@ * `autoload` (optional, defaults to false): if used, the database will

@@ -7,2 +7,4 @@ var model = require('../lib/model')

, util = require('util')
, Datastore = require('../lib/datastore')
, fs = require('fs')
;

@@ -129,3 +131,32 @@

});
it('Can serialize string fields with a new line without breaking the DB', function (done) {
var db1, db2
, badString = "world\r\nearth\nother\rline"
;
if (fs.existsSync('workspace/test1.db')) { fs.unlinkSync('workspace/test1.db'); }
fs.existsSync('workspace/test1.db').should.equal(false);
db1 = new Datastore({ filename: 'workspace/test1.db' });
db1.loadDatabase(function (err) {
assert.isNull(err);
db1.insert({ hello: badString }, function (err) {
assert.isNull(err);
db2 = new Datastore({ filename: 'workspace/test1.db' });
db2.loadDatabase(function (err) {
assert.isNull(err);
db2.find({}, function (err, docs) {
assert.isNull(err);
docs.length.should.equal(1);
docs[0].hello.should.equal(badString);
done();
});
});
});
});
});
}); // ==== End of 'Serialization, deserialization' ==== //

@@ -132,0 +163,0 @@

@@ -9,4 +9,6 @@ var should = require('chai').should()

, model = require('../lib/model')
, customUtils = require('../lib/customUtils')
, Datastore = require('../lib/datastore')
, Persistence = require('../lib/persistence')
, child_process = require('child_process')
;

@@ -251,3 +253,352 @@

});
describe('Prevent dataloss when persisting data', function () {
it('Creating a datastore with in memory as true and a bad filename wont cause an error', function () {
new Datastore({ filename: 'workspace/bad.db~', inMemoryOnly: true });
})
it('Creating a persistent datastore with a bad filename will cause an error', function () {
(function () { new Datastore({ filename: 'workspace/bad.db~' }); }).should.throw();
})
it('If no file exists, ensureDatafileIntegrity creates an empty datafile', function (done) {
var p = new Persistence({ db: { inMemoryOnly: false, filename: 'workspace/it.db' } });
if (fs.existsSync('workspace/it.db')) { fs.unlinkSync('workspace/it.db'); }
if (fs.existsSync('workspace/it.db~~')) { fs.unlinkSync('workspace/it.db~~'); }
fs.existsSync('workspace/it.db').should.equal(false);
fs.existsSync('workspace/it.db~~').should.equal(false);
p.ensureDatafileIntegrity(function (err) {
assert.isNull(err);
fs.existsSync('workspace/it.db').should.equal(true);
fs.existsSync('workspace/it.db~~').should.equal(false);
fs.readFileSync('workspace/it.db', 'utf8').should.equal('');
done();
});
});
it('If only datafile exists, ensureDatafileIntegrity will use it', function (done) {
var p = new Persistence({ db: { inMemoryOnly: false, filename: 'workspace/it.db' } });
if (fs.existsSync('workspace/it.db')) { fs.unlinkSync('workspace/it.db'); }
if (fs.existsSync('workspace/it.db~~')) { fs.unlinkSync('workspace/it.db~~'); }
fs.writeFileSync('workspace/it.db', 'something', 'utf8');
fs.existsSync('workspace/it.db').should.equal(true);
fs.existsSync('workspace/it.db~~').should.equal(false);
p.ensureDatafileIntegrity(function (err) {
assert.isNull(err);
fs.existsSync('workspace/it.db').should.equal(true);
fs.existsSync('workspace/it.db~~').should.equal(false);
fs.readFileSync('workspace/it.db', 'utf8').should.equal('something');
done();
});
});
it('If old datafile exists and datafile doesnt, ensureDatafileIntegrity will use it', function (done) {
var p = new Persistence({ db: { inMemoryOnly: false, filename: 'workspace/it.db' } });
if (fs.existsSync('workspace/it.db')) { fs.unlinkSync('workspace/it.db'); }
if (fs.existsSync('workspace/it.db~~')) { fs.unlinkSync('workspace/it.db~~'); }
fs.writeFileSync('workspace/it.db~~', 'something', 'utf8');
fs.existsSync('workspace/it.db').should.equal(false);
fs.existsSync('workspace/it.db~~').should.equal(true);
p.ensureDatafileIntegrity(function (err) {
assert.isNull(err);
fs.existsSync('workspace/it.db').should.equal(true);
fs.existsSync('workspace/it.db~~').should.equal(false);
fs.readFileSync('workspace/it.db', 'utf8').should.equal('something');
done();
});
});
it('If both old and current datafiles exist, ensureDatafileIntegrity will use the datafile, it means step 4 of persistence failed', function (done) {
var theDb = new Datastore({ filename: 'workspace/it.db' });
if (fs.existsSync('workspace/it.db')) { fs.unlinkSync('workspace/it.db'); }
if (fs.existsSync('workspace/it.db~~')) { fs.unlinkSync('workspace/it.db~~'); }
fs.writeFileSync('workspace/it.db', '{"_id":"0","hello":"world"}', 'utf8');
fs.writeFileSync('workspace/it.db~~', '{"_id":"0","hello":"other"}', 'utf8');
fs.existsSync('workspace/it.db').should.equal(true);
fs.existsSync('workspace/it.db~~').should.equal(true);
theDb.persistence.ensureDatafileIntegrity(function (err) {
assert.isNull(err);
fs.existsSync('workspace/it.db').should.equal(true);
fs.existsSync('workspace/it.db~~').should.equal(true);
fs.readFileSync('workspace/it.db', 'utf8').should.equal('{"_id":"0","hello":"world"}');
theDb.loadDatabase(function (err) {
assert.isNull(err);
theDb.find({}, function (err, docs) {
assert.isNull(err);
docs.length.should.equal(1);
docs[0].hello.should.equal("world");
done();
});
});
});
});
it('persistCachedDatabase should update the contents of the datafile and leave a clean state', function (done) {
d.insert({ hello: 'world' }, function () {
d.find({}, function (err, docs) {
docs.length.should.equal(1);
if (fs.existsSync(testDb)) { fs.unlinkSync(testDb); }
if (fs.existsSync(testDb + '~')) { fs.unlinkSync(testDb + '~'); }
if (fs.existsSync(testDb + '~~')) { fs.unlinkSync(testDb + '~~'); }
fs.existsSync(testDb).should.equal(false);
fs.writeFileSync(testDb + '~', 'something', 'utf8');
fs.writeFileSync(testDb + '~~', 'something else', 'utf8');
fs.existsSync(testDb + '~').should.equal(true);
fs.existsSync(testDb + '~~').should.equal(true);
d.persistence.persistCachedDatabase(function (err) {
var contents = fs.readFileSync(testDb, 'utf8');
assert.isNull(err);
fs.existsSync(testDb).should.equal(true);
fs.existsSync(testDb + '~').should.equal(false);
fs.existsSync(testDb + '~~').should.equal(false);
if (!contents.match(/^{"hello":"world","_id":"[0-9a-zA-Z]{16}"}\n$/)) {
throw "Datafile contents not as expected";
}
done();
});
});
});
});
it('After a persistCachedDatabase, there should be no temp or old filename', function (done) {
d.insert({ hello: 'world' }, function () {
d.find({}, function (err, docs) {
docs.length.should.equal(1);
if (fs.existsSync(testDb)) { fs.unlinkSync(testDb); }
if (fs.existsSync(testDb + '~')) { fs.unlinkSync(testDb + '~'); }
if (fs.existsSync(testDb + '~~')) { fs.unlinkSync(testDb + '~~'); }
fs.existsSync(testDb).should.equal(false);
fs.writeFileSync(testDb + '~', 'bloup', 'utf8');
fs.writeFileSync(testDb + '~~', 'blap', 'utf8');
fs.existsSync(testDb + '~').should.equal(true);
fs.existsSync(testDb + '~~').should.equal(true);
d.persistence.persistCachedDatabase(function (err) {
var contents = fs.readFileSync(testDb, 'utf8');
assert.isNull(err);
fs.existsSync(testDb).should.equal(true);
fs.existsSync(testDb + '~').should.equal(false);
fs.existsSync(testDb + '~~').should.equal(false);
if (!contents.match(/^{"hello":"world","_id":"[0-9a-zA-Z]{16}"}\n$/)) {
throw "Datafile contents not as expected";
}
done();
});
});
});
});
it('persistCachedDatabase should update the contents of the datafile and leave a clean state even if there is a temp or old datafile', function (done) {
d.insert({ hello: 'world' }, function () {
d.find({}, function (err, docs) {
docs.length.should.equal(1);
if (fs.existsSync(testDb)) { fs.unlinkSync(testDb); }
fs.writeFileSync(testDb + '~', 'blabla', 'utf8');
fs.writeFileSync(testDb + '~~', 'bloblo', 'utf8');
fs.existsSync(testDb).should.equal(false);
fs.existsSync(testDb + '~').should.equal(true);
fs.existsSync(testDb + '~~').should.equal(true);
d.persistence.persistCachedDatabase(function (err) {
var contents = fs.readFileSync(testDb, 'utf8');
assert.isNull(err);
fs.existsSync(testDb).should.equal(true);
fs.existsSync(testDb + '~').should.equal(false);
fs.existsSync(testDb + '~~').should.equal(false);
if (!contents.match(/^{"hello":"world","_id":"[0-9a-zA-Z]{16}"}\n$/)) {
throw "Datafile contents not as expected";
}
done();
});
});
});
});
it('persistCachedDatabase should update the contents of the datafile and leave a clean state even if there is a temp or old datafile', function (done) {
var dbFile = 'workspace/test2.db', theDb;
if (fs.existsSync(dbFile)) { fs.unlinkSync(dbFile); }
if (fs.existsSync(dbFile + '~')) { fs.unlinkSync(dbFile + '~'); }
if (fs.existsSync(dbFile + '~~')) { fs.unlinkSync(dbFile + '~~'); }
theDb = new Datastore({ filename: dbFile });
theDb.loadDatabase(function (err) {
var contents = fs.readFileSync(dbFile, 'utf8');
assert.isNull(err);
fs.existsSync(dbFile).should.equal(true);
fs.existsSync(dbFile + '~').should.equal(false);
fs.existsSync(dbFile + '~~').should.equal(false);
if (contents != "") {
throw "Datafile contents not as expected";
}
done();
});
});
it('Persistence works as expected when everything goes fine', function (done) {
var dbFile = 'workspace/test2.db', theDb, theDb2, doc1, doc2;
async.waterfall([
async.apply(customUtils.ensureFileDoesntExist, dbFile)
, async.apply(customUtils.ensureFileDoesntExist, dbFile + '~')
, async.apply(customUtils.ensureFileDoesntExist, dbFile + '~~')
, function (cb) {
theDb = new Datastore({ filename: dbFile });
theDb.loadDatabase(cb);
}
, function (cb) {
theDb.find({}, function (err, docs) {
assert.isNull(err);
docs.length.should.equal(0);
return cb();
});
}
, function (cb) {
theDb.insert({ a: 'hello' }, function (err, _doc1) {
assert.isNull(err);
doc1 = _doc1;
theDb.insert({ a: 'world' }, function (err, _doc2) {
assert.isNull(err);
doc2 = _doc2;
return cb();
});
});
}
, function (cb) {
theDb.find({}, function (err, docs) {
assert.isNull(err);
docs.length.should.equal(2);
_.find(docs, function (item) { return item._id === doc1._id }).a.should.equal('hello');
_.find(docs, function (item) { return item._id === doc2._id }).a.should.equal('world');
return cb();
});
}
, function (cb) {
theDb.loadDatabase(cb);
}
, function (cb) { // No change
theDb.find({}, function (err, docs) {
assert.isNull(err);
docs.length.should.equal(2);
_.find(docs, function (item) { return item._id === doc1._id }).a.should.equal('hello');
_.find(docs, function (item) { return item._id === doc2._id }).a.should.equal('world');
return cb();
});
}
, function (cb) {
fs.existsSync(dbFile).should.equal(true);
fs.existsSync(dbFile + '~').should.equal(false);
fs.existsSync(dbFile + '~~').should.equal(false);
return cb();
}
, function (cb) {
theDb2 = new Datastore({ filename: dbFile });
theDb2.loadDatabase(cb);
}
, function (cb) { // No change in second db
theDb2.find({}, function (err, docs) {
assert.isNull(err);
docs.length.should.equal(2);
_.find(docs, function (item) { return item._id === doc1._id }).a.should.equal('hello');
_.find(docs, function (item) { return item._id === doc2._id }).a.should.equal('world');
return cb();
});
}
, function (cb) {
fs.existsSync(dbFile).should.equal(true);
fs.existsSync(dbFile + '~').should.equal(false);
fs.existsSync(dbFile + '~~').should.equal(false);
return cb();
}
], done);
});
// This test is a bit complicated since it depends on the time I/O actions take to execute
// That depends on the machine and the load on the machine when the tests are run
// It is timed for my machine with nothing else running but may not work as expected on others (it will not fail but may not be a proof)
// Every new version of NeDB passes it on my machine before rtelease
it('If system crashes during a loadDatabase, the former version is not lost', function (done) {
var cp, N = 150000, toWrite = "", i;
// Ensuring the state is clean
if (fs.existsSync('workspace/lac.db')) { fs.unlinkSync('workspace/lac.db'); }
if (fs.existsSync('workspace/lac.db~')) { fs.unlinkSync('workspace/lac.db~'); }
// Creating a db file with 150k records (a bit long to load)
for (i = 0; i < N; i += 1) {
toWrite += model.serialize({ _id: customUtils.uid(16), hello: 'world' }) + '\n';
}
fs.writeFileSync('workspace/lac.db', toWrite, 'utf8');
// Loading it in a separate process that we will crash before finishing the loadDatabase
cp = child_process.fork('test_lac/loadAndCrash.test')
// Kill the child process when we're at step 3 of persistCachedDatabase (during write to datafile)
setTimeout(function() {
cp.kill('SIGINT');
// If the timing is correct, only the temp datafile contains data
// The datafile was in the middle of being written and is empty
// Let the process crash be finished then load database without a crash, and test we didn't lose data
setTimeout(function () {
var db = new Datastore({ filename: 'workspace/lac.db' });
db.loadDatabase(function (err) {
assert.isNull(err);
db.count({}, function (err, n) {
// Data has not been lost
assert.isNull(err);
n.should.equal(150000);
// State is clean, the temp datafile has been erased and the datafile contains all the data
fs.existsSync('workspace/lac.db').should.equal(true);
fs.existsSync('workspace/lac.db~').should.equal(false);
done();
});
});
}, 100);
}, 2000);
});
});
});

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc