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;
...