Nedb = function (options, onError) {
/**
* Create a new collection
* @param {String} options.name
* @param {Function} onError 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
*/
// validate name
if (!(options && options.name && typeof options.name === 'string')) {
throw new Error('Nedb - missing name param, e.g. new Nedb({ name: "table1" })');
}
this.name = options.name;
local.dbTableDrop(this, local.nop);
local.dbTableDict[this.name] = this;
// Persistence handling
this.persistence = new local.Persistence({ db: this });
// This new executor is ready if we don't use persistence
// If we do, it will only be ready once loadDatabase is called
this.executor = new local.Executor();
// Indexed by field name, dot notation can be used
// _id is always indexed and since _ids are generated randomly the underlying
// binary is always well-balanced
this.indexes = {
_id: new local.Index({ fieldName: '_id', unique: true }),
createdAt: new local.Index({ fieldName: 'createdAt' }),
updatedAt: new local.Index({ fieldName: 'updatedAt' })
};
this.ttlIndexes = {};
this.load(options, onError);
}
...
*
* this package will run a standalone, browser-compatible version of the nedb v1.8.0 database
* with zero npm-dependencies
*
* browser example:
* <script src="assets.nedb-lite.js"></script>
* <script>
* var table = new window.Nedb({ name: 'table1' });
* table.insert({ field1: 'hello', field2: 'world'}, console.log.bind(console));
* </script>
*
* node example:
* var Nedb = require('./assets.nedb-lite.js');
* var table = new Nedb({ name: 'table1' });
* table.insert({ field1: 'hello', field2: 'world'}, console.log.bind(console));
...
function Executor() { this.buffer = []; this.ready = false; // This queue will execute all commands, one-by-one in order this.queue = local.asyncQueue(function (task, cb) { var newArguments = []; // task.arguments is an array-like object on which adding a new field doesn't work, so we transform it into a real array for (var i = 0; i < task.arguments.length; i += 1) { newArguments.push(task.arguments[i]); } var lastArg = task.arguments[task.arguments.length - 1]; // Always tell the queue task is complete. Execute callback if any was given. if (typeof lastArg === 'function') { // Callback was supplied newArguments[newArguments.length - 1] = function () { if (typeof setImmediate === 'function') { setImmediate(cb); } else { setTimeout(cb); } lastArg.apply(null, arguments); }; } else if (!lastArg && task.arguments.length !== 0) { // false/undefined/null supplied as callbback newArguments[newArguments.length - 1] = function () { cb(); }; } else { // Nothing supplied as callback newArguments.push(function () { cb(); }); } task.fn.apply(task.this, newArguments); }); }
...
this.name = options.name;
local.dbTableDrop(this, local.nop);
local.dbTableDict[this.name] = this;
// Persistence handling
this.persistence = new local.Persistence({ db: this });
// This new executor is ready if we don't use persistence
// If we do, it will only be ready once loadDatabase is called
this.executor = new local.Executor();
// Indexed by field name, dot notation can be used
// _id is always indexed and since _ids are generated randomly the underlying
// binary is always well-balanced
this.indexes = {
_id: new local.Index({ fieldName: '_id', unique: true }),
createdAt: new local.Index({ fieldName: 'createdAt' }),
updatedAt: new local.Index({ fieldName: 'updatedAt' })
...
function Index(options) { this.fieldName = options.fieldName; this.unique = options.unique || false; this.sparse = options.sparse || false; this.treeOptions = { unique: this.unique }; this.reset(); // No data in the beginning }
...
// This new executor is ready if we don't use persistence
// If we do, it will only be ready once loadDatabase is called
this.executor = new local.Executor();
// Indexed by field name, dot notation can be used
// _id is always indexed and since _ids are generated randomly the underlying
// binary is always well-balanced
this.indexes = {
_id: new local.Index({ fieldName: '_id', unique: true }),
createdAt: new local.Index({ fieldName: 'createdAt' }),
updatedAt: new local.Index({ fieldName: 'updatedAt' })
};
this.ttlIndexes = {};
this.load(options, onError);
};
local.Nedb = local.local = local;
...
function Persistence(options) { var i, j, randomString; this.db = options.db; }
...
if (!(options && options.name && typeof options.name === 'string')) {
throw new Error('Nedb - missing name param, e.g. new Nedb({ name: "table1" })');
}
this.name = options.name;
local.dbTableDrop(this, local.nop);
local.dbTableDict[this.name] = this;
// Persistence handling
this.persistence = new local.Persistence({ db: this });
// This new executor is ready if we don't use persistence
// If we do, it will only be ready once loadDatabase is called
this.executor = new local.Executor();
// Indexed by field name, dot notation can be used
// _id is always indexed and since _ids are generated randomly the underlying
// binary is always well-balanced
this.indexes = {
...
assert = function (passed, message) {
/*
* this function will throw the error message if passed is falsey
*/
var error;
if (passed) {
return;
}
error = message && message.message
// if message is an error-object, then leave it as is
? message
: new Error(typeof message === 'string'
// if message is a string, then leave it as is
? message
// else JSON.stringify message
: JSON.stringify(message));
throw error;
}
...
local.testCase_assertXxx_default = function (options, onError) {
/*
* this function will test assertXxx's default handling-behavior
*/
options = {};
// test assertion passed
local.utility2.assert(true, true);
// test assertion failed with undefined message
local.utility2.tryCatchOnError(function () {
local.utility2.assert(false);
}, function (error) {
// validate error occurred
local.utility2.assert(error, error);
// validate error-message
...
asyncEachSeries = function (arr, iterator, callback) { var completed, iterate; if (!arr.length) { return callback(); } completed = 0; iterate = function () { iterator(arr[completed], function (error) { if (error) { callback(error); callback = local.nop; } else { completed += 1; if (completed >= arr.length) { callback(); } else { iterate(); } } }); }; iterate(); }
...
if (valid) {
validDocs.push(doc);
} else {
expiredDocsIds.push(doc._id);
}
});
local.asyncEachSeries(expiredDocsIds, function (_id, cb) {
self._remove({
_id: _id
}, {}, function (error) {
if (error) {
return callback(error);
}
return cb();
...
asyncQueue = function (worker) { var self; function only_once(fn) { var called = false; return function () { if (called) { throw new Error("Callback was already called."); } called = true; fn.apply(null, arguments); }; } self = { tasks: [], push: function (data, callback) { if (data.constructor !== Array) { data = [data]; } data.forEach(function (task) { var item = { data: task, callback: typeof callback === 'function' ? callback : null }; self.tasks.push(item); }); setTimeout(self.process); }, process: function () { var task; if (!self.isRunningTask && self.tasks.length) { task = self.tasks.shift(); self.isRunningTask = true; worker(task.data, only_once(function () { self.isRunningTask = null; if (task.callback) { task.callback.apply(task, arguments); } self.process(); })); } } }; return self; }
...
*/
function Executor() {
this.buffer = [];
this.ready = false;
// This queue will execute all commands, one-by-one in order
this.queue = local.asyncQueue(function (task, cb) {
var newArguments = [];
// task.arguments is an array-like object on which adding a new field doesn't work, so we transform it into a real array
for (var i = 0; i < task.arguments.length; i += 1) {
newArguments.push(task.arguments[i]);
}
var lastArg = task.arguments[task.arguments.length - 1];
...
function AVLTree(options) { this.tree = new _AVLTree(options); }
n/a
function BinarySearchTree(options) { options = options || {}; this.left = null; this.right = null; this.parent = options.parent !== undefined ? options.parent : null; if (options.hasOwnProperty('key')) { this.key = options.key; } this.data = options.hasOwnProperty('value') ? [options.value] : []; this.unique = options.unique || false; }
n/a
crudOptionsSetDefault = function (options, defaults) {
/*
* this function will set default-values for options
*/
options = local.utility2.objectSetDefault(options, defaults);
options.table = local.dbTableDict.TestCrud;
return options;
}
...
/*
* this function will test dbTableFindOneById's default handling-behavior
*/
options = {};
local.utility2.onNext(options, function (error, data) {
switch (options.modeNext) {
case 1:
options = local.crudOptionsSetDefault(options, {
id: '00_test_dbTableFindOneById'
});
options.table.findOne({ id: options.id }, options.onNext);
break;
case 2:
// validate data
local.utility2.assertJsonEqual(data.id, options.id);
...
function Cursor(db, query, execFn) { this.db = db; this.query = query || {}; if (execFn) { this.execFn = execFn; } }
n/a
dbExport = function () {
/*
* this function will export the database as a serialized tableList
*/
var data;
data = '';
Object.keys(local.dbTableDict).map(function (key) {
data += local.dbTableDict[key].export() + '\n\n';
});
return data.slice(0, -2);
}
...
};
});
/* istanbul ignore next */
local.testRun = function (event) {
var reader, tmp;
switch (event && event.currentTarget.id) {
case 'nedbExportButton1':
tmp = window.URL.createObjectURL(new window.Blob([local.Nedb.dbExport()]));
document.querySelector('#nedbExportA1').href = tmp;
document.querySelector('#nedbExportA1').click();
setTimeout(function () {
window.URL.revokeObjectURL(tmp);
}, 30000);
break;
case 'nedbImportButton1':
...
dbImport = function (tableList, onError) {
/*
* this function will import the serialized tableList
*/
var onParallel;
onParallel = function () {
onParallel.counter -= 1;
if (onParallel.counter === 0) {
onError();
}
};
onParallel.counter = 0;
onParallel.counter += 1;
tableList.trim().split('\n\n').forEach(function (table) {
onParallel.counter += 1;
local.dbTableCreate({
persistenceData: table,
name: JSON.parse((/.*/).exec(table)[0])
}, onParallel);
});
onParallel();
}
...
console.log('importing nedb-database ...');
reader = new window.FileReader();
tmp = document.querySelector('#nedbImportInput1').files[0];
if (!tmp) {
return;
}
reader.addEventListener('load', function () {
local.Nedb.dbImport(reader.result, function () {
console.log('... imported nedb-database');
});
});
reader.readAsText(tmp);
break;
case 'nedbResetButton1':
document.querySelector('#outputTextarea1').value = '';
...
dbReset = function (onError) {
/*
* this function will reset nedb's persistence
*/
var onParallel;
onParallel = function () {
onParallel.counter -= 1;
if (onParallel.counter === 0) {
onError();
}
};
onParallel.counter = 0;
onParallel.counter += 1;
// drop all tables
Object.keys(local.dbTableDict).forEach(function (key) {
onParallel.counter += 1;
local.dbTableDrop({ name: key }, onParallel);
});
onParallel.counter += 1;
local.storeClear(onParallel);
onParallel();
}
...
});
});
reader.readAsText(tmp);
break;
case 'nedbResetButton1':
document.querySelector('#outputTextarea1').value = '';
console.log('resetting nedb-database ...');
local.Nedb.dbReset(function () {
console.log('... resetted nedb-database');
});
break;
case 'testRunButton1':
local.modeTest = true;
local.utility2.testRun(local);
break;
...
dbTableCreate = function (options, onError) { var self; self = local.dbTableDict[options.name]; if (self) { setTimeout(function () { self.load(options, onError); }); return self; } return new local.Nedb(options, onError); }
...
<label>edit or paste script below to\n\
<a\n\
href="https://kaizhu256.github.io/node-nedb-lite/build/doc.api.html"\n\
target="_blank"\n\
>eval</a>\n\
</label>\n\
<textarea class="onkeyup" id="inputTextarea1">\n\
window.table1 = window.Nedb.dbTableCreate({ name: "table1" });\n\
table1.insert({ field1: "hello", field2: "world"}, function () {\n\
console.log();\n\
console.log(table1.export());\n\
});\n\
\n\
window.table2 = window.Nedb.dbTableCreate({ name: "table2" });\n\
table2.insert({ field1: "hello", field2: "world"}, function () {\n\
...
dbTableDrop = function (options, onError) {
/*
* this function will drop the table with the given options.name
*/
var self;
self = local.dbTableDict[options.name];
if (!self) {
onError();
return;
}
delete local.dbTableDict[options.name];
self.persistence = self.prototype = self;
self.persistCachedDatabase = self.persistNewState = function () {
var ii;
// coverage-hack
for (ii = -1; ii < arguments.length; ii += 1) {
if (typeof arguments[ii] === 'function') {
arguments[ii]();
return;
}
}
};
local.storeRemoveItem(self.name, onError);
}
...
local.testCase_dbTableDrop_default = function (options, onError) {
/*
* this function will test dbTableDrop's default handling-behavior
*/
options = {};
options.name = 'testCase_dbTableDrop_default';
options.table = local.dbTableCreate(options);
local.dbTableDrop(options.table, onError);
// test undefined-table handling-behavior
local.dbTableDrop(options.table, local.utility2.onErrorDefault);
};
local.testCase_dbTableFindOneById_default = function (options, onError) {
/*
* this function will test dbTableFindOneById's default handling-behavior
...
function Executor() { this.buffer = []; this.ready = false; // This queue will execute all commands, one-by-one in order this.queue = local.asyncQueue(function (task, cb) { var newArguments = []; // task.arguments is an array-like object on which adding a new field doesn't work, so we transform it into a real array for (var i = 0; i < task.arguments.length; i += 1) { newArguments.push(task.arguments[i]); } var lastArg = task.arguments[task.arguments.length - 1]; // Always tell the queue task is complete. Execute callback if any was given. if (typeof lastArg === 'function') { // Callback was supplied newArguments[newArguments.length - 1] = function () { if (typeof setImmediate === 'function') { setImmediate(cb); } else { setTimeout(cb); } lastArg.apply(null, arguments); }; } else if (!lastArg && task.arguments.length !== 0) { // false/undefined/null supplied as callbback newArguments[newArguments.length - 1] = function () { cb(); }; } else { // Nothing supplied as callback newArguments.push(function () { cb(); }); } task.fn.apply(task.this, newArguments); }); }
n/a
fsDir = function () {
/*
* this function will return the persistence-dir
*/
if (local.fsDirInitialized) {
return local.fsDirInitialized;
}
local.fsDirInitialized = 'tmp/nedb.persistence.' + local.NODE_ENV;
// mkdirp fsDirInitialized
local.child_process.spawnSync('mkdir', ['-p', local.fsDirInitialized], {
stdio: ['ignore', 1, 2]
});
return local.fsDirInitialized;
}
...
switch (local.modeJs) {
// run node js-env code - function
case 'node':
local.storeClear = function (onError) {
local.child_process.spawn('sh', ['-c', 'rm ' + local.fsDir\
() + '/*'], {
stdio: ['ignore', 1, 2]
}).once('exit', onError);
};
local.storeGetItem = function (key, onError) {
local.assert(typeof key === 'string');
local.fs.readFile(
...
function Index(options) { this.fieldName = options.fieldName; this.unique = options.unique || false; this.sparse = options.sparse || false; this.treeOptions = { unique: this.unique }; this.reset(); // No data in the beginning }
n/a
inherits = function (ctor, superCtor) { ctor.super_ = superCtor; ctor.prototype = Object.create(superCtor.prototype, { constructor: { value: ctor, enumerable: false, writable: true, configurable: true } }); }
...
}
/**
* Inherit basic functions from the basic binary search tree
*/
local.inherits(_AVLTree, BinarySearchTree);
/**
* Keep a pointer to the internal tree constructor for testing purposes
*/
AVLTree._AVLTree = _AVLTree;
...
isDate = function (obj) { return Object.prototype.toString.call(obj) === '[object Date]'; }
...
: modeNext + 1;
switch (modeNext) {
// STEP 1: get candidates list by checking indexes from most to least frequent usecase
case 1:
// For a basic match
usableQueryKeys = [];
Object.keys(query).forEach(function (k) {
if (typeof query[k] === 'string' || typeof query[k] === 'number' || typeof query[k] === 'boolea\
n' || local.isDate(query[k]) || query[k] === null) {
usableQueryKeys.push(k);
}
});
usableQueryKeys = usableQueryKeys.filter(function (element) {
return self.indexes.hasOwnProperty(element);
});
if (usableQueryKeys.length > 0) {
...
isRegExp = function (obj) { return Object.prototype.toString.call(obj) === '[object RegExp]'; }
...
throw new Error("$nin operator called with a non-array");
}
return !comparisonFunctions.$in(a, b);
};
comparisonFunctions.$regex = function(a, b) {
if (!local.isRegExp(b)) {
throw new Error("$regex operator called with non regular expression");
}
if (typeof a !== 'string') {
return false
} else {
return b.test(a);
...
jsonStringifyOrdered = function (element, replacer, space) {
/*
* this function will JSON.stringify the element,
* with object-keys sorted and circular-references removed
*/
var circularList, stringify, tmp;
stringify = function (element) {
/*
* this function will recursively JSON.stringify the element,
* with object-keys sorted and circular-references removed
*/
// if element is an object, then recurse its items with object-keys sorted
if (element &&
typeof element === 'object' &&
typeof element.toJSON !== 'function') {
// ignore circular-reference
if (circularList.indexOf(element) >= 0) {
return;
}
circularList.push(element);
// if element is an array, then recurse its elements
if (Array.isArray(element)) {
return '[' + element.map(function (element) {
tmp = stringify(element);
return typeof tmp === 'string'
? tmp
: 'null';
}).join(',') + ']';
}
return '{' + Object.keys(element)
// sort object-keys
.sort()
.map(function (key) {
tmp = stringify(element[key]);
return typeof tmp === 'string'
? JSON.stringify(key) + ':' + tmp
: undefined;
})
.filter(function (element) {
return typeof element === 'string';
})
.join(',') + '}';
}
// else JSON.stringify as normal
return JSON.stringify(element);
};
circularList = [];
return JSON.stringify(element && typeof element === 'object'
? JSON.parse(stringify(element))
: element, replacer, space);
}
...
console['_' + key] = console[key];
console[key] = function () {
console['_' + key].apply(console, arguments);
document.querySelector('#outputTextarea1').value +=
Array.prototype.slice.call(arguments).map(function (arg) {
return typeof arg === 'string'
? arg
: local.Nedb.jsonStringifyOrdered(arg, null, 4);
}).join(' ') + '\n';
};
});
/* istanbul ignore next */
local.testRun = function (event) {
var reader, tmp;
switch (event && event.currentTarget.id) {
...
listUnique = function (list) {
/*
* this function will remove duplicate elements from the array
*/
var seen;
seen = {};
return list.filter(function (element) {
if (seen.hasOwnProperty(element)) {
return;
}
seen[element] = true;
return true;
});
}
...
return;
}
if (!Array.isArray(key)) {
this.tree.insert(key, doc);
} else {
// If an insert fails due to a unique constraint, roll back all inserts before it
keys = local.listUnique(key).map(projectForUnique);
for (i = 0; i < keys.length; i += 1) {
try {
this.tree.insert(keys[i], doc);
} catch (e) {
error = e;
failingI = i;
...
local = function (options, onError) {
/**
* Create a new collection
* @param {String} options.name
* @param {Function} onError 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
*/
// validate name
if (!(options && options.name && typeof options.name === 'string')) {
throw new Error('Nedb - missing name param, e.g. new Nedb({ name: "table1" })');
}
this.name = options.name;
local.dbTableDrop(this, local.nop);
local.dbTableDict[this.name] = this;
// Persistence handling
this.persistence = new local.Persistence({ db: this });
// This new executor is ready if we don't use persistence
// If we do, it will only be ready once loadDatabase is called
this.executor = new local.Executor();
// Indexed by field name, dot notation can be used
// _id is always indexed and since _ids are generated randomly the underlying
// binary is always well-balanced
this.indexes = {
_id: new local.Index({ fieldName: '_id', unique: true }),
createdAt: new local.Index({ fieldName: 'createdAt' }),
updatedAt: new local.Index({ fieldName: 'updatedAt' })
};
this.ttlIndexes = {};
this.load(options, onError);
}
n/a
middleware = function (request, response, nextMiddleware) { var options; options = {}; local.utility2.onNext(options, function (error) { // recurse with next middleware in middlewareList if (options.modeNext < self.middlewareList.length) { // run the sub-middleware self.middlewareList[options.modeNext]( request, response, options.onNext ); return; } // default to nextMiddleware nextMiddleware(error); }); options.modeNext = -1; options.onNext(); }
n/a
middlewareError = function (error, request, response) {
/*
* this function will run the middleware that will handle errors
*/
// if error occurred, then respond with '500 Internal Server Error',
// else respond with '404 Not Found'
local.utility2.serverRespondDefault(request, response, error
? (error.statusCode >= 400 && error.statusCode < 600
? error.statusCode
: 500)
: 404, error);
}
n/a
nop = function () {
/*
* this function will do nothing
*/
return;
}
...
};
local.testCase_dbImport_default = function (options, onError) {
/*
* this function will test dbImport's default handling-behavior
*/
// jslint-hack
local.utility2.nop(options);
local.dbImport('"testCase_dbImport_default"\n{"id":0}', onError);
};
local.testCase_dbTableCreate_default = function (options, onError) {
/*
* this function will test dbTableCreate's default handling-behavior
*/
...
function Persistence(options) { var i, j, randomString; this.db = options.db; }
n/a
sortCompare = function (aa, bb) {
/*
* this function will sort-compare aa vs bb
*/
var type1, type2;
// compare equal
if (aa === bb) {
return 0;
}
// compare undefined
if (aa === undefined) {
return -1;
}
if (bb === undefined) {
return 1;
}
// compare null
if (aa === null) {
return -1;
}
if (bb === null) {
return 1;
}
// compare different-types
type1 = typeof aa;
type2 = typeof bb;
if (type1 !== type2) {
if (type1 === 'boolean') {
return -1;
}
if (type2 === 'boolean') {
return 1;
}
if (type1 === 'number') {
return -1;
}
if (type2 === 'number') {
return 1;
}
if (type1 === 'string') {
return -1;
}
if (type2 === 'string') {
return 1;
}
}
// default compare
return aa < bb
? -1
: aa > bb
? 1
: 0;
}
...
direction: self._sort[key]
});
}
res.sort(function (a, b) {
var criterion, compare, i;
for (i = 0; i < criteria.length; i++) {
criterion = criteria[i];
compare = criterion.direction * local.sortCompare(model.getDotValue(a, criter\
ion.key), model.getDotValue(b, criterion.key));
if (compare !== 0) {
return compare;
}
}
return 0;
});
...
storeAction = function (options, onError) { var argList, data, done, onError2, request, store; if (!local.store) { argList = arguments; local.storePromiseList.push(function () { local.storeAction.apply(null, argList); }); return; } onError2 = function () { if (done) { return; } done = true; onError( request && (request.error || request.transaction.error), data || request.result ); }; switch (options.action) { case 'clear': case 'removeItem': case 'setItem': store = local.store.transaction('nedbdata', 'readwrite').objectStore('nedbdata'); break; default: store = local.store.transaction('nedbdata', 'readonly').objectStore('nedbdata'); } switch (options.action) { case 'clear': request = store.clear(); break; case 'getItem': request = store.get(options.key); break; case 'keys': data = []; request = store.openCursor(); request.onsuccess = function () { if (!request.result) { onError2(); return; } data.push(request.result.key); request.result.continue(); }; break; case 'length': request = store.count(); break; case 'removeItem': request = store.delete(options.key); break; case 'setItem': request = store.put(options.value, options.key); break; } ['onabort', 'onerror', 'onsuccess'].forEach(function (handler) { request[handler] = request[handler] || onError2; }); }
...
}
['onabort', 'onerror', 'onsuccess'].forEach(function (handler) {
request[handler] = request[handler] || onError2;
});
};
local.storeClear = function (onError) {
local.storeAction({ action: 'clear' }, onError);
};
local.storeGetItem = function (key, onError) {
local.assert(typeof key === 'string');
local.storeAction({ action: 'getItem', key: key }, onError);
};
...
storeClear = function (onError) { local.child_process.spawn('sh', ['-c', 'rm ' + local.fsDir() + '/*'], { stdio: ['ignore', 1, 2] }).once('exit', onError); }
...
onParallel.counter += 1;
// drop all tables
Object.keys(local.dbTableDict).forEach(function (key) {
onParallel.counter += 1;
local.dbTableDrop({ name: key }, onParallel);
});
onParallel.counter += 1;
local.storeClear(onParallel);
onParallel();
};
local.dbTableCreate = function (options, onError) {
var self;
self = local.dbTableDict[options.name];
if (self) {
...
storeGetItem = function (key, onError) { local.assert(typeof key === 'string'); local.fs.readFile( local.fsDir() + '/' + encodeURIComponent(key), 'utf8', function (error, data) { // jslint-hack local.nop(error); onError(null, data || ''); } ); }
...
toPersist += model.serialize(doc) + '\n';
});
if (toPersist.length === 0) {
return callback();
}
local.storeGetItem(self.db.name, function (error, data) {
local.storeSetItem(self.db.name, (data || '') + toPersist, callback);
});
};
/**
* From a database's raw data, return the corresponding
...
storeInit = function () { var modeNext, onNext, request; if (!local.global.indexedDB) { return; } modeNext = 0; onNext = function (error) { local.store = local.global.nedbStore; // validate no error occurred local.assert(local.store || !error, error); modeNext += 1; switch (modeNext) { // init indexedDB case 1: if (local.store) { onNext(); return; } request = local.global.indexedDB.open('NeDB'); request.onerror = function () { onNext(request.error); }; request.onsuccess = function () { local.global.nedbStore = request.result; onNext(); }; request.onupgradeneeded = function () { if (!request.result.objectStoreNames.contains('nedbdata')) { request.result.createObjectStore('nedbdata'); } }; break; // run promised actions case 2: while (local.storePromiseList.length) { local.storePromiseList.shift()(); } break; } }; onNext(); }
...
/* jslint-ignore-end */
// run shared js-env code - post-init
(function () {
// init indexedDB store
local.storeInit();
// re-init local
Object.keys(local).forEach(function (key) {
local[key.replace((/.*\//), '')] = local[key];
});
}());
switch (local.modeJs) {
...
storeKeys = function (onError) { local.fs.readdir(local.fsDir(), function (error, data) { onError(error, data && data.map(decodeURIComponent)); }); }
n/a
storeLength = function (onError) { local.fs.readdir(local.fsDir(), function (error, data) { onError(error, data && data.length); }); }
n/a
storeRemoveItem = function (key, onError) { local.assert(typeof key === 'string'); local.fs.unlink(local.fsDir() + '/' + encodeURIComponent(key), function (error) { // jslint-hack local.nop(error); onError(); }); }
...
for (ii = -1; ii < arguments.length; ii += 1) {
if (typeof arguments[ii] === 'function') {
arguments[ii]();
return;
}
}
};
local.storeRemoveItem(self.name, onError);
};
local.fsDir = function () {
/*
* this function will return the persistence-dir
*/
if (local.fsDirInitialized) {
...
storeSetItem = function (key, value, onError) { var tmp; local.assert(typeof key === 'string'); local.assert(typeof value === 'string'); tmp = local.os.tmpdir() + '/' + Date.now() + Math.random(); // save to tmp local.fs.writeFile(tmp, value, function (error) { // jslint-hack local.nop(error); // rename tmp to key local.fs.rename(tmp, local.fsDir() + '/' + encodeURIComponent(key), onError); }); }
...
if (!data) {
onNext();
return;
}
self.isLoaded = null;
data += '\n';
data = data.slice(data.indexOf('\n') + 1);
local.storeSetItem(self.name, data, onNext);
break;
case 2:
if (self.isLoaded) {
onNext();
return;
}
self.isLoaded = true;
...
testCase_assertXxx_default = function (options, onError) {
/*
* this function will test assertXxx's default handling-behavior
*/
options = {};
// test assertion passed
local.utility2.assert(true, true);
// test assertion failed with undefined message
local.utility2.tryCatchOnError(function () {
local.utility2.assert(false);
}, function (error) {
// validate error occurred
local.utility2.assert(error, error);
// validate error-message
local.utility2.assertJsonEqual(error.message, '');
});
// test assertion failed with string message
local.utility2.tryCatchOnError(function () {
local.utility2.assert(false, 'hello');
}, function (error) {
// validate error occurred
local.utility2.assert(error, error);
// validate error-message
local.utility2.assertJsonEqual(error.message, 'hello');
});
// test assertion failed with error object
local.utility2.tryCatchOnError(function () {
local.utility2.assert(false, local.utility2.errorDefault);
}, function (error) {
// validate error occurred
local.utility2.assert(error, error);
});
// test assertion failed with json object
local.utility2.tryCatchOnError(function () {
local.utility2.assert(false, { aa: 1 });
}, function (error) {
// validate error occurred
local.utility2.assert(error, error);
// validate error-message
local.utility2.assertJsonEqual(error.message, '{"aa":1}');
});
options.list = ['', 0, false, null, undefined];
options.list.forEach(function (aa, ii) {
options.list.forEach(function (bb, jj) {
if (ii === jj) {
// test assertJsonEqual's handling-behavior
local.utility2.assertJsonEqual(aa, bb);
} else {
// test assertJsonNotEqual's handling-behavior
local.utility2.assertJsonNotEqual(aa, bb);
}
});
});
onError();
}
n/a
testCase_build_app = function (options, onError) {
/*
* this function will test build's app handling-behavior
*/
var onParallel;
onParallel = local.utility2.onParallel(onError);
onParallel.counter += 1;
options = {};
options = [{
file: '/assets.app.js',
url: '/assets.app.js'
}, {
file: '/assets.app.min.js',
url: '/assets.app.min.js'
}, {
file: '/assets.example.js',
url: '/assets.example.js'
}, {
file: '/assets.' + local.utility2.envDict.npm_package_name + '.css',
url: '/assets.' + local.utility2.envDict.npm_package_name + '.css'
}, {
file: '/assets.' + local.utility2.envDict.npm_package_name + '.js',
url: '/assets.' + local.utility2.envDict.npm_package_name + '.js'
}, {
file: '/assets.' + local.utility2.envDict.npm_package_name + '.min.js',
transform: function (data) {
return local.utility2.uglifyIfProduction(
local.utility2.bufferToString(data)
);
},
url: '/assets.' + local.utility2.envDict.npm_package_name + '.js'
}, {
file: '/assets.test.js',
url: '/assets.test.js'
}, {
file: '/assets.utility2.rollup.js',
url: '/assets.utility2.rollup.js'
}, {
file: '/index.html',
url: '/index.html'
}, {
file: '/jsonp.utility2.stateInit',
url: '/jsonp.utility2.stateInit?callback=window.utility2.stateInit'
}];
options.forEach(function (options) {
onParallel.counter += 1;
local.utility2.ajax(options, function (error, xhr) {
onParallel.counter += 1;
// validate no error occurred
onParallel(error);
switch (local.path.extname(options.file)) {
case '.css':
case '.js':
case '.json':
local.utility2.jslintAndPrintConditional(
xhr.responseText,
options.file
);
// validate no error occurred
local.utility2.assert(
!local.utility2.jslint.errorText,
local.utility2.jslint.errorText
);
break;
}
local.utility2.fsWriteFileWithMkdirp(
local.utility2.envDict.npm_config_dir_build + '/app' + options.file,
(options.transform || local.utility2.echo)(xhr.response),
onParallel
);
});
});
onParallel();
}
n/a
testCase_build_doc = function (options, onError) {
/*
* this function will test build's doc handling-behavior
*/
options = {};
local.utility2.onNext(options, function (error) {
switch (options.modeNext) {
case 1:
options.moduleDict = {
Nedb: {
exampleList: [],
exports: local.Nedb
},
'Nedb.customUtils': {
exampleList: [],
exports: local.Nedb.customUtils
},
'Nedb.model': {
exampleList: [],
exports: local.Nedb.model
},
'Nedb.persistence': {
exampleList: [],
exports: local.Nedb.persistence
},
'Nedb.persistence.prototype': {
exampleList: [],
exports: local.Nedb.persistence.prototype
},
'Nedb.prototype': {
exampleList: [],
exports: local.Nedb.prototype
}
};
Object.keys(options.moduleDict).forEach(function (key) {
options.moduleDict[key].example = [
'README.md',
'test.js',
'index.js'
]
.concat(options.moduleDict[key].exampleList)
.map(function (file) {
return '\n\n\n\n\n\n\n\n' +
local.fs.readFileSync(file, 'utf8') +
'\n\n\n\n\n\n\n\n';
}).join('');
});
// create doc.api.html
local.utility2.fsWriteFileWithMkdirp(
local.utility2.envDict.npm_config_dir_build + '/doc.api.html',
local.utility2.docApiCreate(options),
options.onNext
);
break;
case 2:
local.utility2.browserTest({
modeBrowserTest: 'screenCapture',
url: 'file://' + local.utility2.envDict.npm_config_dir_build +
'/doc.api.html'
}, options.onNext);
break;
default:
onError(error);
}
});
options.modeNext = 0;
options.onNext();
}
n/a
testCase_dbExport_default = function (options, onError) {
/*
* this function will test dbExport's default handling-behavior
*/
options = {};
options.name = 'testCase_dbExport_default';
options.table = local.dbTableCreate(options);
options.table.ensureIndex({
fieldName: 'id',
unique: true
}, local.utility2.onErrorDefault);
options.data = local.dbExport();
// validate data
local.utility2.assert(options.data.indexOf('"testCase_dbExport_default"\n' +
'{"$$indexCreated":{"fieldName":"createdAt","unique":false,"sparse":false}}\n' +
'{"$$indexCreated":{"fieldName":"updatedAt","unique":false,"sparse":false}}\n' +
'{"$$indexCreated":{"fieldName":"id","unique":true,"sparse":false}}')
>= 0, options.data);
onError();
}
n/a
testCase_dbImport_default = function (options, onError) {
/*
* this function will test dbImport's default handling-behavior
*/
// jslint-hack
local.utility2.nop(options);
local.dbImport('"testCase_dbImport_default"\n{"id":0}', onError);
}
n/a
testCase_dbTableCreate_default = function (options, onError) {
/*
* this function will test dbTableCreate's default handling-behavior
*/
options = {};
options.name = 'testCase_dbTableCreate_default';
options.table = local.dbTableCreate(options);
// test re-create handling-behavior
options.table = local.dbTableCreate(options);
// test reset handling-behavior
options.reset = true;
options.table = local.dbTableCreate(options);
onError();
}
n/a
testCase_dbTableCreate_error = function (options, onError) {
/*
* this function will test dbTableCreate's error handling-behavior
*/
options = {};
options.error = local.utility2.errorDefault;
options.name = 'testCase_dbTableCreate_error';
options.table = local.dbTableCreate(options, function (error) {
// validate error occurred
local.utility2.assert(error, error);
onError();
});
}
n/a
testCase_dbTableDrop_default = function (options, onError) {
/*
* this function will test dbTableDrop's default handling-behavior
*/
options = {};
options.name = 'testCase_dbTableDrop_default';
options.table = local.dbTableCreate(options);
local.dbTableDrop(options.table, onError);
// test undefined-table handling-behavior
local.dbTableDrop(options.table, local.utility2.onErrorDefault);
}
n/a
testCase_dbTableFindOneById_default = function (options, onError) {
/*
* this function will test dbTableFindOneById's default handling-behavior
*/
options = {};
local.utility2.onNext(options, function (error, data) {
switch (options.modeNext) {
case 1:
options = local.crudOptionsSetDefault(options, {
id: '00_test_dbTableFindOneById'
});
options.table.findOne({ id: options.id }, options.onNext);
break;
case 2:
// validate data
local.utility2.assertJsonEqual(data.id, options.id);
options.onNext();
break;
default:
onError(error);
}
});
options.modeNext = 0;
options.onNext();
}
...
options = {};
local.utility2.onNext(options, function (error, data) {
switch (options.modeNext) {
case 1:
options = local.crudOptionsSetDefault(options, {
id: '00_test_dbTableRemoveOneById'
});
local.testCase_dbTableFindOneById_default(options, options.onNext);
break;
case 2:
options.table.remove({ id: options.id }, options.onNext);
break;
case 3:
options.table.findOne({ id: options.id }, options.onNext);
break;
...
testCase_dbTableRemoveOneById_default = function (options, onError) {
/*
* this function will test dbTableRemoveOneById's default handling-behavior
*/
options = {};
local.utility2.onNext(options, function (error, data) {
switch (options.modeNext) {
case 1:
options = local.crudOptionsSetDefault(options, {
id: '00_test_dbTableRemoveOneById'
});
local.testCase_dbTableFindOneById_default(options, options.onNext);
break;
case 2:
options.table.remove({ id: options.id }, options.onNext);
break;
case 3:
options.table.findOne({ id: options.id }, options.onNext);
break;
case 4:
// validate data was removed
local.utility2.assertJsonEqual(data, null);
options.onNext();
break;
default:
onError(error, data);
}
});
options.modeNext = 0;
options.onNext();
}
n/a
testCase_jsonStringifyOrdered_default = function (options, onError) {
/*
* this function will test jsonStringifyOrdered's default handling-behavior
*/
options = {};
// test data-type handling-behavior
[undefined, null, false, true, 0, 1, 1.5, 'a', {}, []].forEach(function (data) {
options.aa = local.utility2.jsonStringifyOrdered(data);
options.bb = JSON.stringify(data);
local.utility2.assertJsonEqual(options.aa, options.bb);
});
// test data-ordering handling-behavior
options = {
// test nested dict handling-behavior
ff: { hh: 2, gg: 1},
// test nested array handling-behavior
ee: [1, null, undefined],
dd: local.utility2.nop,
cc: undefined,
bb: null,
aa: 1
};
// test circular-reference handling-behavior
options.zz = options;
local.utility2.assertJsonEqual(
options,
{ aa: 1, bb: null, ee: [ 1, null, null ], ff: { gg: 1, hh: 2 } }
);
onError();
}
n/a
testCase_webpage_default = function (options, onError) {
/*
* this function will test the webpage's default handling-behavior
*/
options = {
modeCoverageMerge: true,
url: local.utility2.serverLocalHost + '?modeTest=1'
};
local.utility2.browserTest(options, onError);
}
n/a
function uid(len) { return byteArrayToBase64(randomBytes(Math.ceil(Math.max(8, len * 2)))).replace(/[+\/]/g, '').slice(0, len); }
...
});
};
/**
* Create a new _id that's not already in use
*/
Datastore.prototype.createNewId = function () {
var tentativeId = customUtils.uid(16);
// Try as many times as needed to get an unused _id. As explained in customUtils, the probability of this ever happening is \
extremely small, so this is O(1)
if (this.indexes._id.getMatching(tentativeId).length > 0) {
tentativeId = this.createNewId();
}
return tentativeId;
};
...
function areThingsEqual(a, b) { var aKeys, bKeys, i; // Strings, booleans, numbers, null if (a === null || typeof a === 'string' || typeof a === 'boolean' || typeof a === 'number' || b === null || typeof b === 'string' || typeof b === 'boolean' || typeof b === 'number') { return a === b; } // Dates if (local.isDate(a) || local.isDate(b)) { return local.isDate(a) && local.isDate(b) && a.getTime() === b.getTime(); } // Arrays (no match since arrays are used as a $in) // undefined (no match since they mean field doesn't exist and can't be serialized) if ((!(Array.isArray(a) && Array.isArray(b)) && (Array.isArray(a) || Array.isArray(b))) || a === undefined || b === undefine\ d) { return false; } // General objects (check for deep equality) // a and b should be objects at this point try { aKeys = Object.keys(a); bKeys = Object.keys(b); } catch (e) { return false; } if (aKeys.length !== bKeys.length) { return false; } for (i = 0; i < aKeys.length; i += 1) { if (bKeys.indexOf(aKeys[i]) === -1) { return false; } if (!areThingsEqual(a[aKeys[i]], b[aKeys[i]])) { return false; } } return true; }
n/a
function checkObject(obj) { if (Array.isArray(obj)) { obj.forEach(function(o) { checkObject(o); }); } if (typeof obj === 'object' && obj !== null) { Object.keys(obj).forEach(function(k) { checkKey(k, obj[k]); checkObject(obj[k]); }); } }
...
var now = new Date().toISOString();
if (preparedDoc.createdAt === undefined) {
preparedDoc.createdAt = now;
}
if (preparedDoc.updatedAt === undefined) {
preparedDoc.updatedAt = now;
}
model.checkObject(preparedDoc);
}
return preparedDoc;
};
/**
* If newDoc is an array of documents, this will insert all documents in the cache
...
function deepCopy(obj, strictKeys) { var res; if (typeof obj === 'boolean' || typeof obj === 'number' || typeof obj === 'string' || obj === null || (local.isDate(obj))) { return obj; } if (Array.isArray(obj)) { res = []; obj.forEach(function(o) { res.push(deepCopy(o, strictKeys)); }); return res; } if (typeof obj === 'object') { res = {}; Object.keys(obj).forEach(function(k) { if (!strictKeys || (k[0] !== '$' && k.indexOf('.') === -1)) { res[k] = deepCopy(obj[k], strictKeys); } }); return res; } return undefined; // For now everything else is undefined. We should probably throw an error instead }
...
return callback(e);
}
this.persistence.persistNewState(Array.isArray(preparedDoc) ? preparedDoc : [preparedDoc], function (error) {
if (error) {
return callback(error);
}
return callback(null, model.deepCopy(preparedDoc));
});
};
/**
* Create a new _id that's not already in use
*/
Datastore.prototype.createNewId = function () {
...
function deserialize(rawData) { return JSON.parse(rawData, function(k, v) { if (k === '$$date') { return new Date(v).toISOString(); } if (typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean' || v === null) { return v; } if (v && v.$$date) { return v.$$date; } return v; }); }
...
corruptItems = -1 // Last line of every data file is usually blank so not really corrupt
;
for (i = 0; i < data.length; i += 1) {
var doc;
try {
doc = model.deserialize(data[i]);
if (doc._id) {
if (doc.$$deleted === true) {
delete dataById[doc._id];
} else {
dataById[doc._id] = doc;
}
} else if (doc.$$indexCreated && doc.$$indexCreated.fieldName != undefined) {
...
function getDotValue(obj, field) { var fieldParts = typeof field === 'string' ? field.split('.') : field, i, objs; if (!obj) { return undefined; } // field cannot be empty so that means we should return undefined so that nothing can match if (fieldParts.length === 0) { return obj; } if (fieldParts.length === 1) { return obj[fieldParts[0]]; } if (Array.isArray(obj[fieldParts[0]])) { // If the next field is an integer, return only this item of the array i = parseInt(fieldParts[1], 10); if (typeof i === 'number' && !isNaN(i)) { return getDotValue(obj[fieldParts[0]][i], fieldParts.slice(2)) } // Return the array of values objs = new Array(); for (i = 0; i < obj[fieldParts[0]].length; i += 1) { objs.push(getDotValue(obj[fieldParts[0]][i], fieldParts.slice(1))); } return objs; } else { return getDotValue(obj[fieldParts[0]], fieldParts.slice(1)); } }
...
candidates.forEach(function (candidate) {
var toPush;
if (action === 1) { // pick-type projection
toPush = {
$set: {}
};
keys.forEach(function (k) {
toPush.$set[k] = model.getDotValue(candidate, k);
if (toPush.$set[k] === undefined) {
delete toPush.$set[k];
}
});
toPush = model.modify({}, toPush);
} else { // omit-type projection
toPush = {
...
function isPrimitiveType(obj) { return (typeof obj === 'boolean' || typeof obj === 'number' || typeof obj === 'string' || obj === null || local.isDate(obj) || Array.isArray(obj)); }
n/a
function match(obj, query) { var queryKeys, queryKey, queryValue, i; // Primitive query against a primitive type // This is a bit of a hack since we construct an object with an arbitrary key only to dereference it later // But I don't have time for a cleaner implementation now if (isPrimitiveType(obj) || isPrimitiveType(query)) { return matchQueryPart({ needAKey: obj }, 'needAKey', query); } // Normal query queryKeys = Object.keys(query); for (i = 0; i < queryKeys.length; i += 1) { queryKey = queryKeys[i]; queryValue = query[queryKey]; if (queryKey[0] === '$') { if (!logicalOperators[queryKey]) { throw new Error("Unknown logical operator " + queryKey); } if (!logicalOperators[queryKey](obj, queryValue)) { return false; } } else { if (!matchQueryPart(obj, queryKey, queryValue)) { return false; } } } return true; }
...
this.db.getCandidates(this.query, function (error, candidates) {
if (error) {
return callback(error);
}
try {
for (i = 0; i < candidates.length; i += 1) {
if (model.match(candidates[i], self.query)) {
// If a sort is defined, wait for the results to be sorted before applying limit and skip
if (!self._sort) {
if (self._skip && self._skip > skipped) {
skipped += 1;
} else {
res.push(candidates[i]);
added += 1;
...
function modify(obj, updateQuery) { var keys = Object.keys(updateQuery), firstChars = keys.map(function(item) { return item[0]; }), dollarFirstChars = firstChars.filter(function(c) { return c === '$'; }), newDoc, modifiers; if (keys.indexOf('_id') !== -1 && updateQuery._id !== obj._id) { throw new Error("You cannot change a document's _id"); } if (dollarFirstChars.length !== 0 && dollarFirstChars.length !== firstChars.length) { throw new Error("You cannot mix modifiers and normal fields"); } if (dollarFirstChars.length === 0) { // Simply replace the object with the update query contents newDoc = deepCopy(updateQuery); newDoc._id = obj._id; } else { // Apply modifiers modifiers = local.listUnique(keys); newDoc = deepCopy(obj); modifiers.forEach(function(m) { var keys; if (!modifierFunctions[m]) { throw new Error("Unknown modifier " + m); } // Can't rely on Object.keys throwing on non objects since ES6 // Not 100% satisfying as non objects can be interpreted as objects but no false negatives so we can live with it if (typeof updateQuery[m] !== 'object') { throw new Error("Modifier " + m + "'s argument must be an object"); } keys = Object.keys(updateQuery[m]); keys.forEach(function(k) { modifierFunctions[m](newDoc, k, updateQuery[m][k]); }); }); } // Check result is valid and return it checkObject(newDoc); if (obj._id !== newDoc._id) { throw new Error("You can't change a document's _id"); } return newDoc; }
...
};
keys.forEach(function (k) {
toPush.$set[k] = model.getDotValue(candidate, k);
if (toPush.$set[k] === undefined) {
delete toPush.$set[k];
}
});
toPush = model.modify({}, toPush);
} else { // omit-type projection
toPush = {
$unset: {}
};
keys.forEach(function (k) {
toPush.$unset[k] = true
});
...
function serialize(obj) { var res; res = JSON.stringify(obj, function(k, v) { checkKey(k, v); if (v === undefined) { return undefined; } if (v === null) { return null; } // Hackish way of checking if object is Date (this way it works between execution contexts in node-webkit). // We can't use value directly because for dates it is already string in this function (date.toJSON was already called),\ so we use this if (typeof this[k].getTime === 'function') { return { $$date: this[k].getTime() }; } return v; }); return res; }
...
* this function will export the table as serialized-text
*/
var data, self;
self = this;
data = '';
data += JSON.stringify(String(this.name)) + '\n';
self.getAllData().forEach(function (doc) {
data += local.model.serialize(doc) + '\n';
});
Object.keys(self.indexes).forEach(function (fieldName) {
if (fieldName === '_id') {
return;
}
data += local.model.serialize({ $$indexCreated: {
fieldName: fieldName,
...
function Persistence(options) { var i, j, randomString; this.db = options.db; }
n/a
compactDatafile = function () { this.db.executor.push({ this: this, fn: this.persistCachedDatabase, arguments: [] }); }
n/a
loadDatabase = function (cb) {
var callback = cb,
self = this;
self.db.resetIndexes();
var dir, modeNext, onNext;
modeNext = 0;
onNext = function (error) {
modeNext = error
? Infinity
: modeNext + 1;
switch (modeNext) {
case 1:
local.storeGetItem(self.db.name, function(error, rawData) {
try {
var treatedData = self.treatRawData(rawData || '');
} catch (e) {
return onNext(e);
}
// Recreate all indexes in the datafile
Object.keys(treatedData.indexes).forEach(function(key) {
self.db.indexes[key] = new Index(treatedData.indexes[key]);
});
// Fill cached database (i.e. all indexes) with data
try {
self.db.resetIndexes(treatedData.data);
} catch (e) {
self.db.resetIndexes(); // Rollback any index which didn't fail
return onNext(e);
}
self.db.persistence.persistCachedDatabase(onNext);
});
break;
default:
if (error) {
return callback(error);
}
/**
* Queue all tasks in buffer (in the same order they came in)
* Automatically sets executor as ready
*/
self.db.executor.ready = true;
while (self.db.executor.buffer.length) {
self.db.executor.queue.push(self.db.executor.buffer.shift());
}
return callback();
}
};
onNext();
}
...
break;
case 2:
if (self.isLoaded) {
onNext();
return;
}
self.isLoaded = true;
self.loadDatabase(onNext);
break;
default:
onError(error, self);
}
};
onNext(options.error);
return self;
...
persistCachedDatabase = function (cb) { var callback = cb, toPersist = '', self = this; this.db.getAllData().forEach(function(doc) { toPersist += model.serialize(doc) + '\n'; }); Object.keys(this.db.indexes).forEach(function(fieldName) { if (fieldName != '_id') { // The special _id index is managed by datastore.js, the others need to be persisted toPersist += model.serialize({ $$indexCreated: { fieldName: fieldName, unique: self.db.indexes[fieldName].unique, sparse: self.db.indexes[fieldName].sparse } }) + '\n'; } }); local.storeSetItem(this.db.name, toPersist, function(error) { if (error) { return callback(error); } return callback(); }); }
...
try {
self.db.resetIndexes(treatedData.data);
} catch (e) {
self.db.resetIndexes(); // Rollback any index which didn't fail
return onNext(e);
}
self.db.persistence.persistCachedDatabase(onNext);
});
break;
default:
if (error) {
return callback(error);
}
...
persistNewState = function (newDocs, cb) {
/**
* Persist new state for the given newDocs (can be insertion, update or removal)
* Use an append-only format
* @param {Array} newDocs Can be empty if no doc was updated/removed
* @param {Function} cb Optional, signature: error
*/
var self = this,
toPersist = '',
callback = cb;
newDocs.forEach(function(doc) {
toPersist += model.serialize(doc) + '\n';
});
if (toPersist.length === 0) {
return callback();
}
local.storeGetItem(self.db.name, function (error, data) {
local.storeSetItem(self.db.name, (data || '') + toPersist, callback);
});
}
...
this.indexes[options.fieldName].insert(this.getAllData());
} catch (e) {
delete this.indexes[options.fieldName];
return callback(e);
}
// We may want to force all options to be persisted including defaults, not just the ones passed the index creation function
this.persistence.persistNewState([{
$$indexCreated: options
}], function (error) {
if (error) {
return callback(error);
}
return callback();
});
...
treatRawData = function (rawData) { var data = rawData.split('\n'), dataById = {}, tdata = [], i, indexes = {}, corruptItems = -1 // Last line of every data file is usually blank so not really corrupt ; for (i = 0; i < data.length; i += 1) { var doc; try { doc = model.deserialize(data[i]); if (doc._id) { if (doc.$$deleted === true) { delete dataById[doc._id]; } else { dataById[doc._id] = doc; } } else if (doc.$$indexCreated && doc.$$indexCreated.fieldName != undefined) { indexes[doc.$$indexCreated.fieldName] = doc.$$indexCreated; } else if (typeof doc.$$indexRemoved === 'string') { delete indexes[doc.$$indexRemoved]; } } catch (errorCaught) { corruptItems += 1; // validate no error occurred local.assert(!corruptItems, errorCaught); } } Object.keys(dataById).forEach(function(k) { tdata.push(dataById[k]); }); return { data: tdata, indexes: indexes }; }
...
modeNext = error
? Infinity
: modeNext + 1;
switch (modeNext) {
case 1:
local.storeGetItem(self.db.name, function(error, rawData) {
try {
var treatedData = self.treatRawData(rawData || '');
} catch (e) {
return onNext(e);
}
// Recreate all indexes in the datafile
Object.keys(treatedData.indexes).forEach(function(key) {
self.db.indexes[key] = new Index(treatedData.indexes[key]);
...
addToIndexes = function (doc) { var i, failingIndex, error, keys = Object.keys(this.indexes); for (i = 0; i < keys.length; i += 1) { try { this.indexes[keys[i]].insert(doc); } catch (e) { failingIndex = i; error = e; break; } } // If an error happened, we need to rollback the insert on all other indexes if (error) { for (i = 0; i < failingIndex; i += 1) { this.indexes[keys[i]].remove(doc); } throw error; } }
...
* If newDoc is an array of documents, this will insert all documents in the cache
* @api private
*/
Datastore.prototype._insertInCache = function (preparedDoc) {
if (Array.isArray(preparedDoc)) {
this._insertMultipleDocsInCache(preparedDoc);
} else {
this.addToIndexes(preparedDoc);
}
};
/**
* If one insertion fails (e.g. because of a unique constraint), roll back all previous
* inserts and throws the error
* @api private
...
count = function (query, callback) { var cursor = new Cursor(this, query, function (error, docs, callback) { if (error) { return callback(error); } return callback(null, docs.length); }); if (typeof callback === 'function') { cursor.exec(callback); } else { return cursor; } }
...
return;
}
data.push(request.result.key);
request.result.continue();
};
break;
case 'length':
request = store.count();
break;
case 'removeItem':
request = store.delete(options.key);
break;
case 'setItem':
request = store.put(options.value, options.key);
break;
...
createNewId = function () { var tentativeId = customUtils.uid(16); // Try as many times as needed to get an unused _id. As explained in customUtils, the probability of this ever happening is \ extremely small, so this is O(1) if (this.indexes._id.getMatching(tentativeId).length > 0) { tentativeId = this.createNewId(); } return tentativeId; }
...
/**
* Create a new _id that's not already in use
*/
Datastore.prototype.createNewId = function () {
var tentativeId = customUtils.uid(16);
// Try as many times as needed to get an unused _id. As explained in customUtils, the probability of this ever happening is \
extremely small, so this is O(1)
if (this.indexes._id.getMatching(tentativeId).length > 0) {
tentativeId = this.createNewId();
}
return tentativeId;
};
/**
* 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
...
ensureIndex = function (options, cb) {
/**
* Ensure an index is kept for this field. Same parameters as lib/indexes
* For now this function is synchronous, we need to test how much time it takes
* We use an async API for consistency with the rest of the code
* @param {String} options.fieldName
* @param {Boolean} options.unique
* @param {Boolean} options.sparse
* @param {Number} options.expireAfterSeconds - Optional, if set this index becomes a TTL index (only works on Date fields, not \
arrays of Date)
* @param {Function} cb Optional callback, signature: error
*/
var error, callback = cb;
if (!options.fieldName) {
error = new Error("Cannot create an index without a fieldName");
error.missingFieldName = true;
return callback(error);
}
if (this.indexes[options.fieldName]) {
return callback();
}
this.indexes[options.fieldName] = new Index(options);
if (options.expireAfterSeconds !== undefined) {
this.ttlIndexes[options.fieldName] = options.expireAfterSeconds;
} // With this implementation index creation is not necessary to ensure TTL but we stick with MongoDB's API here
try {
this.indexes[options.fieldName].insert(this.getAllData());
} catch (e) {
delete this.indexes[options.fieldName];
return callback(e);
}
// We may want to force all options to be persisted including defaults, not just the ones passed the index creation function
this.persistence.persistNewState([{
$$indexCreated: options
}], function (error) {
if (error) {
return callback(error);
}
return callback();
});
}
...
local.testCase_dbExport_default = function (options, onError) {
/*
* this function will test dbExport's default handling-behavior
*/
options = {};
options.name = 'testCase_dbExport_default';
options.table = local.dbTableCreate(options);
options.table.ensureIndex({
fieldName: 'id',
unique: true
}, local.utility2.onErrorDefault);
options.data = local.dbExport();
// validate data
local.utility2.assert(options.data.indexOf('"testCase_dbExport_default"\n' +
'{"$$indexCreated":{"fieldName":"createdAt","unique":false,"sparse&quo\
t;:false}}\n' +
...
export = function () {
/*
* this function will export the table as serialized-text
*/
var data, self;
self = this;
data = '';
data += JSON.stringify(String(this.name)) + '\n';
self.getAllData().forEach(function (doc) {
data += local.model.serialize(doc) + '\n';
});
Object.keys(self.indexes).forEach(function (fieldName) {
if (fieldName === '_id') {
return;
}
data += local.model.serialize({ $$indexCreated: {
fieldName: fieldName,
unique: self.indexes[fieldName].unique,
sparse: self.indexes[fieldName].sparse
} }) + '\n';
});
return data.slice(0, -1);
}
...
target="_blank"\n\
>eval</a>\n\
</label>\n\
<textarea class="onkeyup" id="inputTextarea1">\n\
window.table1 = window.Nedb.dbTableCreate({ name: "table1" });\n\
table1.insert({ field1: "hello", field2: "world"}, function () {\n\
console.log();\n\
console.log(table1.export());\n\
});\n\
\n\
window.table2 = window.Nedb.dbTableCreate({ name: "table2" });\n\
table2.insert({ field1: "hello", field2: "world"}, function () {\n\
console.log();\n\
console.log(table2.export());\n\
});\n\
...
find = function (query, projection, callback) { switch (arguments.length) { case 1: projection = {}; // callback is undefined, will return a cursor break; case 2: if (typeof projection === 'function') { callback = projection; projection = {}; } // If not assume projection is an object and callback undefined break; } var cursor = new Cursor(this, query, function (error, docs, callback) { var res = [], i; if (error) { return callback(error); } for (i = 0; i < docs.length; i += 1) { res.push(model.deepCopy(docs[i])); } return callback(null, res); }); cursor.projection(projection); if (typeof callback === 'function') { cursor.exec(callback); } else { return cursor; } }
n/a
findOne = function (query, projection, callback) { switch (arguments.length) { case 1: projection = {}; // callback is undefined, will return a cursor break; case 2: if (typeof projection === 'function') { callback = projection; projection = {}; } // If not assume projection is an object and callback undefined break; } var cursor = new Cursor(this, query, function (error, docs, callback) { if (error) { return callback(error); } if (docs.length === 1) { return callback(null, model.deepCopy(docs[0])); } else { return callback(null, null); } }); cursor.projection(projection).limit(1); if (typeof callback === 'function') { cursor.exec(callback); } else { return cursor; } }
...
options = {};
local.utility2.onNext(options, function (error, data) {
switch (options.modeNext) {
case 1:
options = local.crudOptionsSetDefault(options, {
id: '00_test_dbTableFindOneById'
});
options.table.findOne({ id: options.id }, options.onNext);
break;
case 2:
// validate data
local.utility2.assertJsonEqual(data.id, options.id);
options.onNext();
break;
default:
...
getAllData = function () { return this.indexes._id.getAll(); }
...
/*
* this function will export the table as serialized-text
*/
var data, self;
self = this;
data = '';
data += JSON.stringify(String(this.name)) + '\n';
self.getAllData().forEach(function (doc) {
data += local.model.serialize(doc) + '\n';
});
Object.keys(self.indexes).forEach(function (fieldName) {
if (fieldName === '_id') {
return;
}
data += local.model.serialize({ $$indexCreated: {
...
getCandidates = function (query, dontExpireStaleDocs, callback) {
/**
* Return the list of candidates for a given query
* Crude implementation for now, we return the candidates given by the first usable index if any
* We try the following query types, in this order: basic match, $in match, comparison match
* One way to make it better would be to enable the use of multiple indexes if the first usable index
* returns too much data. I may do it in the future.
*
* Returned candidates will be scanned to find and remove all expired documents
*
* @param {Query} query
* @param {Boolean} dontExpireStaleDocs Optional, defaults to false, if true don't remove stale docs. Useful for the remove func\
tion which shouldn't be impacted by expirations
* @param {Function} callback Signature error, candidates
*/
var self = this,
usableQueryKeys;
if (typeof dontExpireStaleDocs === 'function') {
callback = dontExpireStaleDocs;
dontExpireStaleDocs = false;
}
var modeNext, onNext;
modeNext = 0;
onNext = function (error, docs) {
modeNext = error
? Infinity
: modeNext + 1;
switch (modeNext) {
// STEP 1: get candidates list by checking indexes from most to least frequent usecase
case 1:
// For a basic match
usableQueryKeys = [];
Object.keys(query).forEach(function (k) {
if (typeof query[k] === 'string' || typeof query[k] === 'number' || typeof query[k] === 'boolean' || local.isDat\
e(query[k]) || query[k] === null) {
usableQueryKeys.push(k);
}
});
usableQueryKeys = usableQueryKeys.filter(function (element) {
return self.indexes.hasOwnProperty(element);
});
if (usableQueryKeys.length > 0) {
return onNext(null, self.indexes[usableQueryKeys[0]].getMatching(query[usableQueryKeys[0]]));
}
// For a $in match
usableQueryKeys = [];
Object.keys(query).forEach(function (k) {
if (query[k] && query[k].hasOwnProperty('$in')) {
usableQueryKeys.push(k);
}
});
usableQueryKeys = usableQueryKeys.filter(function (element) {
return self.indexes.hasOwnProperty(element);
});
if (usableQueryKeys.length > 0) {
return onNext(null, self.indexes[usableQueryKeys[0]].getMatching(query[usableQueryKeys[0]].$in));
}
// For a comparison match
usableQueryKeys = [];
Object.keys(query).forEach(function (k) {
if (query[k] && (query[k].hasOwnProperty('$lt') || query[k].hasOwnProperty('$lte') || query[k].hasOwnProperty('$\
gt') || query[k].hasOwnProperty('$gte'))) {
usableQueryKeys.push(k);
}
});
usableQueryKeys = usableQueryKeys.filter(function (element) {
return self.indexes.hasOwnProperty(element);
});
if (usableQueryKeys.length > 0) {
return onNext(null, self.indexes[usableQueryKeys[0]].getBetweenBounds(query[usableQueryKeys[0]]));
}
// By default, return all the DB data
return onNext(null, self.getAllData());
// STEP 2: remove all expired documents
default:
if (dontExpireStaleDocs) {
return callback(null, docs);
}
var expiredDocsIds = [],
validDocs = [],
ttlIndexesFieldNames = Object.keys(self.ttlIndexes);
docs.forEach(function (doc) {
var valid = true;
ttlIndexesFieldNames.forEach(function (i) {
if (doc[i] !== undefined && local.isDate(doc[i]) && Date.now() > doc[i].getTime() + self.ttlIndexes[i] * 100\
0) {
valid = false;
}
});
if (valid) {
validDocs.push(doc); ...
...
if (self.execFn) {
return self.execFn(error, res, _callback);
} else {
return _callback(error, res);
}
}
this.db.getCandidates(this.query, function (error, candidates) {
if (error) {
return callback(error);
}
try {
for (i = 0; i < candidates.length; i += 1) {
if (model.match(candidates[i], self.query)) {
...
insert = function () { this.executor.push({ this: this, fn: this._insert, arguments: arguments }); }
...
<a\n\
href="https://kaizhu256.github.io/node-nedb-lite/build/doc.api.html"\n\
target="_blank"\n\
>eval</a>\n\
</label>\n\
<textarea class="onkeyup" id="inputTextarea1">\n\
window.table1 = window.Nedb.dbTableCreate({ name: "table1" });\n\
table1.insert({ field1: "hello", field2: "world"}, function () {\\
n\
console.log();\n\
console.log(table1.export());\n\
});\n\
\n\
window.table2 = window.Nedb.dbTableCreate({ name: "table2" });\n\
table2.insert({ field1: "hello", field2: "world"}, function () {\n\
console.log();\n\
...
load = function (options, onError) { var data, modeNext, onNext, self; self = this; modeNext = 0; onNext = function (error) { modeNext = error ? Infinity : modeNext + 1; switch (modeNext) { case 1: onError = onError || function (error) { // validate no error occurred local.assert(!error, error); }; data = (options.persistenceData || '').trim(); if (options.reset) { data = 'undefined'; } if (!data) { onNext(); return; } self.isLoaded = null; data += '\n'; data = data.slice(data.indexOf('\n') + 1); local.storeSetItem(self.name, data, onNext); break; case 2: if (self.isLoaded) { onNext(); return; } self.isLoaded = true; self.loadDatabase(onNext); break; default: onError(error, self); } }; onNext(options.error); return self; }
...
// binary is always well-balanced
this.indexes = {
_id: new local.Index({ fieldName: '_id', unique: true }),
createdAt: new local.Index({ fieldName: 'createdAt' }),
updatedAt: new local.Index({ fieldName: 'updatedAt' })
};
this.ttlIndexes = {};
this.load(options, onError);
};
local.Nedb = local.local = local;
// init modeJs
local.modeJs = (function () {
try {
return typeof navigator.userAgent === 'string' &&
typeof document.querySelector('body') === 'object' &&
...
loadDatabase = function () { this.executor.push({ this: this.persistence, fn: this.persistence.loadDatabase, arguments: arguments }, true); }
...
break;
case 2:
if (self.isLoaded) {
onNext();
return;
}
self.isLoaded = true;
self.loadDatabase(onNext);
break;
default:
onError(error, self);
}
};
onNext(options.error);
return self;
...
prepareDocumentForInsertion = function (newDoc) { var preparedDoc, self = this; if (Array.isArray(newDoc)) { preparedDoc = []; newDoc.forEach(function (doc) { preparedDoc.push(self.prepareDocumentForInsertion(doc)); }); } else { preparedDoc = model.deepCopy(newDoc); if (preparedDoc._id === undefined) { preparedDoc._id = this.createNewId(); } var now = new Date().toISOString(); if (preparedDoc.createdAt === undefined) { preparedDoc.createdAt = now; } if (preparedDoc.updatedAt === undefined) { preparedDoc.updatedAt = now; } model.checkObject(preparedDoc); } return preparedDoc; }
...
* @api private Use Datastore.insert which has the same signature
*/
Datastore.prototype._insert = function (newDoc, cb) {
var callback = cb,
preparedDoc;
try {
preparedDoc = this.prepareDocumentForInsertion(newDoc)
this._insertInCache(preparedDoc);
} catch (e) {
return callback(e);
}
this.persistence.persistNewState(Array.isArray(preparedDoc) ? preparedDoc : [preparedDoc], function (error) {
if (error) {
...
remove = function () { this.executor.push({ this: this, fn: this._remove, arguments: arguments }); }
...
case 1:
options = local.crudOptionsSetDefault(options, {
id: '00_test_dbTableRemoveOneById'
});
local.testCase_dbTableFindOneById_default(options, options.onNext);
break;
case 2:
options.table.remove({ id: options.id }, options.onNext);
break;
case 3:
options.table.findOne({ id: options.id }, options.onNext);
break;
case 4:
// validate data was removed
local.utility2.assertJsonEqual(data, null);
...
removeFromIndexes = function (doc) { var self = this; Object.keys(this.indexes).forEach(function (i) { self.indexes[i].remove(doc); }); }
...
failingI = i;
break;
}
}
if (error) {
for (i = 0; i < failingI; i += 1) {
this.removeFromIndexes(preparedDocs[i]);
}
throw error;
}
};
Datastore.prototype.insert = function () {
...
removeIndex = function (fieldName, cb) { var callback = cb; delete this.indexes[fieldName]; this.persistence.persistNewState([{ $$indexRemoved: fieldName }], function (error) { if (error) { return callback(error); } return callback(); }); }
n/a
resetIndexes = function (newData) { var self = this; Object.keys(this.indexes).forEach(function (i) { self.indexes[i].reset(newData); }); }
...
* This operation is very quick at startup for a big collection (60ms for ~10k docs)
* @param {Function} cb Optional callback, signature: error
*/
Persistence.prototype.loadDatabase = function(cb) {
var callback = cb,
self = this;
self.db.resetIndexes();
var dir, modeNext, onNext;
modeNext = 0;
onNext = function (error) {
modeNext = error
? Infinity
: modeNext + 1;
...
update = function () { this.executor.push({ this: this, fn: this._update, arguments: arguments }); }
...
* If one update violates a constraint, all changes are rolled back
*/
Datastore.prototype.updateIndexes = function (oldDoc, newDoc) {
var i, failingIndex, error, keys = Object.keys(this.indexes);
for (i = 0; i < keys.length; i += 1) {
try {
this.indexes[keys[i]].update(oldDoc, newDoc);
} catch (e) {
failingIndex = i;
error = e;
break;
}
}
...
updateIndexes = function (oldDoc, newDoc) { var i, failingIndex, error, keys = Object.keys(this.indexes); for (i = 0; i < keys.length; i += 1) { try { this.indexes[keys[i]].update(oldDoc, newDoc); } catch (e) { failingIndex = i; error = e; break; } } // If an error happened, we need to rollback the update on all other indexes if (error) { for (i = 0; i < failingIndex; i += 1) { this.indexes[keys[i]].revertUpdate(oldDoc, newDoc); } throw error; } }
...
}
} catch (error) {
return callback(error);
}
// Change the docs in memory
try {
self.updateIndexes(modifications);
} catch (error) {
return callback(error);
}
// Update the datafile
var updatedDocs = modifications.map(function (element) {
return element.newDoc;
...