(function(e){if("function"==typeof bootstrap)bootstrap("nedb",e);else if("object"==typeof exports)module.exports=e();else if("function"==typeof define&&define.amd)define(e);else if("undefined"!=typeof ses){if(!ses.ok())return;ses.makeNedb=e}else"undefined"!=typeof window?window.Nedb=e():global.Nedb=e()})(function(){var define,ses,bootstrap,module,exports; return (function(e,t,n){function i(n,s){if(!t[n]){if(!e[n]){var o=typeof require=="function"&&require;if(!s&&o)return o(n,!0);if(r)return r(n,!0);throw new Error("Cannot find module '"+n+"'")}var u=t[n]={exports:{}};e[n][0].call(u.exports,function(t){var r=e[n][1][t];return i(r?r:t)},u,u.exports)}return t[n].exports}var r=typeof require=="function"&&require;for(var s=0;s 0 && this._events[type].length > m) { this._events[type].warned = true; console.error('(node) warning: possible EventEmitter memory ' + 'leak detected. %d listeners added. ' + 'Use emitter.setMaxListeners() to increase limit.', this._events[type].length); console.trace(); } } // If we've already got an array, just append. this._events[type].push(listener); } else { // Adding the second element, need to change to array. this._events[type] = [this._events[type], listener]; } return this; }; EventEmitter.prototype.on = EventEmitter.prototype.addListener; EventEmitter.prototype.once = function(type, listener) { var self = this; self.on(type, function g() { self.removeListener(type, g); listener.apply(this, arguments); }); return this; }; EventEmitter.prototype.removeListener = function(type, listener) { if ('function' !== typeof listener) { throw new Error('removeListener only takes instances of Function'); } // does not use listeners(), so no side effect of creating _events[type] if (!this._events || !this._events[type]) return this; var list = this._events[type]; if (isArray(list)) { var i = indexOf(list, listener); if (i < 0) return this; list.splice(i, 1); if (list.length == 0) delete this._events[type]; } else if (this._events[type] === listener) { delete this._events[type]; } return this; }; EventEmitter.prototype.removeAllListeners = function(type) { if (arguments.length === 0) { this._events = {}; return this; } // does not use listeners(), so no side effect of creating _events[type] if (type && this._events && this._events[type]) this._events[type] = null; return this; }; EventEmitter.prototype.listeners = function(type) { if (!this._events) this._events = {}; if (!this._events[type]) this._events[type] = []; if (!isArray(this._events[type])) { this._events[type] = [this._events[type]]; } return this._events[type]; }; EventEmitter.listenerCount = function(emitter, type) { var ret; if (!emitter._events || !emitter._events[type]) ret = 0; else if (typeof emitter._events[type] === 'function') ret = 1; else ret = emitter._events[type].length; return ret; }; },{"__browserify_process":4}],2:[function(require,module,exports){ var process=require("__browserify_process");function filter (xs, fn) { var res = []; for (var i = 0; i < xs.length; i++) { if (fn(xs[i], i, xs)) res.push(xs[i]); } return res; } // resolves . and .. elements in a path array with directory names there // must be no slashes, empty elements, or device names (c:\) in the array // (so also no leading and trailing slashes - it does not distinguish // relative and absolute paths) function normalizeArray(parts, allowAboveRoot) { // if the path tries to go above the root, `up` ends up > 0 var up = 0; for (var i = parts.length; i >= 0; i--) { var last = parts[i]; if (last == '.') { parts.splice(i, 1); } else if (last === '..') { parts.splice(i, 1); up++; } else if (up) { parts.splice(i, 1); up--; } } // if the path is allowed to go above the root, restore leading ..s if (allowAboveRoot) { for (; up--; up) { parts.unshift('..'); } } return parts; } // Regex to split a filename into [*, dir, basename, ext] // posix version var splitPathRe = /^(.+\/(?!$)|\/)?((?:.+?)?(\.[^.]*)?)$/; // path.resolve([from ...], to) // posix version exports.resolve = function() { var resolvedPath = '', resolvedAbsolute = false; for (var i = arguments.length; i >= -1 && !resolvedAbsolute; i--) { var path = (i >= 0) ? arguments[i] : process.cwd(); // Skip empty and invalid entries if (typeof path !== 'string' || !path) { continue; } resolvedPath = path + '/' + resolvedPath; resolvedAbsolute = path.charAt(0) === '/'; } // At this point the path should be resolved to a full absolute path, but // handle relative paths to be safe (might happen when process.cwd() fails) // Normalize the path resolvedPath = normalizeArray(filter(resolvedPath.split('/'), function(p) { return !!p; }), !resolvedAbsolute).join('/'); return ((resolvedAbsolute ? '/' : '') + resolvedPath) || '.'; }; // path.normalize(path) // posix version exports.normalize = function(path) { var isAbsolute = path.charAt(0) === '/', trailingSlash = path.slice(-1) === '/'; // Normalize the path path = normalizeArray(filter(path.split('/'), function(p) { return !!p; }), !isAbsolute).join('/'); if (!path && !isAbsolute) { path = '.'; } if (path && trailingSlash) { path += '/'; } return (isAbsolute ? '/' : '') + path; }; // posix version exports.join = function() { var paths = Array.prototype.slice.call(arguments, 0); return exports.normalize(filter(paths, function(p, index) { return p && typeof p === 'string'; }).join('/')); }; exports.dirname = function(path) { var dir = splitPathRe.exec(path)[1] || ''; var isWindows = false; if (!dir) { // No dirname return '.'; } else if (dir.length === 1 || (isWindows && dir.length <= 3 && dir.charAt(1) === ':')) { // It is just a slash or a drive letter with a slash return dir; } else { // It is a full dirname, strip trailing slash return dir.substring(0, dir.length - 1); } }; exports.basename = function(path, ext) { var f = splitPathRe.exec(path)[2] || ''; // TODO: make this comparison case-insensitive on windows? if (ext && f.substr(-1 * ext.length) === ext) { f = f.substr(0, f.length - ext.length); } return f; }; exports.extname = function(path) { return splitPathRe.exec(path)[3] || ''; }; exports.relative = function(from, to) { from = exports.resolve(from).substr(1); to = exports.resolve(to).substr(1); function trim(arr) { var start = 0; for (; start < arr.length; start++) { if (arr[start] !== '') break; } var end = arr.length - 1; for (; end >= 0; end--) { if (arr[end] !== '') break; } if (start > end) return []; return arr.slice(start, end - start + 1); } var fromParts = trim(from.split('/')); var toParts = trim(to.split('/')); var length = Math.min(fromParts.length, toParts.length); var samePartsLength = length; for (var i = 0; i < length; i++) { if (fromParts[i] !== toParts[i]) { samePartsLength = i; break; } } var outputParts = []; for (var i = samePartsLength; i < fromParts.length; i++) { outputParts.push('..'); } outputParts = outputParts.concat(toParts.slice(samePartsLength)); return outputParts.join('/'); }; exports.sep = '/'; },{"__browserify_process":4}],3:[function(require,module,exports){ var events = require('events'); exports.isArray = isArray; exports.isDate = function(obj){return Object.prototype.toString.call(obj) === '[object Date]'}; exports.isRegExp = function(obj){return Object.prototype.toString.call(obj) === '[object RegExp]'}; exports.print = function () {}; exports.puts = function () {}; exports.debug = function() {}; exports.inspect = function(obj, showHidden, depth, colors) { var seen = []; var stylize = function(str, styleType) { // http://en.wikipedia.org/wiki/ANSI_escape_code#graphics var styles = { 'bold' : [1, 22], 'italic' : [3, 23], 'underline' : [4, 24], 'inverse' : [7, 27], 'white' : [37, 39], 'grey' : [90, 39], 'black' : [30, 39], 'blue' : [34, 39], 'cyan' : [36, 39], 'green' : [32, 39], 'magenta' : [35, 39], 'red' : [31, 39], 'yellow' : [33, 39] }; var style = { 'special': 'cyan', 'number': 'blue', 'boolean': 'yellow', 'undefined': 'grey', 'null': 'bold', 'string': 'green', 'date': 'magenta', // "name": intentionally not styling 'regexp': 'red' }[styleType]; if (style) { return '\u001b[' + styles[style][0] + 'm' + str + '\u001b[' + styles[style][1] + 'm'; } else { return str; } }; if (! colors) { stylize = function(str, styleType) { return str; }; } function format(value, recurseTimes) { // Provide a hook for user-specified inspect functions. // Check that value is an object with an inspect function on it if (value && typeof value.inspect === 'function' && // Filter out the util module, it's inspect function is special value !== exports && // Also filter out any prototype objects using the circular check. !(value.constructor && value.constructor.prototype === value)) { return value.inspect(recurseTimes); } // Primitive types cannot have properties switch (typeof value) { case 'undefined': return stylize('undefined', 'undefined'); case 'string': var simple = '\'' + JSON.stringify(value).replace(/^"|"$/g, '') .replace(/'/g, "\\'") .replace(/\\"/g, '"') + '\''; return stylize(simple, 'string'); case 'number': return stylize('' + value, 'number'); case 'boolean': return stylize('' + value, 'boolean'); } // For some reason typeof null is "object", so special case here. if (value === null) { return stylize('null', 'null'); } // Look up the keys of the object. var visible_keys = Object_keys(value); var keys = showHidden ? Object_getOwnPropertyNames(value) : visible_keys; // Functions without properties can be shortcutted. if (typeof value === 'function' && keys.length === 0) { if (isRegExp(value)) { return stylize('' + value, 'regexp'); } else { var name = value.name ? ': ' + value.name : ''; return stylize('[Function' + name + ']', 'special'); } } // Dates without properties can be shortcutted if (isDate(value) && keys.length === 0) { return stylize(value.toUTCString(), 'date'); } var base, type, braces; // Determine the object type if (isArray(value)) { type = 'Array'; braces = ['[', ']']; } else { type = 'Object'; braces = ['{', '}']; } // Make functions say that they are functions if (typeof value === 'function') { var n = value.name ? ': ' + value.name : ''; base = (isRegExp(value)) ? ' ' + value : ' [Function' + n + ']'; } else { base = ''; } // Make dates with properties first say the date if (isDate(value)) { base = ' ' + value.toUTCString(); } if (keys.length === 0) { return braces[0] + base + braces[1]; } if (recurseTimes < 0) { if (isRegExp(value)) { return stylize('' + value, 'regexp'); } else { return stylize('[Object]', 'special'); } } seen.push(value); var output = keys.map(function(key) { var name, str; if (value.__lookupGetter__) { if (value.__lookupGetter__(key)) { if (value.__lookupSetter__(key)) { str = stylize('[Getter/Setter]', 'special'); } else { str = stylize('[Getter]', 'special'); } } else { if (value.__lookupSetter__(key)) { str = stylize('[Setter]', 'special'); } } } if (visible_keys.indexOf(key) < 0) { name = '[' + key + ']'; } if (!str) { if (seen.indexOf(value[key]) < 0) { if (recurseTimes === null) { str = format(value[key]); } else { str = format(value[key], recurseTimes - 1); } if (str.indexOf('\n') > -1) { if (isArray(value)) { str = str.split('\n').map(function(line) { return ' ' + line; }).join('\n').substr(2); } else { str = '\n' + str.split('\n').map(function(line) { return ' ' + line; }).join('\n'); } } } else { str = stylize('[Circular]', 'special'); } } if (typeof name === 'undefined') { if (type === 'Array' && key.match(/^\d+$/)) { return str; } name = JSON.stringify('' + key); if (name.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)) { name = name.substr(1, name.length - 2); name = stylize(name, 'name'); } else { name = name.replace(/'/g, "\\'") .replace(/\\"/g, '"') .replace(/(^"|"$)/g, "'"); name = stylize(name, 'string'); } } return name + ': ' + str; }); seen.pop(); var numLinesEst = 0; var length = output.reduce(function(prev, cur) { numLinesEst++; if (cur.indexOf('\n') >= 0) numLinesEst++; return prev + cur.length + 1; }, 0); if (length > 50) { output = braces[0] + (base === '' ? '' : base + '\n ') + ' ' + output.join(',\n ') + ' ' + braces[1]; } else { output = braces[0] + base + ' ' + output.join(', ') + ' ' + braces[1]; } return output; } return format(obj, (typeof depth === 'undefined' ? 2 : depth)); }; function isArray(ar) { return Array.isArray(ar) || (typeof ar === 'object' && Object.prototype.toString.call(ar) === '[object Array]'); } function isRegExp(re) { typeof re === 'object' && Object.prototype.toString.call(re) === '[object RegExp]'; } function isDate(d) { return typeof d === 'object' && Object.prototype.toString.call(d) === '[object Date]'; } function pad(n) { return n < 10 ? '0' + n.toString(10) : n.toString(10); } var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; // 26 Feb 16:19:34 function timestamp() { var d = new Date(); var time = [pad(d.getHours()), pad(d.getMinutes()), pad(d.getSeconds())].join(':'); return [d.getDate(), months[d.getMonth()], time].join(' '); } exports.log = function (msg) {}; exports.pump = null; var Object_keys = Object.keys || function (obj) { var res = []; for (var key in obj) res.push(key); return res; }; var Object_getOwnPropertyNames = Object.getOwnPropertyNames || function (obj) { var res = []; for (var key in obj) { if (Object.hasOwnProperty.call(obj, key)) res.push(key); } return res; }; var Object_create = Object.create || function (prototype, properties) { // from es5-shim var object; if (prototype === null) { object = { '__proto__' : null }; } else { if (typeof prototype !== 'object') { throw new TypeError( 'typeof prototype[' + (typeof prototype) + '] != \'object\'' ); } var Type = function () {}; Type.prototype = prototype; object = new Type(); object.__proto__ = prototype; } if (typeof properties !== 'undefined' && Object.defineProperties) { Object.defineProperties(object, properties); } return object; }; exports.inherits = function(ctor, superCtor) { ctor.super_ = superCtor; ctor.prototype = Object_create(superCtor.prototype, { constructor: { value: ctor, enumerable: false, writable: true, configurable: true } }); }; var formatRegExp = /%[sdj%]/g; exports.format = function(f) { if (typeof f !== 'string') { var objects = []; for (var i = 0; i < arguments.length; i++) { objects.push(exports.inspect(arguments[i])); } return objects.join(' '); } var i = 1; var args = arguments; var len = args.length; var str = String(f).replace(formatRegExp, function(x) { if (x === '%%') return '%'; if (i >= len) return x; switch (x) { case '%s': return String(args[i++]); case '%d': return Number(args[i++]); case '%j': return JSON.stringify(args[i++]); default: return x; } }); for(var x = args[i]; i < len; x = args[++i]){ if (x === null || typeof x !== 'object') { str += ' ' + x; } else { str += ' ' + exports.inspect(x); } } return str; }; },{"events":1}],4:[function(require,module,exports){ // shim for using process in browser var process = module.exports = {}; process.nextTick = (function () { var canSetImmediate = typeof window !== 'undefined' && window.setImmediate; var canPost = typeof window !== 'undefined' && window.postMessage && window.addEventListener ; if (canSetImmediate) { return function (f) { return window.setImmediate(f) }; } if (canPost) { var queue = []; window.addEventListener('message', function (ev) { if (ev.source === window && ev.data === 'process-tick') { ev.stopPropagation(); if (queue.length > 0) { var fn = queue.shift(); fn(); } } }, true); return function nextTick(fn) { queue.push(fn); window.postMessage('process-tick', '*'); }; } return function nextTick(fn) { setTimeout(fn, 0); }; })(); process.title = 'browser'; process.browser = true; process.env = {}; process.argv = []; process.binding = function (name) { throw new Error('process.binding is not supported'); } // TODO(shtylman) process.cwd = function () { return '/' }; process.chdir = function (dir) { throw new Error('process.chdir is not supported'); }; },{}],5:[function(require,module,exports){ /** * Manage access to data, be it to find, update or remove it */ var model = require('./model') , _ = require('underscore') ; /** * Create a new cursor for this collection * @param {Datastore} db - The datastore this cursor is bound to * @param {Query} query - The query this cursor will operate on * @param {Function} execDn - Handler to be executed after cursor has found the results and before the callback passed to find/findOne/update/remove */ function Cursor (db, query, execFn) { this.db = db; this.query = query || {}; if (execFn) { this.execFn = execFn; } } /** * Set a limit to the number of results */ Cursor.prototype.limit = function(limit) { this._limit = limit; return this; }; /** * Skip a the number of results */ Cursor.prototype.skip = function(skip) { this._skip = skip; return this; }; /** * Sort results of the query * @param {SortQuery} sortQuery - SortQuery is { field: order }, field can use the dot-notation, order is 1 for ascending and -1 for descending */ Cursor.prototype.sort = function(sortQuery) { this._sort = sortQuery; return this; }; /** * Add the use of a projection * @param {Object} projection - MongoDB-style projection. {} means take all fields. Then it's { key1: 1, key2: 1 } to take only key1 and key2 * { key1: 0, key2: 0 } to omit only key1 and key2. Except _id, you can't mix takes and omits */ Cursor.prototype.projection = function(projection) { this._projection = projection; return this; }; /** * Apply the projection */ Cursor.prototype.project = function (candidates) { var res = [], self = this , keepId, action, keys ; if (this._projection === undefined || Object.keys(this._projection).length === 0) { return candidates; } keepId = this._projection._id === 0 ? false : true; this._projection = _.omit(this._projection, '_id'); // Check for consistency keys = Object.keys(this._projection); keys.forEach(function (k) { if (action !== undefined && self._projection[k] !== action) { throw "Can't both keep and omit fields except for _id"; } action = self._projection[k]; }); // Do the actual projection candidates.forEach(function (candidate) { var toPush = action === 1 ? _.pick(candidate, keys) : _.omit(candidate, keys); if (keepId) { toPush._id = candidate._id; } else { delete toPush._id; } res.push(toPush); }); return res; }; /** * Get all matching elements * Will return pointers to matched elements (shallow copies), returning full copies is the role of find or findOne * This is an internal function, use exec which uses the executor * * @param {Function} callback - Signature: err, results */ Cursor.prototype._exec = function(callback) { var candidates = this.db.getCandidates(this.query) , res = [], added = 0, skipped = 0, self = this , error = null , i, keys, key ; try { for (i = 0; i < candidates.length; i += 1) { if (model.match(candidates[i], this.query)) { // If a sort is defined, wait for the results to be sorted before applying limit and skip if (!this._sort) { if (this._skip && this._skip > skipped) { skipped += 1; } else { res.push(candidates[i]); added += 1; if (this._limit && this._limit <= added) { break; } } } else { res.push(candidates[i]); } } } } catch (err) { return callback(err); } // Apply all sorts if (this._sort) { keys = Object.keys(this._sort); // Sorting var criteria = []; for (i = 0; i < keys.length; i++) { key = keys[i]; criteria.push({ key: key, 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 * model.compareThings(model.getDotValue(a, criterion.key), model.getDotValue(b, criterion.key)); if (compare !== 0) { return compare; } } return 0; }); // Applying limit and skip var limit = this._limit || res.length , skip = this._skip || 0; res = res.slice(skip, skip + limit); } // Apply projection try { res = this.project(res); } catch (e) { error = e; res = undefined; } if (this.execFn) { return this.execFn(error, res, callback); } else { return callback(error, res); } }; Cursor.prototype.exec = function () { this.db.executor.push({ this: this, fn: this._exec, arguments: arguments }); }; // Interface module.exports = Cursor; },{"./model":10,"underscore":18}],6:[function(require,module,exports){ /** * Specific customUtils for the browser, where we don't have access to the Crypto and Buffer modules */ /** * Taken from the crypto-browserify module * https://github.com/dominictarr/crypto-browserify * NOTE: Math.random() does not guarantee "cryptographic quality" but we actually don't need it */ function randomBytes (size) { var bytes = new Array(size); var r; for (var i = 0, r; i < size; i++) { if ((i & 0x03) == 0) r = Math.random() * 0x100000000; bytes[i] = r >>> ((i & 0x03) << 3) & 0xff; } return bytes; } /** * Taken from the base64-js module * https://github.com/beatgammit/base64-js/ */ function byteArrayToBase64 (uint8) { var lookup = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' , extraBytes = uint8.length % 3 // if we have 1 byte left, pad 2 bytes , output = "" , temp, length, i; function tripletToBase64 (num) { return lookup[num >> 18 & 0x3F] + lookup[num >> 12 & 0x3F] + lookup[num >> 6 & 0x3F] + lookup[num & 0x3F]; }; // go through the array every three bytes, we'll deal with trailing stuff later for (i = 0, length = uint8.length - extraBytes; i < length; i += 3) { temp = (uint8[i] << 16) + (uint8[i + 1] << 8) + (uint8[i + 2]); output += tripletToBase64(temp); } // pad the end with zeros, but make sure to not forget the extra bytes switch (extraBytes) { case 1: temp = uint8[uint8.length - 1]; output += lookup[temp >> 2]; output += lookup[(temp << 4) & 0x3F]; output += '=='; break; case 2: temp = (uint8[uint8.length - 2] << 8) + (uint8[uint8.length - 1]); output += lookup[temp >> 10]; output += lookup[(temp >> 4) & 0x3F]; output += lookup[(temp << 2) & 0x3F]; output += '='; break; } return output; } /** * Return a random alphanumerical string of length len * There is a very small probability (less than 1/1,000,000) for the length to be less than len * (il the base64 conversion yields too many pluses and slashes) but * that's not an issue here * The probability of a collision is extremely small (need 3*10^12 documents to have one chance in a million of a collision) * See http://en.wikipedia.org/wiki/Birthday_problem */ function uid (len) { return byteArrayToBase64(randomBytes(Math.ceil(Math.max(8, len * 2)))).replace(/[+\/]/g, '').slice(0, len); } module.exports.uid = uid; },{}],7:[function(require,module,exports){ var customUtils = require('./customUtils') , model = require('./model') , async = require('async') , Executor = require('./executor') , Index = require('./indexes') , util = require('util') , _ = require('underscore') , Persistence = require('./persistence') , Cursor = require('./cursor') ; /** * Create a new collection * @param {String} options.filename Optional, datastore will be in-memory only if not provided * @param {Boolean} options.inMemoryOnly Optional, default to false * @param {Boolean} options.nodeWebkitAppName Optional, specify the name of your NW app if you want options.filename to be relative to the directory where * Node Webkit stores application data such as cookies and local storage (the best place to store data in my opinion) * @param {Boolean} options.autoload Optional, defaults to false * @param {Function} options.onload Optional, if autoload is used this will be called after the load database with the error object as parameter. If you don't pass it the error will be thrown */ function Datastore (options) { var filename; // Retrocompatibility with v0.6 and before if (typeof options === 'string') { filename = options; this.inMemoryOnly = false; // Default } else { options = options || {}; filename = options.filename; this.inMemoryOnly = options.inMemoryOnly || false; this.autoload = options.autoload || false; } // Determine whether in memory or persistent if (!filename || typeof filename !== 'string' || filename.length === 0) { this.filename = null; this.inMemoryOnly = true; } else { this.filename = filename; } // Persistence handling this.persistence = new Persistence({ db: this, nodeWebkitAppName: options.nodeWebkitAppName }); // 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 Executor(); if (this.inMemoryOnly) { this.executor.ready = true; } // 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 = {}; this.indexes._id = new Index({ fieldName: '_id', unique: true }); // Queue a load of the database right away and call the onload handler // By default (no onload handler), if there is an error there, no operation will be possible so warn the user by throwing an exception if (this.autoload) { this.loadDatabase(options.onload || function (err) { if (err) { throw err; } }); } } /** * Load the database from the datafile, and trigger the execution of buffered commands if any */ Datastore.prototype.loadDatabase = function () { this.executor.push({ this: this.persistence, fn: this.persistence.loadDatabase, arguments: arguments }, true); }; /** * Get an array of all the data in the database */ Datastore.prototype.getAllData = function () { return this.indexes._id.getAll(); }; /** * Reset all currently defined indexes */ Datastore.prototype.resetIndexes = function (newData) { var self = this; Object.keys(this.indexes).forEach(function (i) { self.indexes[i].reset(newData); }); }; /** * 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 {Function} cb Optional callback, signature: err */ Datastore.prototype.ensureIndex = function (options, cb) { var callback = cb || function () {}; options = options || {}; if (!options.fieldName) { return callback({ missingFieldName: true }); } if (this.indexes[options.fieldName]) { return callback(null); } this.indexes[options.fieldName] = new Index(options); try { this.indexes[options.fieldName].insert(this.getAllData()); } catch (e) { delete this.indexes[options.fieldName]; return callback(e); } this.persistence.persistNewState([{ $$indexCreated: options }], function (err) { if (err) { return callback(err); } return callback(null); }); }; /** * Remove an index * @param {String} fieldName * @param {Function} cb Optional callback, signature: err */ Datastore.prototype.removeIndex = function (fieldName, cb) { var callback = cb || function () {}; delete this.indexes[fieldName]; this.persistence.persistNewState([{ $$indexRemoved: fieldName }], function (err) { if (err) { return callback(err); } return callback(null); }); }; /** * Add one or several document(s) to all indexes */ Datastore.prototype.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; } }; /** * Remove one or several document(s) from all indexes */ Datastore.prototype.removeFromIndexes = function (doc) { var self = this; Object.keys(this.indexes).forEach(function (i) { self.indexes[i].remove(doc); }); }; /** * Update one or several documents in all indexes * To update multiple documents, oldDoc must be an array of { oldDoc, newDoc } pairs * 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; } } // 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; } }; /** * 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. * * TODO: needs to be moved to the Cursor module */ Datastore.prototype.getCandidates = function (query) { var indexNames = Object.keys(this.indexes) , usableQueryKeys; // For a basic match usableQueryKeys = []; Object.keys(query).forEach(function (k) { if (typeof query[k] === 'string' || typeof query[k] === 'number' || typeof query[k] === 'boolean' || util.isDate(query[k]) || query[k] === null) { usableQueryKeys.push(k); } }); usableQueryKeys = _.intersection(usableQueryKeys, indexNames); if (usableQueryKeys.length > 0) { return this.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 = _.intersection(usableQueryKeys, indexNames); if (usableQueryKeys.length > 0) { return this.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 = _.intersection(usableQueryKeys, indexNames); if (usableQueryKeys.length > 0) { return this.indexes[usableQueryKeys[0]].getBetweenBounds(query[usableQueryKeys[0]]); } // By default, return all the DB data return this.getAllData(); }; /** * Insert a new document * @param {Function} cb Optional callback, signature: err, insertedDoc * * @api private Use Datastore.insert which has the same signature */ Datastore.prototype._insert = function (newDoc, cb) { var callback = cb || function () {} ; try { this._insertInCache(newDoc); } catch (e) { return callback(e); } this.persistence.persistNewState(util.isArray(newDoc) ? newDoc : [newDoc], function (err) { if (err) { return callback(err); } return callback(null, newDoc); }); }; /** * 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 * @api private */ Datastore.prototype.prepareDocumentForInsertion = function (newDoc) { var preparedDoc, self = this; if (util.isArray(newDoc)) { preparedDoc = []; newDoc.forEach(function (doc) { preparedDoc.push(self.prepareDocumentForInsertion(doc)); }); } else { newDoc._id = newDoc._id || this.createNewId(); preparedDoc = model.deepCopy(newDoc); model.checkObject(preparedDoc); } return preparedDoc; }; /** * If newDoc is an array of documents, this will insert all documents in the cache * @api private */ Datastore.prototype._insertInCache = function (newDoc) { if (util.isArray(newDoc)) { this._insertMultipleDocsInCache(newDoc); } else { this.addToIndexes(this.prepareDocumentForInsertion(newDoc)); } }; /** * If one insertion fails (e.g. because of a unique constraint), roll back all previous * inserts and throws the error * @api private */ Datastore.prototype._insertMultipleDocsInCache = function (newDocs) { var i, failingI, error , preparedDocs = this.prepareDocumentForInsertion(newDocs) ; for (i = 0; i < preparedDocs.length; i += 1) { try { this.addToIndexes(preparedDocs[i]); } catch (e) { error = e; failingI = i; break; } } if (error) { for (i = 0; i < failingI; i += 1) { this.removeFromIndexes(preparedDocs[i]); } throw error; } }; Datastore.prototype.insert = function () { this.executor.push({ this: this, fn: this._insert, arguments: arguments }); }; /** * Count all documents matching the query * @param {Object} query MongoDB-style query */ Datastore.prototype.count = function(query, callback) { var cursor = new Cursor(this, query, function(err, docs, callback) { if (err) { return callback(err); } return callback(null, docs.length); }); if (typeof callback === 'function') { cursor.exec(callback); } else { return cursor; } }; /** * Find all documents matching the query * If no callback is passed, we return the cursor so that user can limit, skip and finally exec * @param {Object} query MongoDB-style query * @param {Object} projection MongoDB-style projection */ Datastore.prototype.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(err, docs, callback) { var res = [], i; if (err) { return callback(err); } 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; } }; /** * Find one document matching the query * @param {Object} query MongoDB-style query * @param {Object} projection MongoDB-style projection */ Datastore.prototype.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(err, docs, callback) { if (err) { return callback(err); } 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; } }; /** * Update all docs matching query * For now, very naive implementation (recalculating the whole database) * @param {Object} query * @param {Object} updateQuery * @param {Object} options Optional options * options.multi If true, can update multiple documents (defaults to false) * options.upsert If true, document is inserted if the query doesn't match anything * @param {Function} cb Optional callback, signature: err, numReplaced, upsert (set to true if the update was in fact an upsert) * * @api private Use Datastore.update which has the same signature */ Datastore.prototype._update = function (query, updateQuery, options, cb) { var callback , self = this , numReplaced = 0 , multi, upsert , i ; if (typeof options === 'function') { cb = options; options = {}; } callback = cb || function () {}; multi = options.multi !== undefined ? options.multi : false; upsert = options.upsert !== undefined ? options.upsert : false; async.waterfall([ function (cb) { // If upsert option is set, check whether we need to insert the doc if (!upsert) { return cb(); } // Need to use an internal function not tied to the executor to avoid deadlock var cursor = new Cursor(self, query); cursor.limit(1)._exec(function (err, docs) { if (err) { return callback(err); } if (docs.length === 1) { return cb(); } else { return self._insert(model.modify(query, updateQuery), function (err, newDoc) { if (err) { return callback(err); } return callback(null, 1, newDoc); }); } }); } , function () { // Perform the update var modifiedDoc , candidates = self.getCandidates(query) , modifications = [] ; // Preparing update (if an error is thrown here neither the datafile nor // the in-memory indexes are affected) try { for (i = 0; i < candidates.length; i += 1) { if (model.match(candidates[i], query) && (multi || numReplaced === 0)) { numReplaced += 1; modifiedDoc = model.modify(candidates[i], updateQuery); modifications.push({ oldDoc: candidates[i], newDoc: modifiedDoc }); } } } catch (err) { return callback(err); } // Change the docs in memory try { self.updateIndexes(modifications); } catch (err) { return callback(err); } // Update the datafile self.persistence.persistNewState(_.pluck(modifications, 'newDoc'), function (err) { if (err) { return callback(err); } return callback(null, numReplaced); }); } ]); }; Datastore.prototype.update = function () { this.executor.push({ this: this, fn: this._update, arguments: arguments }); }; /** * Remove all docs matching the query * For now very naive implementation (similar to update) * @param {Object} query * @param {Object} options Optional options * options.multi If true, can update multiple documents (defaults to false) * @param {Function} cb Optional callback, signature: err, numRemoved * * @api private Use Datastore.remove which has the same signature */ Datastore.prototype._remove = function (query, options, cb) { var callback , self = this , numRemoved = 0 , multi , removedDocs = [] , candidates = this.getCandidates(query) ; if (typeof options === 'function') { cb = options; options = {}; } callback = cb || function () {}; multi = options.multi !== undefined ? options.multi : false; try { candidates.forEach(function (d) { if (model.match(d, query) && (multi || numRemoved === 0)) { numRemoved += 1; removedDocs.push({ $$deleted: true, _id: d._id }); self.removeFromIndexes(d); } }); } catch (err) { return callback(err); } self.persistence.persistNewState(removedDocs, function (err) { if (err) { return callback(err); } return callback(null, numRemoved); }); }; Datastore.prototype.remove = function () { this.executor.push({ this: this, fn: this._remove, arguments: arguments }); }; module.exports = Datastore; },{"./cursor":5,"./customUtils":6,"./executor":8,"./indexes":9,"./model":10,"./persistence":11,"async":13,"underscore":18,"util":3}],8:[function(require,module,exports){ var process=require("__browserify_process");/** * Responsible for sequentially executing actions on the database */ var async = require('async') ; function Executor () { this.buffer = []; this.ready = false; // This queue will execute all commands, one-by-one in order this.queue = async.queue(function (task, cb) { var callback , lastArg = task.arguments[task.arguments.length - 1] , i, 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 (i = 0; i < task.arguments.length; i += 1) { newArguments.push(task.arguments[i]); } // Always tell the queue task is complete. Execute callback if any was given. if (typeof lastArg === 'function') { callback = function () { if (typeof setImmediate === 'function') { setImmediate(cb); } else { process.nextTick(cb); } lastArg.apply(null, arguments); }; newArguments[newArguments.length - 1] = callback; } else { callback = function () { cb(); }; newArguments.push(callback); } task.fn.apply(task.this, newArguments); }, 1); } /** * If executor is ready, queue task (and process it immediately if executor was idle) * If not, buffer task for later processing * @param {Object} task * task.this - Object to use as this * task.fn - Function to execute * task.arguments - Array of arguments * @param {Boolean} forceQueuing Optional (defaults to false) force executor to queue task even if it is not ready */ Executor.prototype.push = function (task, forceQueuing) { if (this.ready || forceQueuing) { this.queue.push(task); } else { this.buffer.push(task); } }; /** * Queue all tasks in buffer (in the same order they came in) * Automatically sets executor as ready */ Executor.prototype.processBuffer = function () { var i; this.ready = true; for (i = 0; i < this.buffer.length; i += 1) { this.queue.push(this.buffer[i]); } this.buffer = []; }; // Interface module.exports = Executor; },{"__browserify_process":4,"async":13}],9:[function(require,module,exports){ var BinarySearchTree = require('binary-search-tree').AVLTree , model = require('./model') , _ = require('underscore') , util = require('util') ; /** * Two indexed pointers are equal iif they point to the same place */ function checkValueEquality (a, b) { return a === b; } /** * Type-aware projection */ function projectForUnique (elt) { if (elt === null) { return '$null'; } if (typeof elt === 'string') { return '$string' + elt; } if (typeof elt === 'boolean') { return '$boolean' + elt; } if (typeof elt === 'number') { return '$number' + elt; } if (util.isArray(elt)) { return '$date' + elt.getTime(); } return elt; // Arrays and objects, will check for pointer equality } /** * Create a new index * All methods on an index guarantee that either the whole operation was successful and the index changed * or the operation was unsuccessful and an error is thrown while the index is unchanged * @param {String} options.fieldName On which field should the index apply (can use dot notation to index on sub fields) * @param {Boolean} options.unique Optional, enforce a unique constraint (default: false) * @param {Boolean} options.sparse Optional, allow a sparse index (we can have documents for which fieldName is undefined) (default: false) */ function Index (options) { this.fieldName = options.fieldName; this.unique = options.unique || false; this.sparse = options.sparse || false; this.treeOptions = { unique: this.unique, compareKeys: model.compareThings, checkValueEquality: checkValueEquality }; this.reset(); // No data in the beginning } /** * Reset an index * @param {Document or Array of documents} newData Optional, data to initialize the index with * If an error is thrown during insertion, the index is not modified */ Index.prototype.reset = function (newData) { this.tree = new BinarySearchTree(this.treeOptions); if (newData) { this.insert(newData); } }; /** * Insert a new document in the index * If an array is passed, we insert all its elements (if one insertion fails the index is not modified) * O(log(n)) */ Index.prototype.insert = function (doc) { var key, self = this , keys, i, failingI, error ; if (util.isArray(doc)) { this.insertMultipleDocs(doc); return; } key = model.getDotValue(doc, this.fieldName); // We don't index documents that don't contain the field if the index is sparse if (key === undefined && this.sparse) { return; } if (!util.isArray(key)) { this.tree.insert(key, doc); } else { // If an insert fails due to a unique constraint, roll back all inserts before it keys = _.uniq(key, projectForUnique); for (i = 0; i < keys.length; i += 1) { try { this.tree.insert(keys[i], doc); } catch (e) { error = e; failingI = i; break; } } if (error) { for (i = 0; i < failingI; i += 1) { this.tree.delete(keys[i], doc); } throw error; } } }; /** * Insert an array of documents in the index * If a constraint is violated, the changes should be rolled back and an error thrown * * @API private */ Index.prototype.insertMultipleDocs = function (docs) { var i, error, failingI; for (i = 0; i < docs.length; i += 1) { try { this.insert(docs[i]); } catch (e) { error = e; failingI = i; break; } } if (error) { for (i = 0; i < failingI; i += 1) { this.remove(docs[i]); } throw error; } }; /** * Remove a document from the index * If an array is passed, we remove all its elements * The remove operation is safe with regards to the 'unique' constraint * O(log(n)) */ Index.prototype.remove = function (doc) { var key, self = this; if (util.isArray(doc)) { doc.forEach(function (d) { self.remove(d); }); return; } key = model.getDotValue(doc, this.fieldName); if (key === undefined && this.sparse) { return; } if (!util.isArray(key)) { this.tree.delete(key, doc); } else { _.uniq(key, projectForUnique).forEach(function (_key) { self.tree.delete(_key, doc); }); } }; /** * Update a document in the index * If a constraint is violated, changes are rolled back and an error thrown * Naive implementation, still in O(log(n)) */ Index.prototype.update = function (oldDoc, newDoc) { if (util.isArray(oldDoc)) { this.updateMultipleDocs(oldDoc); return; } this.remove(oldDoc); try { this.insert(newDoc); } catch (e) { this.insert(oldDoc); throw e; } }; /** * Update multiple documents in the index * If a constraint is violated, the changes need to be rolled back * and an error thrown * @param {Array of oldDoc, newDoc pairs} pairs * * @API private */ Index.prototype.updateMultipleDocs = function (pairs) { var i, failingI, error; for (i = 0; i < pairs.length; i += 1) { this.remove(pairs[i].oldDoc); } for (i = 0; i < pairs.length; i += 1) { try { this.insert(pairs[i].newDoc); } catch (e) { error = e; failingI = i; break; } } // If an error was raised, roll back changes in the inverse order if (error) { for (i = 0; i < failingI; i += 1) { this.remove(pairs[i].newDoc); } for (i = 0; i < pairs.length; i += 1) { this.insert(pairs[i].oldDoc); } throw error; } }; /** * Revert an update */ Index.prototype.revertUpdate = function (oldDoc, newDoc) { var revert = []; if (!util.isArray(oldDoc)) { this.update(newDoc, oldDoc); } else { oldDoc.forEach(function (pair) { revert.push({ oldDoc: pair.newDoc, newDoc: pair.oldDoc }); }); this.update(revert); } }; // Append all elements in toAppend to array function append (array, toAppend) { var i; for (i = 0; i < toAppend.length; i += 1) { array.push(toAppend[i]); } } /** * Get all documents in index whose key match value (if it is a Thing) or one of the elements of value (if it is an array of Things) * @param {Thing} value Value to match the key against * @return {Array of documents} */ Index.prototype.getMatching = function (value) { var res, self = this; if (!util.isArray(value)) { return this.tree.search(value); } else { res = []; value.forEach(function (v) { append(res, self.getMatching(v)); }); return res; } }; /** * Get all documents in index whose key is between bounds are they are defined by query * Documents are sorted by key * @param {Query} query * @return {Array of documents} */ Index.prototype.getBetweenBounds = function (query) { return this.tree.betweenBounds(query); }; /** * Get all elements in the index * @return {Array of documents} */ Index.prototype.getAll = function () { var res = []; this.tree.executeOnEveryNode(function (node) { var i; for (i = 0; i < node.data.length; i += 1) { res.push(node.data[i]); } }); return res; }; // Interface module.exports = Index; },{"./model":10,"binary-search-tree":14,"underscore":18,"util":3}],10:[function(require,module,exports){ /** * Handle models (i.e. docs) * Serialization/deserialization * Copying * Querying, update */ var util = require('util') , _ = require('underscore') , modifierFunctions = {} , lastStepModifierFunctions = {} , comparisonFunctions = {} , logicalOperators = {} , arrayComparisonFunctions = {} ; /** * Check a key, throw an error if the key is non valid * @param {String} k key * @param {Model} v value, needed to treat the Date edge case * Non-treatable edge cases here: if part of the object if of the form { $$date: number } or { $$deleted: true } * Its serialized-then-deserialized version it will transformed into a Date object * But you really need to want it to trigger such behaviour, even when warned not to use '$' at the beginning of the field names... */ function checkKey (k, v) { if (k[0] === '$' && !(k === '$$date' && typeof v === 'number') && !(k === '$$deleted' && v === true) && !(k === '$$indexCreated') && !(k === '$$indexRemoved')) { throw 'Field names cannot begin with the $ character'; } if (k.indexOf('.') !== -1) { throw 'Field names cannot contain a .'; } } /** * Check a DB object and throw an error if it's not valid * Works by applying the above checkKey function to all fields recursively */ function checkObject (obj) { if (util.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]); }); } } /** * Serialize an object to be persisted to a one-line string * For serialization/deserialization, we use the native JSON parser and not eval or Function * That gives us less freedom but data entered in the database may come from users * so eval and the like are not safe * Accepted primitive types: Number, String, Boolean, Date, null * Accepted secondary types: Objects, Arrays */ 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; } /** * From a one-line representation of an object generate by the serialize function * Return the object itself */ function deserialize (rawData) { return JSON.parse(rawData, function (k, v) { if (k === '$$date') { return new Date(v); } if (typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean' || v === null) { return v; } if (v && v.$$date) { return v.$$date; } return v; }); } /** * Deep copy a DB object */ function deepCopy (obj) { var res; if ( typeof obj === 'boolean' || typeof obj === 'number' || typeof obj === 'string' || obj === null || (util.isDate(obj)) ) { return obj; } if (util.isArray(obj)) { res = []; obj.forEach(function (o) { res.push(o); }); return res; } if (typeof obj === 'object') { res = {}; Object.keys(obj).forEach(function (k) { res[k] = deepCopy(obj[k]); }); return res; } return undefined; // For now everything else is undefined. We should probably throw an error instead } /** * Tells if an object is a primitive type or a "real" object * Arrays are considered primitive */ function isPrimitiveType (obj) { return ( typeof obj === 'boolean' || typeof obj === 'number' || typeof obj === 'string' || obj === null || util.isDate(obj) || util.isArray(obj)); } /** * Utility functions for comparing things * Assumes type checking was already done (a and b already have the same type) * compareNSB works for numbers, strings and booleans */ function compareNSB (a, b) { if (a < b) { return -1; } if (a > b) { return 1; } return 0; } function compareArrays (a, b) { var i, comp; for (i = 0; i < Math.min(a.length, b.length); i += 1) { comp = compareThings(a[i], b[i]); if (comp !== 0) { return comp; } } // Common section was identical, longest one wins return compareNSB(a.length, b.length); } /** * Compare { things U undefined } * Things are defined as any native types (string, number, boolean, null, date) and objects * We need to compare with undefined as it will be used in indexes * In the case of objects and arrays, we deep-compare * If two objects dont have the same type, the (arbitrary) type hierarchy is: undefined, null, number, strings, boolean, dates, arrays, objects * Return -1 if a < b, 1 if a > b and 0 if a = b (note that equality here is NOT the same as defined in areThingsEqual!) */ function compareThings (a, b) { var aKeys, bKeys, comp, i; // undefined if (a === undefined) { return b === undefined ? 0 : -1; } if (b === undefined) { return a === undefined ? 0 : 1; } // null if (a === null) { return b === null ? 0 : -1; } if (b === null) { return a === null ? 0 : 1; } // Numbers if (typeof a === 'number') { return typeof b === 'number' ? compareNSB(a, b) : -1; } if (typeof b === 'number') { return typeof a === 'number' ? compareNSB(a, b) : 1; } // Strings if (typeof a === 'string') { return typeof b === 'string' ? compareNSB(a, b) : -1; } if (typeof b === 'string') { return typeof a === 'string' ? compareNSB(a, b) : 1; } // Booleans if (typeof a === 'boolean') { return typeof b === 'boolean' ? compareNSB(a, b) : -1; } if (typeof b === 'boolean') { return typeof a === 'boolean' ? compareNSB(a, b) : 1; } // Dates if (util.isDate(a)) { return util.isDate(b) ? compareNSB(a.getTime(), b.getTime()) : -1; } if (util.isDate(b)) { return util.isDate(a) ? compareNSB(a.getTime(), b.getTime()) : 1; } // Arrays (first element is most significant and so on) if (util.isArray(a)) { return util.isArray(b) ? compareArrays(a, b) : -1; } if (util.isArray(b)) { return util.isArray(a) ? compareArrays(a, b) : 1; } // Objects aKeys = Object.keys(a).sort(); bKeys = Object.keys(b).sort(); for (i = 0; i < Math.min(aKeys.length, bKeys.length); i += 1) { comp = compareThings(a[aKeys[i]], b[bKeys[i]]); if (comp !== 0) { return comp; } } return compareNSB(aKeys.length, bKeys.length); } // ============================================================== // Updating documents // ============================================================== /** * The signature of modifier functions is as follows * Their structure is always the same: recursively follow the dot notation while creating * the nested documents if needed, then apply the "last step modifier" * @param {Object} obj The model to modify * @param {String} field Can contain dots, in that case that means we will set a subfield recursively * @param {Model} value */ /** * Set a field to a new value */ lastStepModifierFunctions.$set = function (obj, field, value) { obj[field] = value; }; /** * Unset a field */ lastStepModifierFunctions.$unset = function (obj, field, value) { delete obj[field]; }; /** * Push an element to the end of an array field */ lastStepModifierFunctions.$push = function (obj, field, value) { // Create the array if it doesn't exist if (!obj.hasOwnProperty(field)) { obj[field] = []; } if (!util.isArray(obj[field])) { throw "Can't $push an element on non-array values"; } if (value !== null && typeof value === 'object' && value.$each) { if (Object.keys(value).length > 1) { throw "Can't use another field in conjunction with $each"; } if (!util.isArray(value.$each)) { throw "$each requires an array value"; } value.$each.forEach(function (v) { obj[field].push(v); }); } else { obj[field].push(value); } }; /** * Add an element to an array field only if it is not already in it * No modification if the element is already in the array * Note that it doesn't check whether the original array contains duplicates */ lastStepModifierFunctions.$addToSet = function (obj, field, value) { var addToSet = true; // Create the array if it doesn't exist if (!obj.hasOwnProperty(field)) { obj[field] = []; } if (!util.isArray(obj[field])) { throw "Can't $addToSet an element on non-array values"; } if (value !== null && typeof value === 'object' && value.$each) { if (Object.keys(value).length > 1) { throw "Can't use another field in conjunction with $each"; } if (!util.isArray(value.$each)) { throw "$each requires an array value"; } value.$each.forEach(function (v) { lastStepModifierFunctions.$addToSet(obj, field, v); }); } else { obj[field].forEach(function (v) { if (compareThings(v, value) === 0) { addToSet = false; } }); if (addToSet) { obj[field].push(value); } } }; /** * Remove the first or last element of an array */ lastStepModifierFunctions.$pop = function (obj, field, value) { if (!util.isArray(obj[field])) { throw "Can't $pop an element from non-array values"; } if (typeof value !== 'number') { throw value + " isn't an integer, can't use it with $pop"; } if (value === 0) { return; } if (value > 0) { obj[field] = obj[field].slice(0, obj[field].length - 1); } else { obj[field] = obj[field].slice(1); } }; /** * Removes all instances of a value from an existing array */ lastStepModifierFunctions.$pull = function (obj, field, value) { var arr, i; if (!util.isArray(obj[field])) { throw "Can't $pull an element from non-array values"; } arr = obj[field]; for (i = arr.length - 1; i >= 0; i -= 1) { if (match(arr[i], value)) { arr.splice(i, 1); } } }; /** * Increment a numeric field's value */ lastStepModifierFunctions.$inc = function (obj, field, value) { if (typeof value !== 'number') { throw value + " must be a number"; } if (typeof obj[field] !== 'number') { if (!_.has(obj, field)) { obj[field] = value; } else { throw "Don't use the $inc modifier on non-number fields"; } } else { obj[field] += value; } }; // Given its name, create the complete modifier function function createModifierFunction (modifier) { return function (obj, field, value) { var fieldParts = typeof field === 'string' ? field.split('.') : field; if (fieldParts.length === 1) { lastStepModifierFunctions[modifier](obj, field, value); } else { obj[fieldParts[0]] = obj[fieldParts[0]] || {}; modifierFunctions[modifier](obj[fieldParts[0]], fieldParts.slice(1), value); } }; } // Actually create all modifier functions Object.keys(lastStepModifierFunctions).forEach(function (modifier) { modifierFunctions[modifier] = createModifierFunction(modifier); }); /** * Modify a DB object according to an update query * For now the updateQuery only replaces the object */ function modify (obj, updateQuery) { var keys = Object.keys(updateQuery) , firstChars = _.map(keys, function (item) { return item[0]; }) , dollarFirstChars = _.filter(firstChars, function (c) { return c === '$'; }) , newDoc, modifiers ; if (keys.indexOf('_id') !== -1 && updateQuery._id !== obj._id) { throw "You cannot change a document's _id"; } if (dollarFirstChars.length !== 0 && dollarFirstChars.length !== firstChars.length) { throw "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 = _.uniq(keys); newDoc = deepCopy(obj); modifiers.forEach(function (m) { var keys; if (!modifierFunctions[m]) { throw "Unknown modifier " + m; } try { keys = Object.keys(updateQuery[m]); } catch (e) { throw "Modifier " + m + "'s argument must be an object"; } 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 "You can't change a document's _id"; } return newDoc; }; // ============================================================== // Finding documents // ============================================================== /** * Get a value from object with dot notation * @param {Object} obj * @param {String} field */ 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 (util.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)); } } /** * Check whether 'things' are equal * Things are defined as any native types (string, number, boolean, null, date) and objects * In the case of object, we check deep equality * Returns true if they are, false otherwise */ 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 (util.isDate(a) || util.isDate(b)) { return util.isDate(a) && util.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 (util.isArray(a) || util.isArray(b) || a === undefined || b === undefined) { 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; } /** * Check that two values are comparable */ function areComparable (a, b) { if (typeof a !== 'string' && typeof a !== 'number' && !util.isDate(a) && typeof b !== 'string' && typeof b !== 'number' && !util.isDate(b)) { return false; } if (typeof a !== typeof b) { return false; } return true; } /** * Arithmetic and comparison operators * @param {Native value} a Value in the object * @param {Native value} b Value in the query */ comparisonFunctions.$lt = function (a, b) { return areComparable(a, b) && a < b; }; comparisonFunctions.$lte = function (a, b) { return areComparable(a, b) && a <= b; }; comparisonFunctions.$gt = function (a, b) { return areComparable(a, b) && a > b; }; comparisonFunctions.$gte = function (a, b) { return areComparable(a, b) && a >= b; }; comparisonFunctions.$ne = function (a, b) { if (!a) { return true; } return !areThingsEqual(a, b); }; comparisonFunctions.$in = function (a, b) { var i; if (!util.isArray(b)) { throw "$in operator called with a non-array"; } for (i = 0; i < b.length; i += 1) { if (areThingsEqual(a, b[i])) { return true; } } return false; }; comparisonFunctions.$nin = function (a, b) { if (!util.isArray(b)) { throw "$nin operator called with a non-array"; } return !comparisonFunctions.$in(a, b); }; comparisonFunctions.$regex = function (a, b) { if (!util.isRegExp(b)) { throw "$regex operator called with non regular expression"; } if (typeof a !== 'string') { return false } else { return b.test(a); } }; comparisonFunctions.$exists = function (value, exists) { if (exists || exists === '') { // This will be true for all values of exists except false, null, undefined and 0 exists = true; // That's strange behaviour (we should only use true/false) but that's the way Mongo does it... } else { exists = false; } if (value === undefined) { return !exists } else { return exists; } }; // Specific to arrays comparisonFunctions.$size = function (obj, value) { if (!util.isArray(obj)) { return false; } if (value % 1 !== 0) { throw "$size operator called without an integer"; } return (obj.length == value); }; arrayComparisonFunctions.$size = true; /** * Match any of the subqueries * @param {Model} obj * @param {Array of Queries} query */ logicalOperators.$or = function (obj, query) { var i; if (!util.isArray(query)) { throw "$or operator used without an array"; } for (i = 0; i < query.length; i += 1) { if (match(obj, query[i])) { return true; } } return false; }; /** * Match all of the subqueries * @param {Model} obj * @param {Array of Queries} query */ logicalOperators.$and = function (obj, query) { var i; if (!util.isArray(query)) { throw "$and operator used without an array"; } for (i = 0; i < query.length; i += 1) { if (!match(obj, query[i])) { return false; } } return true; }; /** * Inverted match of the query * @param {Model} obj * @param {Query} query */ logicalOperators.$not = function (obj, query) { return !match(obj, query); }; /** * Use a function to match * @param {Model} obj * @param {Query} query */ logicalOperators.$where = function (obj, fn) { var result; if (!_.isFunction(fn)) { throw "$where operator used without a function"; } result = fn.call(obj); if (!_.isBoolean(result)) { throw "$where function must return boolean"; } return result; }; /** * Tell if a given document matches a query * @param {Object} obj Document to check * @param {Object} query */ 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 "Unknown logical operator " + queryKey; } if (!logicalOperators[queryKey](obj, queryValue)) { return false; } } else { if (!matchQueryPart(obj, queryKey, queryValue)) { return false; } } } return true; }; /** * Match an object against a specific { key: value } part of a query * if the treatObjAsValue flag is set, don't try to match every part separately, but the array as a whole */ function matchQueryPart (obj, queryKey, queryValue, treatObjAsValue) { var objValue = getDotValue(obj, queryKey) , i, keys, firstChars, dollarFirstChars; // Check if the value is an array if we don't force a treatment as value if (util.isArray(objValue) && !treatObjAsValue) { // Check if we are using an array-specific comparison function if (queryValue !== null && typeof queryValue === 'object' && !util.isRegExp(queryValue)) { keys = Object.keys(queryValue); for (i = 0; i < keys.length; i += 1) { if (arrayComparisonFunctions[keys[i]]) { return matchQueryPart(obj, queryKey, queryValue, true); } } } // If not, treat it as an array of { obj, query } where there needs to be at least one match for (i = 0; i < objValue.length; i += 1) { if (matchQueryPart({ k: objValue[i] }, 'k', queryValue)) { return true; } // k here could be any string } return false; } // queryValue is an actual object. Determine whether it contains comparison operators // or only normal fields. Mixed objects are not allowed if (queryValue !== null && typeof queryValue === 'object' && !util.isRegExp(queryValue)) { keys = Object.keys(queryValue); firstChars = _.map(keys, function (item) { return item[0]; }); dollarFirstChars = _.filter(firstChars, function (c) { return c === '$'; }); if (dollarFirstChars.length !== 0 && dollarFirstChars.length !== firstChars.length) { throw "You cannot mix operators and normal fields"; } // queryValue is an object of this form: { $comparisonOperator1: value1, ... } if (dollarFirstChars.length > 0) { for (i = 0; i < keys.length; i += 1) { if (!comparisonFunctions[keys[i]]) { throw "Unknown comparison function " + keys[i]; } if (!comparisonFunctions[keys[i]](objValue, queryValue[keys[i]])) { return false; } } return true; } } // Using regular expressions with basic querying if (util.isRegExp(queryValue)) { return comparisonFunctions.$regex(objValue, queryValue); } // queryValue is either a native value or a normal object // Basic matching is possible if (!areThingsEqual(objValue, queryValue)) { return false; } return true; } // Interface module.exports.serialize = serialize; module.exports.deserialize = deserialize; module.exports.deepCopy = deepCopy; module.exports.checkObject = checkObject; module.exports.isPrimitiveType = isPrimitiveType; module.exports.modify = modify; module.exports.getDotValue = getDotValue; module.exports.match = match; module.exports.areThingsEqual = areThingsEqual; module.exports.compareThings = compareThings; },{"underscore":18,"util":3}],11:[function(require,module,exports){ var process=require("__browserify_process");/** * Handle every persistence-related task * The interface Datastore expects to be implemented is * * Persistence.loadDatabase(callback) and callback has signature err * * Persistence.persistNewState(newDocs, callback) where newDocs is an array of documents and callback has signature err */ var storage = require('./storage') , path = require('path') , model = require('./model') , async = require('async') , customUtils = require('./customUtils') , Index = require('./indexes') ; /** * Create a new Persistence object for database options.db * @param {Datastore} options.db * @param {Boolean} options.nodeWebkitAppName Optional, specify the name of your NW app if you want options.filename to be relative to the directory where * Node Webkit stores application data such as cookies and local storage (the best place to store data in my opinion) */ function Persistence (options) { this.db = options.db; this.inMemoryOnly = this.db.inMemoryOnly; this.filename = this.db.filename; if (!this.inMemoryOnly && this.filename) { if (this.filename.charAt(this.filename.length - 1) === '~') { throw "The datafile name can't end with a ~, which is reserved for automatic backup files"; } else { this.tempFilename = this.filename + '~'; this.oldFilename = this.filename + '~~'; } } // For NW apps, store data in the same directory where NW stores application data if (this.filename && options.nodeWebkitAppName) { console.log("=================================================================="); console.log("WARNING: The nodeWebkitAppName option is deprecated"); console.log("To get the path to the directory where Node Webkit stores the data"); console.log("for your app, use the internal nw.gui module like this"); console.log("require('nw.gui').App.dataPath"); console.log("See https://github.com/rogerwang/node-webkit/issues/500"); console.log("=================================================================="); this.filename = Persistence.getNWAppFilename(options.nodeWebkitAppName, this.filename); this.tempFilename = Persistence.getNWAppFilename(options.nodeWebkitAppName, this.tempFilename); this.oldFilename = Persistence.getNWAppFilename(options.nodeWebkitAppName, this.oldFilename); } }; /** * Check if a directory exists and create it on the fly if it is not the case * cb is optional, signature: err */ Persistence.ensureDirectoryExists = function (dir, cb) { var callback = cb || function () {} ; storage.mkdirp(dir, function (err) { return callback(err); }); }; Persistence.ensureFileDoesntExist = function (file, callback) { storage.exists(file, function (exists) { if (!exists) { return callback(null); } storage.unlink(file, function (err) { return callback(err); }); }); }; /** * Return the path the datafile if the given filename is relative to the directory where Node Webkit stores * data for this application. Probably the best place to store data */ Persistence.getNWAppFilename = function (appName, relativeFilename) { var home; switch (process.platform) { case 'win32': case 'win64': home = process.env.LOCALAPPDATA || process.env.APPDATA; if (!home) { throw "Couldn't find the base application data folder"; } home = path.join(home, appName); break; case 'darwin': home = process.env.HOME; if (!home) { throw "Couldn't find the base application data directory"; } home = path.join(home, 'Library', 'Application Support', appName); break; case 'linux': home = process.env.HOME; if (!home) { throw "Couldn't find the base application data directory"; } home = path.join(home, '.config', appName); break; default: throw "Can't use the Node Webkit relative path for platform " + process.platform; break; } return path.join(home, 'nedb-data', relativeFilename); } /** * Persist cached database * This serves as a compaction function since the cache always contains only the number of documents in the collection * while the data file is append-only so it may grow larger * @param {Function} cb Optional callback, signature: err */ Persistence.prototype.persistCachedDatabase = function (cb) { var callback = cb || function () {} , toPersist = '' , self = this ; if (this.inMemoryOnly) { return callback(null); } 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'; } }); async.waterfall([ async.apply(Persistence.ensureFileDoesntExist, self.tempFilename) , async.apply(Persistence.ensureFileDoesntExist, self.oldFilename) , function (cb) { storage.exists(self.filename, function (exists) { if (exists) { storage.rename(self.filename, self.oldFilename, function (err) { return cb(err); }); } else { return cb(); } }); } , function (cb) { storage.writeFile(self.tempFilename, toPersist, function (err) { return cb(err); }); } , function (cb) { storage.rename(self.tempFilename, self.filename, function (err) { return cb(err); }); } , async.apply(Persistence.ensureFileDoesntExist, self.oldFilename) ], function (err) { if (err) { return callback(err); } else { return callback(null); } }) }; /** * Queue a rewrite of the datafile */ Persistence.prototype.compactDatafile = function () { this.db.executor.push({ this: this, fn: this.persistCachedDatabase, arguments: [] }); }; /** * Set automatic compaction every interval ms * @param {Number} interval in milliseconds, with an enforced minimum of 5 seconds */ Persistence.prototype.setAutocompactionInterval = function (interval) { var self = this , minInterval = 5000 , realInterval = Math.max(interval || 0, minInterval) ; this.stopAutocompaction(); this.autocompactionIntervalId = setInterval(function () { self.compactDatafile(); }, realInterval); }; /** * Stop autocompaction (do nothing if autocompaction was not running) */ Persistence.prototype.stopAutocompaction = function () { if (this.autocompactionIntervalId) { clearInterval(this.autocompactionIntervalId); } }; /** * 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: err */ Persistence.prototype.persistNewState = function (newDocs, cb) { var self = this , toPersist = '' , callback = cb || function () {} ; // In-memory only datastore if (self.inMemoryOnly) { return callback(null); } newDocs.forEach(function (doc) { toPersist += model.serialize(doc) + '\n'; }); if (toPersist.length === 0) { return callback(null); } storage.appendFile(self.filename, toPersist, 'utf8', function (err) { return callback(err); }); }; /** * From a database's raw data, return the corresponding * machine understandable collection */ Persistence.treatRawData = function (rawData) { var data = rawData.split('\n') , dataById = {} , tdata = [] , i , indexes = {} ; 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 (e) { } } Object.keys(dataById).forEach(function (k) { tdata.push(dataById[k]); }); return { data: tdata, indexes: indexes }; }; /** * Ensure that this.filename contains the most up-to-date version of the data * Even if a loadDatabase crashed before */ Persistence.prototype.ensureDatafileIntegrity = function (callback) { var self = this ; storage.exists(self.filename, function (filenameExists) { // Write was successful if (filenameExists) { return callback(null); } storage.exists(self.oldFilename, function (oldFilenameExists) { // New database if (!oldFilenameExists) { return storage.writeFile(self.filename, '', 'utf8', function (err) { callback(err); }); } // Write failed, use old version storage.rename(self.oldFilename, self.filename, function (err) { return callback(err); }); }); }); }; /** * Load the database * 1) Create all indexes * 2) Insert all data * 3) Compact the database * This means pulling data out of the data file or creating it if it doesn't exist * Also, all data is persisted right away, which has the effect of compacting the database file * This operation is very quick at startup for a big collection (60ms for ~10k docs) * @param {Function} cb Optional callback, signature: err */ Persistence.prototype.loadDatabase = function (cb) { var callback = cb || function () {} , self = this ; self.db.resetIndexes(); // In-memory only datastore if (self.inMemoryOnly) { return callback(null); } async.waterfall([ function (cb) { Persistence.ensureDirectoryExists(path.dirname(self.filename), function (err) { self.ensureDatafileIntegrity(function (exists) { storage.readFile(self.filename, 'utf8', function (err, rawData) { if (err) { return cb(err); } var treatedData = Persistence.treatRawData(rawData); // 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 cb(e); } self.db.persistence.persistCachedDatabase(cb); }); }); }); } ], function (err) { if (err) { return callback(err); } self.db.executor.processBuffer(); return callback(null); }); }; // Interface module.exports = Persistence; },{"./customUtils":6,"./indexes":9,"./model":10,"./storage":12,"__browserify_process":4,"async":13,"path":2}],12:[function(require,module,exports){ /** * Way data is stored for this database * For a Node.js/Node Webkit database it's the file system * For a browser-side database it's localStorage when supported * * This version is the Node.js/Node Webkit version */ function exists (filename, callback) { // In this specific case this always answers that the file doesn't exist if (typeof localStorage === 'undefined') { console.log("WARNING - This browser doesn't support localStorage, no data will be saved in NeDB!"); return callback(); } if (localStorage.getItem(filename) !== null) { return callback(true); } else { return callback(false); } } function rename (filename, newFilename, callback) { if (typeof localStorage === 'undefined') { console.log("WARNING - This browser doesn't support localStorage, no data will be saved in NeDB!"); return callback(); } if (localStorage.getItem(filename) === null) { localStorage.removeItem(newFilename); } else { localStorage.setItem(newFilename, localStorage.getItem(filename)); localStorage.removeItem(filename); } return callback(); } function writeFile (filename, contents, options, callback) { if (typeof localStorage === 'undefined') { console.log("WARNING - This browser doesn't support localStorage, no data will be saved in NeDB!"); return callback(); } // Options do not matter in browser setup if (typeof options === 'function') { callback = options; } localStorage.setItem(filename, contents); return callback(); } function appendFile (filename, toAppend, options, callback) { if (typeof localStorage === 'undefined') { console.log("WARNING - This browser doesn't support localStorage, no data will be saved in NeDB!"); return callback(); } // Options do not matter in browser setup if (typeof options === 'function') { callback = options; } var contents = localStorage.getItem(filename) || ''; contents += toAppend; localStorage.setItem(filename, contents); return callback(); } function readFile (filename, options, callback) { if (typeof localStorage === 'undefined') { console.log("WARNING - This browser doesn't support localStorage, no data will be saved in NeDB!"); return callback(); } // Options do not matter in browser setup if (typeof options === 'function') { callback = options; } var contents = localStorage.getItem(filename) || ''; return callback(null, contents); } function unlink (filename, callback) { if (typeof localStorage === 'undefined') { console.log("WARNING - This browser doesn't support localStorage, no data will be saved in NeDB!"); return callback(); } localStorage.removeItem(filename); return callback(); } // Nothing done, no directories will be used on the browser function mkdirp (dir, callback) { return callback(); } // Interface module.exports.exists = exists; module.exports.rename = rename; module.exports.writeFile = writeFile; module.exports.appendFile = appendFile; module.exports.readFile = readFile; module.exports.unlink = unlink; module.exports.mkdirp = mkdirp; },{}],13:[function(require,module,exports){ var process=require("__browserify_process");/*global setImmediate: false, setTimeout: false, console: false */ (function () { var async = {}; // global on the server, window in the browser var root, previous_async; root = this; if (root != null) { previous_async = root.async; } async.noConflict = function () { root.async = previous_async; return async; }; function only_once(fn) { var called = false; return function() { if (called) throw new Error("Callback was already called."); called = true; fn.apply(root, arguments); } } //// cross-browser compatiblity functions //// var _each = function (arr, iterator) { if (arr.forEach) { return arr.forEach(iterator); } for (var i = 0; i < arr.length; i += 1) { iterator(arr[i], i, arr); } }; var _map = function (arr, iterator) { if (arr.map) { return arr.map(iterator); } var results = []; _each(arr, function (x, i, a) { results.push(iterator(x, i, a)); }); return results; }; var _reduce = function (arr, iterator, memo) { if (arr.reduce) { return arr.reduce(iterator, memo); } _each(arr, function (x, i, a) { memo = iterator(memo, x, i, a); }); return memo; }; var _keys = function (obj) { if (Object.keys) { return Object.keys(obj); } var keys = []; for (var k in obj) { if (obj.hasOwnProperty(k)) { keys.push(k); } } return keys; }; //// exported async module functions //// //// nextTick implementation with browser-compatible fallback //// if (typeof process === 'undefined' || !(process.nextTick)) { if (typeof setImmediate === 'function') { async.nextTick = function (fn) { // not a direct alias for IE10 compatibility setImmediate(fn); }; async.setImmediate = async.nextTick; } else { async.nextTick = function (fn) { setTimeout(fn, 0); }; async.setImmediate = async.nextTick; } } else { async.nextTick = process.nextTick; if (typeof setImmediate !== 'undefined') { async.setImmediate = function (fn) { // not a direct alias for IE10 compatibility setImmediate(fn); }; } else { async.setImmediate = async.nextTick; } } async.each = function (arr, iterator, callback) { callback = callback || function () {}; if (!arr.length) { return callback(); } var completed = 0; _each(arr, function (x) { iterator(x, only_once(function (err) { if (err) { callback(err); callback = function () {}; } else { completed += 1; if (completed >= arr.length) { callback(null); } } })); }); }; async.forEach = async.each; async.eachSeries = function (arr, iterator, callback) { callback = callback || function () {}; if (!arr.length) { return callback(); } var completed = 0; var iterate = function () { iterator(arr[completed], function (err) { if (err) { callback(err); callback = function () {}; } else { completed += 1; if (completed >= arr.length) { callback(null); } else { iterate(); } } }); }; iterate(); }; async.forEachSeries = async.eachSeries; async.eachLimit = function (arr, limit, iterator, callback) { var fn = _eachLimit(limit); fn.apply(null, [arr, iterator, callback]); }; async.forEachLimit = async.eachLimit; var _eachLimit = function (limit) { return function (arr, iterator, callback) { callback = callback || function () {}; if (!arr.length || limit <= 0) { return callback(); } var completed = 0; var started = 0; var running = 0; (function replenish () { if (completed >= arr.length) { return callback(); } while (running < limit && started < arr.length) { started += 1; running += 1; iterator(arr[started - 1], function (err) { if (err) { callback(err); callback = function () {}; } else { completed += 1; running -= 1; if (completed >= arr.length) { callback(); } else { replenish(); } } }); } })(); }; }; var doParallel = function (fn) { return function () { var args = Array.prototype.slice.call(arguments); return fn.apply(null, [async.each].concat(args)); }; }; var doParallelLimit = function(limit, fn) { return function () { var args = Array.prototype.slice.call(arguments); return fn.apply(null, [_eachLimit(limit)].concat(args)); }; }; var doSeries = function (fn) { return function () { var args = Array.prototype.slice.call(arguments); return fn.apply(null, [async.eachSeries].concat(args)); }; }; var _asyncMap = function (eachfn, arr, iterator, callback) { var results = []; arr = _map(arr, function (x, i) { return {index: i, value: x}; }); eachfn(arr, function (x, callback) { iterator(x.value, function (err, v) { results[x.index] = v; callback(err); }); }, function (err) { callback(err, results); }); }; async.map = doParallel(_asyncMap); async.mapSeries = doSeries(_asyncMap); async.mapLimit = function (arr, limit, iterator, callback) { return _mapLimit(limit)(arr, iterator, callback); }; var _mapLimit = function(limit) { return doParallelLimit(limit, _asyncMap); }; // reduce only has a series version, as doing reduce in parallel won't // work in many situations. async.reduce = function (arr, memo, iterator, callback) { async.eachSeries(arr, function (x, callback) { iterator(memo, x, function (err, v) { memo = v; callback(err); }); }, function (err) { callback(err, memo); }); }; // inject alias async.inject = async.reduce; // foldl alias async.foldl = async.reduce; async.reduceRight = function (arr, memo, iterator, callback) { var reversed = _map(arr, function (x) { return x; }).reverse(); async.reduce(reversed, memo, iterator, callback); }; // foldr alias async.foldr = async.reduceRight; var _filter = function (eachfn, arr, iterator, callback) { var results = []; arr = _map(arr, function (x, i) { return {index: i, value: x}; }); eachfn(arr, function (x, callback) { iterator(x.value, function (v) { if (v) { results.push(x); } callback(); }); }, function (err) { callback(_map(results.sort(function (a, b) { return a.index - b.index; }), function (x) { return x.value; })); }); }; async.filter = doParallel(_filter); async.filterSeries = doSeries(_filter); // select alias async.select = async.filter; async.selectSeries = async.filterSeries; var _reject = function (eachfn, arr, iterator, callback) { var results = []; arr = _map(arr, function (x, i) { return {index: i, value: x}; }); eachfn(arr, function (x, callback) { iterator(x.value, function (v) { if (!v) { results.push(x); } callback(); }); }, function (err) { callback(_map(results.sort(function (a, b) { return a.index - b.index; }), function (x) { return x.value; })); }); }; async.reject = doParallel(_reject); async.rejectSeries = doSeries(_reject); var _detect = function (eachfn, arr, iterator, main_callback) { eachfn(arr, function (x, callback) { iterator(x, function (result) { if (result) { main_callback(x); main_callback = function () {}; } else { callback(); } }); }, function (err) { main_callback(); }); }; async.detect = doParallel(_detect); async.detectSeries = doSeries(_detect); async.some = function (arr, iterator, main_callback) { async.each(arr, function (x, callback) { iterator(x, function (v) { if (v) { main_callback(true); main_callback = function () {}; } callback(); }); }, function (err) { main_callback(false); }); }; // any alias async.any = async.some; async.every = function (arr, iterator, main_callback) { async.each(arr, function (x, callback) { iterator(x, function (v) { if (!v) { main_callback(false); main_callback = function () {}; } callback(); }); }, function (err) { main_callback(true); }); }; // all alias async.all = async.every; async.sortBy = function (arr, iterator, callback) { async.map(arr, function (x, callback) { iterator(x, function (err, criteria) { if (err) { callback(err); } else { callback(null, {value: x, criteria: criteria}); } }); }, function (err, results) { if (err) { return callback(err); } else { var fn = function (left, right) { var a = left.criteria, b = right.criteria; return a < b ? -1 : a > b ? 1 : 0; }; callback(null, _map(results.sort(fn), function (x) { return x.value; })); } }); }; async.auto = function (tasks, callback) { callback = callback || function () {}; var keys = _keys(tasks); if (!keys.length) { return callback(null); } var results = {}; var listeners = []; var addListener = function (fn) { listeners.unshift(fn); }; var removeListener = function (fn) { for (var i = 0; i < listeners.length; i += 1) { if (listeners[i] === fn) { listeners.splice(i, 1); return; } } }; var taskComplete = function () { _each(listeners.slice(0), function (fn) { fn(); }); }; addListener(function () { if (_keys(results).length === keys.length) { callback(null, results); callback = function () {}; } }); _each(keys, function (k) { var task = (tasks[k] instanceof Function) ? [tasks[k]]: tasks[k]; var taskCallback = function (err) { var args = Array.prototype.slice.call(arguments, 1); if (args.length <= 1) { args = args[0]; } if (err) { var safeResults = {}; _each(_keys(results), function(rkey) { safeResults[rkey] = results[rkey]; }); safeResults[k] = args; callback(err, safeResults); // stop subsequent errors hitting callback multiple times callback = function () {}; } else { results[k] = args; async.setImmediate(taskComplete); } }; var requires = task.slice(0, Math.abs(task.length - 1)) || []; var ready = function () { return _reduce(requires, function (a, x) { return (a && results.hasOwnProperty(x)); }, true) && !results.hasOwnProperty(k); }; if (ready()) { task[task.length - 1](taskCallback, results); } else { var listener = function () { if (ready()) { removeListener(listener); task[task.length - 1](taskCallback, results); } }; addListener(listener); } }); }; async.waterfall = function (tasks, callback) { callback = callback || function () {}; if (tasks.constructor !== Array) { var err = new Error('First argument to waterfall must be an array of functions'); return callback(err); } if (!tasks.length) { return callback(); } var wrapIterator = function (iterator) { return function (err) { if (err) { callback.apply(null, arguments); callback = function () {}; } else { var args = Array.prototype.slice.call(arguments, 1); var next = iterator.next(); if (next) { args.push(wrapIterator(next)); } else { args.push(callback); } async.setImmediate(function () { iterator.apply(null, args); }); } }; }; wrapIterator(async.iterator(tasks))(); }; var _parallel = function(eachfn, tasks, callback) { callback = callback || function () {}; if (tasks.constructor === Array) { eachfn.map(tasks, function (fn, callback) { if (fn) { fn(function (err) { var args = Array.prototype.slice.call(arguments, 1); if (args.length <= 1) { args = args[0]; } callback.call(null, err, args); }); } }, callback); } else { var results = {}; eachfn.each(_keys(tasks), function (k, callback) { tasks[k](function (err) { var args = Array.prototype.slice.call(arguments, 1); if (args.length <= 1) { args = args[0]; } results[k] = args; callback(err); }); }, function (err) { callback(err, results); }); } }; async.parallel = function (tasks, callback) { _parallel({ map: async.map, each: async.each }, tasks, callback); }; async.parallelLimit = function(tasks, limit, callback) { _parallel({ map: _mapLimit(limit), each: _eachLimit(limit) }, tasks, callback); }; async.series = function (tasks, callback) { callback = callback || function () {}; if (tasks.constructor === Array) { async.mapSeries(tasks, function (fn, callback) { if (fn) { fn(function (err) { var args = Array.prototype.slice.call(arguments, 1); if (args.length <= 1) { args = args[0]; } callback.call(null, err, args); }); } }, callback); } else { var results = {}; async.eachSeries(_keys(tasks), function (k, callback) { tasks[k](function (err) { var args = Array.prototype.slice.call(arguments, 1); if (args.length <= 1) { args = args[0]; } results[k] = args; callback(err); }); }, function (err) { callback(err, results); }); } }; async.iterator = function (tasks) { var makeCallback = function (index) { var fn = function () { if (tasks.length) { tasks[index].apply(null, arguments); } return fn.next(); }; fn.next = function () { return (index < tasks.length - 1) ? makeCallback(index + 1): null; }; return fn; }; return makeCallback(0); }; async.apply = function (fn) { var args = Array.prototype.slice.call(arguments, 1); return function () { return fn.apply( null, args.concat(Array.prototype.slice.call(arguments)) ); }; }; var _concat = function (eachfn, arr, fn, callback) { var r = []; eachfn(arr, function (x, cb) { fn(x, function (err, y) { r = r.concat(y || []); cb(err); }); }, function (err) { callback(err, r); }); }; async.concat = doParallel(_concat); async.concatSeries = doSeries(_concat); async.whilst = function (test, iterator, callback) { if (test()) { iterator(function (err) { if (err) { return callback(err); } async.whilst(test, iterator, callback); }); } else { callback(); } }; async.doWhilst = function (iterator, test, callback) { iterator(function (err) { if (err) { return callback(err); } if (test()) { async.doWhilst(iterator, test, callback); } else { callback(); } }); }; async.until = function (test, iterator, callback) { if (!test()) { iterator(function (err) { if (err) { return callback(err); } async.until(test, iterator, callback); }); } else { callback(); } }; async.doUntil = function (iterator, test, callback) { iterator(function (err) { if (err) { return callback(err); } if (!test()) { async.doUntil(iterator, test, callback); } else { callback(); } }); }; async.queue = function (worker, concurrency) { if (concurrency === undefined) { concurrency = 1; } function _insert(q, data, pos, callback) { if(data.constructor !== Array) { data = [data]; } _each(data, function(task) { var item = { data: task, callback: typeof callback === 'function' ? callback : null }; if (pos) { q.tasks.unshift(item); } else { q.tasks.push(item); } if (q.saturated && q.tasks.length === concurrency) { q.saturated(); } async.setImmediate(q.process); }); } var workers = 0; var q = { tasks: [], concurrency: concurrency, saturated: null, empty: null, drain: null, push: function (data, callback) { _insert(q, data, false, callback); }, unshift: function (data, callback) { _insert(q, data, true, callback); }, process: function () { if (workers < q.concurrency && q.tasks.length) { var task = q.tasks.shift(); if (q.empty && q.tasks.length === 0) { q.empty(); } workers += 1; var next = function () { workers -= 1; if (task.callback) { task.callback.apply(task, arguments); } if (q.drain && q.tasks.length + workers === 0) { q.drain(); } q.process(); }; var cb = only_once(next); worker(task.data, cb); } }, length: function () { return q.tasks.length; }, running: function () { return workers; } }; return q; }; async.cargo = function (worker, payload) { var working = false, tasks = []; var cargo = { tasks: tasks, payload: payload, saturated: null, empty: null, drain: null, push: function (data, callback) { if(data.constructor !== Array) { data = [data]; } _each(data, function(task) { tasks.push({ data: task, callback: typeof callback === 'function' ? callback : null }); if (cargo.saturated && tasks.length === payload) { cargo.saturated(); } }); async.setImmediate(cargo.process); }, process: function process() { if (working) return; if (tasks.length === 0) { if(cargo.drain) cargo.drain(); return; } var ts = typeof payload === 'number' ? tasks.splice(0, payload) : tasks.splice(0); var ds = _map(ts, function (task) { return task.data; }); if(cargo.empty) cargo.empty(); working = true; worker(ds, function () { working = false; var args = arguments; _each(ts, function (data) { if (data.callback) { data.callback.apply(null, args); } }); process(); }); }, length: function () { return tasks.length; }, running: function () { return working; } }; return cargo; }; var _console_fn = function (name) { return function (fn) { var args = Array.prototype.slice.call(arguments, 1); fn.apply(null, args.concat([function (err) { var args = Array.prototype.slice.call(arguments, 1); if (typeof console !== 'undefined') { if (err) { if (console.error) { console.error(err); } } else if (console[name]) { _each(args, function (x) { console[name](x); }); } } }])); }; }; async.log = _console_fn('log'); async.dir = _console_fn('dir'); /*async.info = _console_fn('info'); async.warn = _console_fn('warn'); async.error = _console_fn('error');*/ async.memoize = function (fn, hasher) { var memo = {}; var queues = {}; hasher = hasher || function (x) { return x; }; var memoized = function () { var args = Array.prototype.slice.call(arguments); var callback = args.pop(); var key = hasher.apply(null, args); if (key in memo) { callback.apply(null, memo[key]); } else if (key in queues) { queues[key].push(callback); } else { queues[key] = [callback]; fn.apply(null, args.concat([function () { memo[key] = arguments; var q = queues[key]; delete queues[key]; for (var i = 0, l = q.length; i < l; i++) { q[i].apply(null, arguments); } }])); } }; memoized.memo = memo; memoized.unmemoized = fn; return memoized; }; async.unmemoize = function (fn) { return function () { return (fn.unmemoized || fn).apply(null, arguments); }; }; async.times = function (count, iterator, callback) { var counter = []; for (var i = 0; i < count; i++) { counter.push(i); } return async.map(counter, iterator, callback); }; async.timesSeries = function (count, iterator, callback) { var counter = []; for (var i = 0; i < count; i++) { counter.push(i); } return async.mapSeries(counter, iterator, callback); }; async.compose = function (/* functions... */) { var fns = Array.prototype.reverse.call(arguments); return function () { var that = this; var args = Array.prototype.slice.call(arguments); var callback = args.pop(); async.reduce(fns, args, function (newargs, fn, cb) { fn.apply(that, newargs.concat([function () { var err = arguments[0]; var nextargs = Array.prototype.slice.call(arguments, 1); cb(err, nextargs); }])) }, function (err, results) { callback.apply(that, [err].concat(results)); }); }; }; var _applyEach = function (eachfn, fns /*args...*/) { var go = function () { var that = this; var args = Array.prototype.slice.call(arguments); var callback = args.pop(); return eachfn(fns, function (fn, cb) { fn.apply(that, args.concat([cb])); }, callback); }; if (arguments.length > 2) { var args = Array.prototype.slice.call(arguments, 2); return go.apply(this, args); } else { return go; } }; async.applyEach = doParallel(_applyEach); async.applyEachSeries = doSeries(_applyEach); async.forever = function (fn, callback) { function next(err) { if (err) { if (callback) { return callback(err); } throw err; } fn(next); } next(); }; // AMD / RequireJS if (typeof define !== 'undefined' && define.amd) { define([], function () { return async; }); } // Node.js else if (typeof module !== 'undefined' && module.exports) { module.exports = async; } // included directly via