From 4f853dcfdf4ef2b1968ae1aab3429966dc22cc41 Mon Sep 17 00:00:00 2001 From: Alinson Xavier Date: Tue, 30 Sep 2014 09:37:33 -0400 Subject: [PATCH] Remember articles and citations. --- src/app/config.js | 64 +- src/app/crawler.js | 233 +- src/app/main.js | 25 +- src/app/scopus.js | 24 +- src/app/views/main_menu.js | 26 +- src/app/views/show_map.js | 48 +- src/css/main.css | 7 +- src/node_modules/nedb/.npmignore | 22 + src/node_modules/nedb/LICENSE | 22 + src/node_modules/nedb/README.md | 639 ++ .../nedb/benchmarks/commonUtilities.js | 305 + .../nedb/benchmarks/ensureIndex.js | 51 + src/node_modules/nedb/benchmarks/find.js | 30 + src/node_modules/nedb/benchmarks/findOne.js | 31 + .../nedb/benchmarks/findWithIn.js | 30 + src/node_modules/nedb/benchmarks/insert.js | 33 + .../nedb/benchmarks/loadDatabase.js | 38 + src/node_modules/nedb/benchmarks/remove.js | 39 + src/node_modules/nedb/benchmarks/update.js | 39 + .../browser-specific/lib/customUtils.js | 78 + .../browser-specific/lib/storage.js | 96 + .../nedb/browser-version/build.js | 101 + .../nedb/browser-version/out/nedb.js | 6446 +++++++++++++++++ .../nedb/browser-version/out/nedb.min.js | 3 + .../nedb/browser-version/package.json | 8 + .../nedb/browser-version/test/index.html | 23 + .../nedb/browser-version/test/mocha.css | 199 + .../nedb/browser-version/test/mocha.js | 4859 +++++++++++++ .../nedb/browser-version/test/nedb-browser.js | 298 + .../browser-version/test/testPersistence.html | 13 + .../browser-version/test/testPersistence.js | 18 + .../test/testPersistence2.html | 13 + .../browser-version/test/testPersistence2.js | 27 + src/node_modules/nedb/index.js | 3 + src/node_modules/nedb/lib/cursor.js | 185 + src/node_modules/nedb/lib/customUtils.js | 23 + src/node_modules/nedb/lib/datastore.js | 594 ++ src/node_modules/nedb/lib/executor.js | 77 + src/node_modules/nedb/lib/indexes.js | 294 + src/node_modules/nedb/lib/model.js | 757 ++ src/node_modules/nedb/lib/persistence.js | 335 + src/node_modules/nedb/lib/storage.js | 15 + .../nedb/node_modules/async/LICENSE | 19 + .../nedb/node_modules/async/README.md | 1425 ++++ .../nedb/node_modules/async/component.json | 11 + .../nedb/node_modules/async/lib/async.js | 958 +++ .../nedb/node_modules/async/package.json | 46 + .../binary-search-tree/.npmignore | 16 + .../node_modules/binary-search-tree/Makefile | 7 + .../node_modules/binary-search-tree/README.md | 123 + .../node_modules/binary-search-tree/index.js | 2 + .../binary-search-tree/lib/avltree.js | 455 ++ .../binary-search-tree/lib/bst.js | 543 ++ .../binary-search-tree/lib/customUtils.js | 38 + .../binary-search-tree/package.json | 43 + .../binary-search-tree/test/avltree.test.js | 1083 +++ .../binary-search-tree/test/bst.test.js | 1043 +++ .../nedb/node_modules/mkdirp/.npmignore | 2 + .../nedb/node_modules/mkdirp/.travis.yml | 5 + .../nedb/node_modules/mkdirp/LICENSE | 21 + .../nedb/node_modules/mkdirp/examples/pow.js | 6 + .../nedb/node_modules/mkdirp/index.js | 82 + .../nedb/node_modules/mkdirp/package.json | 37 + .../nedb/node_modules/mkdirp/readme.markdown | 63 + .../nedb/node_modules/mkdirp/test/chmod.js | 38 + .../nedb/node_modules/mkdirp/test/clobber.js | 37 + .../nedb/node_modules/mkdirp/test/mkdirp.js | 28 + .../nedb/node_modules/mkdirp/test/perm.js | 32 + .../node_modules/mkdirp/test/perm_sync.js | 39 + .../nedb/node_modules/mkdirp/test/race.js | 41 + .../nedb/node_modules/mkdirp/test/rel.js | 32 + .../nedb/node_modules/mkdirp/test/return.js | 25 + .../node_modules/mkdirp/test/return_sync.js | 24 + .../nedb/node_modules/mkdirp/test/root.js | 18 + .../nedb/node_modules/mkdirp/test/sync.js | 32 + .../nedb/node_modules/mkdirp/test/umask.js | 28 + .../node_modules/mkdirp/test/umask_sync.js | 32 + .../nedb/node_modules/underscore/.npmignore | 4 + .../nedb/node_modules/underscore/.travis.yml | 5 + .../nedb/node_modules/underscore/CNAME | 1 + .../node_modules/underscore/CONTRIBUTING.md | 9 + .../nedb/node_modules/underscore/LICENSE | 22 + .../nedb/node_modules/underscore/README.md | 19 + .../nedb/node_modules/underscore/favicon.ico | Bin 0 -> 1406 bytes .../nedb/node_modules/underscore/index.html | 2467 +++++++ .../nedb/node_modules/underscore/index.js | 1 + .../nedb/node_modules/underscore/package.json | 39 + .../node_modules/underscore/underscore-min.js | 1 + .../node_modules/underscore/underscore.js | 1226 ++++ src/node_modules/nedb/package.json | 54 + src/node_modules/nedb/test/.db.test.js.swo | Bin 0 -> 118784 bytes src/node_modules/nedb/test/cursor.test.js | 786 ++ src/node_modules/nedb/test/customUtil.test.js | 26 + src/node_modules/nedb/test/db.test.js | 2419 +++++++ src/node_modules/nedb/test/executor.test.js | 163 + src/node_modules/nedb/test/indexes.test.js | 768 ++ src/node_modules/nedb/test/mocha.opts | 2 + src/node_modules/nedb/test/model.test.js | 1276 ++++ .../nedb/test/persistence.test.js | 645 ++ .../nedb/test_lac/loadAndCrash.test.js | 5 + src/package.json | 2 +- 101 files changed, 32395 insertions(+), 150 deletions(-) create mode 100644 src/node_modules/nedb/.npmignore create mode 100644 src/node_modules/nedb/LICENSE create mode 100644 src/node_modules/nedb/README.md create mode 100644 src/node_modules/nedb/benchmarks/commonUtilities.js create mode 100644 src/node_modules/nedb/benchmarks/ensureIndex.js create mode 100644 src/node_modules/nedb/benchmarks/find.js create mode 100644 src/node_modules/nedb/benchmarks/findOne.js create mode 100644 src/node_modules/nedb/benchmarks/findWithIn.js create mode 100644 src/node_modules/nedb/benchmarks/insert.js create mode 100644 src/node_modules/nedb/benchmarks/loadDatabase.js create mode 100644 src/node_modules/nedb/benchmarks/remove.js create mode 100644 src/node_modules/nedb/benchmarks/update.js create mode 100644 src/node_modules/nedb/browser-version/browser-specific/lib/customUtils.js create mode 100644 src/node_modules/nedb/browser-version/browser-specific/lib/storage.js create mode 100644 src/node_modules/nedb/browser-version/build.js create mode 100644 src/node_modules/nedb/browser-version/out/nedb.js create mode 100644 src/node_modules/nedb/browser-version/out/nedb.min.js create mode 100644 src/node_modules/nedb/browser-version/package.json create mode 100644 src/node_modules/nedb/browser-version/test/index.html create mode 100644 src/node_modules/nedb/browser-version/test/mocha.css create mode 100644 src/node_modules/nedb/browser-version/test/mocha.js create mode 100644 src/node_modules/nedb/browser-version/test/nedb-browser.js create mode 100644 src/node_modules/nedb/browser-version/test/testPersistence.html create mode 100644 src/node_modules/nedb/browser-version/test/testPersistence.js create mode 100644 src/node_modules/nedb/browser-version/test/testPersistence2.html create mode 100644 src/node_modules/nedb/browser-version/test/testPersistence2.js create mode 100644 src/node_modules/nedb/index.js create mode 100644 src/node_modules/nedb/lib/cursor.js create mode 100644 src/node_modules/nedb/lib/customUtils.js create mode 100644 src/node_modules/nedb/lib/datastore.js create mode 100644 src/node_modules/nedb/lib/executor.js create mode 100644 src/node_modules/nedb/lib/indexes.js create mode 100644 src/node_modules/nedb/lib/model.js create mode 100644 src/node_modules/nedb/lib/persistence.js create mode 100644 src/node_modules/nedb/lib/storage.js create mode 100644 src/node_modules/nedb/node_modules/async/LICENSE create mode 100644 src/node_modules/nedb/node_modules/async/README.md create mode 100644 src/node_modules/nedb/node_modules/async/component.json create mode 100755 src/node_modules/nedb/node_modules/async/lib/async.js create mode 100644 src/node_modules/nedb/node_modules/async/package.json create mode 100644 src/node_modules/nedb/node_modules/binary-search-tree/.npmignore create mode 100644 src/node_modules/nedb/node_modules/binary-search-tree/Makefile create mode 100644 src/node_modules/nedb/node_modules/binary-search-tree/README.md create mode 100644 src/node_modules/nedb/node_modules/binary-search-tree/index.js create mode 100644 src/node_modules/nedb/node_modules/binary-search-tree/lib/avltree.js create mode 100644 src/node_modules/nedb/node_modules/binary-search-tree/lib/bst.js create mode 100644 src/node_modules/nedb/node_modules/binary-search-tree/lib/customUtils.js create mode 100644 src/node_modules/nedb/node_modules/binary-search-tree/package.json create mode 100644 src/node_modules/nedb/node_modules/binary-search-tree/test/avltree.test.js create mode 100644 src/node_modules/nedb/node_modules/binary-search-tree/test/bst.test.js create mode 100644 src/node_modules/nedb/node_modules/mkdirp/.npmignore create mode 100644 src/node_modules/nedb/node_modules/mkdirp/.travis.yml create mode 100644 src/node_modules/nedb/node_modules/mkdirp/LICENSE create mode 100644 src/node_modules/nedb/node_modules/mkdirp/examples/pow.js create mode 100644 src/node_modules/nedb/node_modules/mkdirp/index.js create mode 100644 src/node_modules/nedb/node_modules/mkdirp/package.json create mode 100644 src/node_modules/nedb/node_modules/mkdirp/readme.markdown create mode 100644 src/node_modules/nedb/node_modules/mkdirp/test/chmod.js create mode 100644 src/node_modules/nedb/node_modules/mkdirp/test/clobber.js create mode 100644 src/node_modules/nedb/node_modules/mkdirp/test/mkdirp.js create mode 100644 src/node_modules/nedb/node_modules/mkdirp/test/perm.js create mode 100644 src/node_modules/nedb/node_modules/mkdirp/test/perm_sync.js create mode 100644 src/node_modules/nedb/node_modules/mkdirp/test/race.js create mode 100644 src/node_modules/nedb/node_modules/mkdirp/test/rel.js create mode 100644 src/node_modules/nedb/node_modules/mkdirp/test/return.js create mode 100644 src/node_modules/nedb/node_modules/mkdirp/test/return_sync.js create mode 100644 src/node_modules/nedb/node_modules/mkdirp/test/root.js create mode 100644 src/node_modules/nedb/node_modules/mkdirp/test/sync.js create mode 100644 src/node_modules/nedb/node_modules/mkdirp/test/umask.js create mode 100644 src/node_modules/nedb/node_modules/mkdirp/test/umask_sync.js create mode 100644 src/node_modules/nedb/node_modules/underscore/.npmignore create mode 100644 src/node_modules/nedb/node_modules/underscore/.travis.yml create mode 100644 src/node_modules/nedb/node_modules/underscore/CNAME create mode 100644 src/node_modules/nedb/node_modules/underscore/CONTRIBUTING.md create mode 100644 src/node_modules/nedb/node_modules/underscore/LICENSE create mode 100644 src/node_modules/nedb/node_modules/underscore/README.md create mode 100644 src/node_modules/nedb/node_modules/underscore/favicon.ico create mode 100644 src/node_modules/nedb/node_modules/underscore/index.html create mode 100644 src/node_modules/nedb/node_modules/underscore/index.js create mode 100644 src/node_modules/nedb/node_modules/underscore/package.json create mode 100644 src/node_modules/nedb/node_modules/underscore/underscore-min.js create mode 100644 src/node_modules/nedb/node_modules/underscore/underscore.js create mode 100644 src/node_modules/nedb/package.json create mode 100644 src/node_modules/nedb/test/.db.test.js.swo create mode 100644 src/node_modules/nedb/test/cursor.test.js create mode 100644 src/node_modules/nedb/test/customUtil.test.js create mode 100644 src/node_modules/nedb/test/db.test.js create mode 100644 src/node_modules/nedb/test/executor.test.js create mode 100644 src/node_modules/nedb/test/indexes.test.js create mode 100644 src/node_modules/nedb/test/mocha.opts create mode 100644 src/node_modules/nedb/test/model.test.js create mode 100644 src/node_modules/nedb/test/persistence.test.js create mode 100644 src/node_modules/nedb/test_lac/loadAndCrash.test.js diff --git a/src/app/config.js b/src/app/config.js index 56cc3b9..fbef57b 100644 --- a/src/app/config.js +++ b/src/app/config.js @@ -1,21 +1,41 @@ var visjs_options = { nodes: { - borderWidth: 3, - borderWidthSelected: 3, - color: { - border: '#fff', - background: '#fff', - highlight: { + borderWidth: 2, + borderWidthSelected: 2 + }, + groups: { + standard: { + color: { border: '#fff', - background: '#dd3' + background: '#fff', + highlight: { + border: '#fff', + background: '#dd3' + }, + }, + }, + processing: { + color: { + border: '#fff', + background: '#da2d2f', + highlight: { + border: '#fff', + background: '#dd3' + }, }, - hover: { - border: '#2B7CE9', - background: '#D2E5FF' - } }, - }, + leaf: { + color: { + border: '#fff', + background: '#31ae31', + highlight: { + border: '#fff', + background: '#dd3' + }, + }, + } + }, edges: { color: { color:'rgba(255, 255, 255, 0.15)', @@ -26,29 +46,23 @@ var visjs_options = { width: 1.5, arrowScaleFactor: 0.25 }, - tooltip: { delay: 300, fontColor: "#fff", - fontSize: 14, // px - fontFace: "verdana", + fontSize: 14, color: { border: "#000", background: "rgba(0,0,0,0.5)" } }, - physics: { barnesHut: { - enabled: true, - gravitationalConstant: -2000, - centralGravity: 0.2, - springLength: 95, - springConstant: 0.04, - damping: 0.15 + enabled: true, + gravitationalConstant: -2000, + centralGravity: 0.2, + springLength: 95, + springConstant: 0.04, + damping: 0.15 } } }; - -var node_queued_color = jQuery.extend({},visjs_options.nodes.color); -node_queued_color.background = '#da2d2f'; diff --git a/src/app/crawler.js b/src/app/crawler.js index 38f5d5f..c1e15e4 100644 --- a/src/app/crawler.js +++ b/src/app/crawler.js @@ -1,88 +1,175 @@ -function ScholarCrawler(parser, node_ids, nodes, edges) +function ScholarCrawler(parser, nodes, edges) { - this.stack = []; + this.stack = []; + this.edge_ids = {}; + this.node_ids = {}; - this.node_ids = node_ids; - this.parser = parser; - this.nodes = nodes; - this.edges = edges; + this.nodes = nodes; + this.edges = edges; + this.parser = parser; - this.delay = 1000; - this.max_depth = 2; - this.minimum_citations = 0; + this.delay = 1000; + this.minimum_citations = 0; }; -ScholarCrawler.prototype.formatArticle = function(node) +ScholarCrawler.prototype.article_to_node = function(article) { - node.label = ''; - node.shape = "dot"; - node.original_title = node.title; - node.title = - "" + node.authors + - " " + node.title + ". " + - node.source + ", " + node.year + "."; - node.mass = node.n_citations/2 + 1; - node.radius = 3*Math.pow(node.n_citations, 0.8) + 3; - return node; + return { + id: article._id, + _id: article._id, + article: article, + group: 'standard', + label: '', + shape: 'dot', + mass: article.n_citations/2 + 1, + radius: 3*Math.pow(article.n_citations, 0.8) + 3, + title: "" + article.authors + + " " + + article.title + ". " + article.source + ", " + article.year + "." + }; }; -ScholarCrawler.prototype.process = function(url, parent_node) +ScholarCrawler.prototype.add_citations = function(parent_node, levels) { - var crawler = this; - - this.parser.parse(url, function(children) - { - for(i=0; i 0) { - crawler.push(child['citations_url'], child); - child.color = node_queued_color; - } - - crawler.node_ids.push(child['id']); - crawler.nodes.add(child); - } - } - - if(!(parent_node === null)) - { - parent_node.color = visjs_options.nodes.color; - crawler.nodes.update(parent_node); - } - - }); -}; + assert(levels >= 0, "levels should be non-negative"); + + var crawler = this; + + if(parent_node.is_dummy) + return this._add_citations_from_scopus(parent_node, levels); + + articles_db.findOne({_id: parent_node._id}, function(err, parent_article_db) + { + if(parent_article_db === null || !parent_article_db.is_cached) + crawler._add_citations_from_scopus(parent_node, levels); + else + crawler._add_citations_from_db(crawler.article_to_node(parent_node.article), levels); + }); +} + +ScholarCrawler.prototype._add_citations_from_db = function(parent_node, levels) +{ + assert(levels >= 0, "levels should be non-negative"); + + var crawler = this; + citations_db.find({to: parent_node.article._id}, function(err, citations) + { + citations.forEach(function(citation) + { + articles_db.findOne({_id: citation.from}, function(err, child_article) + { + crawler._add_child_article(child_article, parent_node, levels); + }); + }); + + parent_node.group = "standard"; + crawler.nodes.update(parent_node); + }); +} + +ScholarCrawler.prototype._add_citations_from_scopus = function(parent_node, levels) +{ + assert(levels >= 0, "levels should be non-negative"); + + var crawler = this; + this.parser.parse(parent_node.article.citations_url, function(child_articles) + { + child_articles.forEach(function(child_article) + { + articles_db.findOne({_id:child_article._id}, function(err, child_article_db) + { + if(child_article_db === null) + { + child_article.is_cached = false; + articles_db.insert(child_article); + crawler._add_child_article(child_article, parent_node, levels); + } + else + { + crawler._add_child_article(child_article_db, parent_node, levels); + } + }); + }); + + if(!parent_node.is_dummy) + { + parent_node.group = "standard"; + crawler.nodes.update(parent_node); -ScholarCrawler.prototype.push = function(url, parent_node) + parent_node.article.is_cached = true; + articles_db.update({_id: parent_node.article._id}, parent_node.article, {}); + } + }); +} + +ScholarCrawler.prototype._add_child_article = function(child_article, parent_node, levels) +{ + assert(levels >= 0, "levels should be non-negative"); + + var child_node = this.article_to_node(child_article); + + if(child_article.n_citations < this.minimum_citations) + return; + + if(!parent_node.is_dummy) + { + edge = { + id: $.md5(child_node._id + parent_node._id), + from: child_node._id, + to: parent_node._id + }; + + edge._id = edge.id; + + if(!(edge._id in this.edge_ids)) + { + this.edge_ids[edge._id] = true; + this.edges.add(edge); + citations_db.insert(edge); + } + } + + if(child_node.article.n_citations == 0) + child_node.group = "standard"; + else if(levels == 0) + child_node.group = "leaf"; + else + this.push(child_node, levels-1); + + if(!(child_node._id in this.node_ids)) + { + this.node_ids[child_node._id] = true; + this.nodes.add(child_node); + } + else + { + this.nodes.update(child_node); + } +} + +ScholarCrawler.prototype.push = function(parent_node, levels) { - this.stack.push([url, parent_node]); + assert(levels >= 0, "levels should be non-negative"); + + this.stack.push([parent_node, levels]); + if(!parent_node.is_dummy) + { + parent_node.group = "processing"; + if(parent_node._id in this.node_ids) + { + this.nodes.update(parent_node); + } + } }; -ScholarCrawler.prototype.start = function() +ScholarCrawler.prototype.next = function() { - if(this.stack.length > 0) - { - var args = this.stack.pop(); - this.process.apply(this, args); - } - - var crawler = this; - setTimeout(function() { crawler.start() }, this.delay); + if(this.stack.length > 0) + { + var args = this.stack.pop(); + this.add_citations(args[0], args[1]); + } + + var crawler = this; + setTimeout(function() { crawler.next() }, 1000); }; diff --git a/src/app/main.js b/src/app/main.js index a314875..f80bbca 100644 --- a/src/app/main.js +++ b/src/app/main.js @@ -1,7 +1,26 @@ -var gui = require('nw.gui'); +var assert = require('assert'); +var gui = require('nw.gui'); +var nedb = require('nedb'); +var path = require('path'); var win = gui.Window.get(); win.maximize(); -var view = new MainMenuView(); -view.render(document.getElementById("main")); +function createDB(name) +{ + return new nedb({ + filename : path.join(gui.App.dataPath, name + '.db'), + autoload: true + }); +} + +function open_external(url) +{ + gui.Shell.openExternal(url); + return false; +} + +var articles_db = createDB("articles"); +var citations_db = createDB("citations"); + +new MainMenuView().render(document.getElementById("main")); diff --git a/src/app/scopus.js b/src/app/scopus.js index 624790f..e1474ca 100644 --- a/src/app/scopus.js +++ b/src/app/scopus.js @@ -1,4 +1,6 @@ -function ScopusParser() {}; +function ScopusParser() +{ +}; ScopusParser.prototype._parse_results_page = function(url, callback) { @@ -23,32 +25,32 @@ ScopusParser.prototype._parse_results_page = function(url, callback) var article = {}; $(li).find(".docTitle a").each(function(index, tag) { - article['url'] = tag.href; - article['title'] = $(tag).text(); + article.url = tag.href; + article.title = $(tag).text(); }); $(li).find("a[href*='citedby']").each(function(index, tag) { - article['citations_url'] = tag.href; - article['n_citations'] = parseInt($(tag).text()); + article.citations_url = tag.href; + article.n_citations = parseInt($(tag).text()); }); $(li).find('.hidden-label').each(function(index, tag) { if($(tag).text().indexOf("Year") == 0) - article['year'] = parseInt($.trim($(tag).next().text())); + article.year = parseInt($.trim($(tag).next().text())); if($(tag).text().indexOf("Authors") == 0) - article['authors'] = $.trim($(tag).next().text()); + article.authors = $.trim($(tag).next().text()); if($(tag).text().indexOf("Source") == 0) - article['source'] = $.trim($(tag).next().text()); + article.source = $.trim($(tag).next().text()); }); if(!('n_citations' in article)) { - article['citations_url'] = undefined; - article['n_citations'] = 0; + article.citations_url = undefined; + article.n_citations = 0; } - article['id'] = $.md5(article['title'] + article['authors']); + article._id = $.md5(article.title + article.authors); articles.push(article); }); diff --git a/src/app/views/main_menu.js b/src/app/views/main_menu.js index 829b489..aa3f5c6 100644 --- a/src/app/views/main_menu.js +++ b/src/app/views/main_menu.js @@ -2,19 +2,19 @@ function MainMenuView() {}; MainMenuView.prototype.render = function(container) { - var main_menu_div = document.createElement("div"); - $(main_menu_div).addClass("main_menu"); + var main_menu_div = document.createElement("div"); + $(main_menu_div).addClass("main_menu"); - $(main_menu_div).load("app/templates/main_menu.html", function() { - document.forms[0].onsubmit = function() { - var seed_url = $("input[name=url]").val(); - var view = new ShowMapView(seed_url); - view.render(container); - return false; - }; - }); + $(main_menu_div).load("app/templates/main_menu.html", function() { + document.forms[0].onsubmit = function() { + var seed_url = $("input[name=url]").val(); + var view = new ShowMapView(seed_url); + view.render(container); + return false; + }; + }); - $(container).empty(); - $(container).removeClass(); - container.appendChild(main_menu_div); + $(container).empty(); + $(container).removeClass(); + container.appendChild(main_menu_div); } diff --git a/src/app/views/show_map.js b/src/app/views/show_map.js index e13729e..e79b183 100644 --- a/src/app/views/show_map.js +++ b/src/app/views/show_map.js @@ -1,34 +1,36 @@ function ShowMapView(seed_url) { - this.seed_url = seed_url; + this.seed_url = seed_url; }; ShowMapView.prototype.render = function(container) { - var node_ids = []; - var edges = new vis.DataSet(); - var nodes = new vis.DataSet(); + var edges = new vis.DataSet(); + var nodes = new vis.DataSet(); - var parser = new ScopusParser(); - var crawler = new ScholarCrawler(parser, node_ids, nodes, edges); - crawler.push(this.seed_url, null); - crawler.start(); + var parser = new ScopusParser(); + var crawler = new ScholarCrawler(parser, nodes, edges); + crawler.push({ is_dummy: true, article: { citations_url: this.seed_url } }, 2); + crawler.next(); - var network_div = document.createElement('div'); - $(network_div).addClass("mynetwork"); - $(container).empty(); - container.appendChild(network_div) + document.crawler = crawler; - var data = { nodes: nodes, edges: edges }; - var network = new vis.Network(network_div, data, visjs_options); + var network_div = document.createElement('div'); + $(network_div).addClass("mynetwork"); + $(container).empty(); + container.appendChild(network_div) - network.on('doubleClick', function(params) { - gui.Shell.openExternal(nodes.get(params.nodes[0]).url) - }); + var data = { nodes: nodes, edges: edges }; + var network = new vis.Network(network_div, data, visjs_options); - network.on("resize", function(params) { - var height = $(window).height(); - var width = $(window).width(); - $(".mynetwork").css("width", width); - $(".mynetwork").css("height", height); - }); + network.on('doubleClick', function(params) { + if(params.nodes.length > 0) + crawler.push(nodes.get(params.nodes[0]), 1); + }); + + network.on("resize", function(params) { + var height = $(window).height(); + var width = $(window).width(); + $(".mynetwork").css("width", width); + $(".mynetwork").css("height", height); + }); }; diff --git a/src/css/main.css b/src/css/main.css index a288e8c..5e24741 100644 --- a/src/css/main.css +++ b/src/css/main.css @@ -23,7 +23,12 @@ html, body { color: rgba(255, 255, 255, 0.5); } -.node_tooltip emph { +.node_tooltip a { color: #fff; font-weight: bold; + text-decoration: none; +} + +.node_tooltip a:hover { + text-decoration: undeline; } diff --git a/src/node_modules/nedb/.npmignore b/src/node_modules/nedb/.npmignore new file mode 100644 index 0000000..00516a9 --- /dev/null +++ b/src/node_modules/nedb/.npmignore @@ -0,0 +1,22 @@ +lib-cov +*.seed +*.log +*.csv +*.dat +*.out +*.pid +*.gz + +pids +logs +results + +npm-debug.log +workspace +node_modules + +browser-version/src +browser-version/node_modules + +*.swp +*~ diff --git a/src/node_modules/nedb/LICENSE b/src/node_modules/nedb/LICENSE new file mode 100644 index 0000000..3fb9850 --- /dev/null +++ b/src/node_modules/nedb/LICENSE @@ -0,0 +1,22 @@ +(The MIT License) + +Copyright (c) 2013 Louis Chatriot <louis.chatriot@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/src/node_modules/nedb/README.md b/src/node_modules/nedb/README.md new file mode 100644 index 0000000..8334340 --- /dev/null +++ b/src/node_modules/nedb/README.md @@ -0,0 +1,639 @@ +# NeDB (Node embedded database) + + + +**Embedded persistent database for Node.js, written in Javascript, with no dependency** (except npm +modules of course). You can **think of it as a SQLite for Node.js projects**, which +can be used with a simple `require` statement. The API is a subset of MongoDB's. You can use it as a persistent or an in-memory only datastore. + +NeDB is not intended to be a replacement of large-scale databases such as MongoDB! Its goal is to provide you with a clean and easy way to query data and persist it to disk, for web applications that do not need lots of concurrent connections, for example a continuous integration and deployment server and desktop applications built with Node Webkit. + +NeDB was benchmarked against the popular client-side database TaffyDB and NeDB is much, much faster. That's why there is now a browser version, which can also provide persistence. + +Check the change log in the wiki if you think nedb doesn't behave as the documentation describes! Most of the issues I get are due to non-latest version NeDBs. + +You want to help out? You can contribute time or bitcoins, check out how! + + +## Installation, tests +Module name on npm is `nedb`. +```javascript +npm install nedb --save // Put latest version in your package.json + +npm test // You'll need the dev dependencies to test it +``` + +## API +It's a subset of MongoDB's API (the most used operations). The current API will not change, but I will add operations as they are needed. Summary of the API: + +* Creating/loading a database +* Compacting the database +* Inserting documents +* Finding documents + * Basic Querying + * Operators ($lt, $lte, $gt, $gte, $in, $nin, $ne, $exists, $regex) + * Array fields + * Logical operators $or, $and, $not, $where + * Sorting and paginating + * Projections +* Counting documents +* Updating documents +* Removing documents +* Indexing +* Browser version + +### Creating/loading a database +You can use NeDB as an in-memory only datastore or as a persistent datastore. One datastore is the equivalent of a MongoDB collection. The constructor is used as follows `new Datastore(options)` where `options` is an object with the following fields: + +* `filename` (optional): path to the file where the data is persisted. If left blank, the datastore is automatically considered in-memory only. It cannot end with a `~` which is used in the temporary files NeDB uses to perform crash-safe writes +* `inMemoryOnly` (optional, defaults to false): as the name implies. +* `autoload` (optional, defaults to false): if used, the database will + automatically be loaded from the datafile upon creation (you don't +need to call `loadDatabase`). Any command +issued before load is finished is buffered and will be executed when +load is done. +* `onload` (optional): if you use autoloading, this is the handler called after the `loadDatabase`. It takes one `error` argument. If you use autoloading without specifying this handler, and an error happens during load, an error will be thrown. +* `nodeWebkitAppName` (optional, **DEPRECATED**): if you are using NeDB from whithin a Node Webkit app, specify its name (the same one you use in the `package.json`) in this field and the `filename` will be relative to the directory Node Webkit uses to store the rest of the application's data (local storage etc.). It works on Linux, OS X and Windows. Now that you can use `require('nw.gui').App.dataPath` in Node Webkit to get the path to the data directory for your application, you should not use this option anymore and it will be removed. + +If you use a persistent datastore without the `autoload` option, you need to call `loadDatabase` manually. +This function fetches the data from datafile and prepares the database. **Don't forget it!** If you use a +persistent datastore, no command (insert, find, update, remove) will be executed before `loadDatabase` +is called, so make sure to call it yourself or use the `autoload` +option. + +```javascript +// Type 1: In-memory only datastore (no need to load the database) +var Datastore = require('nedb') + , db = new Datastore(); + + +// Type 2: Persistent datastore with manual loading +var Datastore = require('nedb') + , db = new Datastore({ filename: 'path/to/datafile' }); +db.loadDatabase(function (err) { // Callback is optional + // Now commands will be executed +}); + + +// Type 3: Persistent datastore with automatic loading +var Datastore = require('nedb') + , db = new Datastore({ filename: 'path/to/datafile', autoload: true }); +// You can issue commands right away + + +// Type 4: Persistent datastore for a Node Webkit app called 'nwtest' +// For example on Linux, the datafile will be ~/.config/nwtest/nedb-data/something.db +var Datastore = require('nedb') + , path = require('path') + , db = new Datastore({ filename: path.join(require('nw.gui').App.dataPath, 'something.db') }); + + +// Of course you can create multiple datastores if you need several +// collections. In this case it's usually a good idea to use autoload for all collections. +db = {}; +db.users = new Datastore('path/to/users.db'); +db.robots = new Datastore('path/to/robots.db'); + +// You need to load each database (here we do it asynchronously) +db.users.loadDatabase(); +db.robots.loadDatabase(); +``` + +### Compacting the database +Under the hood, NeDB's persistence uses an append-only format, meaning that all updates and deletes actually result in lines added at the end of the datafile. The reason for this is that disk space is very cheap and appends are much faster than rewrites since they don't do a seek. The database is automatically compacted (i.e. put back in the one-line-per-document format) everytime your application restarts. + +You can manually call the compaction function with `yourDatabase.persistence.compactDatafile` which takes no argument. It queues a compaction of the datafile in the executor, to be executed sequentially after all pending operations. + +You can also set automatic compaction at regular intervals with `yourDatabase.persistence.setAutocompactionInterval(interval)`, `interval` in milliseconds (a minimum of 5s is enforced), and stop automatic compaction with `yourDatabase.persistence.stopAutocompaction()`. + +Keep in mind that compaction takes a bit of time (not too much: 130ms for 50k records on my slow machine) and no other operation can happen when it does, so most projects actually don't need to use it. + + +### Inserting documents +The native types are `String`, `Number`, `Boolean`, `Date` and `null`. You can also use +arrays and subdocuments (objects). If a field is `undefined`, it will not be saved (this is different from +MongoDB which transforms `undefined` in `null`, something I find counter-intuitive). + +If the document does not contain an `_id` field, NeDB will automatically generated one for you (a 16-characters alphanumerical string). The `_id` of a document, once set, cannot be modified. + +Field names cannot begin by '$' or contain a '.'. + +```javascript +var doc = { hello: 'world' + , n: 5 + , today: new Date() + , nedbIsAwesome: true + , notthere: null + , notToBeSaved: undefined // Will not be saved + , fruits: [ 'apple', 'orange', 'pear' ] + , infos: { name: 'nedb' } + }; + +db.insert(doc, function (err, newDoc) { // Callback is optional + // newDoc is the newly inserted document, including its _id + // newDoc has no key called notToBeSaved since its value was undefined +}); +``` + +You can also bulk-insert an array of documents. This operation is atomic, meaning that if one insert fails due to a unique constraint being violated, all changes are rolled back. +```javascript +db.insert([{ a: 5 }, { a: 42 }], function (err, newDocs) { + // Two documents were inserted in the database + // newDocs is an array with these documents, augmented with their _id +}); + +// If there is a unique constraint on field 'a', this will fail +db.insert([{ a: 5 }, { a: 42 }, { a: 5 }], function (err) { + // err is a 'uniqueViolated' error + // The database was not modified +}); +``` + +### Finding documents +Use `find` to look for multiple documents matching you query, or `findOne` to look for one specific document. You can select documents based on field equality or use comparison operators (`$lt`, `$lte`, `$gt`, `$gte`, `$in`, `$nin`, `$ne`). You can also use logical operators `$or`, `$and`, `$not` and `$where`. See below for the syntax. + +You can use regular expressions in two ways: in basic querying in place of a string, or with the `$regex` operator. + +You can sort and paginate results using the cursor API (see below). + +You can use standard projections to restrict the fields to appear in the results (see below). + +#### Basic querying +Basic querying means are looking for documents whose fields match the ones you specify. You can use regular expression to match strings. +You can use the dot notation to navigate inside nested documents, arrays, arrays of subdocuments and to match a specific element of an array. + +```javascript +// Let's say our datastore contains the following collection +// { _id: 'id1', planet: 'Mars', system: 'solar', inhabited: false, satellites: ['Phobos', 'Deimos'] } +// { _id: 'id2', planet: 'Earth', system: 'solar', inhabited: true, humans: { genders: 2, eyes: true } } +// { _id: 'id3', planet: 'Jupiter', system: 'solar', inhabited: false } +// { _id: 'id4', planet: 'Omicron Persei 8', system: 'futurama', inhabited: true, humans: { genders: 7 } } +// { _id: 'id5', completeData: { planets: [ { name: 'Earth', number: 3 }, { name: 'Mars', number: 2 }, { name: 'Pluton', number: 9 } ] } } + +// Finding all planets in the solar system +db.find({ system: 'solar' }, function (err, docs) { + // docs is an array containing documents Mars, Earth, Jupiter + // If no document is found, docs is equal to [] +}); + +// Finding all planets whose name contain the substring 'ar' using a regular expression +db.find({ planet: /ar/ }, function (err, docs) { + // docs contains Mars and Earth +}); + +// Finding all inhabited planets in the solar system +db.find({ system: 'solar', inhabited: true }, function (err, docs) { + // docs is an array containing document Earth only +}); + +// Use the dot-notation to match fields in subdocuments +db.find({ "humans.genders": 2 }, function (err, docs) { + // docs contains Earth +}); + +// Use the dot-notation to navigate arrays of subdocuments +db.find({ "completeData.planets.name": "Mars" }, function (err, docs) { + // docs contains document 5 +}); + +db.find({ "completeData.planets.name": "Jupiter" }, function (err, docs) { + // docs is empty +}); + +db.find({ "completeData.planets.0.name": "Earth" }, function (err, docs) { + // docs contains document 5 + // If we had tested against "Mars" docs would be empty because we are matching against a specific array element +}); + + +// You can also deep-compare objects. Don't confuse this with dot-notation! +db.find({ humans: { genders: 2 } }, function (err, docs) { + // docs is empty, because { genders: 2 } is not equal to { genders: 2, eyes: true } +}); + +// Find all documents in the collection +db.find({}, function (err, docs) { +}); + +// The same rules apply when you want to only find one document +db.findOne({ _id: 'id1' }, function (err, doc) { + // doc is the document Mars + // If no document is found, doc is null +}); +``` + +#### Operators ($lt, $lte, $gt, $gte, $in, $nin, $ne, $exists, $regex) +The syntax is `{ field: { $op: value } }` where `$op` is any comparison operator: + +* `$lt`, `$lte`: less than, less than or equal +* `$gt`, `$gte`: greater than, greater than or equal +* `$in`: member of. `value` must be an array of values +* `$ne`, `$nin`: not equal, not a member of +* `$exists`: checks whether the document posses the property `field`. `value` should be true or false +* `$regex`: checks whether a string is matched by the regular expression. Contrary to MongoDB, the use of `$options` with `$regex` is not supported, because it doesn't give you more power than regex flags. Basic queries are more readable so only use the `$regex` operator when you need to use another operator with it (see example below) + +```javascript +// $lt, $lte, $gt and $gte work on numbers and strings +db.find({ "humans.genders": { $gt: 5 } }, function (err, docs) { + // docs contains Omicron Persei 8, whose humans have more than 5 genders (7). +}); + +// When used with strings, lexicographical order is used +db.find({ planet: { $gt: 'Mercury' }}, function (err, docs) { + // docs contains Omicron Persei 8 +}) + +// Using $in. $nin is used in the same way +db.find({ planet: { $in: ['Earth', 'Jupiter'] }}, function (err, docs) { + // docs contains Earth and Jupiter +}); + +// Using $exists +db.find({ satellites: { $exists: true } }, function (err, docs) { + // docs contains only Mars +}); + +// Using $regex with another operator +db.find({ planet: { $regex: /ar/, $nin: ['Jupiter', 'Earth'] } }, function (err, docs) { + // docs only contains Mars because Earth was excluded from the match by $nin +}); +``` + +#### Array fields +When a field in a document is an array, NeDB first tries to see if there is an array-specific comparison function (for now there is only `$size`) being used +and tries it first. If there isn't, the query is treated as a query on every element and there is a match if at least one element matches. + +```javascript +// Using an array-specific comparison function +// Note: you can't use nested comparison functions, e.g. { $size: { $lt: 5 } } will throw an error +db.find({ satellites: { $size: 2 } }, function (err, docs) { + // docs contains Mars +}); + +db.find({ satellites: { $size: 1 } }, function (err, docs) { + // docs is empty +}); + +// If a document's field is an array, matching it means matching any element of the array +db.find({ satellites: 'Phobos' }, function (err, docs) { + // docs contains Mars. Result would have been the same if query had been { satellites: 'Deimos' } +}); + +// This also works for queries that use comparison operators +db.find({ satellites: { $lt: 'Amos' } }, function (err, docs) { + // docs is empty since Phobos and Deimos are after Amos in lexicographical order +}); + +// This also works with the $in and $nin operator +db.find({ satellites: { $in: ['Moon', 'Deimos'] } }, function (err, docs) { + // docs contains Mars (the Earth document is not complete!) +}); +``` + +#### Logical operators $or, $and, $not, $where +You can combine queries using logical operators: + +* For `$or` and `$and`, the syntax is `{ $op: [query1, query2, ...] }`. +* For `$not`, the syntax is `{ $not: query }` +* For `$where`, the syntax is `{ $where: function () { /* object is "this", return a boolean */ } }` + +```javascript +db.find({ $or: [{ planet: 'Earth' }, { planet: 'Mars' }] }, function (err, docs) { + // docs contains Earth and Mars +}); + +db.find({ $not: { planet: 'Earth' } }, function (err, docs) { + // docs contains Mars, Jupiter, Omicron Persei 8 +}); + +db.find({ $where: function () { return Object.keys(this) > 6; } }, function (err, docs) { + // docs with more than 6 properties +}); + +// You can mix normal queries, comparison queries and logical operators +db.find({ $or: [{ planet: 'Earth' }, { planet: 'Mars' }], inhabited: true }, function (err, docs) { + // docs contains Earth +}); + +``` + +#### Sorting and paginating +If you don't specify a callback to `find`, `findOne` or `count`, a `Cursor` object is returned. You can modify the cursor with `sort`, `skip` and `limit` and then execute it with `exec(callback)`. + +```javascript +// Let's say the database contains these 4 documents +// doc1 = { _id: 'id1', planet: 'Mars', system: 'solar', inhabited: false, satellites: ['Phobos', 'Deimos'] } +// doc2 = { _id: 'id2', planet: 'Earth', system: 'solar', inhabited: true, humans: { genders: 2, eyes: true } } +// doc3 = { _id: 'id3', planet: 'Jupiter', system: 'solar', inhabited: false } +// doc4 = { _id: 'id4', planet: 'Omicron Persei 8', system: 'futurama', inhabited: true, humans: { genders: 7 } } + +// No query used means all results are returned (before the Cursor modifiers) +db.find({}).sort({ planet: 1 }).skip(1).limit(2).exec(function (err, docs) { + // docs is [doc3, doc1] +}); + +// You can sort in reverse order like this +db.find({ system: 'solar' }).sort({ planet: -1 }).exec(function (err, docs) { + // docs is [doc1, doc3, doc2] +}); + +// You can sort on one field, then another, and so on like this: +db.find({}).sort({ firstField: 1, secondField: -1 }) ... // You understand how this works! +``` + +#### Projections +You can give `find` and `findOne` an optional second argument, `projections`. The syntax is the same as MongoDB: `{ a: 1, b: 1 }` to return only the `a` and `b` fields, `{ a: 0, b: 0 }` to omit these two fields. You cannot use both modes at the time, except for `_id` which is by default always returned and which you can choose to omit. + +```javascript +// Same database as above + +// Keeping only the given fields +db.find({ planet: 'Mars' }, { planet: 1, system: 1 }, function (err, docs) { + // docs is [{ planet: 'Mars', system: 'solar', _id: 'id1' }] +}); + +// Keeping only the given fields but removing _id +db.find({ planet: 'Mars' }, { planet: 1, system: 1, _id: 0 }, function (err, docs) { + // docs is [{ planet: 'Mars', system: 'solar' }] +}); + +// Omitting only the given fields and removing _id +db.find({ planet: 'Mars' }, { planet: 0, system: 0, _id: 0 }, function (err, docs) { + // docs is [{ inhabited: false, satellites: ['Phobos', 'Deimos'] }] +}); + +// Failure: using both modes at the same time +db.find({ planet: 'Mars' }, { planet: 0, system: 1 }, function (err, docs) { + // err is the error message, docs is undefined +}); + +// You can also use it in a Cursor way but this syntax is not compatible with MongoDB +// If upstream compatibility is important don't use this method +db.find({ planet: 'Mars' }).projection({ planet: 1, system: 1 }).exec(function (err, docs) { + // docs is [{ planet: 'Mars', system: 'solar', _id: 'id1' }] +}); +``` + + + +### Counting documents +You can use `count` to count documents. It has the same syntax as `find`. For example: + +```javascript +// Count all planets in the solar system +db.count({ system: 'solar' }, function (err, count) { + // count equals to 3 +}); + +// Count all documents in the datastore +db.count({}, function (err, count) { + // count equals to 4 +}); +``` + + +### Updating documents +`db.update(query, update, options, callback)` will update all documents matching `query` according to the `update` rules: +* `query` is the same kind of finding query you use with `find` and `findOne` +* `update` specifies how the documents should be modified. It is either a new document or a set of modifiers (you cannot use both together, it doesn't make sense!) + * A new document will replace the matched docs + * The modifiers create the fields they need to modify if they don't exist, and you can apply them to subdocs. Available field modifiers are `$set` to change a field's value, `$unset` to delete a field and `$inc` to increment a field's value. To work on arrays, you have `$push`, `$pop`, `$addToSet`, `$pull`, and the special `$each`. See examples below for the syntax. +* `options` is an object with two possible parameters + * `multi` (defaults to `false`) which allows the modification of several documents if set to true + * `upsert` (defaults to `false`) if you want to insert a new document corresponding to the `update` rules if your `query` doesn't match anything +* `callback` (optional) signature: `err`, `numReplaced`, `newDoc` + * `numReplaced` is the number of documents replaced + * `newDoc` is the created document if the upsert mode was chosen and a document was inserted + +**Note**: you can't change a document's _id. + +```javascript +// Let's use the same example collection as in the "finding document" part +// { _id: 'id1', planet: 'Mars', system: 'solar', inhabited: false } +// { _id: 'id2', planet: 'Earth', system: 'solar', inhabited: true } +// { _id: 'id3', planet: 'Jupiter', system: 'solar', inhabited: false } +// { _id: 'id4', planet: 'Omicron Persia 8', system: 'futurama', inhabited: true } + +// Replace a document by another +db.update({ planet: 'Jupiter' }, { planet: 'Pluton'}, {}, function (err, numReplaced) { + // numReplaced = 1 + // The doc #3 has been replaced by { _id: 'id3', planet: 'Pluton' } + // Note that the _id is kept unchanged, and the document has been replaced + // (the 'system' and inhabited fields are not here anymore) +}); + +// Set an existing field's value +db.update({ system: 'solar' }, { $set: { system: 'solar system' } }, { multi: true }, function (err, numReplaced) { + // numReplaced = 3 + // Field 'system' on Mars, Earth, Jupiter now has value 'solar system' +}); + +// Setting the value of a non-existing field in a subdocument by using the dot-notation +db.update({ planet: 'Mars' }, { $set: { "data.satellites": 2, "data.red": true } }, {}, function () { + // Mars document now is { _id: 'id1', system: 'solar', inhabited: false + // , data: { satellites: 2, red: true } + // } + // Not that to set fields in subdocuments, you HAVE to use dot-notation + // Using object-notation will just replace the top-level field + db.update({ planet: 'Mars' }, { $set: { data: { satellites: 3 } } }, {}, function () { + // Mars document now is { _id: 'id1', system: 'solar', inhabited: false + // , data: { satellites: 3 } + // } + // You lost the "data.red" field which is probably not the intended behavior + }); +}); + +// Deleting a field +db.update({ planet: 'Mars' }, { $unset: { planet: true } }, {}, function () { + // Now the document for Mars doesn't contain the planet field + // You can unset nested fields with the dot notation of course +}); + +// Upserting a document +db.update({ planet: 'Pluton' }, { planet: 'Pluton', inhabited: false }, { upsert: true }, function (err, numReplaced, upsert) { + // numReplaced = 1, upsert = { _id: 'id5', planet: 'Pluton', inhabited: false } + // A new document { _id: 'id5', planet: 'Pluton', inhabited: false } has been added to the collection +}); + +// If you upsert with a modifier, the upserted doc is the query modified by the modifier +// This is simpler than it sounds :) +db.update({ planet: 'Pluton' }, { $inc: { distance: 38 } }, { upsert: true }, function () { + // A new document { _id: 'id5', planet: 'Pluton', distance: 38 } has been added to the collection +}); + +// If we insert a new document { _id: 'id6', fruits: ['apple', 'orange', 'pear'] } in the collection, +// let's see how we can modify the array field atomically + +// $push inserts new elements at the end of the array +db.update({ _id: 'id6' }, { $push: { fruits: 'banana' } }, {}, function () { + // Now the fruits array is ['apple', 'orange', 'pear', 'banana'] +}); + +// $pop removes an element from the end (if used with 1) or the front (if used with -1) of the array +db.update({ _id: 'id6' }, { $pop: { fruits: 1 } }, {}, function () { + // Now the fruits array is ['apple', 'orange'] + // With { $pop: { fruits: -1 } }, it would have been ['orange', 'pear'] +}); + +// $addToSet adds an element to an array only if it isn't already in it +// Equality is deep-checked (i.e. $addToSet will not insert an object in an array already containing the same object) +// Note that it doesn't check whether the array contained duplicates before or not +db.update({ _id: 'id6' }, { $addToSet: { fruits: 'apple' } }, {}, function () { + // The fruits array didn't change + // If we had used a fruit not in the array, e.g. 'banana', it would have been added to the array +}); + +// $pull removes all values matching a value or even any NeDB query from the array +db.update({ _id: 'id6' }, { $pull: { fruits: 'apple' } }, {}, function () { + // Now the fruits array is ['orange', 'pear'] +}); +db.update({ _id: 'id6' }, { $pull: { fruits: $in: ['apple', 'pear'] } }, {}, function () { + // Now the fruits array is ['orange'] +}); + + + +// $each can be used to $push or $addToSet multiple values at once +// This example works the same way with $addToSet +db.update({ _id: 'id6' }, { $push: { fruits: {$each: ['banana', 'orange'] } } }, {}, function () { + // Now the fruits array is ['apple', 'orange', 'pear', 'banana', 'orange'] +}); +``` + +### Removing documents +`db.remove(query, options, callback)` will remove all documents matching `query` according to `options` +* `query` is the same as the ones used for finding and updating +* `options` only one option for now: `multi` which allows the removal of multiple documents if set to true. Default is false +* `callback` is optional, signature: err, numRemoved + +```javascript +// Let's use the same example collection as in the "finding document" part +// { _id: 'id1', planet: 'Mars', system: 'solar', inhabited: false } +// { _id: 'id2', planet: 'Earth', system: 'solar', inhabited: true } +// { _id: 'id3', planet: 'Jupiter', system: 'solar', inhabited: false } +// { _id: 'id4', planet: 'Omicron Persia 8', system: 'futurama', inhabited: true } + +// Remove one document from the collection +// options set to {} since the default for multi is false +db.remove({ _id: 'id2' }, {}, function (err, numRemoved) { + // numRemoved = 1 +}); + +// Remove multiple documents +db.remove({ system: 'solar' }, { multi: true }, function (err, numRemoved) { + // numRemoved = 3 + // All planets from the solar system were removed +}); +``` + +### Indexing +NeDB supports indexing. It gives a very nice speed boost and can be used to enforce a unique constraint on a field. You can index any field, including fields in nested documents using the dot notation. For now, indexes are only used to speed up basic queries and queries using `$in`, `$lt`, `$lte`, `$gt` and `$gte`. + +To create an index, use `datastore.ensureIndex(options, cb)`, where callback is optional and get passed an error if any (usually a unique constraint that was violated). `ensureIndex` can be called when you want, even after some data was inserted, though it's best to call it at application startup. The options are: + +* **fieldName** (required): name of the field to index. Use the dot notation to index a field in a nested document. +* **unique** (optional, defaults to `false`): enforce field uniqueness. Note that a unique index will raise an error if you try to index two documents for which the field is not defined. +* **sparse** (optional, defaults to `false`): don't index documents for which the field is not defined. Use this option along with "unique" if you want to accept multiple documents for which it is not defined. + +Note: the `_id` is automatically indexed with a unique constraint, no need to call `ensureIndex` on it. + +You can remove a previously created index with `datastore.removeIndex(fieldName, cb)`. + +If your datastore is persistent, the indexes you created are persisted in the datafile, when you load the database a second time they are automatically created for you. No need to remove any `ensureIndex` though, if it is called on a database that already has the index, nothing happens. + +```javascript +db.ensureIndex({ fieldName: 'somefield' }, function (err) { + // If there was an error, err is not null +}); + +// Using a unique constraint with the index +db.ensureIndex({ fieldName: 'somefield', unique: true }, function (err) { +}); + +// Using a sparse unique index +db.ensureIndex({ fieldName: 'somefield', unique: true, sparse: true }, function (err) { +}); + + +// Format of the error message when the unique constraint is not met +db.insert({ somefield: 'nedb' }, function (err) { + // err is null + db.insert({ somefield: 'nedb' }, function (err) { + // err is { errorType: 'uniqueViolated' + // , key: 'name' + // , message: 'Unique constraint violated for key name' } + }); +}); + +// Remove index on field somefield +db.removeIndex('somefield', function (err) { +}); +``` + +**Note:** the `ensureIndex` function creates the index synchronously, so it's best to use it at application startup. It's quite fast so it doesn't increase startup time much (35 ms for a collection containing 10,000 documents). + + +## Browser version +As of v0.8.0, you can use NeDB in the browser! You can find it and its minified version in the repository, in the `browser-version/out` directory. You only need to require `nedb.js` or `nedb.min.js` in your HTML file and the global object `Nedb` can be used right away, with the same API as the server version: + +``` + + +``` + +It has been tested and is compatible with Chrome, Safari, Firefox, IE 10, IE 9. Please open an issue if you need compatibility with IE 8/IE 7, I think it will need some work and am not sure it is needed, since most complex webapplications - the ones that would need NeDB - only work on modern browsers anyway. To launch the tests, simply open the file `browser-version/test/index.html` in a browser and you'll see the results of the tests for this browser. + +If you fork and modify nedb, you can build the browser version from the sources, the build script is `browser-version/build.js`. + +As of v0.11, NeDB is also persistent on the browser. To use this, simply create the collection with the `filename` option which will be the name of the `localStorage` variable storing data. Persistence should work on all browsers where NeDB works. + +**Browser persistence is still young! It has been tested on most major browsers but please report any bugs you find** + + +## Performance +### Speed +NeDB is not intended to be a replacement of large-scale databases such as MongoDB, and as such was not designed for speed. That said, it is still pretty fast on the expected datasets, especially if you use indexing. On my machine (3 years old, no SSD), with a collection containing 10,000 documents, with indexing: +* Insert: **5,950 ops/s** +* Find: **25,440 ops/s** +* Update: **4,490 ops/s** +* Remove: **6,620 ops/s** + +You can run the simple benchmarks I use by executing the scripts in the `benchmarks` folder. Run them with the `--help` flag to see how they work. + +### Memory footprint +A copy of the whole database is kept in memory. This is not much on the +expected kind of datasets (20MB for 10,000 2KB documents). If requested, I'll introduce an +option to not use this cache to decrease memory footprint (at the cost +of a lower speed). + + +## Use in other services +* connect-nedb-session is a session store for +Connect and Express, backed by nedb +* If you mostly use NeDB for logging purposes and don't want the memory footprint of your application to grow too large, you can use NeDB Logger to insert documents in a NeDB-readable database +* If you've outgrown NeDB, switching to MongoDB won't be too hard as it is the same API. Use this utility to transfer the data from a NeDB database to a MongoDB collection + + +## Contribute! +You want to help? You can contribute time or bitcoins. + +### Helping on the codebase +Issues reporting and pull requests are always appreciated. For issues, make sure to always include a code snippet and describe the expected vs actual behavior. If you send a pull request, make sure to stick to NeDB's coding style and always test all the code you submit. You can look at the current tests to see how to do it + +### Bitcoins +You don't have time? You can support NeDB by sending bitcoins to this adress: 1dDZLnWpBbodPiN8sizzYrgaz5iahFyb1 + + +## License + +See [License](LICENSE) diff --git a/src/node_modules/nedb/benchmarks/commonUtilities.js b/src/node_modules/nedb/benchmarks/commonUtilities.js new file mode 100644 index 0000000..044258d --- /dev/null +++ b/src/node_modules/nedb/benchmarks/commonUtilities.js @@ -0,0 +1,305 @@ +/** + * Functions that are used in several benchmark tests + */ + +var customUtils = require('../lib/customUtils') + , fs = require('fs') + , path = require('path') + , Datastore = require('../lib/datastore') + , Persistence = require('../lib/persistence') + , executeAsap // process.nextTick or setImmediate depending on your Node version + ; + +try { + executeAsap = setImmediate; +} catch (e) { + executeAsap = process.nextTick; +} + + +/** + * Configure the benchmark + */ +module.exports.getConfiguration = function (benchDb) { + var d, n + , program = require('commander') + ; + + program + .option('-n --number [number]', 'Size of the collection to test on', parseInt) + .option('-i --with-index', 'Use an index') + .option('-m --in-memory', 'Test with an in-memory only store') + .parse(process.argv); + + n = program.number || 10000; + + console.log("----------------------------"); + console.log("Test with " + n + " documents"); + console.log(program.withIndex ? "Use an index" : "Don't use an index"); + console.log(program.inMemory ? "Use an in-memory datastore" : "Use a persistent datastore"); + console.log("----------------------------"); + + d = new Datastore({ filename: benchDb + , inMemoryOnly: program.inMemory + }); + + return { n: n, d: d, program: program }; +}; + + +/** + * Ensure the workspace exists and the db datafile is empty + */ +module.exports.prepareDb = function (filename, cb) { + Persistence.ensureDirectoryExists(path.dirname(filename), function () { + fs.exists(filename, function (exists) { + if (exists) { + fs.unlink(filename, cb); + } else { return cb(); } + }); + }); +}; + + +/** + * Return an array with the numbers from 0 to n-1, in a random order + * Uses Fisher Yates algorithm + * Useful to get fair tests + */ +function getRandomArray (n) { + var res = [] + , i, j, temp + ; + + for (i = 0; i < n; i += 1) { res[i] = i; } + + for (i = n - 1; i >= 1; i -= 1) { + j = Math.floor((i + 1) * Math.random()); + temp = res[i]; + res[i] = res[j]; + res[j] = temp; + } + + return res; +}; +module.exports.getRandomArray = getRandomArray; + + +/** + * Insert a certain number of documents for testing + */ +module.exports.insertDocs = function (d, n, profiler, cb) { + var beg = new Date() + , order = getRandomArray(n) + ; + + profiler.step('Begin inserting ' + n + ' docs'); + + function runFrom(i) { + if (i === n) { // Finished + console.log("===== RESULT (insert) ===== " + Math.floor(1000* n / profiler.elapsedSinceLastStep()) + " ops/s"); + profiler.step('Finished inserting ' + n + ' docs'); + return cb(); + } + + d.insert({ docNumber: order[i] }, function (err) { + executeAsap(function () { + runFrom(i + 1); + }); + }); + } + runFrom(0); +}; + + +/** + * Find documents with find + */ +module.exports.findDocs = function (d, n, profiler, cb) { + var beg = new Date() + , order = getRandomArray(n) + ; + + profiler.step("Finding " + n + " documents"); + + function runFrom(i) { + if (i === n) { // Finished + console.log("===== RESULT (find) ===== " + Math.floor(1000* n / profiler.elapsedSinceLastStep()) + " ops/s"); + profiler.step('Finished finding ' + n + ' docs'); + return cb(); + } + + d.find({ docNumber: order[i] }, function (err, docs) { + if (docs.length !== 1 || docs[0].docNumber !== order[i]) { return cb('One find didnt work'); } + executeAsap(function () { + runFrom(i + 1); + }); + }); + } + runFrom(0); +}; + + +/** + * Find documents with find and the $in operator + */ +module.exports.findDocsWithIn = function (d, n, profiler, cb) { + var beg = new Date() + , order = getRandomArray(n) + , ins = [], i, j + , arraySize = Math.min(10, n) // The array for $in needs to be smaller than n (inclusive) + ; + + // Preparing all the $in arrays, will take some time + for (i = 0; i < n; i += 1) { + ins[i] = []; + + for (j = 0; j < arraySize; j += 1) { + ins[i].push((i + j) % n); + } + } + + profiler.step("Finding " + n + " documents WITH $IN OPERATOR"); + + function runFrom(i) { + if (i === n) { // Finished + console.log("===== RESULT (find with in selector) ===== " + Math.floor(1000* n / profiler.elapsedSinceLastStep()) + " ops/s"); + profiler.step('Finished finding ' + n + ' docs'); + return cb(); + } + + d.find({ docNumber: { $in: ins[i] } }, function (err, docs) { + if (docs.length !== arraySize) { return cb('One find didnt work'); } + executeAsap(function () { + runFrom(i + 1); + }); + }); + } + runFrom(0); +}; + + +/** + * Find documents with findOne + */ +module.exports.findOneDocs = function (d, n, profiler, cb) { + var beg = new Date() + , order = getRandomArray(n) + ; + + profiler.step("FindingOne " + n + " documents"); + + function runFrom(i) { + if (i === n) { // Finished + console.log("===== RESULT (findOne) ===== " + Math.floor(1000* n / profiler.elapsedSinceLastStep()) + " ops/s"); + profiler.step('Finished finding ' + n + ' docs'); + return cb(); + } + + d.findOne({ docNumber: order[i] }, function (err, doc) { + if (!doc || doc.docNumber !== order[i]) { return cb('One find didnt work'); } + executeAsap(function () { + runFrom(i + 1); + }); + }); + } + runFrom(0); +}; + + +/** + * Update documents + * options is the same as the options object for update + */ +module.exports.updateDocs = function (options, d, n, profiler, cb) { + var beg = new Date() + , order = getRandomArray(n) + ; + + profiler.step("Updating " + n + " documents"); + + function runFrom(i) { + if (i === n) { // Finished + console.log("===== RESULT (update) ===== " + Math.floor(1000* n / profiler.elapsedSinceLastStep()) + " ops/s"); + profiler.step('Finished updating ' + n + ' docs'); + return cb(); + } + + // Will not actually modify the document but will take the same time + d.update({ docNumber: order[i] }, { docNumber: order[i] }, options, function (err, nr) { + if (err) { return cb(err); } + if (nr !== 1) { return cb('One update didnt work'); } + executeAsap(function () { + runFrom(i + 1); + }); + }); + } + runFrom(0); +}; + + +/** + * Remove documents + * options is the same as the options object for update + */ +module.exports.removeDocs = function (options, d, n, profiler, cb) { + var beg = new Date() + , order = getRandomArray(n) + ; + + profiler.step("Removing " + n + " documents"); + + function runFrom(i) { + if (i === n) { // Finished + console.log("===== RESULT (1 remove + 1 insert) ===== " + Math.floor(1000* n / profiler.elapsedSinceLastStep()) + " ops/s"); + console.log("====== IMPORTANT: Please note that this is the time that was needed to perform " + n + " removes and " + n + " inserts"); + console.log("====== The extra inserts are needed to keep collection size at " + n + " items for the benchmark to make sense"); + console.log("====== Use the insert speed logged above to calculate the actual remove speed, which is higher (should be significantly so if you use indexing)"); + profiler.step('Finished removing ' + n + ' docs'); + return cb(); + } + + d.remove({ docNumber: order[i] }, options, function (err, nr) { + if (err) { return cb(err); } + if (nr !== 1) { return cb('One remove didnt work'); } + d.insert({ docNumber: order[i] }, function (err) { // We need to reinsert the doc so that we keep the collection's size at n + // So actually we're calculating the average time taken by one insert + one remove + executeAsap(function () { + runFrom(i + 1); + }); + }); + }); + } + runFrom(0); +}; + + +/** + * Load database + */ +module.exports.loadDatabase = function (d, n, profiler, cb) { + var beg = new Date() + , order = getRandomArray(n) + ; + + profiler.step("Loading the database " + n + " times"); + + function runFrom(i) { + if (i === n) { // Finished + console.log("===== RESULT ===== " + Math.floor(1000* n / profiler.elapsedSinceLastStep()) + " ops/s"); + profiler.step('Finished loading a database' + n + ' times'); + return cb(); + } + + d.loadDatabase(function (err) { + executeAsap(function () { + runFrom(i + 1); + }); + }); + } + runFrom(0); +}; + + + + diff --git a/src/node_modules/nedb/benchmarks/ensureIndex.js b/src/node_modules/nedb/benchmarks/ensureIndex.js new file mode 100644 index 0000000..9be2a92 --- /dev/null +++ b/src/node_modules/nedb/benchmarks/ensureIndex.js @@ -0,0 +1,51 @@ +var Datastore = require('../lib/datastore') + , benchDb = 'workspace/insert.bench.db' + , async = require('async') + , commonUtilities = require('./commonUtilities') + , execTime = require('exec-time') + , profiler = new execTime('INSERT BENCH') + , d = new Datastore(benchDb) + , program = require('commander') + , n + ; + +program + .option('-n --number [number]', 'Size of the collection to test on', parseInt) + .option('-i --with-index', 'Test with an index') + .parse(process.argv); + +n = program.number || 10000; + +console.log("----------------------------"); +console.log("Test with " + n + " documents"); +console.log("----------------------------"); + +async.waterfall([ + async.apply(commonUtilities.prepareDb, benchDb) +, function (cb) { + d.loadDatabase(function (err) { + if (err) { return cb(err); } + cb(); + }); + } +, function (cb) { profiler.beginProfiling(); return cb(); } +, async.apply(commonUtilities.insertDocs, d, n, profiler) +, function (cb) { + var i; + + profiler.step('Begin calling ensureIndex ' + n + ' times'); + + for (i = 0; i < n; i += 1) { + d.ensureIndex({ fieldName: 'docNumber' }); + delete d.indexes.docNumber; + } + + console.log("Average time for one ensureIndex: " + (profiler.elapsedSinceLastStep() / n) + "ms"); + profiler.step('Finished calling ensureIndex ' + n + ' times'); + } +], function (err) { + profiler.step("Benchmark finished"); + + if (err) { return console.log("An error was encountered: ", err); } +}); + diff --git a/src/node_modules/nedb/benchmarks/find.js b/src/node_modules/nedb/benchmarks/find.js new file mode 100644 index 0000000..435c14b --- /dev/null +++ b/src/node_modules/nedb/benchmarks/find.js @@ -0,0 +1,30 @@ +var Datastore = require('../lib/datastore') + , benchDb = 'workspace/find.bench.db' + , fs = require('fs') + , path = require('path') + , async = require('async') + , execTime = require('exec-time') + , profiler = new execTime('FIND BENCH') + , commonUtilities = require('./commonUtilities') + , config = commonUtilities.getConfiguration(benchDb) + , d = config.d + , n = config.n + ; + +async.waterfall([ + async.apply(commonUtilities.prepareDb, benchDb) +, function (cb) { + d.loadDatabase(function (err) { + if (err) { return cb(err); } + if (config.program.withIndex) { d.ensureIndex({ fieldName: 'docNumber' }); } + cb(); + }); + } +, function (cb) { profiler.beginProfiling(); return cb(); } +, async.apply(commonUtilities.insertDocs, d, n, profiler) +, async.apply(commonUtilities.findDocs, d, n, profiler) +], function (err) { + profiler.step("Benchmark finished"); + + if (err) { return console.log("An error was encountered: ", err); } +}); diff --git a/src/node_modules/nedb/benchmarks/findOne.js b/src/node_modules/nedb/benchmarks/findOne.js new file mode 100644 index 0000000..08cffb7 --- /dev/null +++ b/src/node_modules/nedb/benchmarks/findOne.js @@ -0,0 +1,31 @@ +var Datastore = require('../lib/datastore') + , benchDb = 'workspace/findOne.bench.db' + , fs = require('fs') + , path = require('path') + , async = require('async') + , execTime = require('exec-time') + , profiler = new execTime('FINDONE BENCH') + , commonUtilities = require('./commonUtilities') + , config = commonUtilities.getConfiguration(benchDb) + , d = config.d + , n = config.n + ; + +async.waterfall([ + async.apply(commonUtilities.prepareDb, benchDb) +, function (cb) { + d.loadDatabase(function (err) { + if (err) { return cb(err); } + if (config.program.withIndex) { d.ensureIndex({ fieldName: 'docNumber' }); } + cb(); + }); + } +, function (cb) { profiler.beginProfiling(); return cb(); } +, async.apply(commonUtilities.insertDocs, d, n, profiler) +, function (cb) { setTimeout(function () {cb();}, 500); } +, async.apply(commonUtilities.findOneDocs, d, n, profiler) +], function (err) { + profiler.step("Benchmark finished"); + + if (err) { return console.log("An error was encountered: ", err); } +}); diff --git a/src/node_modules/nedb/benchmarks/findWithIn.js b/src/node_modules/nedb/benchmarks/findWithIn.js new file mode 100644 index 0000000..caff912 --- /dev/null +++ b/src/node_modules/nedb/benchmarks/findWithIn.js @@ -0,0 +1,30 @@ +var Datastore = require('../lib/datastore') + , benchDb = 'workspace/find.bench.db' + , fs = require('fs') + , path = require('path') + , async = require('async') + , execTime = require('exec-time') + , profiler = new execTime('FIND BENCH') + , commonUtilities = require('./commonUtilities') + , config = commonUtilities.getConfiguration(benchDb) + , d = config.d + , n = config.n + ; + +async.waterfall([ + async.apply(commonUtilities.prepareDb, benchDb) +, function (cb) { + d.loadDatabase(function (err) { + if (err) { return cb(err); } + if (config.program.withIndex) { d.ensureIndex({ fieldName: 'docNumber' }); } + cb(); + }); + } +, function (cb) { profiler.beginProfiling(); return cb(); } +, async.apply(commonUtilities.insertDocs, d, n, profiler) +, async.apply(commonUtilities.findDocsWithIn, d, n, profiler) +], function (err) { + profiler.step("Benchmark finished"); + + if (err) { return console.log("An error was encountered: ", err); } +}); diff --git a/src/node_modules/nedb/benchmarks/insert.js b/src/node_modules/nedb/benchmarks/insert.js new file mode 100644 index 0000000..c4dfb1d --- /dev/null +++ b/src/node_modules/nedb/benchmarks/insert.js @@ -0,0 +1,33 @@ +var Datastore = require('../lib/datastore') + , benchDb = 'workspace/insert.bench.db' + , async = require('async') + , execTime = require('exec-time') + , profiler = new execTime('INSERT BENCH') + , commonUtilities = require('./commonUtilities') + , config = commonUtilities.getConfiguration(benchDb) + , d = config.d + , n = config.n + ; + +async.waterfall([ + async.apply(commonUtilities.prepareDb, benchDb) +, function (cb) { + d.loadDatabase(function (err) { + if (err) { return cb(err); } + if (config.program.withIndex) { + d.ensureIndex({ fieldName: 'docNumber' }); + n = 2 * n; // We will actually insert twice as many documents + // because the index is slower when the collection is already + // big. So the result given by the algorithm will be a bit worse than + // actual performance + } + cb(); + }); + } +, function (cb) { profiler.beginProfiling(); return cb(); } +, async.apply(commonUtilities.insertDocs, d, n, profiler) +], function (err) { + profiler.step("Benchmark finished"); + + if (err) { return console.log("An error was encountered: ", err); } +}); diff --git a/src/node_modules/nedb/benchmarks/loadDatabase.js b/src/node_modules/nedb/benchmarks/loadDatabase.js new file mode 100644 index 0000000..4f7e964 --- /dev/null +++ b/src/node_modules/nedb/benchmarks/loadDatabase.js @@ -0,0 +1,38 @@ +var Datastore = require('../lib/datastore') + , benchDb = 'workspace/loaddb.bench.db' + , fs = require('fs') + , path = require('path') + , async = require('async') + , commonUtilities = require('./commonUtilities') + , execTime = require('exec-time') + , profiler = new execTime('LOADDB BENCH') + , d = new Datastore(benchDb) + , program = require('commander') + , n + ; + +program + .option('-n --number [number]', 'Size of the collection to test on', parseInt) + .option('-i --with-index', 'Test with an index') + .parse(process.argv); + +n = program.number || 10000; + +console.log("----------------------------"); +console.log("Test with " + n + " documents"); +console.log(program.withIndex ? "Use an index" : "Don't use an index"); +console.log("----------------------------"); + +async.waterfall([ + async.apply(commonUtilities.prepareDb, benchDb) +, function (cb) { + d.loadDatabase(cb); + } +, function (cb) { profiler.beginProfiling(); return cb(); } +, async.apply(commonUtilities.insertDocs, d, n, profiler) +, async.apply(commonUtilities.loadDatabase, d, n, profiler) +], function (err) { + profiler.step("Benchmark finished"); + + if (err) { return console.log("An error was encountered: ", err); } +}); diff --git a/src/node_modules/nedb/benchmarks/remove.js b/src/node_modules/nedb/benchmarks/remove.js new file mode 100644 index 0000000..cd28366 --- /dev/null +++ b/src/node_modules/nedb/benchmarks/remove.js @@ -0,0 +1,39 @@ +var Datastore = require('../lib/datastore') + , benchDb = 'workspace/remove.bench.db' + , fs = require('fs') + , path = require('path') + , async = require('async') + , execTime = require('exec-time') + , profiler = new execTime('REMOVE BENCH') + , commonUtilities = require('./commonUtilities') + , config = commonUtilities.getConfiguration(benchDb) + , d = config.d + , n = config.n + ; + +async.waterfall([ + async.apply(commonUtilities.prepareDb, benchDb) +, function (cb) { + d.loadDatabase(function (err) { + if (err) { return cb(err); } + if (config.program.withIndex) { d.ensureIndex({ fieldName: 'docNumber' }); } + cb(); + }); + } +, function (cb) { profiler.beginProfiling(); return cb(); } +, async.apply(commonUtilities.insertDocs, d, n, profiler) + +// Test with remove only one document +, function (cb) { profiler.step('MULTI: FALSE'); return cb(); } +, async.apply(commonUtilities.removeDocs, { multi: false }, d, n, profiler) + +// Test with multiple documents +, function (cb) { d.remove({}, { multi: true }, function () { return cb(); }); } +, async.apply(commonUtilities.insertDocs, d, n, profiler) +, function (cb) { profiler.step('MULTI: TRUE'); return cb(); } +, async.apply(commonUtilities.removeDocs, { multi: true }, d, n, profiler) +], function (err) { + profiler.step("Benchmark finished"); + + if (err) { return console.log("An error was encountered: ", err); } +}); diff --git a/src/node_modules/nedb/benchmarks/update.js b/src/node_modules/nedb/benchmarks/update.js new file mode 100644 index 0000000..81f5a08 --- /dev/null +++ b/src/node_modules/nedb/benchmarks/update.js @@ -0,0 +1,39 @@ +var Datastore = require('../lib/datastore') + , benchDb = 'workspace/update.bench.db' + , fs = require('fs') + , path = require('path') + , async = require('async') + , execTime = require('exec-time') + , profiler = new execTime('UPDATE BENCH') + , commonUtilities = require('./commonUtilities') + , config = commonUtilities.getConfiguration(benchDb) + , d = config.d + , n = config.n + ; + +async.waterfall([ + async.apply(commonUtilities.prepareDb, benchDb) +, function (cb) { + d.loadDatabase(function (err) { + if (err) { return cb(err); } + if (config.program.withIndex) { d.ensureIndex({ fieldName: 'docNumber' }); } + cb(); + }); + } +, function (cb) { profiler.beginProfiling(); return cb(); } +, async.apply(commonUtilities.insertDocs, d, n, profiler) + +// Test with update only one document +, function (cb) { profiler.step('MULTI: FALSE'); return cb(); } +, async.apply(commonUtilities.updateDocs, { multi: false }, d, n, profiler) + +// Test with multiple documents +, function (cb) { d.remove({}, { multi: true }, function (err) { return cb(); }); } +, async.apply(commonUtilities.insertDocs, d, n, profiler) +, function (cb) { profiler.step('MULTI: TRUE'); return cb(); } +, async.apply(commonUtilities.updateDocs, { multi: true }, d, n, profiler) +], function (err) { + profiler.step("Benchmark finished"); + + if (err) { return console.log("An error was encountered: ", err); } +}); diff --git a/src/node_modules/nedb/browser-version/browser-specific/lib/customUtils.js b/src/node_modules/nedb/browser-version/browser-specific/lib/customUtils.js new file mode 100644 index 0000000..d419162 --- /dev/null +++ b/src/node_modules/nedb/browser-version/browser-specific/lib/customUtils.js @@ -0,0 +1,78 @@ +/** + * 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; diff --git a/src/node_modules/nedb/browser-version/browser-specific/lib/storage.js b/src/node_modules/nedb/browser-version/browser-specific/lib/storage.js new file mode 100644 index 0000000..e81f776 --- /dev/null +++ b/src/node_modules/nedb/browser-version/browser-specific/lib/storage.js @@ -0,0 +1,96 @@ +/** + * 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; + diff --git a/src/node_modules/nedb/browser-version/build.js b/src/node_modules/nedb/browser-version/build.js new file mode 100644 index 0000000..c6dc7dc --- /dev/null +++ b/src/node_modules/nedb/browser-version/build.js @@ -0,0 +1,101 @@ +/** + * Build the browser version of nedb + */ + +var fs = require('fs') + , path = require('path') + , child_process = require('child_process') + , toCopy = ['lib', 'node_modules'] + , async, browserify, uglify + ; + +// Ensuring both node_modules (the source one and build one), src and out directories exist +function ensureDirExists (name) { + try { + fs.mkdirSync(path.join(__dirname, name)); + } catch (e) { + if (e.code !== 'EEXIST') { + console.log("Error ensuring that node_modules exists"); + process.exit(1); + } + } +} +ensureDirExists('../node_modules'); +ensureDirExists('node_modules'); +ensureDirExists('out'); +ensureDirExists('src'); + + +// Installing build dependencies and require them +console.log("Installing build dependencies"); +child_process.exec('npm install', { cwd: __dirname }, function (err, stdout, stderr) { + if (err) { console.log("Error reinstalling dependencies"); process.exit(1); } + + fs = require('fs-extra'); + async = require('async'); + browserify = require('browserify'); + uglify = require('uglify-js'); + + async.waterfall([ + function (cb) { + console.log("Installing source dependencies if needed"); + + child_process.exec('npm install', { cwd: path.join(__dirname, '..') }, function (err) { return cb(err); }); + } + , function (cb) { + console.log("Removing contents of the src directory"); + + async.eachSeries(fs.readdirSync(path.join(__dirname, 'src')), function (item, _cb) { + fs.remove(path.join(__dirname, 'src', item), _cb); + }, cb); + } + , function (cb) { + console.log("Copying source files"); + + async.eachSeries(toCopy, function (item, _cb) { + fs.copy(path.join(__dirname, '..', item), path.join(__dirname, 'src', item), _cb); + }, cb); + } + , function (cb) { + console.log("Copying browser specific files to replace their server-specific counterparts"); + + async.eachSeries(fs.readdirSync(path.join(__dirname, 'browser-specific')), function (item, _cb) { + fs.copy(path.join(__dirname, 'browser-specific', item), path.join(__dirname, 'src', item), _cb); + }, cb); + } + , function (cb) { + console.log("Browserifying the code"); + + var b = browserify() + , srcPath = path.join(__dirname, 'src/lib/datastore.js'); + + b.add(srcPath); + b.bundle({ standalone: 'Nedb' }, function (err, out) { + if (err) { return cb(err); } + fs.writeFile(path.join(__dirname, 'out/nedb.js'), out, 'utf8', function (err) { + if (err) { + return cb(err); + } else { + return cb(null, out); + } + }); + }); + } + , function (out, cb) { + console.log("Creating the minified version"); + + var compressedCode = uglify.minify(out, { fromString: true }); + fs.writeFile(path.join(__dirname, 'out/nedb.min.js'), compressedCode.code, 'utf8', cb); + } + ], function (err) { + if (err) { + console.log("Error during build"); + console.log(err); + } else { + console.log("Build finished with success"); + } + }); +}); + + + diff --git a/src/node_modules/nedb/browser-version/out/nedb.js b/src/node_modules/nedb/browser-version/out/nedb.js new file mode 100644 index 0000000..edae81c --- /dev/null +++ b/src/node_modules/nedb/browser-version/out/nedb.js @@ -0,0 +1,6446 @@ +(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 + + + + + + + + + diff --git a/src/node_modules/nedb/browser-version/test/mocha.css b/src/node_modules/nedb/browser-version/test/mocha.css new file mode 100644 index 0000000..799de1e --- /dev/null +++ b/src/node_modules/nedb/browser-version/test/mocha.css @@ -0,0 +1,199 @@ +@charset "UTF-8"; +body { + font: 20px/1.5 "Helvetica Neue", Helvetica, Arial, sans-serif; + padding: 60px 50px; +} + +#mocha ul, #mocha li { + margin: 0; + padding: 0; +} + +#mocha ul { + list-style: none; +} + +#mocha h1, #mocha h2 { + margin: 0; +} + +#mocha h1 { + margin-top: 15px; + font-size: 1em; + font-weight: 200; +} + +#mocha h1 a { + text-decoration: none; + color: inherit; +} + +#mocha h1 a:hover { + text-decoration: underline; +} + +#mocha .suite .suite h1 { + margin-top: 0; + font-size: .8em; +} + +#mocha h2 { + font-size: 12px; + font-weight: normal; + cursor: pointer; +} + +#mocha .suite { + margin-left: 15px; +} + +#mocha .test { + margin-left: 15px; +} + +#mocha .test:hover h2::after { + position: relative; + top: 0; + right: -10px; + content: '(view source)'; + font-size: 12px; + font-family: arial; + color: #888; +} + +#mocha .test.pending:hover h2::after { + content: '(pending)'; + font-family: arial; +} + +#mocha .test.pass.medium .duration { + background: #C09853; +} + +#mocha .test.pass.slow .duration { + background: #B94A48; +} + +#mocha .test.pass::before { + content: '✓'; + font-size: 12px; + display: block; + float: left; + margin-right: 5px; + color: #00d6b2; +} + +#mocha .test.pass .duration { + font-size: 9px; + margin-left: 5px; + padding: 2px 5px; + color: white; + -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.2); + -moz-box-shadow: inset 0 1px 1px rgba(0,0,0,.2); + box-shadow: inset 0 1px 1px rgba(0,0,0,.2); + -webkit-border-radius: 5px; + -moz-border-radius: 5px; + -ms-border-radius: 5px; + -o-border-radius: 5px; + border-radius: 5px; +} + +#mocha .test.pass.fast .duration { + display: none; +} + +#mocha .test.pending { + color: #0b97c4; +} + +#mocha .test.pending::before { + content: '◦'; + color: #0b97c4; +} + +#mocha .test.fail { + color: #c00; +} + +#mocha .test.fail pre { + color: black; +} + +#mocha .test.fail::before { + content: '✖'; + font-size: 12px; + display: block; + float: left; + margin-right: 5px; + color: #c00; +} + +#mocha .test pre.error { + color: #c00; +} + +#mocha .test pre { + display: inline-block; + font: 12px/1.5 monaco, monospace; + margin: 5px; + padding: 15px; + border: 1px solid #eee; + border-bottom-color: #ddd; + -webkit-border-radius: 3px; + -webkit-box-shadow: 0 1px 3px #eee; +} + +#report.pass .test.fail { + display: none; +} + +#report.fail .test.pass { + display: none; +} + +#error { + color: #c00; + font-size: 1.5 em; + font-weight: 100; + letter-spacing: 1px; +} + +#stats { + position: fixed; + top: 15px; + right: 10px; + font-size: 12px; + margin: 0; + color: #888; +} + +#stats .progress { + float: right; + padding-top: 0; +} + +#stats em { + color: black; +} + +#stats a { + text-decoration: none; + color: inherit; +} + +#stats a:hover { + border-bottom: 1px solid #eee; +} + +#stats li { + display: inline-block; + margin: 0 5px; + list-style: none; + padding-top: 11px; +} + +code .comment { color: #ddd } +code .init { color: #2F6FAD } +code .string { color: #5890AD } +code .keyword { color: #8A6343 } +code .number { color: #2F6FAD } diff --git a/src/node_modules/nedb/browser-version/test/mocha.js b/src/node_modules/nedb/browser-version/test/mocha.js new file mode 100644 index 0000000..ae6b10d --- /dev/null +++ b/src/node_modules/nedb/browser-version/test/mocha.js @@ -0,0 +1,4859 @@ +;(function(){ + + +// CommonJS require() + +function require(p){ + var path = require.resolve(p) + , mod = require.modules[path]; + if (!mod) throw new Error('failed to require "' + p + '"'); + if (!mod.exports) { + mod.exports = {}; + mod.call(mod.exports, mod, mod.exports, require.relative(path)); + } + return mod.exports; + } + +require.modules = {}; + +require.resolve = function (path){ + var orig = path + , reg = path + '.js' + , index = path + '/index.js'; + return require.modules[reg] && reg + || require.modules[index] && index + || orig; + }; + +require.register = function (path, fn){ + require.modules[path] = fn; + }; + +require.relative = function (parent) { + return function(p){ + if ('.' != p.charAt(0)) return require(p); + + var path = parent.split('/') + , segs = p.split('/'); + path.pop(); + + for (var i = 0; i < segs.length; i++) { + var seg = segs[i]; + if ('..' == seg) path.pop(); + else if ('.' != seg) path.push(seg); + } + + return require(path.join('/')); + }; + }; + + +require.register("browser/debug.js", function(module, exports, require){ + +module.exports = function(type){ + return function(){ + + } +}; +}); // module: browser/debug.js + +require.register("browser/diff.js", function(module, exports, require){ + +}); // module: browser/diff.js + +require.register("browser/events.js", function(module, exports, require){ + +/** + * Module exports. + */ + +exports.EventEmitter = EventEmitter; + +/** + * Check if `obj` is an array. + */ + +function isArray(obj) { + return '[object Array]' == {}.toString.call(obj); +} + +/** + * Event emitter constructor. + * + * @api public + */ + +function EventEmitter(){}; + +/** + * Adds a listener. + * + * @api public + */ + +EventEmitter.prototype.on = function (name, fn) { + if (!this.$events) { + this.$events = {}; + } + + if (!this.$events[name]) { + this.$events[name] = fn; + } else if (isArray(this.$events[name])) { + this.$events[name].push(fn); + } else { + this.$events[name] = [this.$events[name], fn]; + } + + return this; +}; + +EventEmitter.prototype.addListener = EventEmitter.prototype.on; + +/** + * Adds a volatile listener. + * + * @api public + */ + +EventEmitter.prototype.once = function (name, fn) { + var self = this; + + function on () { + self.removeListener(name, on); + fn.apply(this, arguments); + }; + + on.listener = fn; + this.on(name, on); + + return this; +}; + +/** + * Removes a listener. + * + * @api public + */ + +EventEmitter.prototype.removeListener = function (name, fn) { + if (this.$events && this.$events[name]) { + var list = this.$events[name]; + + if (isArray(list)) { + var pos = -1; + + for (var i = 0, l = list.length; i < l; i++) { + if (list[i] === fn || (list[i].listener && list[i].listener === fn)) { + pos = i; + break; + } + } + + if (pos < 0) { + return this; + } + + list.splice(pos, 1); + + if (!list.length) { + delete this.$events[name]; + } + } else if (list === fn || (list.listener && list.listener === fn)) { + delete this.$events[name]; + } + } + + return this; +}; + +/** + * Removes all listeners for an event. + * + * @api public + */ + +EventEmitter.prototype.removeAllListeners = function (name) { + if (name === undefined) { + this.$events = {}; + return this; + } + + if (this.$events && this.$events[name]) { + this.$events[name] = null; + } + + return this; +}; + +/** + * Gets all listeners for a certain event. + * + * @api public + */ + +EventEmitter.prototype.listeners = function (name) { + if (!this.$events) { + this.$events = {}; + } + + if (!this.$events[name]) { + this.$events[name] = []; + } + + if (!isArray(this.$events[name])) { + this.$events[name] = [this.$events[name]]; + } + + return this.$events[name]; +}; + +/** + * Emits an event. + * + * @api public + */ + +EventEmitter.prototype.emit = function (name) { + if (!this.$events) { + return false; + } + + var handler = this.$events[name]; + + if (!handler) { + return false; + } + + var args = [].slice.call(arguments, 1); + + if ('function' == typeof handler) { + handler.apply(this, args); + } else if (isArray(handler)) { + var listeners = handler.slice(); + + for (var i = 0, l = listeners.length; i < l; i++) { + listeners[i].apply(this, args); + } + } else { + return false; + } + + return true; +}; +}); // module: browser/events.js + +require.register("browser/fs.js", function(module, exports, require){ + +}); // module: browser/fs.js + +require.register("browser/path.js", function(module, exports, require){ + +}); // module: browser/path.js + +require.register("browser/progress.js", function(module, exports, require){ + +/** + * Expose `Progress`. + */ + +module.exports = Progress; + +/** + * Initialize a new `Progress` indicator. + */ + +function Progress() { + this.percent = 0; + this.size(0); + this.fontSize(11); + this.font('helvetica, arial, sans-serif'); +} + +/** + * Set progress size to `n`. + * + * @param {Number} n + * @return {Progress} for chaining + * @api public + */ + +Progress.prototype.size = function(n){ + this._size = n; + return this; +}; + +/** + * Set text to `str`. + * + * @param {String} str + * @return {Progress} for chaining + * @api public + */ + +Progress.prototype.text = function(str){ + this._text = str; + return this; +}; + +/** + * Set font size to `n`. + * + * @param {Number} n + * @return {Progress} for chaining + * @api public + */ + +Progress.prototype.fontSize = function(n){ + this._fontSize = n; + return this; +}; + +/** + * Set font `family`. + * + * @param {String} family + * @return {Progress} for chaining + */ + +Progress.prototype.font = function(family){ + this._font = family; + return this; +}; + +/** + * Update percentage to `n`. + * + * @param {Number} n + * @return {Progress} for chaining + */ + +Progress.prototype.update = function(n){ + this.percent = n; + return this; +}; + +/** + * Draw on `ctx`. + * + * @param {CanvasRenderingContext2d} ctx + * @return {Progress} for chaining + */ + +Progress.prototype.draw = function(ctx){ + var percent = Math.min(this.percent, 100) + , size = this._size + , half = size / 2 + , x = half + , y = half + , rad = half - 1 + , fontSize = this._fontSize; + + ctx.font = fontSize + 'px ' + this._font; + + var angle = Math.PI * 2 * (percent / 100); + ctx.clearRect(0, 0, size, size); + + // outer circle + ctx.strokeStyle = '#9f9f9f'; + ctx.beginPath(); + ctx.arc(x, y, rad, 0, angle, false); + ctx.stroke(); + + // inner circle + ctx.strokeStyle = '#eee'; + ctx.beginPath(); + ctx.arc(x, y, rad - 1, 0, angle, true); + ctx.stroke(); + + // text + var text = this._text || (percent | 0) + '%' + , w = ctx.measureText(text).width; + + ctx.fillText( + text + , x - w / 2 + 1 + , y + fontSize / 2 - 1); + + return this; +}; + +}); // module: browser/progress.js + +require.register("browser/tty.js", function(module, exports, require){ + +exports.isatty = function(){ + return true; +}; + +exports.getWindowSize = function(){ + return [window.innerHeight, window.innerWidth]; +}; +}); // module: browser/tty.js + +require.register("context.js", function(module, exports, require){ + +/** + * Expose `Context`. + */ + +module.exports = Context; + +/** + * Initialize a new `Context`. + * + * @api private + */ + +function Context(){} + +/** + * Set or get the context `Runnable` to `runnable`. + * + * @param {Runnable} runnable + * @return {Context} + * @api private + */ + +Context.prototype.runnable = function(runnable){ + if (0 == arguments.length) return this._runnable; + this.test = this._runnable = runnable; + return this; +}; + +/** + * Set test timeout `ms`. + * + * @param {Number} ms + * @return {Context} self + * @api private + */ + +Context.prototype.timeout = function(ms){ + this.runnable().timeout(ms); + return this; +}; + +/** + * Set test slowness threshold `ms`. + * + * @param {Number} ms + * @return {Context} self + * @api private + */ + +Context.prototype.slow = function(ms){ + this.runnable().slow(ms); + return this; +}; + +/** + * Inspect the context void of `._runnable`. + * + * @return {String} + * @api private + */ + +Context.prototype.inspect = function(){ + return JSON.stringify(this, function(key, val){ + if ('_runnable' == key) return; + if ('test' == key) return; + return val; + }, 2); +}; + +}); // module: context.js + +require.register("hook.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Runnable = require('./runnable'); + +/** + * Expose `Hook`. + */ + +module.exports = Hook; + +/** + * Initialize a new `Hook` with the given `title` and callback `fn`. + * + * @param {String} title + * @param {Function} fn + * @api private + */ + +function Hook(title, fn) { + Runnable.call(this, title, fn); + this.type = 'hook'; +} + +/** + * Inherit from `Runnable.prototype`. + */ + +Hook.prototype = new Runnable; +Hook.prototype.constructor = Hook; + + +/** + * Get or set the test `err`. + * + * @param {Error} err + * @return {Error} + * @api public + */ + +Hook.prototype.error = function(err){ + if (0 == arguments.length) { + var err = this._error; + this._error = null; + return err; + } + + this._error = err; +}; + + +}); // module: hook.js + +require.register("interfaces/bdd.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Suite = require('../suite') + , Test = require('../test'); + +/** + * BDD-style interface: + * + * describe('Array', function(){ + * describe('#indexOf()', function(){ + * it('should return -1 when not present', function(){ + * + * }); + * + * it('should return the index when present', function(){ + * + * }); + * }); + * }); + * + */ + +module.exports = function(suite){ + var suites = [suite]; + + suite.on('pre-require', function(context, file, mocha){ + + /** + * Execute before running tests. + */ + + context.before = function(fn){ + suites[0].beforeAll(fn); + }; + + /** + * Execute after running tests. + */ + + context.after = function(fn){ + suites[0].afterAll(fn); + }; + + /** + * Execute before each test case. + */ + + context.beforeEach = function(fn){ + suites[0].beforeEach(fn); + }; + + /** + * Execute after each test case. + */ + + context.afterEach = function(fn){ + suites[0].afterEach(fn); + }; + + /** + * Describe a "suite" with the given `title` + * and callback `fn` containing nested suites + * and/or tests. + */ + + context.describe = context.context = function(title, fn){ + var suite = Suite.create(suites[0], title); + suites.unshift(suite); + fn(); + suites.shift(); + return suite; + }; + + /** + * Pending describe. + */ + + context.xdescribe = + context.xcontext = + context.describe.skip = function(title, fn){ + var suite = Suite.create(suites[0], title); + suite.pending = true; + suites.unshift(suite); + fn(); + suites.shift(); + }; + + /** + * Exclusive suite. + */ + + context.describe.only = function(title, fn){ + var suite = context.describe(title, fn); + mocha.grep(suite.fullTitle()); + }; + + /** + * Describe a specification or test-case + * with the given `title` and callback `fn` + * acting as a thunk. + */ + + context.it = context.specify = function(title, fn){ + var suite = suites[0]; + if (suite.pending) var fn = null; + var test = new Test(title, fn); + suite.addTest(test); + return test; + }; + + /** + * Exclusive test-case. + */ + + context.it.only = function(title, fn){ + var test = context.it(title, fn); + mocha.grep(test.fullTitle()); + }; + + /** + * Pending test case. + */ + + context.xit = + context.xspecify = + context.it.skip = function(title){ + context.it(title); + }; + }); +}; + +}); // module: interfaces/bdd.js + +require.register("interfaces/exports.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Suite = require('../suite') + , Test = require('../test'); + +/** + * TDD-style interface: + * + * exports.Array = { + * '#indexOf()': { + * 'should return -1 when the value is not present': function(){ + * + * }, + * + * 'should return the correct index when the value is present': function(){ + * + * } + * } + * }; + * + */ + +module.exports = function(suite){ + var suites = [suite]; + + suite.on('require', visit); + + function visit(obj) { + var suite; + for (var key in obj) { + if ('function' == typeof obj[key]) { + var fn = obj[key]; + switch (key) { + case 'before': + suites[0].beforeAll(fn); + break; + case 'after': + suites[0].afterAll(fn); + break; + case 'beforeEach': + suites[0].beforeEach(fn); + break; + case 'afterEach': + suites[0].afterEach(fn); + break; + default: + suites[0].addTest(new Test(key, fn)); + } + } else { + var suite = Suite.create(suites[0], key); + suites.unshift(suite); + visit(obj[key]); + suites.shift(); + } + } + } +}; +}); // module: interfaces/exports.js + +require.register("interfaces/index.js", function(module, exports, require){ + +exports.bdd = require('./bdd'); +exports.tdd = require('./tdd'); +exports.qunit = require('./qunit'); +exports.exports = require('./exports'); + +}); // module: interfaces/index.js + +require.register("interfaces/qunit.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Suite = require('../suite') + , Test = require('../test'); + +/** + * QUnit-style interface: + * + * suite('Array'); + * + * test('#length', function(){ + * var arr = [1,2,3]; + * ok(arr.length == 3); + * }); + * + * test('#indexOf()', function(){ + * var arr = [1,2,3]; + * ok(arr.indexOf(1) == 0); + * ok(arr.indexOf(2) == 1); + * ok(arr.indexOf(3) == 2); + * }); + * + * suite('String'); + * + * test('#length', function(){ + * ok('foo'.length == 3); + * }); + * + */ + +module.exports = function(suite){ + var suites = [suite]; + + suite.on('pre-require', function(context){ + + /** + * Execute before running tests. + */ + + context.before = function(fn){ + suites[0].beforeAll(fn); + }; + + /** + * Execute after running tests. + */ + + context.after = function(fn){ + suites[0].afterAll(fn); + }; + + /** + * Execute before each test case. + */ + + context.beforeEach = function(fn){ + suites[0].beforeEach(fn); + }; + + /** + * Execute after each test case. + */ + + context.afterEach = function(fn){ + suites[0].afterEach(fn); + }; + + /** + * Describe a "suite" with the given `title`. + */ + + context.suite = function(title){ + if (suites.length > 1) suites.shift(); + var suite = Suite.create(suites[0], title); + suites.unshift(suite); + }; + + /** + * Describe a specification or test-case + * with the given `title` and callback `fn` + * acting as a thunk. + */ + + context.test = function(title, fn){ + suites[0].addTest(new Test(title, fn)); + }; + }); +}; + +}); // module: interfaces/qunit.js + +require.register("interfaces/tdd.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Suite = require('../suite') + , Test = require('../test'); + +/** + * TDD-style interface: + * + * suite('Array', function(){ + * suite('#indexOf()', function(){ + * suiteSetup(function(){ + * + * }); + * + * test('should return -1 when not present', function(){ + * + * }); + * + * test('should return the index when present', function(){ + * + * }); + * + * suiteTeardown(function(){ + * + * }); + * }); + * }); + * + */ + +module.exports = function(suite){ + var suites = [suite]; + + suite.on('pre-require', function(context, file, mocha){ + + /** + * Execute before each test case. + */ + + context.setup = function(fn){ + suites[0].beforeEach(fn); + }; + + /** + * Execute after each test case. + */ + + context.teardown = function(fn){ + suites[0].afterEach(fn); + }; + + /** + * Execute before the suite. + */ + + context.suiteSetup = function(fn){ + suites[0].beforeAll(fn); + }; + + /** + * Execute after the suite. + */ + + context.suiteTeardown = function(fn){ + suites[0].afterAll(fn); + }; + + /** + * Describe a "suite" with the given `title` + * and callback `fn` containing nested suites + * and/or tests. + */ + + context.suite = function(title, fn){ + var suite = Suite.create(suites[0], title); + suites.unshift(suite); + fn(); + suites.shift(); + return suite; + }; + + /** + * Exclusive test-case. + */ + + context.suite.only = function(title, fn){ + var suite = context.suite(title, fn); + mocha.grep(suite.fullTitle()); + }; + + /** + * Describe a specification or test-case + * with the given `title` and callback `fn` + * acting as a thunk. + */ + + context.test = function(title, fn){ + var test = new Test(title, fn); + suites[0].addTest(test); + return test; + }; + + /** + * Exclusive test-case. + */ + + context.test.only = function(title, fn){ + var test = context.test(title, fn); + mocha.grep(test.fullTitle()); + }; + }); +}; + +}); // module: interfaces/tdd.js + +require.register("mocha.js", function(module, exports, require){ +/*! + * mocha + * Copyright(c) 2011 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var path = require('browser/path') + , utils = require('./utils'); + +/** + * Expose `Mocha`. + */ + +exports = module.exports = Mocha; + +/** + * Expose internals. + */ + +exports.utils = utils; +exports.interfaces = require('./interfaces'); +exports.reporters = require('./reporters'); +exports.Runnable = require('./runnable'); +exports.Context = require('./context'); +exports.Runner = require('./runner'); +exports.Suite = require('./suite'); +exports.Hook = require('./hook'); +exports.Test = require('./test'); + +/** + * Return image `name` path. + * + * @param {String} name + * @return {String} + * @api private + */ + +function image(name) { + return __dirname + '/../images/' + name + '.png'; +} + +/** + * Setup mocha with `options`. + * + * Options: + * + * - `ui` name "bdd", "tdd", "exports" etc + * - `reporter` reporter instance, defaults to `mocha.reporters.Dot` + * - `globals` array of accepted globals + * - `timeout` timeout in milliseconds + * - `slow` milliseconds to wait before considering a test slow + * - `ignoreLeaks` ignore global leaks + * - `grep` string or regexp to filter tests with + * + * @param {Object} options + * @api public + */ + +function Mocha(options) { + options = options || {}; + this.files = []; + this.options = options; + this.grep(options.grep); + this.suite = new exports.Suite('', new exports.Context); + this.ui(options.ui); + this.reporter(options.reporter); + if (options.timeout) this.timeout(options.timeout); + if (options.slow) this.slow(options.slow); +} + +/** + * Add test `file`. + * + * @param {String} file + * @api public + */ + +Mocha.prototype.addFile = function(file){ + this.files.push(file); + return this; +}; + +/** + * Set reporter to `reporter`, defaults to "dot". + * + * @param {String|Function} reporter name of a reporter or a reporter constructor + * @api public + */ + +Mocha.prototype.reporter = function(reporter){ + if ('function' == typeof reporter) { + this._reporter = reporter; + } else { + reporter = reporter || 'dot'; + try { + this._reporter = require('./reporters/' + reporter); + } catch (err) { + this._reporter = require(reporter); + } + if (!this._reporter) throw new Error('invalid reporter "' + reporter + '"'); + } + return this; +}; + +/** + * Set test UI `name`, defaults to "bdd". + * + * @param {String} bdd + * @api public + */ + +Mocha.prototype.ui = function(name){ + name = name || 'bdd'; + this._ui = exports.interfaces[name]; + if (!this._ui) throw new Error('invalid interface "' + name + '"'); + this._ui = this._ui(this.suite); + return this; +}; + +/** + * Load registered files. + * + * @api private + */ + +Mocha.prototype.loadFiles = function(fn){ + var self = this; + var suite = this.suite; + var pending = this.files.length; + this.files.forEach(function(file){ + file = path.resolve(file); + suite.emit('pre-require', global, file, self); + suite.emit('require', require(file), file, self); + suite.emit('post-require', global, file, self); + --pending || (fn && fn()); + }); +}; + +/** + * Enable growl support. + * + * @api private + */ + +Mocha.prototype._growl = function(runner, reporter) { + var notify = require('growl'); + + runner.on('end', function(){ + var stats = reporter.stats; + if (stats.failures) { + var msg = stats.failures + ' of ' + runner.total + ' tests failed'; + notify(msg, { name: 'mocha', title: 'Failed', image: image('error') }); + } else { + notify(stats.passes + ' tests passed in ' + stats.duration + 'ms', { + name: 'mocha' + , title: 'Passed' + , image: image('ok') + }); + } + }); +}; + +/** + * Add regexp to grep, if `re` is a string it is escaped. + * + * @param {RegExp|String} re + * @return {Mocha} + * @api public + */ + +Mocha.prototype.grep = function(re){ + this.options.grep = 'string' == typeof re + ? new RegExp(utils.escapeRegexp(re)) + : re; + return this; +}; + +/** + * Invert `.grep()` matches. + * + * @return {Mocha} + * @api public + */ + +Mocha.prototype.invert = function(){ + this.options.invert = true; + return this; +}; + +/** + * Ignore global leaks. + * + * @return {Mocha} + * @api public + */ + +Mocha.prototype.ignoreLeaks = function(){ + this.options.ignoreLeaks = true; + return this; +}; + +/** + * Enable global leak checking. + * + * @return {Mocha} + * @api public + */ + +Mocha.prototype.checkLeaks = function(){ + this.options.ignoreLeaks = false; + return this; +}; + +/** + * Enable growl support. + * + * @return {Mocha} + * @api public + */ + +Mocha.prototype.growl = function(){ + this.options.growl = true; + return this; +}; + +/** + * Ignore `globals` array or string. + * + * @param {Array|String} globals + * @return {Mocha} + * @api public + */ + +Mocha.prototype.globals = function(globals){ + this.options.globals = (this.options.globals || []).concat(globals); + return this; +}; + +/** + * Set the timeout in milliseconds. + * + * @param {Number} timeout + * @return {Mocha} + * @api public + */ + +Mocha.prototype.timeout = function(timeout){ + this.suite.timeout(timeout); + return this; +}; + +/** + * Set slowness threshold in milliseconds. + * + * @param {Number} slow + * @return {Mocha} + * @api public + */ + +Mocha.prototype.slow = function(slow){ + this.suite.slow(slow); + return this; +}; + +/** + * Run tests and invoke `fn()` when complete. + * + * @param {Function} fn + * @return {Runner} + * @api public + */ + +Mocha.prototype.run = function(fn){ + if (this.files.length) this.loadFiles(); + var suite = this.suite; + var options = this.options; + var runner = new exports.Runner(suite); + var reporter = new this._reporter(runner); + runner.ignoreLeaks = options.ignoreLeaks; + if (options.grep) runner.grep(options.grep, options.invert); + if (options.globals) runner.globals(options.globals); + if (options.growl) this._growl(runner, reporter); + return runner.run(fn); +}; + +}); // module: mocha.js + +require.register("ms.js", function(module, exports, require){ + +/** + * Helpers. + */ + +var s = 1000; +var m = s * 60; +var h = m * 60; +var d = h * 24; + +/** + * Parse or format the given `val`. + * + * @param {String|Number} val + * @return {String|Number} + * @api public + */ + +module.exports = function(val){ + if ('string' == typeof val) return parse(val); + return format(val); +} + +/** + * Parse the given `str` and return milliseconds. + * + * @param {String} str + * @return {Number} + * @api private + */ + +function parse(str) { + var m = /^((?:\d+)?\.?\d+) *(ms|seconds?|s|minutes?|m|hours?|h|days?|d|years?|y)?$/i.exec(str); + if (!m) return; + var n = parseFloat(m[1]); + var type = (m[2] || 'ms').toLowerCase(); + switch (type) { + case 'years': + case 'year': + case 'y': + return n * 31557600000; + case 'days': + case 'day': + case 'd': + return n * 86400000; + case 'hours': + case 'hour': + case 'h': + return n * 3600000; + case 'minutes': + case 'minute': + case 'm': + return n * 60000; + case 'seconds': + case 'second': + case 's': + return n * 1000; + case 'ms': + return n; + } +} + +/** + * Format the given `ms`. + * + * @param {Number} ms + * @return {String} + * @api public + */ + +function format(ms) { + if (ms == d) return (ms / d) + ' day'; + if (ms > d) return (ms / d) + ' days'; + if (ms == h) return (ms / h) + ' hour'; + if (ms > h) return (ms / h) + ' hours'; + if (ms == m) return (ms / m) + ' minute'; + if (ms > m) return (ms / m) + ' minutes'; + if (ms == s) return (ms / s) + ' second'; + if (ms > s) return (ms / s) + ' seconds'; + return ms + ' ms'; +} +}); // module: ms.js + +require.register("reporters/base.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var tty = require('browser/tty') + , diff = require('browser/diff') + , ms = require('../ms'); + +/** + * Save timer references to avoid Sinon interfering (see GH-237). + */ + +var Date = global.Date + , setTimeout = global.setTimeout + , setInterval = global.setInterval + , clearTimeout = global.clearTimeout + , clearInterval = global.clearInterval; + +/** + * Check if both stdio streams are associated with a tty. + */ + +var isatty = tty.isatty(1) && tty.isatty(2); + +/** + * Expose `Base`. + */ + +exports = module.exports = Base; + +/** + * Enable coloring by default. + */ + +exports.useColors = isatty; + +/** + * Default color map. + */ + +exports.colors = { + 'pass': 90 + , 'fail': 31 + , 'bright pass': 92 + , 'bright fail': 91 + , 'bright yellow': 93 + , 'pending': 36 + , 'suite': 0 + , 'error title': 0 + , 'error message': 31 + , 'error stack': 90 + , 'checkmark': 32 + , 'fast': 90 + , 'medium': 33 + , 'slow': 31 + , 'green': 32 + , 'light': 90 + , 'diff gutter': 90 + , 'diff added': 42 + , 'diff removed': 41 +}; + +/** + * Color `str` with the given `type`, + * allowing colors to be disabled, + * as well as user-defined color + * schemes. + * + * @param {String} type + * @param {String} str + * @return {String} + * @api private + */ + +var color = exports.color = function(type, str) { + if (!exports.useColors) return str; + return '\u001b[' + exports.colors[type] + 'm' + str + '\u001b[0m'; +}; + +/** + * Expose term window size, with some + * defaults for when stderr is not a tty. + */ + +exports.window = { + width: isatty + ? process.stdout.getWindowSize + ? process.stdout.getWindowSize(1)[0] + : tty.getWindowSize()[1] + : 75 +}; + +/** + * Expose some basic cursor interactions + * that are common among reporters. + */ + +exports.cursor = { + hide: function(){ + process.stdout.write('\u001b[?25l'); + }, + + show: function(){ + process.stdout.write('\u001b[?25h'); + }, + + deleteLine: function(){ + process.stdout.write('\u001b[2K'); + }, + + beginningOfLine: function(){ + process.stdout.write('\u001b[0G'); + }, + + CR: function(){ + exports.cursor.deleteLine(); + exports.cursor.beginningOfLine(); + } +}; + +/** + * Outut the given `failures` as a list. + * + * @param {Array} failures + * @api public + */ + +exports.list = function(failures){ + console.error(); + failures.forEach(function(test, i){ + // format + var fmt = color('error title', ' %s) %s:\n') + + color('error message', ' %s') + + color('error stack', '\n%s\n'); + + // msg + var err = test.err + , message = err.message || '' + , stack = err.stack || message + , index = stack.indexOf(message) + message.length + , msg = stack.slice(0, index) + , actual = err.actual + , expected = err.expected; + + // actual / expected diff + if ('string' == typeof actual && 'string' == typeof expected) { + var len = Math.max(actual.length, expected.length); + + if (len < 20) msg = errorDiff(err, 'Chars'); + else msg = errorDiff(err, 'Words'); + + // linenos + var lines = msg.split('\n'); + if (lines.length > 4) { + var width = String(lines.length).length; + msg = lines.map(function(str, i){ + return pad(++i, width) + ' |' + ' ' + str; + }).join('\n'); + } + + // legend + msg = '\n' + + color('diff removed', 'actual') + + ' ' + + color('diff added', 'expected') + + '\n\n' + + msg + + '\n'; + + // indent + msg = msg.replace(/^/gm, ' '); + + fmt = color('error title', ' %s) %s:\n%s') + + color('error stack', '\n%s\n'); + } + + // indent stack trace without msg + stack = stack.slice(index ? index + 1 : index) + .replace(/^/gm, ' '); + + console.error(fmt, (i + 1), test.fullTitle(), msg, stack); + }); +}; + +/** + * Initialize a new `Base` reporter. + * + * All other reporters generally + * inherit from this reporter, providing + * stats such as test duration, number + * of tests passed / failed etc. + * + * @param {Runner} runner + * @api public + */ + +function Base(runner) { + var self = this + , stats = this.stats = { suites: 0, tests: 0, passes: 0, pending: 0, failures: 0 } + , failures = this.failures = []; + + if (!runner) return; + this.runner = runner; + + runner.on('start', function(){ + stats.start = new Date; + }); + + runner.on('suite', function(suite){ + stats.suites = stats.suites || 0; + suite.root || stats.suites++; + }); + + runner.on('test end', function(test){ + stats.tests = stats.tests || 0; + stats.tests++; + }); + + runner.on('pass', function(test){ + stats.passes = stats.passes || 0; + + var medium = test.slow() / 2; + test.speed = test.duration > test.slow() + ? 'slow' + : test.duration > medium + ? 'medium' + : 'fast'; + + stats.passes++; + }); + + runner.on('fail', function(test, err){ + stats.failures = stats.failures || 0; + stats.failures++; + test.err = err; + failures.push(test); + }); + + runner.on('end', function(){ + stats.end = new Date; + stats.duration = new Date - stats.start; + }); + + runner.on('pending', function(){ + stats.pending++; + }); +} + +/** + * Output common epilogue used by many of + * the bundled reporters. + * + * @api public + */ + +Base.prototype.epilogue = function(){ + var stats = this.stats + , fmt + , tests; + + console.log(); + + function pluralize(n) { + return 1 == n ? 'test' : 'tests'; + } + + // failure + if (stats.failures) { + fmt = color('bright fail', ' ✖') + + color('fail', ' %d of %d %s failed') + + color('light', ':') + + console.error(fmt, + stats.failures, + this.runner.total, + pluralize(this.runner.total)); + + Base.list(this.failures); + console.error(); + return; + } + + // pass + fmt = color('bright pass', ' ✔') + + color('green', ' %d %s complete') + + color('light', ' (%s)'); + + console.log(fmt, + stats.tests || 0, + pluralize(stats.tests), + ms(stats.duration)); + + // pending + if (stats.pending) { + fmt = color('pending', ' •') + + color('pending', ' %d %s pending'); + + console.log(fmt, stats.pending, pluralize(stats.pending)); + } + + console.log(); +}; + +/** + * Pad the given `str` to `len`. + * + * @param {String} str + * @param {String} len + * @return {String} + * @api private + */ + +function pad(str, len) { + str = String(str); + return Array(len - str.length + 1).join(' ') + str; +} + +/** + * Return a character diff for `err`. + * + * @param {Error} err + * @return {String} + * @api private + */ + +function errorDiff(err, type) { + return diff['diff' + type](err.actual, err.expected).map(function(str){ + str.value = str.value + .replace(/\t/g, '') + .replace(/\r/g, '') + .replace(/\n/g, '\n'); + if (str.added) return colorLines('diff added', str.value); + if (str.removed) return colorLines('diff removed', str.value); + return str.value; + }).join(''); +} + +/** + * Color lines for `str`, using the color `name`. + * + * @param {String} name + * @param {String} str + * @return {String} + * @api private + */ + +function colorLines(name, str) { + return str.split('\n').map(function(str){ + return color(name, str); + }).join('\n'); +} + +}); // module: reporters/base.js + +require.register("reporters/doc.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Base = require('./base') + , utils = require('../utils'); + +/** + * Expose `Doc`. + */ + +exports = module.exports = Doc; + +/** + * Initialize a new `Doc` reporter. + * + * @param {Runner} runner + * @api public + */ + +function Doc(runner) { + Base.call(this, runner); + + var self = this + , stats = this.stats + , total = runner.total + , indents = 2; + + function indent() { + return Array(indents).join(' '); + } + + runner.on('suite', function(suite){ + if (suite.root) return; + ++indents; + console.log('%s
', indent()); + ++indents; + console.log('%s

%s

', indent(), suite.title); + console.log('%s
', indent()); + }); + + runner.on('suite end', function(suite){ + if (suite.root) return; + console.log('%s
', indent()); + --indents; + console.log('%s
', indent()); + --indents; + }); + + runner.on('pass', function(test){ + console.log('%s
%s
', indent(), test.title); + var code = utils.escape(utils.clean(test.fn.toString())); + console.log('%s
%s
', indent(), code); + }); +} + +}); // module: reporters/doc.js + +require.register("reporters/dot.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Base = require('./base') + , color = Base.color; + +/** + * Expose `Dot`. + */ + +exports = module.exports = Dot; + +/** + * Initialize a new `Dot` matrix test reporter. + * + * @param {Runner} runner + * @api public + */ + +function Dot(runner) { + Base.call(this, runner); + + var self = this + , stats = this.stats + , width = Base.window.width * .75 | 0 + , c = '․' + , n = 0; + + runner.on('start', function(){ + process.stdout.write('\n '); + }); + + runner.on('pending', function(test){ + process.stdout.write(color('pending', c)); + }); + + runner.on('pass', function(test){ + if (++n % width == 0) process.stdout.write('\n '); + if ('slow' == test.speed) { + process.stdout.write(color('bright yellow', c)); + } else { + process.stdout.write(color(test.speed, c)); + } + }); + + runner.on('fail', function(test, err){ + if (++n % width == 0) process.stdout.write('\n '); + process.stdout.write(color('fail', c)); + }); + + runner.on('end', function(){ + console.log(); + self.epilogue(); + }); +} + +/** + * Inherit from `Base.prototype`. + */ + +Dot.prototype = new Base; +Dot.prototype.constructor = Dot; + +}); // module: reporters/dot.js + +require.register("reporters/html-cov.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var JSONCov = require('./json-cov') + , fs = require('browser/fs'); + +/** + * Expose `HTMLCov`. + */ + +exports = module.exports = HTMLCov; + +/** + * Initialize a new `JsCoverage` reporter. + * + * @param {Runner} runner + * @api public + */ + +function HTMLCov(runner) { + var jade = require('jade') + , file = __dirname + '/templates/coverage.jade' + , str = fs.readFileSync(file, 'utf8') + , fn = jade.compile(str, { filename: file }) + , self = this; + + JSONCov.call(this, runner, false); + + runner.on('end', function(){ + process.stdout.write(fn({ + cov: self.cov + , coverageClass: coverageClass + })); + }); +} + +/** + * Return coverage class for `n`. + * + * @return {String} + * @api private + */ + +function coverageClass(n) { + if (n >= 75) return 'high'; + if (n >= 50) return 'medium'; + if (n >= 25) return 'low'; + return 'terrible'; +} +}); // module: reporters/html-cov.js + +require.register("reporters/html.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Base = require('./base') + , utils = require('../utils') + , Progress = require('../browser/progress') + , escape = utils.escape; + +/** + * Save timer references to avoid Sinon interfering (see GH-237). + */ + +var Date = global.Date + , setTimeout = global.setTimeout + , setInterval = global.setInterval + , clearTimeout = global.clearTimeout + , clearInterval = global.clearInterval; + +/** + * Expose `Doc`. + */ + +exports = module.exports = HTML; + +/** + * Stats template. + */ + +var statsTemplate = ''; + +/** + * Initialize a new `Doc` reporter. + * + * @param {Runner} runner + * @api public + */ + +function HTML(runner, root) { + Base.call(this, runner); + + var self = this + , stats = this.stats + , total = runner.total + , stat = fragment(statsTemplate) + , items = stat.getElementsByTagName('li') + , passes = items[1].getElementsByTagName('em')[0] + , passesLink = items[1].getElementsByTagName('a')[0] + , failures = items[2].getElementsByTagName('em')[0] + , failuresLink = items[2].getElementsByTagName('a')[0] + , duration = items[3].getElementsByTagName('em')[0] + , canvas = stat.getElementsByTagName('canvas')[0] + , report = fragment('
    ') + , stack = [report] + , progress + , ctx + + root = root || document.getElementById('mocha'); + + if (canvas.getContext) { + var ratio = window.devicePixelRatio || 1; + canvas.style.width = canvas.width; + canvas.style.height = canvas.height; + canvas.width *= ratio; + canvas.height *= ratio; + ctx = canvas.getContext('2d'); + ctx.scale(ratio, ratio); + progress = new Progress; + } + + if (!root) return error('#mocha div missing, add it to your document'); + + // pass toggle + on(passesLink, 'click', function () { + var className = /pass/.test(report.className) ? '' : ' pass'; + report.className = report.className.replace(/fail|pass/g, '') + className; + }); + + // failure toggle + on(failuresLink, 'click', function () { + var className = /fail/.test(report.className) ? '' : ' fail'; + report.className = report.className.replace(/fail|pass/g, '') + className; + }); + + root.appendChild(stat); + root.appendChild(report); + + if (progress) progress.size(40); + + runner.on('suite', function(suite){ + if (suite.root) return; + + // suite + var url = '?grep=' + encodeURIComponent(suite.fullTitle()); + var el = fragment('
  • %s

  • ', url, escape(suite.title)); + + // container + stack[0].appendChild(el); + stack.unshift(document.createElement('ul')); + el.appendChild(stack[0]); + }); + + runner.on('suite end', function(suite){ + if (suite.root) return; + stack.shift(); + }); + + runner.on('fail', function(test, err){ + if ('hook' == test.type || err.uncaught) runner.emit('test end', test); + }); + + runner.on('test end', function(test){ + window.scrollTo(0, document.body.scrollHeight); + + // TODO: add to stats + var percent = stats.tests / total * 100 | 0; + if (progress) progress.update(percent).draw(ctx); + + // update stats + var ms = new Date - stats.start; + text(passes, stats.passes); + text(failures, stats.failures); + text(duration, (ms / 1000).toFixed(2)); + + // test + if ('passed' == test.state) { + var el = fragment('
  • %e%ems

  • ', test.speed, test.title, test.duration); + } else if (test.pending) { + var el = fragment('
  • %e

  • ', test.title); + } else { + var el = fragment('
  • %e

  • ', test.title); + var str = test.err.stack || test.err.toString(); + + // FF / Opera do not add the message + if (!~str.indexOf(test.err.message)) { + str = test.err.message + '\n' + str; + } + + // <=IE7 stringifies to [Object Error]. Since it can be overloaded, we + // check for the result of the stringifying. + if ('[object Error]' == str) str = test.err.message; + + // Safari doesn't give you a stack. Let's at least provide a source line. + if (!test.err.stack && test.err.sourceURL && test.err.line !== undefined) { + str += "\n(" + test.err.sourceURL + ":" + test.err.line + ")"; + } + + el.appendChild(fragment('
    %e
    ', str)); + } + + // toggle code + // TODO: defer + if (!test.pending) { + var h2 = el.getElementsByTagName('h2')[0]; + + on(h2, 'click', function(){ + pre.style.display = 'none' == pre.style.display + ? 'inline-block' + : 'none'; + }); + + var pre = fragment('
    %e
    ', utils.clean(test.fn.toString())); + el.appendChild(pre); + pre.style.display = 'none'; + } + + stack[0].appendChild(el); + }); +} + +/** + * Display error `msg`. + */ + +function error(msg) { + document.body.appendChild(fragment('
    %s
    ', msg)); +} + +/** + * Return a DOM fragment from `html`. + */ + +function fragment(html) { + var args = arguments + , div = document.createElement('div') + , i = 1; + + div.innerHTML = html.replace(/%([se])/g, function(_, type){ + switch (type) { + case 's': return String(args[i++]); + case 'e': return escape(args[i++]); + } + }); + + return div.firstChild; +} + +/** + * Set `el` text to `str`. + */ + +function text(el, str) { + if (el.textContent) { + el.textContent = str; + } else { + el.innerText = str; + } +} + +/** + * Listen on `event` with callback `fn`. + */ + +function on(el, event, fn) { + if (el.addEventListener) { + el.addEventListener(event, fn, false); + } else { + el.attachEvent('on' + event, fn); + } +} + +}); // module: reporters/html.js + +require.register("reporters/index.js", function(module, exports, require){ + +exports.Base = require('./base'); +exports.Dot = require('./dot'); +exports.Doc = require('./doc'); +exports.TAP = require('./tap'); +exports.JSON = require('./json'); +exports.HTML = require('./html'); +exports.List = require('./list'); +exports.Min = require('./min'); +exports.Spec = require('./spec'); +exports.Nyan = require('./nyan'); +exports.XUnit = require('./xunit'); +exports.Markdown = require('./markdown'); +exports.Progress = require('./progress'); +exports.Landing = require('./landing'); +exports.JSONCov = require('./json-cov'); +exports.HTMLCov = require('./html-cov'); +exports.JSONStream = require('./json-stream'); +exports.Teamcity = require('./teamcity'); + +}); // module: reporters/index.js + +require.register("reporters/json-cov.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Base = require('./base'); + +/** + * Expose `JSONCov`. + */ + +exports = module.exports = JSONCov; + +/** + * Initialize a new `JsCoverage` reporter. + * + * @param {Runner} runner + * @param {Boolean} output + * @api public + */ + +function JSONCov(runner, output) { + var self = this + , output = 1 == arguments.length ? true : output; + + Base.call(this, runner); + + var tests = [] + , failures = [] + , passes = []; + + runner.on('test end', function(test){ + tests.push(test); + }); + + runner.on('pass', function(test){ + passes.push(test); + }); + + runner.on('fail', function(test){ + failures.push(test); + }); + + runner.on('end', function(){ + var cov = global._$jscoverage || {}; + var result = self.cov = map(cov); + result.stats = self.stats; + result.tests = tests.map(clean); + result.failures = failures.map(clean); + result.passes = passes.map(clean); + if (!output) return; + process.stdout.write(JSON.stringify(result, null, 2 )); + }); +} + +/** + * Map jscoverage data to a JSON structure + * suitable for reporting. + * + * @param {Object} cov + * @return {Object} + * @api private + */ + +function map(cov) { + var ret = { + instrumentation: 'node-jscoverage' + , sloc: 0 + , hits: 0 + , misses: 0 + , coverage: 0 + , files: [] + }; + + for (var filename in cov) { + var data = coverage(filename, cov[filename]); + ret.files.push(data); + ret.hits += data.hits; + ret.misses += data.misses; + ret.sloc += data.sloc; + } + + if (ret.sloc > 0) { + ret.coverage = (ret.hits / ret.sloc) * 100; + } + + return ret; +}; + +/** + * Map jscoverage data for a single source file + * to a JSON structure suitable for reporting. + * + * @param {String} filename name of the source file + * @param {Object} data jscoverage coverage data + * @return {Object} + * @api private + */ + +function coverage(filename, data) { + var ret = { + filename: filename, + coverage: 0, + hits: 0, + misses: 0, + sloc: 0, + source: {} + }; + + data.source.forEach(function(line, num){ + num++; + + if (data[num] === 0) { + ret.misses++; + ret.sloc++; + } else if (data[num] !== undefined) { + ret.hits++; + ret.sloc++; + } + + ret.source[num] = { + source: line + , coverage: data[num] === undefined + ? '' + : data[num] + }; + }); + + ret.coverage = ret.hits / ret.sloc * 100; + + return ret; +} + +/** + * Return a plain-object representation of `test` + * free of cyclic properties etc. + * + * @param {Object} test + * @return {Object} + * @api private + */ + +function clean(test) { + return { + title: test.title + , fullTitle: test.fullTitle() + , duration: test.duration + } +} + +}); // module: reporters/json-cov.js + +require.register("reporters/json-stream.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Base = require('./base') + , color = Base.color; + +/** + * Expose `List`. + */ + +exports = module.exports = List; + +/** + * Initialize a new `List` test reporter. + * + * @param {Runner} runner + * @api public + */ + +function List(runner) { + Base.call(this, runner); + + var self = this + , stats = this.stats + , total = runner.total; + + runner.on('start', function(){ + console.log(JSON.stringify(['start', { total: total }])); + }); + + runner.on('pass', function(test){ + console.log(JSON.stringify(['pass', clean(test)])); + }); + + runner.on('fail', function(test, err){ + console.log(JSON.stringify(['fail', clean(test)])); + }); + + runner.on('end', function(){ + process.stdout.write(JSON.stringify(['end', self.stats])); + }); +} + +/** + * Return a plain-object representation of `test` + * free of cyclic properties etc. + * + * @param {Object} test + * @return {Object} + * @api private + */ + +function clean(test) { + return { + title: test.title + , fullTitle: test.fullTitle() + , duration: test.duration + } +} +}); // module: reporters/json-stream.js + +require.register("reporters/json.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Base = require('./base') + , cursor = Base.cursor + , color = Base.color; + +/** + * Expose `JSON`. + */ + +exports = module.exports = JSONReporter; + +/** + * Initialize a new `JSON` reporter. + * + * @param {Runner} runner + * @api public + */ + +function JSONReporter(runner) { + var self = this; + Base.call(this, runner); + + var tests = [] + , failures = [] + , passes = []; + + runner.on('test end', function(test){ + tests.push(test); + }); + + runner.on('pass', function(test){ + passes.push(test); + }); + + runner.on('fail', function(test){ + failures.push(test); + }); + + runner.on('end', function(){ + var obj = { + stats: self.stats + , tests: tests.map(clean) + , failures: failures.map(clean) + , passes: passes.map(clean) + }; + + process.stdout.write(JSON.stringify(obj, null, 2)); + }); +} + +/** + * Return a plain-object representation of `test` + * free of cyclic properties etc. + * + * @param {Object} test + * @return {Object} + * @api private + */ + +function clean(test) { + return { + title: test.title + , fullTitle: test.fullTitle() + , duration: test.duration + } +} +}); // module: reporters/json.js + +require.register("reporters/landing.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Base = require('./base') + , cursor = Base.cursor + , color = Base.color; + +/** + * Expose `Landing`. + */ + +exports = module.exports = Landing; + +/** + * Airplane color. + */ + +Base.colors.plane = 0; + +/** + * Airplane crash color. + */ + +Base.colors['plane crash'] = 31; + +/** + * Runway color. + */ + +Base.colors.runway = 90; + +/** + * Initialize a new `Landing` reporter. + * + * @param {Runner} runner + * @api public + */ + +function Landing(runner) { + Base.call(this, runner); + + var self = this + , stats = this.stats + , width = Base.window.width * .75 | 0 + , total = runner.total + , stream = process.stdout + , plane = color('plane', '✈') + , crashed = -1 + , n = 0; + + function runway() { + var buf = Array(width).join('-'); + return ' ' + color('runway', buf); + } + + runner.on('start', function(){ + stream.write('\n '); + cursor.hide(); + }); + + runner.on('test end', function(test){ + // check if the plane crashed + var col = -1 == crashed + ? width * ++n / total | 0 + : crashed; + + // show the crash + if ('failed' == test.state) { + plane = color('plane crash', '✈'); + crashed = col; + } + + // render landing strip + stream.write('\u001b[4F\n\n'); + stream.write(runway()); + stream.write('\n '); + stream.write(color('runway', Array(col).join('⋅'))); + stream.write(plane) + stream.write(color('runway', Array(width - col).join('⋅') + '\n')); + stream.write(runway()); + stream.write('\u001b[0m'); + }); + + runner.on('end', function(){ + cursor.show(); + console.log(); + self.epilogue(); + }); +} + +/** + * Inherit from `Base.prototype`. + */ + +Landing.prototype = new Base; +Landing.prototype.constructor = Landing; + +}); // module: reporters/landing.js + +require.register("reporters/list.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Base = require('./base') + , cursor = Base.cursor + , color = Base.color; + +/** + * Expose `List`. + */ + +exports = module.exports = List; + +/** + * Initialize a new `List` test reporter. + * + * @param {Runner} runner + * @api public + */ + +function List(runner) { + Base.call(this, runner); + + var self = this + , stats = this.stats + , n = 0; + + runner.on('start', function(){ + console.log(); + }); + + runner.on('test', function(test){ + process.stdout.write(color('pass', ' ' + test.fullTitle() + ': ')); + }); + + runner.on('pending', function(test){ + var fmt = color('checkmark', ' -') + + color('pending', ' %s'); + console.log(fmt, test.fullTitle()); + }); + + runner.on('pass', function(test){ + var fmt = color('checkmark', ' ✓') + + color('pass', ' %s: ') + + color(test.speed, '%dms'); + cursor.CR(); + console.log(fmt, test.fullTitle(), test.duration); + }); + + runner.on('fail', function(test, err){ + cursor.CR(); + console.log(color('fail', ' %d) %s'), ++n, test.fullTitle()); + }); + + runner.on('end', self.epilogue.bind(self)); +} + +/** + * Inherit from `Base.prototype`. + */ + +List.prototype = new Base; +List.prototype.constructor = List; + + +}); // module: reporters/list.js + +require.register("reporters/markdown.js", function(module, exports, require){ +/** + * Module dependencies. + */ + +var Base = require('./base') + , utils = require('../utils'); + +/** + * Expose `Markdown`. + */ + +exports = module.exports = Markdown; + +/** + * Initialize a new `Markdown` reporter. + * + * @param {Runner} runner + * @api public + */ + +function Markdown(runner) { + Base.call(this, runner); + + var self = this + , stats = this.stats + , total = runner.total + , level = 0 + , buf = ''; + + function title(str) { + return Array(level).join('#') + ' ' + str; + } + + function indent() { + return Array(level).join(' '); + } + + function mapTOC(suite, obj) { + var ret = obj; + obj = obj[suite.title] = obj[suite.title] || { suite: suite }; + suite.suites.forEach(function(suite){ + mapTOC(suite, obj); + }); + return ret; + } + + function stringifyTOC(obj, level) { + ++level; + var buf = ''; + var link; + for (var key in obj) { + if ('suite' == key) continue; + if (key) link = ' - [' + key + '](#' + utils.slug(obj[key].suite.fullTitle()) + ')\n'; + if (key) buf += Array(level).join(' ') + link; + buf += stringifyTOC(obj[key], level); + } + --level; + return buf; + } + + function generateTOC(suite) { + var obj = mapTOC(suite, {}); + return stringifyTOC(obj, 0); + } + + generateTOC(runner.suite); + + runner.on('suite', function(suite){ + ++level; + var slug = utils.slug(suite.fullTitle()); + buf += '' + '\n'; + buf += title(suite.title) + '\n'; + }); + + runner.on('suite end', function(suite){ + --level; + }); + + runner.on('pass', function(test){ + var code = utils.clean(test.fn.toString()); + buf += test.title + '.\n'; + buf += '\n```js\n'; + buf += code + '\n'; + buf += '```\n\n'; + }); + + runner.on('end', function(){ + process.stdout.write('# TOC\n'); + process.stdout.write(generateTOC(runner.suite)); + process.stdout.write(buf); + }); +} +}); // module: reporters/markdown.js + +require.register("reporters/min.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Base = require('./base'); + +/** + * Expose `Min`. + */ + +exports = module.exports = Min; + +/** + * Initialize a new `Min` minimal test reporter (best used with --watch). + * + * @param {Runner} runner + * @api public + */ + +function Min(runner) { + Base.call(this, runner); + + runner.on('start', function(){ + // clear screen + process.stdout.write('\u001b[2J'); + // set cursor position + process.stdout.write('\u001b[1;3H'); + }); + + runner.on('end', this.epilogue.bind(this)); +} + +/** + * Inherit from `Base.prototype`. + */ + +Min.prototype = new Base; +Min.prototype.constructor = Min; + +}); // module: reporters/min.js + +require.register("reporters/nyan.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Base = require('./base') + , color = Base.color; + +/** + * Expose `Dot`. + */ + +exports = module.exports = NyanCat; + +/** + * Initialize a new `Dot` matrix test reporter. + * + * @param {Runner} runner + * @api public + */ + +function NyanCat(runner) { + Base.call(this, runner); + + var self = this + , stats = this.stats + , width = Base.window.width * .75 | 0 + , rainbowColors = this.rainbowColors = self.generateColors() + , colorIndex = this.colorIndex = 0 + , numerOfLines = this.numberOfLines = 4 + , trajectories = this.trajectories = [[], [], [], []] + , nyanCatWidth = this.nyanCatWidth = 11 + , trajectoryWidthMax = this.trajectoryWidthMax = (width - nyanCatWidth) + , scoreboardWidth = this.scoreboardWidth = 5 + , tick = this.tick = 0 + , n = 0; + + runner.on('start', function(){ + Base.cursor.hide(); + self.draw('start'); + }); + + runner.on('pending', function(test){ + self.draw('pending'); + }); + + runner.on('pass', function(test){ + self.draw('pass'); + }); + + runner.on('fail', function(test, err){ + self.draw('fail'); + }); + + runner.on('end', function(){ + Base.cursor.show(); + for (var i = 0; i < self.numberOfLines; i++) write('\n'); + self.epilogue(); + }); +} + +/** + * Draw the nyan cat with runner `status`. + * + * @param {String} status + * @api private + */ + +NyanCat.prototype.draw = function(status){ + this.appendRainbow(); + this.drawScoreboard(); + this.drawRainbow(); + this.drawNyanCat(status); + this.tick = !this.tick; +}; + +/** + * Draw the "scoreboard" showing the number + * of passes, failures and pending tests. + * + * @api private + */ + +NyanCat.prototype.drawScoreboard = function(){ + var stats = this.stats; + var colors = Base.colors; + + function draw(color, n) { + write(' '); + write('\u001b[' + color + 'm' + n + '\u001b[0m'); + write('\n'); + } + + draw(colors.green, stats.passes); + draw(colors.fail, stats.failures); + draw(colors.pending, stats.pending); + write('\n'); + + this.cursorUp(this.numberOfLines); +}; + +/** + * Append the rainbow. + * + * @api private + */ + +NyanCat.prototype.appendRainbow = function(){ + var segment = this.tick ? '_' : '-'; + var rainbowified = this.rainbowify(segment); + + for (var index = 0; index < this.numberOfLines; index++) { + var trajectory = this.trajectories[index]; + if (trajectory.length >= this.trajectoryWidthMax) trajectory.shift(); + trajectory.push(rainbowified); + } +}; + +/** + * Draw the rainbow. + * + * @api private + */ + +NyanCat.prototype.drawRainbow = function(){ + var self = this; + + this.trajectories.forEach(function(line, index) { + write('\u001b[' + self.scoreboardWidth + 'C'); + write(line.join('')); + write('\n'); + }); + + this.cursorUp(this.numberOfLines); +}; + +/** + * Draw the nyan cat with `status`. + * + * @param {String} status + * @api private + */ + +NyanCat.prototype.drawNyanCat = function(status) { + var self = this; + var startWidth = this.scoreboardWidth + this.trajectories[0].length; + + [0, 1, 2, 3].forEach(function(index) { + write('\u001b[' + startWidth + 'C'); + + switch (index) { + case 0: + write('_,------,'); + write('\n'); + break; + case 1: + var padding = self.tick ? ' ' : ' '; + write('_|' + padding + '/\\_/\\ '); + write('\n'); + break; + case 2: + var padding = self.tick ? '_' : '__'; + var tail = self.tick ? '~' : '^'; + var face; + switch (status) { + case 'pass': + face = '( ^ .^)'; + break; + case 'fail': + face = '( o .o)'; + break; + default: + face = '( - .-)'; + } + write(tail + '|' + padding + face + ' '); + write('\n'); + break; + case 3: + var padding = self.tick ? ' ' : ' '; + write(padding + '"" "" '); + write('\n'); + break; + } + }); + + this.cursorUp(this.numberOfLines); +}; + +/** + * Move cursor up `n`. + * + * @param {Number} n + * @api private + */ + +NyanCat.prototype.cursorUp = function(n) { + write('\u001b[' + n + 'A'); +}; + +/** + * Move cursor down `n`. + * + * @param {Number} n + * @api private + */ + +NyanCat.prototype.cursorDown = function(n) { + write('\u001b[' + n + 'B'); +}; + +/** + * Generate rainbow colors. + * + * @return {Array} + * @api private + */ + +NyanCat.prototype.generateColors = function(){ + var colors = []; + + for (var i = 0; i < (6 * 7); i++) { + var pi3 = Math.floor(Math.PI / 3); + var n = (i * (1.0 / 6)); + var r = Math.floor(3 * Math.sin(n) + 3); + var g = Math.floor(3 * Math.sin(n + 2 * pi3) + 3); + var b = Math.floor(3 * Math.sin(n + 4 * pi3) + 3); + colors.push(36 * r + 6 * g + b + 16); + } + + return colors; +}; + +/** + * Apply rainbow to the given `str`. + * + * @param {String} str + * @return {String} + * @api private + */ + +NyanCat.prototype.rainbowify = function(str){ + var color = this.rainbowColors[this.colorIndex % this.rainbowColors.length]; + this.colorIndex += 1; + return '\u001b[38;5;' + color + 'm' + str + '\u001b[0m'; +}; + +/** + * Stdout helper. + */ + +function write(string) { + process.stdout.write(string); +} + +/** + * Inherit from `Base.prototype`. + */ + +NyanCat.prototype = new Base; +NyanCat.prototype.constructor = NyanCat; + + +}); // module: reporters/nyan.js + +require.register("reporters/progress.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Base = require('./base') + , cursor = Base.cursor + , color = Base.color; + +/** + * Expose `Progress`. + */ + +exports = module.exports = Progress; + +/** + * General progress bar color. + */ + +Base.colors.progress = 90; + +/** + * Initialize a new `Progress` bar test reporter. + * + * @param {Runner} runner + * @param {Object} options + * @api public + */ + +function Progress(runner, options) { + Base.call(this, runner); + + var self = this + , options = options || {} + , stats = this.stats + , width = Base.window.width * .50 | 0 + , total = runner.total + , complete = 0 + , max = Math.max; + + // default chars + options.open = options.open || '['; + options.complete = options.complete || '▬'; + options.incomplete = options.incomplete || '⋅'; + options.close = options.close || ']'; + options.verbose = false; + + // tests started + runner.on('start', function(){ + console.log(); + cursor.hide(); + }); + + // tests complete + runner.on('test end', function(){ + complete++; + var incomplete = total - complete + , percent = complete / total + , n = width * percent | 0 + , i = width - n; + + cursor.CR(); + process.stdout.write('\u001b[J'); + process.stdout.write(color('progress', ' ' + options.open)); + process.stdout.write(Array(n).join(options.complete)); + process.stdout.write(Array(i).join(options.incomplete)); + process.stdout.write(color('progress', options.close)); + if (options.verbose) { + process.stdout.write(color('progress', ' ' + complete + ' of ' + total)); + } + }); + + // tests are complete, output some stats + // and the failures if any + runner.on('end', function(){ + cursor.show(); + console.log(); + self.epilogue(); + }); +} + +/** + * Inherit from `Base.prototype`. + */ + +Progress.prototype = new Base; +Progress.prototype.constructor = Progress; + + +}); // module: reporters/progress.js + +require.register("reporters/spec.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Base = require('./base') + , cursor = Base.cursor + , color = Base.color; + +/** + * Expose `Spec`. + */ + +exports = module.exports = Spec; + +/** + * Initialize a new `Spec` test reporter. + * + * @param {Runner} runner + * @api public + */ + +function Spec(runner) { + Base.call(this, runner); + + var self = this + , stats = this.stats + , indents = 0 + , n = 0; + + function indent() { + return Array(indents).join(' ') + } + + runner.on('start', function(){ + console.log(); + }); + + runner.on('suite', function(suite){ + ++indents; + console.log(color('suite', '%s%s'), indent(), suite.title); + }); + + runner.on('suite end', function(suite){ + --indents; + if (1 == indents) console.log(); + }); + + runner.on('test', function(test){ + process.stdout.write(indent() + color('pass', ' ◦ ' + test.title + ': ')); + }); + + runner.on('pending', function(test){ + var fmt = indent() + color('pending', ' - %s'); + console.log(fmt, test.title); + }); + + runner.on('pass', function(test){ + if ('fast' == test.speed) { + var fmt = indent() + + color('checkmark', ' ✓') + + color('pass', ' %s '); + cursor.CR(); + console.log(fmt, test.title); + } else { + var fmt = indent() + + color('checkmark', ' ✓') + + color('pass', ' %s ') + + color(test.speed, '(%dms)'); + cursor.CR(); + console.log(fmt, test.title, test.duration); + } + }); + + runner.on('fail', function(test, err){ + cursor.CR(); + console.log(indent() + color('fail', ' %d) %s'), ++n, test.title); + }); + + runner.on('end', self.epilogue.bind(self)); +} + +/** + * Inherit from `Base.prototype`. + */ + +Spec.prototype = new Base; +Spec.prototype.constructor = Spec; + + +}); // module: reporters/spec.js + +require.register("reporters/tap.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Base = require('./base') + , cursor = Base.cursor + , color = Base.color; + +/** + * Expose `TAP`. + */ + +exports = module.exports = TAP; + +/** + * Initialize a new `TAP` reporter. + * + * @param {Runner} runner + * @api public + */ + +function TAP(runner) { + Base.call(this, runner); + + var self = this + , stats = this.stats + , n = 1; + + runner.on('start', function(){ + var total = runner.grepTotal(runner.suite); + console.log('%d..%d', 1, total); + }); + + runner.on('test end', function(){ + ++n; + }); + + runner.on('pending', function(test){ + console.log('ok %d %s # SKIP -', n, title(test)); + }); + + runner.on('pass', function(test){ + console.log('ok %d %s', n, title(test)); + }); + + runner.on('fail', function(test, err){ + console.log('not ok %d %s', n, title(test)); + console.log(err.stack.replace(/^/gm, ' ')); + }); +} + +/** + * Return a TAP-safe title of `test` + * + * @param {Object} test + * @return {String} + * @api private + */ + +function title(test) { + return test.fullTitle().replace(/#/g, ''); +} + +}); // module: reporters/tap.js + +require.register("reporters/teamcity.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Base = require('./base'); + +/** + * Expose `Teamcity`. + */ + +exports = module.exports = Teamcity; + +/** + * Initialize a new `Teamcity` reporter. + * + * @param {Runner} runner + * @api public + */ + +function Teamcity(runner) { + Base.call(this, runner); + var stats = this.stats; + + runner.on('start', function() { + console.log("##teamcity[testSuiteStarted name='mocha.suite']"); + }); + + runner.on('test', function(test) { + console.log("##teamcity[testStarted name='" + escape(test.fullTitle()) + "']"); + }); + + runner.on('fail', function(test, err) { + console.log("##teamcity[testFailed name='" + escape(test.fullTitle()) + "' message='" + escape(err.message) + "']"); + }); + + runner.on('pending', function(test) { + console.log("##teamcity[testIgnored name='" + escape(test.fullTitle()) + "' message='pending']"); + }); + + runner.on('test end', function(test) { + console.log("##teamcity[testFinished name='" + escape(test.fullTitle()) + "' duration='" + test.duration + "']"); + }); + + runner.on('end', function() { + console.log("##teamcity[testSuiteFinished name='mocha.suite' duration='" + stats.duration + "']"); + }); +} + +/** + * Escape the given `str`. + */ + +function escape(str) { + return str + .replace(/\|/g, "||") + .replace(/\n/g, "|n") + .replace(/\r/g, "|r") + .replace(/\[/g, "|[") + .replace(/\]/g, "|]") + .replace(/\u0085/g, "|x") + .replace(/\u2028/g, "|l") + .replace(/\u2029/g, "|p") + .replace(/'/g, "|'"); +} + +}); // module: reporters/teamcity.js + +require.register("reporters/xunit.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Base = require('./base') + , utils = require('../utils') + , escape = utils.escape; + +/** + * Save timer references to avoid Sinon interfering (see GH-237). + */ + +var Date = global.Date + , setTimeout = global.setTimeout + , setInterval = global.setInterval + , clearTimeout = global.clearTimeout + , clearInterval = global.clearInterval; + +/** + * Expose `XUnit`. + */ + +exports = module.exports = XUnit; + +/** + * Initialize a new `XUnit` reporter. + * + * @param {Runner} runner + * @api public + */ + +function XUnit(runner) { + Base.call(this, runner); + var stats = this.stats + , tests = [] + , self = this; + + runner.on('pass', function(test){ + tests.push(test); + }); + + runner.on('fail', function(test){ + tests.push(test); + }); + + runner.on('end', function(){ + console.log(tag('testsuite', { + name: 'Mocha Tests' + , tests: stats.tests + , failures: stats.failures + , errors: stats.failures + , skip: stats.tests - stats.failures - stats.passes + , timestamp: (new Date).toUTCString() + , time: stats.duration / 1000 + }, false)); + + tests.forEach(test); + console.log(''); + }); +} + +/** + * Inherit from `Base.prototype`. + */ + +XUnit.prototype = new Base; +XUnit.prototype.constructor = XUnit; + + +/** + * Output tag for the given `test.` + */ + +function test(test) { + var attrs = { + classname: test.parent.fullTitle() + , name: test.title + , time: test.duration / 1000 + }; + + if ('failed' == test.state) { + var err = test.err; + attrs.message = escape(err.message); + console.log(tag('testcase', attrs, false, tag('failure', attrs, false, cdata(err.stack)))); + } else if (test.pending) { + console.log(tag('testcase', attrs, false, tag('skipped', {}, true))); + } else { + console.log(tag('testcase', attrs, true) ); + } +} + +/** + * HTML tag helper. + */ + +function tag(name, attrs, close, content) { + var end = close ? '/>' : '>' + , pairs = [] + , tag; + + for (var key in attrs) { + pairs.push(key + '="' + escape(attrs[key]) + '"'); + } + + tag = '<' + name + (pairs.length ? ' ' + pairs.join(' ') : '') + end; + if (content) tag += content + ''; +} + +}); // module: reporters/xunit.js + +require.register("runnable.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var EventEmitter = require('browser/events').EventEmitter + , debug = require('browser/debug')('mocha:runnable'); + +/** + * Save timer references to avoid Sinon interfering (see GH-237). + */ + +var Date = global.Date + , setTimeout = global.setTimeout + , setInterval = global.setInterval + , clearTimeout = global.clearTimeout + , clearInterval = global.clearInterval; + +/** + * Expose `Runnable`. + */ + +module.exports = Runnable; + +/** + * Initialize a new `Runnable` with the given `title` and callback `fn`. + * + * @param {String} title + * @param {Function} fn + * @api private + */ + +function Runnable(title, fn) { + this.title = title; + this.fn = fn; + this.async = fn && fn.length; + this.sync = ! this.async; + this._timeout = 2000; + this._slow = 75; + this.timedOut = false; +} + +/** + * Inherit from `EventEmitter.prototype`. + */ + +Runnable.prototype = new EventEmitter; +Runnable.prototype.constructor = Runnable; + + +/** + * Set & get timeout `ms`. + * + * @param {Number} ms + * @return {Runnable|Number} ms or self + * @api private + */ + +Runnable.prototype.timeout = function(ms){ + if (0 == arguments.length) return this._timeout; + debug('timeout %d', ms); + this._timeout = ms; + if (this.timer) this.resetTimeout(); + return this; +}; + +/** + * Set & get slow `ms`. + * + * @param {Number} ms + * @return {Runnable|Number} ms or self + * @api private + */ + +Runnable.prototype.slow = function(ms){ + if (0 === arguments.length) return this._slow; + debug('timeout %d', ms); + this._slow = ms; + return this; +}; + +/** + * Return the full title generated by recursively + * concatenating the parent's full title. + * + * @return {String} + * @api public + */ + +Runnable.prototype.fullTitle = function(){ + return this.parent.fullTitle() + ' ' + this.title; +}; + +/** + * Clear the timeout. + * + * @api private + */ + +Runnable.prototype.clearTimeout = function(){ + clearTimeout(this.timer); +}; + +/** + * Inspect the runnable void of private properties. + * + * @return {String} + * @api private + */ + +Runnable.prototype.inspect = function(){ + return JSON.stringify(this, function(key, val){ + if ('_' == key[0]) return; + if ('parent' == key) return '#'; + if ('ctx' == key) return '#'; + return val; + }, 2); +}; + +/** + * Reset the timeout. + * + * @api private + */ + +Runnable.prototype.resetTimeout = function(){ + var self = this + , ms = this.timeout(); + + this.clearTimeout(); + if (ms) { + this.timer = setTimeout(function(){ + self.callback(new Error('timeout of ' + ms + 'ms exceeded')); + self.timedOut = true; + }, ms); + } +}; + +/** + * Run the test and invoke `fn(err)`. + * + * @param {Function} fn + * @api private + */ + +Runnable.prototype.run = function(fn){ + var self = this + , ms = this.timeout() + , start = new Date + , ctx = this.ctx + , finished + , emitted; + + if (ctx) ctx.runnable(this); + + // timeout + if (this.async) { + if (ms) { + this.timer = setTimeout(function(){ + done(new Error('timeout of ' + ms + 'ms exceeded')); + self.timedOut = true; + }, ms); + } + } + + // called multiple times + function multiple(err) { + if (emitted) return; + emitted = true; + self.emit('error', err || new Error('done() called multiple times')); + } + + // finished + function done(err) { + if (self.timedOut) return; + if (finished) return multiple(err); + self.clearTimeout(); + self.duration = new Date - start; + finished = true; + fn(err); + } + + // for .resetTimeout() + this.callback = done; + + // async + if (this.async) { + try { + this.fn.call(ctx, function(err){ + if (err instanceof Error) return done(err); + if (null != err) return done(new Error('done() invoked with non-Error: ' + err)); + done(); + }); + } catch (err) { + done(err); + } + return; + } + + // sync + try { + if (!this.pending) this.fn.call(ctx); + this.duration = new Date - start; + fn(); + } catch (err) { + fn(err); + } +}; + +}); // module: runnable.js + +require.register("runner.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var EventEmitter = require('browser/events').EventEmitter + , debug = require('browser/debug')('mocha:runner') + , Test = require('./test') + , utils = require('./utils') + , filter = utils.filter + , keys = utils.keys + , noop = function(){}; + +/** + * Expose `Runner`. + */ + +module.exports = Runner; + +/** + * Initialize a `Runner` for the given `suite`. + * + * Events: + * + * - `start` execution started + * - `end` execution complete + * - `suite` (suite) test suite execution started + * - `suite end` (suite) all tests (and sub-suites) have finished + * - `test` (test) test execution started + * - `test end` (test) test completed + * - `hook` (hook) hook execution started + * - `hook end` (hook) hook complete + * - `pass` (test) test passed + * - `fail` (test, err) test failed + * + * @api public + */ + +function Runner(suite) { + var self = this; + this._globals = []; + this.suite = suite; + this.total = suite.total(); + this.failures = 0; + this.on('test end', function(test){ self.checkGlobals(test); }); + this.on('hook end', function(hook){ self.checkGlobals(hook); }); + this.grep(/.*/); + this.globals(utils.keys(global).concat(['errno'])); +} + +/** + * Inherit from `EventEmitter.prototype`. + */ + +Runner.prototype = new EventEmitter; +Runner.prototype.constructor = Runner; + + +/** + * Run tests with full titles matching `re`. Updates runner.total + * with number of tests matched. + * + * @param {RegExp} re + * @param {Boolean} invert + * @return {Runner} for chaining + * @api public + */ + +Runner.prototype.grep = function(re, invert){ + debug('grep %s', re); + this._grep = re; + this._invert = invert; + this.total = this.grepTotal(this.suite); + return this; +}; + +/** + * Returns the number of tests matching the grep search for the + * given suite. + * + * @param {Suite} suite + * @return {Number} + * @api public + */ + +Runner.prototype.grepTotal = function(suite) { + var self = this; + var total = 0; + + suite.eachTest(function(test){ + var match = self._grep.test(test.fullTitle()); + if (self._invert) match = !match; + if (match) total++; + }); + + return total; +}; + +/** + * Allow the given `arr` of globals. + * + * @param {Array} arr + * @return {Runner} for chaining + * @api public + */ + +Runner.prototype.globals = function(arr){ + if (0 == arguments.length) return this._globals; + debug('globals %j', arr); + utils.forEach(arr, function(arr){ + this._globals.push(arr); + }, this); + return this; +}; + +/** + * Check for global variable leaks. + * + * @api private + */ + +Runner.prototype.checkGlobals = function(test){ + if (this.ignoreLeaks) return; + var leaks = filterLeaks(this._globals); + + this._globals = this._globals.concat(leaks); + + if (leaks.length > 1) { + this.fail(test, new Error('global leaks detected: ' + leaks.join(', ') + '')); + } else if (leaks.length) { + this.fail(test, new Error('global leak detected: ' + leaks[0])); + } +}; + +/** + * Fail the given `test`. + * + * @param {Test} test + * @param {Error} err + * @api private + */ + +Runner.prototype.fail = function(test, err){ + ++this.failures; + test.state = 'failed'; + if ('string' == typeof err) { + err = new Error('the string "' + err + '" was thrown, throw an Error :)'); + } + this.emit('fail', test, err); +}; + +/** + * Fail the given `hook` with `err`. + * + * Hook failures (currently) hard-end due + * to that fact that a failing hook will + * surely cause subsequent tests to fail, + * causing jumbled reporting. + * + * @param {Hook} hook + * @param {Error} err + * @api private + */ + +Runner.prototype.failHook = function(hook, err){ + this.fail(hook, err); + this.emit('end'); +}; + +/** + * Run hook `name` callbacks and then invoke `fn()`. + * + * @param {String} name + * @param {Function} function + * @api private + */ + +Runner.prototype.hook = function(name, fn){ + var suite = this.suite + , hooks = suite['_' + name] + , self = this + , timer; + + function next(i) { + var hook = hooks[i]; + if (!hook) return fn(); + self.currentRunnable = hook; + + self.emit('hook', hook); + + hook.on('error', function(err){ + self.failHook(hook, err); + }); + + hook.run(function(err){ + hook.removeAllListeners('error'); + var testError = hook.error(); + if (testError) self.fail(self.test, testError); + if (err) return self.failHook(hook, err); + self.emit('hook end', hook); + next(++i); + }); + } + + process.nextTick(function(){ + next(0); + }); +}; + +/** + * Run hook `name` for the given array of `suites` + * in order, and callback `fn(err)`. + * + * @param {String} name + * @param {Array} suites + * @param {Function} fn + * @api private + */ + +Runner.prototype.hooks = function(name, suites, fn){ + var self = this + , orig = this.suite; + + function next(suite) { + self.suite = suite; + + if (!suite) { + self.suite = orig; + return fn(); + } + + self.hook(name, function(err){ + if (err) { + self.suite = orig; + return fn(err); + } + + next(suites.pop()); + }); + } + + next(suites.pop()); +}; + +/** + * Run hooks from the top level down. + * + * @param {String} name + * @param {Function} fn + * @api private + */ + +Runner.prototype.hookUp = function(name, fn){ + var suites = [this.suite].concat(this.parents()).reverse(); + this.hooks(name, suites, fn); +}; + +/** + * Run hooks from the bottom up. + * + * @param {String} name + * @param {Function} fn + * @api private + */ + +Runner.prototype.hookDown = function(name, fn){ + var suites = [this.suite].concat(this.parents()); + this.hooks(name, suites, fn); +}; + +/** + * Return an array of parent Suites from + * closest to furthest. + * + * @return {Array} + * @api private + */ + +Runner.prototype.parents = function(){ + var suite = this.suite + , suites = []; + while (suite = suite.parent) suites.push(suite); + return suites; +}; + +/** + * Run the current test and callback `fn(err)`. + * + * @param {Function} fn + * @api private + */ + +Runner.prototype.runTest = function(fn){ + var test = this.test + , self = this; + + try { + test.on('error', function(err){ + self.fail(test, err); + }); + test.run(fn); + } catch (err) { + fn(err); + } +}; + +/** + * Run tests in the given `suite` and invoke + * the callback `fn()` when complete. + * + * @param {Suite} suite + * @param {Function} fn + * @api private + */ + +Runner.prototype.runTests = function(suite, fn){ + var self = this + , tests = suite.tests + , test; + + function next(err) { + // if we bail after first err + if (self.failures && suite._bail) return fn(); + + // next test + test = tests.shift(); + + // all done + if (!test) return fn(); + + // grep + var match = self._grep.test(test.fullTitle()); + if (self._invert) match = !match; + if (!match) return next(); + + // pending + if (test.pending) { + self.emit('pending', test); + self.emit('test end', test); + return next(); + } + + // execute test and hook(s) + self.emit('test', self.test = test); + self.hookDown('beforeEach', function(){ + self.currentRunnable = self.test; + self.runTest(function(err){ + test = self.test; + + if (err) { + self.fail(test, err); + self.emit('test end', test); + return self.hookUp('afterEach', next); + } + + test.state = 'passed'; + self.emit('pass', test); + self.emit('test end', test); + self.hookUp('afterEach', next); + }); + }); + } + + this.next = next; + next(); +}; + +/** + * Run the given `suite` and invoke the + * callback `fn()` when complete. + * + * @param {Suite} suite + * @param {Function} fn + * @api private + */ + +Runner.prototype.runSuite = function(suite, fn){ + var total = this.grepTotal(suite) + , self = this + , i = 0; + + debug('run suite %s', suite.fullTitle()); + + if (!total) return fn(); + + this.emit('suite', this.suite = suite); + + function next() { + var curr = suite.suites[i++]; + if (!curr) return done(); + self.runSuite(curr, next); + } + + function done() { + self.suite = suite; + self.hook('afterAll', function(){ + self.emit('suite end', suite); + fn(); + }); + } + + this.hook('beforeAll', function(){ + self.runTests(suite, next); + }); +}; + +/** + * Handle uncaught exceptions. + * + * @param {Error} err + * @api private + */ + +Runner.prototype.uncaught = function(err){ + debug('uncaught exception %s', err.message); + var runnable = this.currentRunnable; + if (!runnable || 'failed' == runnable.state) return; + runnable.clearTimeout(); + err.uncaught = true; + this.fail(runnable, err); + + // recover from test + if ('test' == runnable.type) { + this.emit('test end', runnable); + this.hookUp('afterEach', this.next); + return; + } + + // bail on hooks + this.emit('end'); +}; + +/** + * Run the root suite and invoke `fn(failures)` + * on completion. + * + * @param {Function} fn + * @return {Runner} for chaining + * @api public + */ + +Runner.prototype.run = function(fn){ + var self = this + , fn = fn || function(){}; + + debug('start'); + + // uncaught callback + function uncaught(err) { + self.uncaught(err); + } + + // callback + this.on('end', function(){ + debug('end'); + process.removeListener('uncaughtException', uncaught); + fn(self.failures); + }); + + // run suites + this.emit('start'); + this.runSuite(this.suite, function(){ + debug('finished running'); + self.emit('end'); + }); + + // uncaught exception + process.on('uncaughtException', uncaught); + + return this; +}; + +/** + * Filter leaks with the given globals flagged as `ok`. + * + * @param {Array} ok + * @return {Array} + * @api private + */ + +function filterLeaks(ok) { + return filter(keys(global), function(key){ + var matched = filter(ok, function(ok){ + if (~ok.indexOf('*')) return 0 == key.indexOf(ok.split('*')[0]); + return key == ok; + }); + return matched.length == 0 && (!global.navigator || 'onerror' !== key); + }); +} + +}); // module: runner.js + +require.register("suite.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var EventEmitter = require('browser/events').EventEmitter + , debug = require('browser/debug')('mocha:suite') + , milliseconds = require('./ms') + , utils = require('./utils') + , Hook = require('./hook'); + +/** + * Expose `Suite`. + */ + +exports = module.exports = Suite; + +/** + * Create a new `Suite` with the given `title` + * and parent `Suite`. When a suite with the + * same title is already present, that suite + * is returned to provide nicer reporter + * and more flexible meta-testing. + * + * @param {Suite} parent + * @param {String} title + * @return {Suite} + * @api public + */ + +exports.create = function(parent, title){ + var suite = new Suite(title, parent.ctx); + suite.parent = parent; + if (parent.pending) suite.pending = true; + title = suite.fullTitle(); + parent.addSuite(suite); + return suite; +}; + +/** + * Initialize a new `Suite` with the given + * `title` and `ctx`. + * + * @param {String} title + * @param {Context} ctx + * @api private + */ + +function Suite(title, ctx) { + this.title = title; + this.ctx = ctx; + this.suites = []; + this.tests = []; + this.pending = false; + this._beforeEach = []; + this._beforeAll = []; + this._afterEach = []; + this._afterAll = []; + this.root = !title; + this._timeout = 2000; + this._slow = 75; + this._bail = false; +} + +/** + * Inherit from `EventEmitter.prototype`. + */ + +Suite.prototype = new EventEmitter; +Suite.prototype.constructor = Suite; + + +/** + * Return a clone of this `Suite`. + * + * @return {Suite} + * @api private + */ + +Suite.prototype.clone = function(){ + var suite = new Suite(this.title); + debug('clone'); + suite.ctx = this.ctx; + suite.timeout(this.timeout()); + suite.slow(this.slow()); + suite.bail(this.bail()); + return suite; +}; + +/** + * Set timeout `ms` or short-hand such as "2s". + * + * @param {Number|String} ms + * @return {Suite|Number} for chaining + * @api private + */ + +Suite.prototype.timeout = function(ms){ + if (0 == arguments.length) return this._timeout; + if ('string' == typeof ms) ms = milliseconds(ms); + debug('timeout %d', ms); + this._timeout = parseInt(ms, 10); + return this; +}; + +/** + * Set slow `ms` or short-hand such as "2s". + * + * @param {Number|String} ms + * @return {Suite|Number} for chaining + * @api private + */ + +Suite.prototype.slow = function(ms){ + if (0 === arguments.length) return this._slow; + if ('string' == typeof ms) ms = milliseconds(ms); + debug('slow %d', ms); + this._slow = ms; + return this; +}; + +/** + * Sets whether to bail after first error. + * + * @parma {Boolean} bail + * @return {Suite|Number} for chaining + * @api private + */ + +Suite.prototype.bail = function(bail){ + if (0 == arguments.length) return this._bail; + debug('bail %s', bail); + this._bail = bail; + return this; +}; + +/** + * Run `fn(test[, done])` before running tests. + * + * @param {Function} fn + * @return {Suite} for chaining + * @api private + */ + +Suite.prototype.beforeAll = function(fn){ + if (this.pending) return this; + var hook = new Hook('"before all" hook', fn); + hook.parent = this; + hook.timeout(this.timeout()); + hook.slow(this.slow()); + hook.ctx = this.ctx; + this._beforeAll.push(hook); + this.emit('beforeAll', hook); + return this; +}; + +/** + * Run `fn(test[, done])` after running tests. + * + * @param {Function} fn + * @return {Suite} for chaining + * @api private + */ + +Suite.prototype.afterAll = function(fn){ + if (this.pending) return this; + var hook = new Hook('"after all" hook', fn); + hook.parent = this; + hook.timeout(this.timeout()); + hook.slow(this.slow()); + hook.ctx = this.ctx; + this._afterAll.push(hook); + this.emit('afterAll', hook); + return this; +}; + +/** + * Run `fn(test[, done])` before each test case. + * + * @param {Function} fn + * @return {Suite} for chaining + * @api private + */ + +Suite.prototype.beforeEach = function(fn){ + if (this.pending) return this; + var hook = new Hook('"before each" hook', fn); + hook.parent = this; + hook.timeout(this.timeout()); + hook.slow(this.slow()); + hook.ctx = this.ctx; + this._beforeEach.push(hook); + this.emit('beforeEach', hook); + return this; +}; + +/** + * Run `fn(test[, done])` after each test case. + * + * @param {Function} fn + * @return {Suite} for chaining + * @api private + */ + +Suite.prototype.afterEach = function(fn){ + if (this.pending) return this; + var hook = new Hook('"after each" hook', fn); + hook.parent = this; + hook.timeout(this.timeout()); + hook.slow(this.slow()); + hook.ctx = this.ctx; + this._afterEach.push(hook); + this.emit('afterEach', hook); + return this; +}; + +/** + * Add a test `suite`. + * + * @param {Suite} suite + * @return {Suite} for chaining + * @api private + */ + +Suite.prototype.addSuite = function(suite){ + suite.parent = this; + suite.timeout(this.timeout()); + suite.slow(this.slow()); + suite.bail(this.bail()); + this.suites.push(suite); + this.emit('suite', suite); + return this; +}; + +/** + * Add a `test` to this suite. + * + * @param {Test} test + * @return {Suite} for chaining + * @api private + */ + +Suite.prototype.addTest = function(test){ + test.parent = this; + test.timeout(this.timeout()); + test.slow(this.slow()); + test.ctx = this.ctx; + this.tests.push(test); + this.emit('test', test); + return this; +}; + +/** + * Return the full title generated by recursively + * concatenating the parent's full title. + * + * @return {String} + * @api public + */ + +Suite.prototype.fullTitle = function(){ + if (this.parent) { + var full = this.parent.fullTitle(); + if (full) return full + ' ' + this.title; + } + return this.title; +}; + +/** + * Return the total number of tests. + * + * @return {Number} + * @api public + */ + +Suite.prototype.total = function(){ + return utils.reduce(this.suites, function(sum, suite){ + return sum + suite.total(); + }, 0) + this.tests.length; +}; + +/** + * Iterates through each suite recursively to find + * all tests. Applies a function in the format + * `fn(test)`. + * + * @param {Function} fn + * @return {Suite} + * @api private + */ + +Suite.prototype.eachTest = function(fn){ + utils.forEach(this.tests, fn); + utils.forEach(this.suites, function(suite){ + suite.eachTest(fn); + }); + return this; +}; + +}); // module: suite.js + +require.register("test.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Runnable = require('./runnable'); + +/** + * Expose `Test`. + */ + +module.exports = Test; + +/** + * Initialize a new `Test` with the given `title` and callback `fn`. + * + * @param {String} title + * @param {Function} fn + * @api private + */ + +function Test(title, fn) { + Runnable.call(this, title, fn); + this.pending = !fn; + this.type = 'test'; +} + +/** + * Inherit from `Runnable.prototype`. + */ + +Test.prototype = new Runnable; +Test.prototype.constructor = Test; + + +}); // module: test.js + +require.register("utils.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var fs = require('browser/fs') + , path = require('browser/path') + , join = path.join + , debug = require('browser/debug')('mocha:watch'); + +/** + * Ignored directories. + */ + +var ignore = ['node_modules', '.git']; + +/** + * Escape special characters in the given string of html. + * + * @param {String} html + * @return {String} + * @api private + */ + +exports.escape = function(html){ + return String(html) + .replace(/&/g, '&') + .replace(/"/g, '"') + .replace(//g, '>'); +}; + +/** + * Array#forEach (<=IE8) + * + * @param {Array} array + * @param {Function} fn + * @param {Object} scope + * @api private + */ + +exports.forEach = function(arr, fn, scope){ + for (var i = 0, l = arr.length; i < l; i++) + fn.call(scope, arr[i], i); +}; + +/** + * Array#indexOf (<=IE8) + * + * @parma {Array} arr + * @param {Object} obj to find index of + * @param {Number} start + * @api private + */ + +exports.indexOf = function(arr, obj, start){ + for (var i = start || 0, l = arr.length; i < l; i++) { + if (arr[i] === obj) + return i; + } + return -1; +}; + +/** + * Array#reduce (<=IE8) + * + * @param {Array} array + * @param {Function} fn + * @param {Object} initial value + * @api private + */ + +exports.reduce = function(arr, fn, val){ + var rval = val; + + for (var i = 0, l = arr.length; i < l; i++) { + rval = fn(rval, arr[i], i, arr); + } + + return rval; +}; + +/** + * Array#filter (<=IE8) + * + * @param {Array} array + * @param {Function} fn + * @api private + */ + +exports.filter = function(arr, fn){ + var ret = []; + + for (var i = 0, l = arr.length; i < l; i++) { + var val = arr[i]; + if (fn(val, i, arr)) ret.push(val); + } + + return ret; +}; + +/** + * Object.keys (<=IE8) + * + * @param {Object} obj + * @return {Array} keys + * @api private + */ + +exports.keys = Object.keys || function(obj) { + var keys = [] + , has = Object.prototype.hasOwnProperty // for `window` on <=IE8 + + for (var key in obj) { + if (has.call(obj, key)) { + keys.push(key); + } + } + + return keys; +}; + +/** + * Watch the given `files` for changes + * and invoke `fn(file)` on modification. + * + * @param {Array} files + * @param {Function} fn + * @api private + */ + +exports.watch = function(files, fn){ + var options = { interval: 100 }; + files.forEach(function(file){ + debug('file %s', file); + fs.watchFile(file, options, function(curr, prev){ + if (prev.mtime < curr.mtime) fn(file); + }); + }); +}; + +/** + * Ignored files. + */ + +function ignored(path){ + return !~ignore.indexOf(path); +} + +/** + * Lookup files in the given `dir`. + * + * @return {Array} + * @api private + */ + +exports.files = function(dir, ret){ + ret = ret || []; + + fs.readdirSync(dir) + .filter(ignored) + .forEach(function(path){ + path = join(dir, path); + if (fs.statSync(path).isDirectory()) { + exports.files(path, ret); + } else if (path.match(/\.(js|coffee)$/)) { + ret.push(path); + } + }); + + return ret; +}; + +/** + * Compute a slug from the given `str`. + * + * @param {String} str + * @return {String} + * @api private + */ + +exports.slug = function(str){ + return str + .toLowerCase() + .replace(/ +/g, '-') + .replace(/[^-\w]/g, ''); +}; + +/** + * Strip the function definition from `str`, + * and re-indent for pre whitespace. + */ + +exports.clean = function(str) { + str = str + .replace(/^function *\(.*\) *{/, '') + .replace(/\s+\}$/, ''); + + var spaces = str.match(/^\n?( *)/)[1].length + , re = new RegExp('^ {' + spaces + '}', 'gm'); + + str = str.replace(re, ''); + + return exports.trim(str); +}; + +/** + * Escape regular expression characters in `str`. + * + * @param {String} str + * @return {String} + * @api private + */ + +exports.escapeRegexp = function(str){ + return str.replace(/[-\\^$*+?.()|[\]{}]/g, "\\$&"); +}; + +/** + * Trim the given `str`. + * + * @param {String} str + * @return {String} + * @api private + */ + +exports.trim = function(str){ + return str.replace(/^\s+|\s+$/g, ''); +}; + +/** + * Parse the given `qs`. + * + * @param {String} qs + * @return {Object} + * @api private + */ + +exports.parseQuery = function(qs){ + return exports.reduce(qs.replace('?', '').split('&'), function(obj, pair){ + var i = pair.indexOf('=') + , key = pair.slice(0, i) + , val = pair.slice(++i); + + obj[key] = decodeURIComponent(val); + return obj; + }, {}); +}; + +/** + * Highlight the given string of `js`. + * + * @param {String} js + * @return {String} + * @api private + */ + +function highlight(js) { + return js + .replace(//g, '>') + .replace(/\/\/(.*)/gm, '//$1') + .replace(/('.*?')/gm, '$1') + .replace(/(\d+\.\d+)/gm, '$1') + .replace(/(\d+)/gm, '$1') + .replace(/\bnew *(\w+)/gm, 'new $1') + .replace(/\b(function|new|throw|return|var|if|else)\b/gm, '$1') +} + +/** + * Highlight the contents of tag `name`. + * + * @param {String} name + * @api private + */ + +exports.highlightTags = function(name) { + var code = document.getElementsByTagName(name); + for (var i = 0, len = code.length; i < len; ++i) { + code[i].innerHTML = highlight(code[i].innerHTML); + } +}; + +}); // module: utils.js +/** + * Node shims. + * + * These are meant only to allow + * mocha.js to run untouched, not + * to allow running node code in + * the browser. + */ + +process = {}; +process.exit = function(status){}; +process.stdout = {}; +global = window; + +/** + * next tick implementation. + */ + +process.nextTick = (function(){ + // postMessage behaves badly on IE8 + if (window.ActiveXObject || !window.postMessage) { + return function(fn){ fn() }; + } + + // based on setZeroTimeout by David Baron + // - http://dbaron.org/log/20100309-faster-timeouts + var timeouts = [] + , name = 'mocha-zero-timeout' + + window.addEventListener('message', function(e){ + if (e.source == window && e.data == name) { + if (e.stopPropagation) e.stopPropagation(); + if (timeouts.length) timeouts.shift()(); + } + }, true); + + return function(fn){ + timeouts.push(fn); + window.postMessage(name, '*'); + } +})(); + +/** + * Remove uncaughtException listener. + */ + +process.removeListener = function(e){ + if ('uncaughtException' == e) { + window.onerror = null; + } +}; + +/** + * Implements uncaughtException listener. + */ + +process.on = function(e, fn){ + if ('uncaughtException' == e) { + window.onerror = fn; + } +}; + +// boot +;(function(){ + + /** + * Expose mocha. + */ + + var Mocha = window.Mocha = require('mocha'), + mocha = window.mocha = new Mocha({ reporter: 'html' }); + + /** + * Override ui to ensure that the ui functions are initialized. + * Normally this would happen in Mocha.prototype.loadFiles. + */ + + mocha.ui = function(ui){ + Mocha.prototype.ui.call(this, ui); + this.suite.emit('pre-require', window, null, this); + return this; + }; + + /** + * Setup mocha with the given setting options. + */ + + mocha.setup = function(opts){ + if ('string' == typeof opts) opts = { ui: opts }; + for (var opt in opts) this[opt](opts[opt]); + return this; + }; + + /** + * Run mocha, returning the Runner. + */ + + mocha.run = function(fn){ + var options = mocha.options; + mocha.globals('location'); + + var query = Mocha.utils.parseQuery(window.location.search || ''); + if (query.grep) mocha.grep(query.grep); + + return Mocha.prototype.run.call(mocha, function(){ + Mocha.utils.highlightTags('code'); + if (fn) fn(); + }); + }; +})(); +})(); \ No newline at end of file diff --git a/src/node_modules/nedb/browser-version/test/nedb-browser.js b/src/node_modules/nedb/browser-version/test/nedb-browser.js new file mode 100644 index 0000000..606e39b --- /dev/null +++ b/src/node_modules/nedb/browser-version/test/nedb-browser.js @@ -0,0 +1,298 @@ +/** + * Testing the browser version of NeDB + * The goal of these tests is not to be exhaustive, we have the server-side NeDB tests for that + * This is more of a sanity check which executes most of the code at least once and checks + * it behaves as the server version does + */ + +var assert = chai.assert; + +/** + * Given a docs array and an id, return the document whose id matches, or null if none is found + */ +function findById (docs, id) { + return _.find(docs, function (doc) { return doc._id === id; }) || null; +} + + +describe('Basic CRUD functionality', function () { + + it('Able to create a database object in the browser', function () { + var db = new Nedb(); + + assert.equal(db.inMemoryOnly, true); + assert.equal(db.persistence.inMemoryOnly, true); + }); + + it('Insertion and querying', function (done) { + var db = new Nedb(); + + db.insert({ a: 4 }, function (err, newDoc1) { + assert.isNull(err); + db.insert({ a: 40 }, function (err, newDoc2) { + assert.isNull(err); + db.insert({ a: 400 }, function (err, newDoc3) { + assert.isNull(err); + + db.find({ a: { $gt: 36 } }, function (err, docs) { + var doc2 = _.find(docs, function (doc) { return doc._id === newDoc2._id; }) + , doc3 = _.find(docs, function (doc) { return doc._id === newDoc3._id; }) + ; + + assert.isNull(err); + assert.equal(docs.length, 2); + assert.equal(doc2.a, 40); + assert.equal(doc3.a, 400); + + db.find({ a: { $lt: 36 } }, function (err, docs) { + assert.isNull(err); + assert.equal(docs.length, 1); + assert.equal(docs[0].a, 4); + done(); + }); + }); + }); + }); + }); + }); + + it('Querying with regular expressions', function (done) { + var db = new Nedb(); + + db.insert({ planet: 'Earth' }, function (err, newDoc1) { + assert.isNull(err); + db.insert({ planet: 'Mars' }, function (err, newDoc2) { + assert.isNull(err); + db.insert({ planet: 'Jupiter' }, function (err, newDoc3) { + assert.isNull(err); + db.insert({ planet: 'Eaaaaaarth' }, function (err, newDoc4) { + assert.isNull(err); + db.insert({ planet: 'Maaaars' }, function (err, newDoc5) { + assert.isNull(err); + + db.find({ planet: /ar/ }, function (err, docs) { + assert.isNull(err); + assert.equal(docs.length, 4); + assert.equal(_.find(docs, function (doc) { return doc._id === newDoc1._id; }).planet, 'Earth'); + assert.equal(_.find(docs, function (doc) { return doc._id === newDoc2._id; }).planet, 'Mars'); + assert.equal(_.find(docs, function (doc) { return doc._id === newDoc4._id; }).planet, 'Eaaaaaarth'); + assert.equal(_.find(docs, function (doc) { return doc._id === newDoc5._id; }).planet, 'Maaaars'); + + db.find({ planet: /aa+r/ }, function (err, docs) { + assert.isNull(err); + assert.equal(docs.length, 2); + assert.equal(_.find(docs, function (doc) { return doc._id === newDoc4._id; }).planet, 'Eaaaaaarth'); + assert.equal(_.find(docs, function (doc) { return doc._id === newDoc5._id; }).planet, 'Maaaars'); + + done(); + }); + }); + }); + }); + }); + }); + }); + }); + + it('Updating documents', function (done) { + var db = new Nedb(); + + db.insert({ planet: 'Eaaaaarth' }, function (err, newDoc1) { + db.insert({ planet: 'Maaaaars' }, function (err, newDoc2) { + // Simple update + db.update({ _id: newDoc2._id }, { $set: { planet: 'Saturn' } }, {}, function (err, nr) { + assert.isNull(err); + assert.equal(nr, 1); + + db.find({}, function (err, docs) { + assert.equal(docs.length, 2); + assert.equal(findById(docs, newDoc1._id).planet, 'Eaaaaarth'); + assert.equal(findById(docs, newDoc2._id).planet, 'Saturn'); + + // Failing update + db.update({ _id: 'unknown' }, { $inc: { count: 1 } }, {}, function (err, nr) { + assert.isNull(err); + assert.equal(nr, 0); + + db.find({}, function (err, docs) { + assert.equal(docs.length, 2); + assert.equal(findById(docs, newDoc1._id).planet, 'Eaaaaarth'); + assert.equal(findById(docs, newDoc2._id).planet, 'Saturn'); + + // Document replacement + db.update({ planet: 'Eaaaaarth' }, { planet: 'Uranus' }, { multi: false }, function (err, nr) { + assert.isNull(err); + assert.equal(nr, 1); + + db.find({}, function (err, docs) { + assert.equal(docs.length, 2); + assert.equal(findById(docs, newDoc1._id).planet, 'Uranus'); + assert.equal(findById(docs, newDoc2._id).planet, 'Saturn'); + + // Multi update + db.update({}, { $inc: { count: 3 } }, { multi: true }, function (err, nr) { + assert.isNull(err); + assert.equal(nr, 2); + + db.find({}, function (err, docs) { + assert.equal(docs.length, 2); + assert.equal(findById(docs, newDoc1._id).planet, 'Uranus'); + assert.equal(findById(docs, newDoc1._id).count, 3); + assert.equal(findById(docs, newDoc2._id).planet, 'Saturn'); + assert.equal(findById(docs, newDoc2._id).count, 3); + + done(); + }); + }); + }); + }); + }); + }); + }); + }); + }); + }); + }); + + it('Updating documents: special modifiers', function (done) { + var db = new Nedb(); + + db.insert({ planet: 'Earth' }, function (err, newDoc1) { + // Pushing to an array + db.update({}, { $push: { satellites: 'Phobos' } }, {}, function (err, nr) { + assert.isNull(err); + assert.equal(nr, 1); + + db.findOne({}, function (err, doc) { + assert.deepEqual(doc, { planet: 'Earth', _id: newDoc1._id, satellites: ['Phobos'] }); + + db.update({}, { $push: { satellites: 'Deimos' } }, {}, function (err, nr) { + assert.isNull(err); + assert.equal(nr, 1); + + db.findOne({}, function (err, doc) { + assert.deepEqual(doc, { planet: 'Earth', _id: newDoc1._id, satellites: ['Phobos', 'Deimos'] }); + + done(); + }); + }); + }); + }); + }); + }); + + it('Upserts', function (done) { + var db = new Nedb(); + + db.update({ a: 4 }, { $inc: { b: 1 } }, { upsert: true }, function (err, nr, upsert) { + assert.isNull(err); + // Return upserted document + assert.equal(upsert.a, 4); + assert.equal(upsert.b, 1); + assert.equal(nr, 1); + + db.find({}, function (err, docs) { + assert.equal(docs.length, 1); + assert.equal(docs[0].a, 4); + assert.equal(docs[0].b, 1); + + done(); + }); + }); + }); + + it('Removing documents', function (done) { + var db = new Nedb(); + + db.insert({ a: 2 }); + db.insert({ a: 5 }); + db.insert({ a: 7 }); + + // Multi remove + db.remove({ a: { $in: [ 5, 7 ] } }, { multi: true }, function (err, nr) { + assert.isNull(err); + assert.equal(nr, 2); + + db.find({}, function (err, docs) { + assert.equal(docs.length, 1); + assert.equal(docs[0].a, 2); + + // Remove with no match + db.remove({ b: { $exists: true } }, { multi: true }, function (err, nr) { + assert.isNull(err); + assert.equal(nr, 0); + + db.find({}, function (err, docs) { + assert.equal(docs.length, 1); + assert.equal(docs[0].a, 2); + + // Simple remove + db.remove({ a: { $exists: true } }, { multi: true }, function (err, nr) { + assert.isNull(err); + assert.equal(nr, 1); + + db.find({}, function (err, docs) { + assert.equal(docs.length, 0); + + done(); + }); + }); + }); + }); + }); + }); + }); + +}); // ==== End of 'Basic CRUD functionality' ==== // + + +describe('Indexing', function () { + + it('getCandidates works as expected', function (done) { + var db = new Nedb(); + + db.insert({ a: 4 }, function () { + db.insert({ a: 6 }, function () { + db.insert({ a: 7 }, function () { + var candidates = db.getCandidates({ a: 6 }) + assert.equal(candidates.length, 3); + assert.isDefined(_.find(candidates, function (doc) { return doc.a === 4; })); + assert.isDefined(_.find(candidates, function (doc) { return doc.a === 6; })); + assert.isDefined(_.find(candidates, function (doc) { return doc.a === 7; })); + + db.ensureIndex({ fieldName: 'a' }); + + candidates = db.getCandidates({ a: 6 }) + assert.equal(candidates.length, 1); + assert.isDefined(_.find(candidates, function (doc) { return doc.a === 6; })); + + done(); + }); + }); + }); + }); + + it('Can use indexes to enforce a unique constraint', function (done) { + var db = new Nedb(); + + db.ensureIndex({ fieldName: 'u', unique: true }); + + db.insert({ u : 5 }, function (err) { + assert.isNull(err); + + db.insert({ u : 98 }, function (err) { + assert.isNull(err); + + db.insert({ u : 5 }, function (err) { + assert.equal(err.errorType, 'uniqueViolated'); + + done(); + }); + }); + }); + }); + +}); // ==== End of 'Indexing' ==== // + + + diff --git a/src/node_modules/nedb/browser-version/test/testPersistence.html b/src/node_modules/nedb/browser-version/test/testPersistence.html new file mode 100644 index 0000000..d7de616 --- /dev/null +++ b/src/node_modules/nedb/browser-version/test/testPersistence.html @@ -0,0 +1,13 @@ + + + + + Test NeDB persistence in the browser + + + +
    + + + + diff --git a/src/node_modules/nedb/browser-version/test/testPersistence.js b/src/node_modules/nedb/browser-version/test/testPersistence.js new file mode 100644 index 0000000..d4c3d86 --- /dev/null +++ b/src/node_modules/nedb/browser-version/test/testPersistence.js @@ -0,0 +1,18 @@ +console.log("Beginning tests"); +console.log("Please note these tests work on Chrome latest, might not work on other browsers due to discrepancies in how local storage works for the file:// protocol"); + +function testsFailed () { + document.getElementById("results").innerHTML = "TESTS FAILED"; +} + +localStorage.removeItem('test'); +var db = new Nedb({ filename: 'test', autoload: true }); +db.insert({ hello: 'world' }, function (err) { + if (err) { + testsFailed(); + return; + } + + window.location = './testPersistence2.html'; +}); + diff --git a/src/node_modules/nedb/browser-version/test/testPersistence2.html b/src/node_modules/nedb/browser-version/test/testPersistence2.html new file mode 100644 index 0000000..586bcbd --- /dev/null +++ b/src/node_modules/nedb/browser-version/test/testPersistence2.html @@ -0,0 +1,13 @@ + + + + + Test NeDB persistence in the browser - Results + + + +
    + + + + diff --git a/src/node_modules/nedb/browser-version/test/testPersistence2.js b/src/node_modules/nedb/browser-version/test/testPersistence2.js new file mode 100644 index 0000000..0f082eb --- /dev/null +++ b/src/node_modules/nedb/browser-version/test/testPersistence2.js @@ -0,0 +1,27 @@ +console.log("Checking tests results"); +console.log("Please note these tests work on Chrome latest, might not work on other browsers due to discrepancies in how local storage works for the file:// protocol"); + +function testsFailed () { + document.getElementById("results").innerHTML = "TESTS FAILED"; +} + +var db = new Nedb({ filename: 'test', autoload: true }); +db.find({}, function (err, docs) { + if (docs.length !== 1) { + console.log("Unexpected length of document database"); + return testsFailed(); + } + + if (Object.keys(docs[0]).length !== 2) { + console.log("Unexpected length insert document in database"); + return testsFailed(); + } + + if (docs[0].hello !== 'world') { + console.log("Unexpected document"); + return testsFailed(); + } + + document.getElementById("results").innerHTML = "BROWSER PERSISTENCE TEST PASSED"; +}); + diff --git a/src/node_modules/nedb/index.js b/src/node_modules/nedb/index.js new file mode 100644 index 0000000..d198227 --- /dev/null +++ b/src/node_modules/nedb/index.js @@ -0,0 +1,3 @@ +var Datastore = require('./lib/datastore'); + +module.exports = Datastore; diff --git a/src/node_modules/nedb/lib/cursor.js b/src/node_modules/nedb/lib/cursor.js new file mode 100644 index 0000000..e25fe2a --- /dev/null +++ b/src/node_modules/nedb/lib/cursor.js @@ -0,0 +1,185 @@ +/** + * 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; diff --git a/src/node_modules/nedb/lib/customUtils.js b/src/node_modules/nedb/lib/customUtils.js new file mode 100644 index 0000000..5bbe883 --- /dev/null +++ b/src/node_modules/nedb/lib/customUtils.js @@ -0,0 +1,23 @@ +var crypto = require('crypto') + , fs = require('fs') + ; + +/** + * 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 crypto.randomBytes(Math.ceil(Math.max(8, len * 2))) + .toString('base64') + .replace(/[+\/]/g, '') + .slice(0, len); +} + + +// Interface +module.exports.uid = uid; + diff --git a/src/node_modules/nedb/lib/datastore.js b/src/node_modules/nedb/lib/datastore.js new file mode 100644 index 0000000..0eedb4b --- /dev/null +++ b/src/node_modules/nedb/lib/datastore.js @@ -0,0 +1,594 @@ +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; diff --git a/src/node_modules/nedb/lib/executor.js b/src/node_modules/nedb/lib/executor.js new file mode 100644 index 0000000..d275868 --- /dev/null +++ b/src/node_modules/nedb/lib/executor.js @@ -0,0 +1,77 @@ +/** + * 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; diff --git a/src/node_modules/nedb/lib/indexes.js b/src/node_modules/nedb/lib/indexes.js new file mode 100644 index 0000000..ff68d85 --- /dev/null +++ b/src/node_modules/nedb/lib/indexes.js @@ -0,0 +1,294 @@ +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; diff --git a/src/node_modules/nedb/lib/model.js b/src/node_modules/nedb/lib/model.js new file mode 100644 index 0000000..22e69bc --- /dev/null +++ b/src/node_modules/nedb/lib/model.js @@ -0,0 +1,757 @@ +/** + * 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 === undefined) { 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; diff --git a/src/node_modules/nedb/lib/persistence.js b/src/node_modules/nedb/lib/persistence.js new file mode 100644 index 0000000..6407438 --- /dev/null +++ b/src/node_modules/nedb/lib/persistence.js @@ -0,0 +1,335 @@ +/** + * 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; diff --git a/src/node_modules/nedb/lib/storage.js b/src/node_modules/nedb/lib/storage.js new file mode 100644 index 0000000..018d15e --- /dev/null +++ b/src/node_modules/nedb/lib/storage.js @@ -0,0 +1,15 @@ +/** + * 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 + */ + +var fs = require('fs') + , mkdirp = require('mkdirp') + ; + + +module.exports = fs; +module.exports.mkdirp = mkdirp; diff --git a/src/node_modules/nedb/node_modules/async/LICENSE b/src/node_modules/nedb/node_modules/async/LICENSE new file mode 100644 index 0000000..b7f9d50 --- /dev/null +++ b/src/node_modules/nedb/node_modules/async/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2010 Caolan McMahon + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/src/node_modules/nedb/node_modules/async/README.md b/src/node_modules/nedb/node_modules/async/README.md new file mode 100644 index 0000000..951f76e --- /dev/null +++ b/src/node_modules/nedb/node_modules/async/README.md @@ -0,0 +1,1425 @@ +# Async.js + +Async is a utility module which provides straight-forward, powerful functions +for working with asynchronous JavaScript. Although originally designed for +use with [node.js](http://nodejs.org), it can also be used directly in the +browser. Also supports [component](https://github.com/component/component). + +Async provides around 20 functions that include the usual 'functional' +suspects (map, reduce, filter, each…) as well as some common patterns +for asynchronous control flow (parallel, series, waterfall…). All these +functions assume you follow the node.js convention of providing a single +callback as the last argument of your async function. + + +## Quick Examples + +```javascript +async.map(['file1','file2','file3'], fs.stat, function(err, results){ + // results is now an array of stats for each file +}); + +async.filter(['file1','file2','file3'], fs.exists, function(results){ + // results now equals an array of the existing files +}); + +async.parallel([ + function(){ ... }, + function(){ ... } +], callback); + +async.series([ + function(){ ... }, + function(){ ... } +]); +``` + +There are many more functions available so take a look at the docs below for a +full list. This module aims to be comprehensive, so if you feel anything is +missing please create a GitHub issue for it. + +## Common Pitfalls + +### Binding a context to an iterator + +This section is really about bind, not about async. If you are wondering how to +make async execute your iterators in a given context, or are confused as to why +a method of another library isn't working as an iterator, study this example: + +```js +// Here is a simple object with an (unnecessarily roundabout) squaring method +var AsyncSquaringLibrary = { + squareExponent: 2, + square: function(number, callback){ + var result = Math.pow(number, this.squareExponent); + setTimeout(function(){ + callback(null, result); + }, 200); + } +}; + +async.map([1, 2, 3], AsyncSquaringLibrary.square, function(err, result){ + // result is [NaN, NaN, NaN] + // This fails because the `this.squareExponent` expression in the square + // function is not evaluated in the context of AsyncSquaringLibrary, and is + // therefore undefined. +}); + +async.map([1, 2, 3], AsyncSquaringLibrary.square.bind(AsyncSquaringLibrary), function(err, result){ + // result is [1, 4, 9] + // With the help of bind we can attach a context to the iterator before + // passing it to async. Now the square function will be executed in its + // 'home' AsyncSquaringLibrary context and the value of `this.squareExponent` + // will be as expected. +}); +``` + +## Download + +The source is available for download from +[GitHub](http://github.com/caolan/async). +Alternatively, you can install using Node Package Manager (npm): + + npm install async + +__Development:__ [async.js](https://github.com/caolan/async/raw/master/lib/async.js) - 29.6kb Uncompressed + +## In the Browser + +So far it's been tested in IE6, IE7, IE8, FF3.6 and Chrome 5. Usage: + +```html + + +``` + +## Documentation + +### Collections + +* [each](#each) +* [eachSeries](#eachSeries) +* [eachLimit](#eachLimit) +* [map](#map) +* [mapSeries](#mapSeries) +* [mapLimit](#mapLimit) +* [filter](#filter) +* [filterSeries](#filterSeries) +* [reject](#reject) +* [rejectSeries](#rejectSeries) +* [reduce](#reduce) +* [reduceRight](#reduceRight) +* [detect](#detect) +* [detectSeries](#detectSeries) +* [sortBy](#sortBy) +* [some](#some) +* [every](#every) +* [concat](#concat) +* [concatSeries](#concatSeries) + +### Control Flow + +* [series](#series) +* [parallel](#parallel) +* [parallelLimit](#parallellimittasks-limit-callback) +* [whilst](#whilst) +* [doWhilst](#doWhilst) +* [until](#until) +* [doUntil](#doUntil) +* [forever](#forever) +* [waterfall](#waterfall) +* [compose](#compose) +* [applyEach](#applyEach) +* [applyEachSeries](#applyEachSeries) +* [queue](#queue) +* [cargo](#cargo) +* [auto](#auto) +* [iterator](#iterator) +* [apply](#apply) +* [nextTick](#nextTick) +* [times](#times) +* [timesSeries](#timesSeries) + +### Utils + +* [memoize](#memoize) +* [unmemoize](#unmemoize) +* [log](#log) +* [dir](#dir) +* [noConflict](#noConflict) + + +## Collections + +
    + +### each(arr, iterator, callback) + +Applies an iterator function to each item in an array, in parallel. +The iterator is called with an item from the list and a callback for when it +has finished. If the iterator passes an error to this callback, the main +callback for the each function is immediately called with the error. + +Note, that since this function applies the iterator to each item in parallel +there is no guarantee that the iterator functions will complete in order. + +__Arguments__ + +* arr - An array to iterate over. +* iterator(item, callback) - A function to apply to each item in the array. + The iterator is passed a callback(err) which must be called once it has + completed. If no error has occured, the callback should be run without + arguments or with an explicit null argument. +* callback(err) - A callback which is called after all the iterator functions + have finished, or an error has occurred. + +__Example__ + +```js +// assuming openFiles is an array of file names and saveFile is a function +// to save the modified contents of that file: + +async.each(openFiles, saveFile, function(err){ + // if any of the saves produced an error, err would equal that error +}); +``` + +--------------------------------------- + + + +### eachSeries(arr, iterator, callback) + +The same as each only the iterator is applied to each item in the array in +series. The next iterator is only called once the current one has completed +processing. This means the iterator functions will complete in order. + + +--------------------------------------- + + + +### eachLimit(arr, limit, iterator, callback) + +The same as each only no more than "limit" iterators will be simultaneously +running at any time. + +Note that the items are not processed in batches, so there is no guarantee that + the first "limit" iterator functions will complete before any others are +started. + +__Arguments__ + +* arr - An array to iterate over. +* limit - The maximum number of iterators to run at any time. +* iterator(item, callback) - A function to apply to each item in the array. + The iterator is passed a callback(err) which must be called once it has + completed. If no error has occured, the callback should be run without + arguments or with an explicit null argument. +* callback(err) - A callback which is called after all the iterator functions + have finished, or an error has occurred. + +__Example__ + +```js +// Assume documents is an array of JSON objects and requestApi is a +// function that interacts with a rate-limited REST api. + +async.eachLimit(documents, 20, requestApi, function(err){ + // if any of the saves produced an error, err would equal that error +}); +``` + +--------------------------------------- + + +### map(arr, iterator, callback) + +Produces a new array of values by mapping each value in the given array through +the iterator function. The iterator is called with an item from the array and a +callback for when it has finished processing. The callback takes 2 arguments, +an error and the transformed item from the array. If the iterator passes an +error to this callback, the main callback for the map function is immediately +called with the error. + +Note, that since this function applies the iterator to each item in parallel +there is no guarantee that the iterator functions will complete in order, however +the results array will be in the same order as the original array. + +__Arguments__ + +* arr - An array to iterate over. +* iterator(item, callback) - A function to apply to each item in the array. + The iterator is passed a callback(err, transformed) which must be called once + it has completed with an error (which can be null) and a transformed item. +* callback(err, results) - A callback which is called after all the iterator + functions have finished, or an error has occurred. Results is an array of the + transformed items from the original array. + +__Example__ + +```js +async.map(['file1','file2','file3'], fs.stat, function(err, results){ + // results is now an array of stats for each file +}); +``` + +--------------------------------------- + + +### mapSeries(arr, iterator, callback) + +The same as map only the iterator is applied to each item in the array in +series. The next iterator is only called once the current one has completed +processing. The results array will be in the same order as the original. + + +--------------------------------------- + + +### mapLimit(arr, limit, iterator, callback) + +The same as map only no more than "limit" iterators will be simultaneously +running at any time. + +Note that the items are not processed in batches, so there is no guarantee that + the first "limit" iterator functions will complete before any others are +started. + +__Arguments__ + +* arr - An array to iterate over. +* limit - The maximum number of iterators to run at any time. +* iterator(item, callback) - A function to apply to each item in the array. + The iterator is passed a callback(err, transformed) which must be called once + it has completed with an error (which can be null) and a transformed item. +* callback(err, results) - A callback which is called after all the iterator + functions have finished, or an error has occurred. Results is an array of the + transformed items from the original array. + +__Example__ + +```js +async.mapLimit(['file1','file2','file3'], 1, fs.stat, function(err, results){ + // results is now an array of stats for each file +}); +``` + +--------------------------------------- + + +### filter(arr, iterator, callback) + +__Alias:__ select + +Returns a new array of all the values which pass an async truth test. +_The callback for each iterator call only accepts a single argument of true or +false, it does not accept an error argument first!_ This is in-line with the +way node libraries work with truth tests like fs.exists. This operation is +performed in parallel, but the results array will be in the same order as the +original. + +__Arguments__ + +* arr - An array to iterate over. +* iterator(item, callback) - A truth test to apply to each item in the array. + The iterator is passed a callback(truthValue) which must be called with a + boolean argument once it has completed. +* callback(results) - A callback which is called after all the iterator + functions have finished. + +__Example__ + +```js +async.filter(['file1','file2','file3'], fs.exists, function(results){ + // results now equals an array of the existing files +}); +``` + +--------------------------------------- + + +### filterSeries(arr, iterator, callback) + +__alias:__ selectSeries + +The same as filter only the iterator is applied to each item in the array in +series. The next iterator is only called once the current one has completed +processing. The results array will be in the same order as the original. + +--------------------------------------- + + +### reject(arr, iterator, callback) + +The opposite of filter. Removes values that pass an async truth test. + +--------------------------------------- + + +### rejectSeries(arr, iterator, callback) + +The same as reject, only the iterator is applied to each item in the array +in series. + + +--------------------------------------- + + +### reduce(arr, memo, iterator, callback) + +__aliases:__ inject, foldl + +Reduces a list of values into a single value using an async iterator to return +each successive step. Memo is the initial state of the reduction. This +function only operates in series. For performance reasons, it may make sense to +split a call to this function into a parallel map, then use the normal +Array.prototype.reduce on the results. This function is for situations where +each step in the reduction needs to be async, if you can get the data before +reducing it then it's probably a good idea to do so. + +__Arguments__ + +* arr - An array to iterate over. +* memo - The initial state of the reduction. +* iterator(memo, item, callback) - A function applied to each item in the + array to produce the next step in the reduction. The iterator is passed a + callback(err, reduction) which accepts an optional error as its first + argument, and the state of the reduction as the second. If an error is + passed to the callback, the reduction is stopped and the main callback is + immediately called with the error. +* callback(err, result) - A callback which is called after all the iterator + functions have finished. Result is the reduced value. + +__Example__ + +```js +async.reduce([1,2,3], 0, function(memo, item, callback){ + // pointless async: + process.nextTick(function(){ + callback(null, memo + item) + }); +}, function(err, result){ + // result is now equal to the last value of memo, which is 6 +}); +``` + +--------------------------------------- + + +### reduceRight(arr, memo, iterator, callback) + +__Alias:__ foldr + +Same as reduce, only operates on the items in the array in reverse order. + + +--------------------------------------- + + +### detect(arr, iterator, callback) + +Returns the first value in a list that passes an async truth test. The +iterator is applied in parallel, meaning the first iterator to return true will +fire the detect callback with that result. That means the result might not be +the first item in the original array (in terms of order) that passes the test. + +If order within the original array is important then look at detectSeries. + +__Arguments__ + +* arr - An array to iterate over. +* iterator(item, callback) - A truth test to apply to each item in the array. + The iterator is passed a callback(truthValue) which must be called with a + boolean argument once it has completed. +* callback(result) - A callback which is called as soon as any iterator returns + true, or after all the iterator functions have finished. Result will be + the first item in the array that passes the truth test (iterator) or the + value undefined if none passed. + +__Example__ + +```js +async.detect(['file1','file2','file3'], fs.exists, function(result){ + // result now equals the first file in the list that exists +}); +``` + +--------------------------------------- + + +### detectSeries(arr, iterator, callback) + +The same as detect, only the iterator is applied to each item in the array +in series. This means the result is always the first in the original array (in +terms of array order) that passes the truth test. + + +--------------------------------------- + + +### sortBy(arr, iterator, callback) + +Sorts a list by the results of running each value through an async iterator. + +__Arguments__ + +* arr - An array to iterate over. +* iterator(item, callback) - A function to apply to each item in the array. + The iterator is passed a callback(err, sortValue) which must be called once it + has completed with an error (which can be null) and a value to use as the sort + criteria. +* callback(err, results) - A callback which is called after all the iterator + functions have finished, or an error has occurred. Results is the items from + the original array sorted by the values returned by the iterator calls. + +__Example__ + +```js +async.sortBy(['file1','file2','file3'], function(file, callback){ + fs.stat(file, function(err, stats){ + callback(err, stats.mtime); + }); +}, function(err, results){ + // results is now the original array of files sorted by + // modified date +}); +``` + +--------------------------------------- + + +### some(arr, iterator, callback) + +__Alias:__ any + +Returns true if at least one element in the array satisfies an async test. +_The callback for each iterator call only accepts a single argument of true or +false, it does not accept an error argument first!_ This is in-line with the +way node libraries work with truth tests like fs.exists. Once any iterator +call returns true, the main callback is immediately called. + +__Arguments__ + +* arr - An array to iterate over. +* iterator(item, callback) - A truth test to apply to each item in the array. + The iterator is passed a callback(truthValue) which must be called with a + boolean argument once it has completed. +* callback(result) - A callback which is called as soon as any iterator returns + true, or after all the iterator functions have finished. Result will be + either true or false depending on the values of the async tests. + +__Example__ + +```js +async.some(['file1','file2','file3'], fs.exists, function(result){ + // if result is true then at least one of the files exists +}); +``` + +--------------------------------------- + + +### every(arr, iterator, callback) + +__Alias:__ all + +Returns true if every element in the array satisfies an async test. +_The callback for each iterator call only accepts a single argument of true or +false, it does not accept an error argument first!_ This is in-line with the +way node libraries work with truth tests like fs.exists. + +__Arguments__ + +* arr - An array to iterate over. +* iterator(item, callback) - A truth test to apply to each item in the array. + The iterator is passed a callback(truthValue) which must be called with a + boolean argument once it has completed. +* callback(result) - A callback which is called after all the iterator + functions have finished. Result will be either true or false depending on + the values of the async tests. + +__Example__ + +```js +async.every(['file1','file2','file3'], fs.exists, function(result){ + // if result is true then every file exists +}); +``` + +--------------------------------------- + + +### concat(arr, iterator, callback) + +Applies an iterator to each item in a list, concatenating the results. Returns the +concatenated list. The iterators are called in parallel, and the results are +concatenated as they return. There is no guarantee that the results array will +be returned in the original order of the arguments passed to the iterator function. + +__Arguments__ + +* arr - An array to iterate over +* iterator(item, callback) - A function to apply to each item in the array. + The iterator is passed a callback(err, results) which must be called once it + has completed with an error (which can be null) and an array of results. +* callback(err, results) - A callback which is called after all the iterator + functions have finished, or an error has occurred. Results is an array containing + the concatenated results of the iterator function. + +__Example__ + +```js +async.concat(['dir1','dir2','dir3'], fs.readdir, function(err, files){ + // files is now a list of filenames that exist in the 3 directories +}); +``` + +--------------------------------------- + + +### concatSeries(arr, iterator, callback) + +Same as async.concat, but executes in series instead of parallel. + + +## Control Flow + + +### series(tasks, [callback]) + +Run an array of functions in series, each one running once the previous +function has completed. If any functions in the series pass an error to its +callback, no more functions are run and the callback for the series is +immediately called with the value of the error. Once the tasks have completed, +the results are passed to the final callback as an array. + +It is also possible to use an object instead of an array. Each property will be +run as a function and the results will be passed to the final callback as an object +instead of an array. This can be a more readable way of handling results from +async.series. + + +__Arguments__ + +* tasks - An array or object containing functions to run, each function is passed + a callback(err, result) it must call on completion with an error (which can + be null) and an optional result value. +* callback(err, results) - An optional callback to run once all the functions + have completed. This function gets a results array (or object) containing all + the result arguments passed to the task callbacks. + +__Example__ + +```js +async.series([ + function(callback){ + // do some stuff ... + callback(null, 'one'); + }, + function(callback){ + // do some more stuff ... + callback(null, 'two'); + } +], +// optional callback +function(err, results){ + // results is now equal to ['one', 'two'] +}); + + +// an example using an object instead of an array +async.series({ + one: function(callback){ + setTimeout(function(){ + callback(null, 1); + }, 200); + }, + two: function(callback){ + setTimeout(function(){ + callback(null, 2); + }, 100); + } +}, +function(err, results) { + // results is now equal to: {one: 1, two: 2} +}); +``` + +--------------------------------------- + + +### parallel(tasks, [callback]) + +Run an array of functions in parallel, without waiting until the previous +function has completed. If any of the functions pass an error to its +callback, the main callback is immediately called with the value of the error. +Once the tasks have completed, the results are passed to the final callback as an +array. + +It is also possible to use an object instead of an array. Each property will be +run as a function and the results will be passed to the final callback as an object +instead of an array. This can be a more readable way of handling results from +async.parallel. + + +__Arguments__ + +* tasks - An array or object containing functions to run, each function is passed + a callback(err, result) it must call on completion with an error (which can + be null) and an optional result value. +* callback(err, results) - An optional callback to run once all the functions + have completed. This function gets a results array (or object) containing all + the result arguments passed to the task callbacks. + +__Example__ + +```js +async.parallel([ + function(callback){ + setTimeout(function(){ + callback(null, 'one'); + }, 200); + }, + function(callback){ + setTimeout(function(){ + callback(null, 'two'); + }, 100); + } +], +// optional callback +function(err, results){ + // the results array will equal ['one','two'] even though + // the second function had a shorter timeout. +}); + + +// an example using an object instead of an array +async.parallel({ + one: function(callback){ + setTimeout(function(){ + callback(null, 1); + }, 200); + }, + two: function(callback){ + setTimeout(function(){ + callback(null, 2); + }, 100); + } +}, +function(err, results) { + // results is now equals to: {one: 1, two: 2} +}); +``` + +--------------------------------------- + + +### parallelLimit(tasks, limit, [callback]) + +The same as parallel only the tasks are executed in parallel with a maximum of "limit" +tasks executing at any time. + +Note that the tasks are not executed in batches, so there is no guarantee that +the first "limit" tasks will complete before any others are started. + +__Arguments__ + +* tasks - An array or object containing functions to run, each function is passed + a callback(err, result) it must call on completion with an error (which can + be null) and an optional result value. +* limit - The maximum number of tasks to run at any time. +* callback(err, results) - An optional callback to run once all the functions + have completed. This function gets a results array (or object) containing all + the result arguments passed to the task callbacks. + +--------------------------------------- + + +### whilst(test, fn, callback) + +Repeatedly call fn, while test returns true. Calls the callback when stopped, +or an error occurs. + +__Arguments__ + +* test() - synchronous truth test to perform before each execution of fn. +* fn(callback) - A function to call each time the test passes. The function is + passed a callback(err) which must be called once it has completed with an + optional error argument. +* callback(err) - A callback which is called after the test fails and repeated + execution of fn has stopped. + +__Example__ + +```js +var count = 0; + +async.whilst( + function () { return count < 5; }, + function (callback) { + count++; + setTimeout(callback, 1000); + }, + function (err) { + // 5 seconds have passed + } +); +``` + +--------------------------------------- + + +### doWhilst(fn, test, callback) + +The post check version of whilst. To reflect the difference in the order of operations `test` and `fn` arguments are switched. `doWhilst` is to `whilst` as `do while` is to `while` in plain JavaScript. + +--------------------------------------- + + +### until(test, fn, callback) + +Repeatedly call fn, until test returns true. Calls the callback when stopped, +or an error occurs. + +The inverse of async.whilst. + +--------------------------------------- + + +### doUntil(fn, test, callback) + +Like doWhilst except the test is inverted. Note the argument ordering differs from `until`. + +--------------------------------------- + + +### forever(fn, callback) + +Calls the asynchronous function 'fn' repeatedly, in series, indefinitely. +If an error is passed to fn's callback then 'callback' is called with the +error, otherwise it will never be called. + +--------------------------------------- + + +### waterfall(tasks, [callback]) + +Runs an array of functions in series, each passing their results to the next in +the array. However, if any of the functions pass an error to the callback, the +next function is not executed and the main callback is immediately called with +the error. + +__Arguments__ + +* tasks - An array of functions to run, each function is passed a + callback(err, result1, result2, ...) it must call on completion. The first + argument is an error (which can be null) and any further arguments will be + passed as arguments in order to the next task. +* callback(err, [results]) - An optional callback to run once all the functions + have completed. This will be passed the results of the last task's callback. + + + +__Example__ + +```js +async.waterfall([ + function(callback){ + callback(null, 'one', 'two'); + }, + function(arg1, arg2, callback){ + callback(null, 'three'); + }, + function(arg1, callback){ + // arg1 now equals 'three' + callback(null, 'done'); + } +], function (err, result) { + // result now equals 'done' +}); +``` + +--------------------------------------- + +### compose(fn1, fn2...) + +Creates a function which is a composition of the passed asynchronous +functions. Each function consumes the return value of the function that +follows. Composing functions f(), g() and h() would produce the result of +f(g(h())), only this version uses callbacks to obtain the return values. + +Each function is executed with the `this` binding of the composed function. + +__Arguments__ + +* functions... - the asynchronous functions to compose + + +__Example__ + +```js +function add1(n, callback) { + setTimeout(function () { + callback(null, n + 1); + }, 10); +} + +function mul3(n, callback) { + setTimeout(function () { + callback(null, n * 3); + }, 10); +} + +var add1mul3 = async.compose(mul3, add1); + +add1mul3(4, function (err, result) { + // result now equals 15 +}); +``` + +--------------------------------------- + +### applyEach(fns, args..., callback) + +Applies the provided arguments to each function in the array, calling the +callback after all functions have completed. If you only provide the first +argument then it will return a function which lets you pass in the +arguments as if it were a single function call. + +__Arguments__ + +* fns - the asynchronous functions to all call with the same arguments +* args... - any number of separate arguments to pass to the function +* callback - the final argument should be the callback, called when all + functions have completed processing + + +__Example__ + +```js +async.applyEach([enableSearch, updateSchema], 'bucket', callback); + +// partial application example: +async.each( + buckets, + async.applyEach([enableSearch, updateSchema]), + callback +); +``` + +--------------------------------------- + + +### applyEachSeries(arr, iterator, callback) + +The same as applyEach only the functions are applied in series. + +--------------------------------------- + + +### queue(worker, concurrency) + +Creates a queue object with the specified concurrency. Tasks added to the +queue will be processed in parallel (up to the concurrency limit). If all +workers are in progress, the task is queued until one is available. Once +a worker has completed a task, the task's callback is called. + +__Arguments__ + +* worker(task, callback) - An asynchronous function for processing a queued + task, which must call its callback(err) argument when finished, with an + optional error as an argument. +* concurrency - An integer for determining how many worker functions should be + run in parallel. + +__Queue objects__ + +The queue object returned by this function has the following properties and +methods: + +* length() - a function returning the number of items waiting to be processed. +* concurrency - an integer for determining how many worker functions should be + run in parallel. This property can be changed after a queue is created to + alter the concurrency on-the-fly. +* push(task, [callback]) - add a new task to the queue, the callback is called + once the worker has finished processing the task. + instead of a single task, an array of tasks can be submitted. the respective callback is used for every task in the list. +* unshift(task, [callback]) - add a new task to the front of the queue. +* saturated - a callback that is called when the queue length hits the concurrency and further tasks will be queued +* empty - a callback that is called when the last item from the queue is given to a worker +* drain - a callback that is called when the last item from the queue has returned from the worker + +__Example__ + +```js +// create a queue object with concurrency 2 + +var q = async.queue(function (task, callback) { + console.log('hello ' + task.name); + callback(); +}, 2); + + +// assign a callback +q.drain = function() { + console.log('all items have been processed'); +} + +// add some items to the queue + +q.push({name: 'foo'}, function (err) { + console.log('finished processing foo'); +}); +q.push({name: 'bar'}, function (err) { + console.log('finished processing bar'); +}); + +// add some items to the queue (batch-wise) + +q.push([{name: 'baz'},{name: 'bay'},{name: 'bax'}], function (err) { + console.log('finished processing bar'); +}); + +// add some items to the front of the queue + +q.unshift({name: 'bar'}, function (err) { + console.log('finished processing bar'); +}); +``` + +--------------------------------------- + + +### cargo(worker, [payload]) + +Creates a cargo object with the specified payload. Tasks added to the +cargo will be processed altogether (up to the payload limit). If the +worker is in progress, the task is queued until it is available. Once +the worker has completed some tasks, each callback of those tasks is called. + +__Arguments__ + +* worker(tasks, callback) - An asynchronous function for processing an array of + queued tasks, which must call its callback(err) argument when finished, with + an optional error as an argument. +* payload - An optional integer for determining how many tasks should be + processed per round; if omitted, the default is unlimited. + +__Cargo objects__ + +The cargo object returned by this function has the following properties and +methods: + +* length() - a function returning the number of items waiting to be processed. +* payload - an integer for determining how many tasks should be + process per round. This property can be changed after a cargo is created to + alter the payload on-the-fly. +* push(task, [callback]) - add a new task to the queue, the callback is called + once the worker has finished processing the task. + instead of a single task, an array of tasks can be submitted. the respective callback is used for every task in the list. +* saturated - a callback that is called when the queue length hits the concurrency and further tasks will be queued +* empty - a callback that is called when the last item from the queue is given to a worker +* drain - a callback that is called when the last item from the queue has returned from the worker + +__Example__ + +```js +// create a cargo object with payload 2 + +var cargo = async.cargo(function (tasks, callback) { + for(var i=0; i +### auto(tasks, [callback]) + +Determines the best order for running functions based on their requirements. +Each function can optionally depend on other functions being completed first, +and each function is run as soon as its requirements are satisfied. If any of +the functions pass an error to their callback, that function will not complete +(so any other functions depending on it will not run) and the main callback +will be called immediately with the error. Functions also receive an object +containing the results of functions which have completed so far. + +Note, all functions are called with a results object as a second argument, +so it is unsafe to pass functions in the tasks object which cannot handle the +extra argument. For example, this snippet of code: + +```js +async.auto({ + readData: async.apply(fs.readFile, 'data.txt', 'utf-8') +}, callback); +``` + +will have the effect of calling readFile with the results object as the last +argument, which will fail: + +```js +fs.readFile('data.txt', 'utf-8', cb, {}); +``` + +Instead, wrap the call to readFile in a function which does not forward the +results object: + +```js +async.auto({ + readData: function(cb, results){ + fs.readFile('data.txt', 'utf-8', cb); + } +}, callback); +``` + +__Arguments__ + +* tasks - An object literal containing named functions or an array of + requirements, with the function itself the last item in the array. The key + used for each function or array is used when specifying requirements. The + function receives two arguments: (1) a callback(err, result) which must be + called when finished, passing an error (which can be null) and the result of + the function's execution, and (2) a results object, containing the results of + the previously executed functions. +* callback(err, results) - An optional callback which is called when all the + tasks have been completed. The callback will receive an error as an argument + if any tasks pass an error to their callback. Results will always be passed + but if an error occurred, no other tasks will be performed, and the results + object will only contain partial results. + + +__Example__ + +```js +async.auto({ + get_data: function(callback){ + // async code to get some data + }, + make_folder: function(callback){ + // async code to create a directory to store a file in + // this is run at the same time as getting the data + }, + write_file: ['get_data', 'make_folder', function(callback){ + // once there is some data and the directory exists, + // write the data to a file in the directory + callback(null, filename); + }], + email_link: ['write_file', function(callback, results){ + // once the file is written let's email a link to it... + // results.write_file contains the filename returned by write_file. + }] +}); +``` + +This is a fairly trivial example, but to do this using the basic parallel and +series functions would look like this: + +```js +async.parallel([ + function(callback){ + // async code to get some data + }, + function(callback){ + // async code to create a directory to store a file in + // this is run at the same time as getting the data + } +], +function(err, results){ + async.series([ + function(callback){ + // once there is some data and the directory exists, + // write the data to a file in the directory + }, + function(callback){ + // once the file is written let's email a link to it... + } + ]); +}); +``` + +For a complicated series of async tasks using the auto function makes adding +new tasks much easier and makes the code more readable. + + +--------------------------------------- + + +### iterator(tasks) + +Creates an iterator function which calls the next function in the array, +returning a continuation to call the next one after that. It's also possible to +'peek' the next iterator by doing iterator.next(). + +This function is used internally by the async module but can be useful when +you want to manually control the flow of functions in series. + +__Arguments__ + +* tasks - An array of functions to run. + +__Example__ + +```js +var iterator = async.iterator([ + function(){ sys.p('one'); }, + function(){ sys.p('two'); }, + function(){ sys.p('three'); } +]); + +node> var iterator2 = iterator(); +'one' +node> var iterator3 = iterator2(); +'two' +node> iterator3(); +'three' +node> var nextfn = iterator2.next(); +node> nextfn(); +'three' +``` + +--------------------------------------- + + +### apply(function, arguments..) + +Creates a continuation function with some arguments already applied, a useful +shorthand when combined with other control flow functions. Any arguments +passed to the returned function are added to the arguments originally passed +to apply. + +__Arguments__ + +* function - The function you want to eventually apply all arguments to. +* arguments... - Any number of arguments to automatically apply when the + continuation is called. + +__Example__ + +```js +// using apply + +async.parallel([ + async.apply(fs.writeFile, 'testfile1', 'test1'), + async.apply(fs.writeFile, 'testfile2', 'test2'), +]); + + +// the same process without using apply + +async.parallel([ + function(callback){ + fs.writeFile('testfile1', 'test1', callback); + }, + function(callback){ + fs.writeFile('testfile2', 'test2', callback); + } +]); +``` + +It's possible to pass any number of additional arguments when calling the +continuation: + +```js +node> var fn = async.apply(sys.puts, 'one'); +node> fn('two', 'three'); +one +two +three +``` + +--------------------------------------- + + +### nextTick(callback) + +Calls the callback on a later loop around the event loop. In node.js this just +calls process.nextTick, in the browser it falls back to setImmediate(callback) +if available, otherwise setTimeout(callback, 0), which means other higher priority +events may precede the execution of the callback. + +This is used internally for browser-compatibility purposes. + +__Arguments__ + +* callback - The function to call on a later loop around the event loop. + +__Example__ + +```js +var call_order = []; +async.nextTick(function(){ + call_order.push('two'); + // call_order now equals ['one','two'] +}); +call_order.push('one') +``` + + +### times(n, callback) + +Calls the callback n times and accumulates results in the same manner +you would use with async.map. + +__Arguments__ + +* n - The number of times to run the function. +* callback - The function to call n times. + +__Example__ + +```js +// Pretend this is some complicated async factory +var createUser = function(id, callback) { + callback(null, { + id: 'user' + id + }) +} +// generate 5 users +async.times(5, function(n, next){ + createUser(n, function(err, user) { + next(err, user) + }) +}, function(err, users) { + // we should now have 5 users +}); +``` + + +### timesSeries(n, callback) + +The same as times only the iterator is applied to each item in the array in +series. The next iterator is only called once the current one has completed +processing. The results array will be in the same order as the original. + + +## Utils + + +### memoize(fn, [hasher]) + +Caches the results of an async function. When creating a hash to store function +results against, the callback is omitted from the hash and an optional hash +function can be used. + +The cache of results is exposed as the `memo` property of the function returned +by `memoize`. + +__Arguments__ + +* fn - the function you to proxy and cache results from. +* hasher - an optional function for generating a custom hash for storing + results, it has all the arguments applied to it apart from the callback, and + must be synchronous. + +__Example__ + +```js +var slow_fn = function (name, callback) { + // do something + callback(null, result); +}; +var fn = async.memoize(slow_fn); + +// fn can now be used as if it were slow_fn +fn('some name', function () { + // callback +}); +``` + + +### unmemoize(fn) + +Undoes a memoized function, reverting it to the original, unmemoized +form. Comes handy in tests. + +__Arguments__ + +* fn - the memoized function + + +### log(function, arguments) + +Logs the result of an async function to the console. Only works in node.js or +in browsers that support console.log and console.error (such as FF and Chrome). +If multiple arguments are returned from the async function, console.log is +called on each argument in order. + +__Arguments__ + +* function - The function you want to eventually apply all arguments to. +* arguments... - Any number of arguments to apply to the function. + +__Example__ + +```js +var hello = function(name, callback){ + setTimeout(function(){ + callback(null, 'hello ' + name); + }, 1000); +}; +``` +```js +node> async.log(hello, 'world'); +'hello world' +``` + +--------------------------------------- + + +### dir(function, arguments) + +Logs the result of an async function to the console using console.dir to +display the properties of the resulting object. Only works in node.js or +in browsers that support console.dir and console.error (such as FF and Chrome). +If multiple arguments are returned from the async function, console.dir is +called on each argument in order. + +__Arguments__ + +* function - The function you want to eventually apply all arguments to. +* arguments... - Any number of arguments to apply to the function. + +__Example__ + +```js +var hello = function(name, callback){ + setTimeout(function(){ + callback(null, {hello: name}); + }, 1000); +}; +``` +```js +node> async.dir(hello, 'world'); +{hello: 'world'} +``` + +--------------------------------------- + + +### noConflict() + +Changes the value of async back to its original value, returning a reference to the +async object. diff --git a/src/node_modules/nedb/node_modules/async/component.json b/src/node_modules/nedb/node_modules/async/component.json new file mode 100644 index 0000000..bbb0115 --- /dev/null +++ b/src/node_modules/nedb/node_modules/async/component.json @@ -0,0 +1,11 @@ +{ + "name": "async", + "repo": "caolan/async", + "description": "Higher-order functions and common patterns for asynchronous code", + "version": "0.1.23", + "keywords": [], + "dependencies": {}, + "development": {}, + "main": "lib/async.js", + "scripts": [ "lib/async.js" ] +} diff --git a/src/node_modules/nedb/node_modules/async/lib/async.js b/src/node_modules/nedb/node_modules/async/lib/async.js new file mode 100755 index 0000000..1eebb15 --- /dev/null +++ b/src/node_modules/nedb/node_modules/async/lib/async.js @@ -0,0 +1,958 @@ +/*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 \n\n```\n\n## Documentation\n\n### Collections\n\n* [each](#each)\n* [eachSeries](#eachSeries)\n* [eachLimit](#eachLimit)\n* [map](#map)\n* [mapSeries](#mapSeries)\n* [mapLimit](#mapLimit)\n* [filter](#filter)\n* [filterSeries](#filterSeries)\n* [reject](#reject)\n* [rejectSeries](#rejectSeries)\n* [reduce](#reduce)\n* [reduceRight](#reduceRight)\n* [detect](#detect)\n* [detectSeries](#detectSeries)\n* [sortBy](#sortBy)\n* [some](#some)\n* [every](#every)\n* [concat](#concat)\n* [concatSeries](#concatSeries)\n\n### Control Flow\n\n* [series](#series)\n* [parallel](#parallel)\n* [parallelLimit](#parallellimittasks-limit-callback)\n* [whilst](#whilst)\n* [doWhilst](#doWhilst)\n* [until](#until)\n* [doUntil](#doUntil)\n* [forever](#forever)\n* [waterfall](#waterfall)\n* [compose](#compose)\n* [applyEach](#applyEach)\n* [applyEachSeries](#applyEachSeries)\n* [queue](#queue)\n* [cargo](#cargo)\n* [auto](#auto)\n* [iterator](#iterator)\n* [apply](#apply)\n* [nextTick](#nextTick)\n* [times](#times)\n* [timesSeries](#timesSeries)\n\n### Utils\n\n* [memoize](#memoize)\n* [unmemoize](#unmemoize)\n* [log](#log)\n* [dir](#dir)\n* [noConflict](#noConflict)\n\n\n## Collections\n\n\n\n### each(arr, iterator, callback)\n\nApplies an iterator function to each item in an array, in parallel.\nThe iterator is called with an item from the list and a callback for when it\nhas finished. If the iterator passes an error to this callback, the main\ncallback for the each function is immediately called with the error.\n\nNote, that since this function applies the iterator to each item in parallel\nthere is no guarantee that the iterator functions will complete in order.\n\n__Arguments__\n\n* arr - An array to iterate over.\n* iterator(item, callback) - A function to apply to each item in the array.\n The iterator is passed a callback(err) which must be called once it has \n completed. If no error has occured, the callback should be run without \n arguments or with an explicit null argument.\n* callback(err) - A callback which is called after all the iterator functions\n have finished, or an error has occurred.\n\n__Example__\n\n```js\n// assuming openFiles is an array of file names and saveFile is a function\n// to save the modified contents of that file:\n\nasync.each(openFiles, saveFile, function(err){\n // if any of the saves produced an error, err would equal that error\n});\n```\n\n---------------------------------------\n\n\n\n### eachSeries(arr, iterator, callback)\n\nThe same as each only the iterator is applied to each item in the array in\nseries. The next iterator is only called once the current one has completed\nprocessing. This means the iterator functions will complete in order.\n\n\n---------------------------------------\n\n\n\n### eachLimit(arr, limit, iterator, callback)\n\nThe same as each only no more than \"limit\" iterators will be simultaneously \nrunning at any time.\n\nNote that the items are not processed in batches, so there is no guarantee that\n the first \"limit\" iterator functions will complete before any others are \nstarted.\n\n__Arguments__\n\n* arr - An array to iterate over.\n* limit - The maximum number of iterators to run at any time.\n* iterator(item, callback) - A function to apply to each item in the array.\n The iterator is passed a callback(err) which must be called once it has \n completed. If no error has occured, the callback should be run without \n arguments or with an explicit null argument.\n* callback(err) - A callback which is called after all the iterator functions\n have finished, or an error has occurred.\n\n__Example__\n\n```js\n// Assume documents is an array of JSON objects and requestApi is a\n// function that interacts with a rate-limited REST api.\n\nasync.eachLimit(documents, 20, requestApi, function(err){\n // if any of the saves produced an error, err would equal that error\n});\n```\n\n---------------------------------------\n\n\n### map(arr, iterator, callback)\n\nProduces a new array of values by mapping each value in the given array through\nthe iterator function. The iterator is called with an item from the array and a\ncallback for when it has finished processing. The callback takes 2 arguments, \nan error and the transformed item from the array. If the iterator passes an\nerror to this callback, the main callback for the map function is immediately\ncalled with the error.\n\nNote, that since this function applies the iterator to each item in parallel\nthere is no guarantee that the iterator functions will complete in order, however\nthe results array will be in the same order as the original array.\n\n__Arguments__\n\n* arr - An array to iterate over.\n* iterator(item, callback) - A function to apply to each item in the array.\n The iterator is passed a callback(err, transformed) which must be called once \n it has completed with an error (which can be null) and a transformed item.\n* callback(err, results) - A callback which is called after all the iterator\n functions have finished, or an error has occurred. Results is an array of the\n transformed items from the original array.\n\n__Example__\n\n```js\nasync.map(['file1','file2','file3'], fs.stat, function(err, results){\n // results is now an array of stats for each file\n});\n```\n\n---------------------------------------\n\n\n### mapSeries(arr, iterator, callback)\n\nThe same as map only the iterator is applied to each item in the array in\nseries. The next iterator is only called once the current one has completed\nprocessing. The results array will be in the same order as the original.\n\n\n---------------------------------------\n\n\n### mapLimit(arr, limit, iterator, callback)\n\nThe same as map only no more than \"limit\" iterators will be simultaneously \nrunning at any time.\n\nNote that the items are not processed in batches, so there is no guarantee that\n the first \"limit\" iterator functions will complete before any others are \nstarted.\n\n__Arguments__\n\n* arr - An array to iterate over.\n* limit - The maximum number of iterators to run at any time.\n* iterator(item, callback) - A function to apply to each item in the array.\n The iterator is passed a callback(err, transformed) which must be called once \n it has completed with an error (which can be null) and a transformed item.\n* callback(err, results) - A callback which is called after all the iterator\n functions have finished, or an error has occurred. Results is an array of the\n transformed items from the original array.\n\n__Example__\n\n```js\nasync.mapLimit(['file1','file2','file3'], 1, fs.stat, function(err, results){\n // results is now an array of stats for each file\n});\n```\n\n---------------------------------------\n\n\n### filter(arr, iterator, callback)\n\n__Alias:__ select\n\nReturns a new array of all the values which pass an async truth test.\n_The callback for each iterator call only accepts a single argument of true or\nfalse, it does not accept an error argument first!_ This is in-line with the\nway node libraries work with truth tests like fs.exists. This operation is\nperformed in parallel, but the results array will be in the same order as the\noriginal.\n\n__Arguments__\n\n* arr - An array to iterate over.\n* iterator(item, callback) - A truth test to apply to each item in the array.\n The iterator is passed a callback(truthValue) which must be called with a \n boolean argument once it has completed.\n* callback(results) - A callback which is called after all the iterator\n functions have finished.\n\n__Example__\n\n```js\nasync.filter(['file1','file2','file3'], fs.exists, function(results){\n // results now equals an array of the existing files\n});\n```\n\n---------------------------------------\n\n\n### filterSeries(arr, iterator, callback)\n\n__alias:__ selectSeries\n\nThe same as filter only the iterator is applied to each item in the array in\nseries. The next iterator is only called once the current one has completed\nprocessing. The results array will be in the same order as the original.\n\n---------------------------------------\n\n\n### reject(arr, iterator, callback)\n\nThe opposite of filter. Removes values that pass an async truth test.\n\n---------------------------------------\n\n\n### rejectSeries(arr, iterator, callback)\n\nThe same as reject, only the iterator is applied to each item in the array\nin series.\n\n\n---------------------------------------\n\n\n### reduce(arr, memo, iterator, callback)\n\n__aliases:__ inject, foldl\n\nReduces a list of values into a single value using an async iterator to return\neach successive step. Memo is the initial state of the reduction. This\nfunction only operates in series. For performance reasons, it may make sense to\nsplit a call to this function into a parallel map, then use the normal\nArray.prototype.reduce on the results. This function is for situations where\neach step in the reduction needs to be async, if you can get the data before\nreducing it then it's probably a good idea to do so.\n\n__Arguments__\n\n* arr - An array to iterate over.\n* memo - The initial state of the reduction.\n* iterator(memo, item, callback) - A function applied to each item in the\n array to produce the next step in the reduction. The iterator is passed a\n callback(err, reduction) which accepts an optional error as its first \n argument, and the state of the reduction as the second. If an error is \n passed to the callback, the reduction is stopped and the main callback is \n immediately called with the error.\n* callback(err, result) - A callback which is called after all the iterator\n functions have finished. Result is the reduced value.\n\n__Example__\n\n```js\nasync.reduce([1,2,3], 0, function(memo, item, callback){\n // pointless async:\n process.nextTick(function(){\n callback(null, memo + item)\n });\n}, function(err, result){\n // result is now equal to the last value of memo, which is 6\n});\n```\n\n---------------------------------------\n\n\n### reduceRight(arr, memo, iterator, callback)\n\n__Alias:__ foldr\n\nSame as reduce, only operates on the items in the array in reverse order.\n\n\n---------------------------------------\n\n\n### detect(arr, iterator, callback)\n\nReturns the first value in a list that passes an async truth test. The\niterator is applied in parallel, meaning the first iterator to return true will\nfire the detect callback with that result. That means the result might not be\nthe first item in the original array (in terms of order) that passes the test.\n\nIf order within the original array is important then look at detectSeries.\n\n__Arguments__\n\n* arr - An array to iterate over.\n* iterator(item, callback) - A truth test to apply to each item in the array.\n The iterator is passed a callback(truthValue) which must be called with a \n boolean argument once it has completed.\n* callback(result) - A callback which is called as soon as any iterator returns\n true, or after all the iterator functions have finished. Result will be\n the first item in the array that passes the truth test (iterator) or the\n value undefined if none passed.\n\n__Example__\n\n```js\nasync.detect(['file1','file2','file3'], fs.exists, function(result){\n // result now equals the first file in the list that exists\n});\n```\n\n---------------------------------------\n\n\n### detectSeries(arr, iterator, callback)\n\nThe same as detect, only the iterator is applied to each item in the array\nin series. This means the result is always the first in the original array (in\nterms of array order) that passes the truth test.\n\n\n---------------------------------------\n\n\n### sortBy(arr, iterator, callback)\n\nSorts a list by the results of running each value through an async iterator.\n\n__Arguments__\n\n* arr - An array to iterate over.\n* iterator(item, callback) - A function to apply to each item in the array.\n The iterator is passed a callback(err, sortValue) which must be called once it\n has completed with an error (which can be null) and a value to use as the sort\n criteria.\n* callback(err, results) - A callback which is called after all the iterator\n functions have finished, or an error has occurred. Results is the items from\n the original array sorted by the values returned by the iterator calls.\n\n__Example__\n\n```js\nasync.sortBy(['file1','file2','file3'], function(file, callback){\n fs.stat(file, function(err, stats){\n callback(err, stats.mtime);\n });\n}, function(err, results){\n // results is now the original array of files sorted by\n // modified date\n});\n```\n\n---------------------------------------\n\n\n### some(arr, iterator, callback)\n\n__Alias:__ any\n\nReturns true if at least one element in the array satisfies an async test.\n_The callback for each iterator call only accepts a single argument of true or\nfalse, it does not accept an error argument first!_ This is in-line with the\nway node libraries work with truth tests like fs.exists. Once any iterator\ncall returns true, the main callback is immediately called.\n\n__Arguments__\n\n* arr - An array to iterate over.\n* iterator(item, callback) - A truth test to apply to each item in the array.\n The iterator is passed a callback(truthValue) which must be called with a \n boolean argument once it has completed.\n* callback(result) - A callback which is called as soon as any iterator returns\n true, or after all the iterator functions have finished. Result will be\n either true or false depending on the values of the async tests.\n\n__Example__\n\n```js\nasync.some(['file1','file2','file3'], fs.exists, function(result){\n // if result is true then at least one of the files exists\n});\n```\n\n---------------------------------------\n\n\n### every(arr, iterator, callback)\n\n__Alias:__ all\n\nReturns true if every element in the array satisfies an async test.\n_The callback for each iterator call only accepts a single argument of true or\nfalse, it does not accept an error argument first!_ This is in-line with the\nway node libraries work with truth tests like fs.exists.\n\n__Arguments__\n\n* arr - An array to iterate over.\n* iterator(item, callback) - A truth test to apply to each item in the array.\n The iterator is passed a callback(truthValue) which must be called with a \n boolean argument once it has completed.\n* callback(result) - A callback which is called after all the iterator\n functions have finished. Result will be either true or false depending on\n the values of the async tests.\n\n__Example__\n\n```js\nasync.every(['file1','file2','file3'], fs.exists, function(result){\n // if result is true then every file exists\n});\n```\n\n---------------------------------------\n\n\n### concat(arr, iterator, callback)\n\nApplies an iterator to each item in a list, concatenating the results. Returns the\nconcatenated list. The iterators are called in parallel, and the results are\nconcatenated as they return. There is no guarantee that the results array will\nbe returned in the original order of the arguments passed to the iterator function.\n\n__Arguments__\n\n* arr - An array to iterate over\n* iterator(item, callback) - A function to apply to each item in the array.\n The iterator is passed a callback(err, results) which must be called once it \n has completed with an error (which can be null) and an array of results.\n* callback(err, results) - A callback which is called after all the iterator\n functions have finished, or an error has occurred. Results is an array containing\n the concatenated results of the iterator function.\n\n__Example__\n\n```js\nasync.concat(['dir1','dir2','dir3'], fs.readdir, function(err, files){\n // files is now a list of filenames that exist in the 3 directories\n});\n```\n\n---------------------------------------\n\n\n### concatSeries(arr, iterator, callback)\n\nSame as async.concat, but executes in series instead of parallel.\n\n\n## Control Flow\n\n\n### series(tasks, [callback])\n\nRun an array of functions in series, each one running once the previous\nfunction has completed. If any functions in the series pass an error to its\ncallback, no more functions are run and the callback for the series is\nimmediately called with the value of the error. Once the tasks have completed,\nthe results are passed to the final callback as an array.\n\nIt is also possible to use an object instead of an array. Each property will be\nrun as a function and the results will be passed to the final callback as an object\ninstead of an array. This can be a more readable way of handling results from\nasync.series.\n\n\n__Arguments__\n\n* tasks - An array or object containing functions to run, each function is passed\n a callback(err, result) it must call on completion with an error (which can\n be null) and an optional result value.\n* callback(err, results) - An optional callback to run once all the functions\n have completed. This function gets a results array (or object) containing all \n the result arguments passed to the task callbacks.\n\n__Example__\n\n```js\nasync.series([\n function(callback){\n // do some stuff ...\n callback(null, 'one');\n },\n function(callback){\n // do some more stuff ...\n callback(null, 'two');\n }\n],\n// optional callback\nfunction(err, results){\n // results is now equal to ['one', 'two']\n});\n\n\n// an example using an object instead of an array\nasync.series({\n one: function(callback){\n setTimeout(function(){\n callback(null, 1);\n }, 200);\n },\n two: function(callback){\n setTimeout(function(){\n callback(null, 2);\n }, 100);\n }\n},\nfunction(err, results) {\n // results is now equal to: {one: 1, two: 2}\n});\n```\n\n---------------------------------------\n\n\n### parallel(tasks, [callback])\n\nRun an array of functions in parallel, without waiting until the previous\nfunction has completed. If any of the functions pass an error to its\ncallback, the main callback is immediately called with the value of the error.\nOnce the tasks have completed, the results are passed to the final callback as an\narray.\n\nIt is also possible to use an object instead of an array. Each property will be\nrun as a function and the results will be passed to the final callback as an object\ninstead of an array. This can be a more readable way of handling results from\nasync.parallel.\n\n\n__Arguments__\n\n* tasks - An array or object containing functions to run, each function is passed \n a callback(err, result) it must call on completion with an error (which can\n be null) and an optional result value.\n* callback(err, results) - An optional callback to run once all the functions\n have completed. This function gets a results array (or object) containing all \n the result arguments passed to the task callbacks.\n\n__Example__\n\n```js\nasync.parallel([\n function(callback){\n setTimeout(function(){\n callback(null, 'one');\n }, 200);\n },\n function(callback){\n setTimeout(function(){\n callback(null, 'two');\n }, 100);\n }\n],\n// optional callback\nfunction(err, results){\n // the results array will equal ['one','two'] even though\n // the second function had a shorter timeout.\n});\n\n\n// an example using an object instead of an array\nasync.parallel({\n one: function(callback){\n setTimeout(function(){\n callback(null, 1);\n }, 200);\n },\n two: function(callback){\n setTimeout(function(){\n callback(null, 2);\n }, 100);\n }\n},\nfunction(err, results) {\n // results is now equals to: {one: 1, two: 2}\n});\n```\n\n---------------------------------------\n\n\n### parallelLimit(tasks, limit, [callback])\n\nThe same as parallel only the tasks are executed in parallel with a maximum of \"limit\" \ntasks executing at any time.\n\nNote that the tasks are not executed in batches, so there is no guarantee that \nthe first \"limit\" tasks will complete before any others are started.\n\n__Arguments__\n\n* tasks - An array or object containing functions to run, each function is passed \n a callback(err, result) it must call on completion with an error (which can\n be null) and an optional result value.\n* limit - The maximum number of tasks to run at any time.\n* callback(err, results) - An optional callback to run once all the functions\n have completed. This function gets a results array (or object) containing all \n the result arguments passed to the task callbacks.\n\n---------------------------------------\n\n\n### whilst(test, fn, callback)\n\nRepeatedly call fn, while test returns true. Calls the callback when stopped,\nor an error occurs.\n\n__Arguments__\n\n* test() - synchronous truth test to perform before each execution of fn.\n* fn(callback) - A function to call each time the test passes. The function is\n passed a callback(err) which must be called once it has completed with an \n optional error argument.\n* callback(err) - A callback which is called after the test fails and repeated\n execution of fn has stopped.\n\n__Example__\n\n```js\nvar count = 0;\n\nasync.whilst(\n function () { return count < 5; },\n function (callback) {\n count++;\n setTimeout(callback, 1000);\n },\n function (err) {\n // 5 seconds have passed\n }\n);\n```\n\n---------------------------------------\n\n\n### doWhilst(fn, test, callback)\n\nThe post check version of whilst. To reflect the difference in the order of operations `test` and `fn` arguments are switched. `doWhilst` is to `whilst` as `do while` is to `while` in plain JavaScript.\n\n---------------------------------------\n\n\n### until(test, fn, callback)\n\nRepeatedly call fn, until test returns true. Calls the callback when stopped,\nor an error occurs.\n\nThe inverse of async.whilst.\n\n---------------------------------------\n\n\n### doUntil(fn, test, callback)\n\nLike doWhilst except the test is inverted. Note the argument ordering differs from `until`.\n\n---------------------------------------\n\n\n### forever(fn, callback)\n\nCalls the asynchronous function 'fn' repeatedly, in series, indefinitely.\nIf an error is passed to fn's callback then 'callback' is called with the\nerror, otherwise it will never be called.\n\n---------------------------------------\n\n\n### waterfall(tasks, [callback])\n\nRuns an array of functions in series, each passing their results to the next in\nthe array. However, if any of the functions pass an error to the callback, the\nnext function is not executed and the main callback is immediately called with\nthe error.\n\n__Arguments__\n\n* tasks - An array of functions to run, each function is passed a \n callback(err, result1, result2, ...) it must call on completion. The first\n argument is an error (which can be null) and any further arguments will be \n passed as arguments in order to the next task.\n* callback(err, [results]) - An optional callback to run once all the functions\n have completed. This will be passed the results of the last task's callback.\n\n\n\n__Example__\n\n```js\nasync.waterfall([\n function(callback){\n callback(null, 'one', 'two');\n },\n function(arg1, arg2, callback){\n callback(null, 'three');\n },\n function(arg1, callback){\n // arg1 now equals 'three'\n callback(null, 'done');\n }\n], function (err, result) {\n // result now equals 'done' \n});\n```\n\n---------------------------------------\n\n### compose(fn1, fn2...)\n\nCreates a function which is a composition of the passed asynchronous\nfunctions. Each function consumes the return value of the function that\nfollows. Composing functions f(), g() and h() would produce the result of\nf(g(h())), only this version uses callbacks to obtain the return values.\n\nEach function is executed with the `this` binding of the composed function.\n\n__Arguments__\n\n* functions... - the asynchronous functions to compose\n\n\n__Example__\n\n```js\nfunction add1(n, callback) {\n setTimeout(function () {\n callback(null, n + 1);\n }, 10);\n}\n\nfunction mul3(n, callback) {\n setTimeout(function () {\n callback(null, n * 3);\n }, 10);\n}\n\nvar add1mul3 = async.compose(mul3, add1);\n\nadd1mul3(4, function (err, result) {\n // result now equals 15\n});\n```\n\n---------------------------------------\n\n### applyEach(fns, args..., callback)\n\nApplies the provided arguments to each function in the array, calling the\ncallback after all functions have completed. If you only provide the first\nargument then it will return a function which lets you pass in the\narguments as if it were a single function call.\n\n__Arguments__\n\n* fns - the asynchronous functions to all call with the same arguments\n* args... - any number of separate arguments to pass to the function\n* callback - the final argument should be the callback, called when all\n functions have completed processing\n\n\n__Example__\n\n```js\nasync.applyEach([enableSearch, updateSchema], 'bucket', callback);\n\n// partial application example:\nasync.each(\n buckets,\n async.applyEach([enableSearch, updateSchema]),\n callback\n);\n```\n\n---------------------------------------\n\n\n### applyEachSeries(arr, iterator, callback)\n\nThe same as applyEach only the functions are applied in series.\n\n---------------------------------------\n\n\n### queue(worker, concurrency)\n\nCreates a queue object with the specified concurrency. Tasks added to the\nqueue will be processed in parallel (up to the concurrency limit). If all\nworkers are in progress, the task is queued until one is available. Once\na worker has completed a task, the task's callback is called.\n\n__Arguments__\n\n* worker(task, callback) - An asynchronous function for processing a queued\n task, which must call its callback(err) argument when finished, with an \n optional error as an argument.\n* concurrency - An integer for determining how many worker functions should be\n run in parallel.\n\n__Queue objects__\n\nThe queue object returned by this function has the following properties and\nmethods:\n\n* length() - a function returning the number of items waiting to be processed.\n* concurrency - an integer for determining how many worker functions should be\n run in parallel. This property can be changed after a queue is created to\n alter the concurrency on-the-fly.\n* push(task, [callback]) - add a new task to the queue, the callback is called\n once the worker has finished processing the task.\n instead of a single task, an array of tasks can be submitted. the respective callback is used for every task in the list.\n* unshift(task, [callback]) - add a new task to the front of the queue.\n* saturated - a callback that is called when the queue length hits the concurrency and further tasks will be queued\n* empty - a callback that is called when the last item from the queue is given to a worker\n* drain - a callback that is called when the last item from the queue has returned from the worker\n\n__Example__\n\n```js\n// create a queue object with concurrency 2\n\nvar q = async.queue(function (task, callback) {\n console.log('hello ' + task.name);\n callback();\n}, 2);\n\n\n// assign a callback\nq.drain = function() {\n console.log('all items have been processed');\n}\n\n// add some items to the queue\n\nq.push({name: 'foo'}, function (err) {\n console.log('finished processing foo');\n});\nq.push({name: 'bar'}, function (err) {\n console.log('finished processing bar');\n});\n\n// add some items to the queue (batch-wise)\n\nq.push([{name: 'baz'},{name: 'bay'},{name: 'bax'}], function (err) {\n console.log('finished processing bar');\n});\n\n// add some items to the front of the queue\n\nq.unshift({name: 'bar'}, function (err) {\n console.log('finished processing bar');\n});\n```\n\n---------------------------------------\n\n\n### cargo(worker, [payload])\n\nCreates a cargo object with the specified payload. Tasks added to the\ncargo will be processed altogether (up to the payload limit). If the\nworker is in progress, the task is queued until it is available. Once\nthe worker has completed some tasks, each callback of those tasks is called.\n\n__Arguments__\n\n* worker(tasks, callback) - An asynchronous function for processing an array of\n queued tasks, which must call its callback(err) argument when finished, with \n an optional error as an argument.\n* payload - An optional integer for determining how many tasks should be\n processed per round; if omitted, the default is unlimited.\n\n__Cargo objects__\n\nThe cargo object returned by this function has the following properties and\nmethods:\n\n* length() - a function returning the number of items waiting to be processed.\n* payload - an integer for determining how many tasks should be\n process per round. This property can be changed after a cargo is created to\n alter the payload on-the-fly.\n* push(task, [callback]) - add a new task to the queue, the callback is called\n once the worker has finished processing the task.\n instead of a single task, an array of tasks can be submitted. the respective callback is used for every task in the list.\n* saturated - a callback that is called when the queue length hits the concurrency and further tasks will be queued\n* empty - a callback that is called when the last item from the queue is given to a worker\n* drain - a callback that is called when the last item from the queue has returned from the worker\n\n__Example__\n\n```js\n// create a cargo object with payload 2\n\nvar cargo = async.cargo(function (tasks, callback) {\n for(var i=0; i\n### auto(tasks, [callback])\n\nDetermines the best order for running functions based on their requirements.\nEach function can optionally depend on other functions being completed first,\nand each function is run as soon as its requirements are satisfied. If any of\nthe functions pass an error to their callback, that function will not complete\n(so any other functions depending on it will not run) and the main callback\nwill be called immediately with the error. Functions also receive an object\ncontaining the results of functions which have completed so far.\n\nNote, all functions are called with a results object as a second argument, \nso it is unsafe to pass functions in the tasks object which cannot handle the\nextra argument. For example, this snippet of code:\n\n```js\nasync.auto({\n readData: async.apply(fs.readFile, 'data.txt', 'utf-8')\n}, callback);\n```\n\nwill have the effect of calling readFile with the results object as the last\nargument, which will fail:\n\n```js\nfs.readFile('data.txt', 'utf-8', cb, {});\n```\n\nInstead, wrap the call to readFile in a function which does not forward the \nresults object:\n\n```js\nasync.auto({\n readData: function(cb, results){\n fs.readFile('data.txt', 'utf-8', cb);\n }\n}, callback);\n```\n\n__Arguments__\n\n* tasks - An object literal containing named functions or an array of\n requirements, with the function itself the last item in the array. The key\n used for each function or array is used when specifying requirements. The \n function receives two arguments: (1) a callback(err, result) which must be \n called when finished, passing an error (which can be null) and the result of \n the function's execution, and (2) a results object, containing the results of\n the previously executed functions.\n* callback(err, results) - An optional callback which is called when all the\n tasks have been completed. The callback will receive an error as an argument\n if any tasks pass an error to their callback. Results will always be passed\n\tbut if an error occurred, no other tasks will be performed, and the results\n\tobject will only contain partial results.\n \n\n__Example__\n\n```js\nasync.auto({\n get_data: function(callback){\n // async code to get some data\n },\n make_folder: function(callback){\n // async code to create a directory to store a file in\n // this is run at the same time as getting the data\n },\n write_file: ['get_data', 'make_folder', function(callback){\n // once there is some data and the directory exists,\n // write the data to a file in the directory\n callback(null, filename);\n }],\n email_link: ['write_file', function(callback, results){\n // once the file is written let's email a link to it...\n // results.write_file contains the filename returned by write_file.\n }]\n});\n```\n\nThis is a fairly trivial example, but to do this using the basic parallel and\nseries functions would look like this:\n\n```js\nasync.parallel([\n function(callback){\n // async code to get some data\n },\n function(callback){\n // async code to create a directory to store a file in\n // this is run at the same time as getting the data\n }\n],\nfunction(err, results){\n async.series([\n function(callback){\n // once there is some data and the directory exists,\n // write the data to a file in the directory\n },\n function(callback){\n // once the file is written let's email a link to it...\n }\n ]);\n});\n```\n\nFor a complicated series of async tasks using the auto function makes adding\nnew tasks much easier and makes the code more readable.\n\n\n---------------------------------------\n\n\n### iterator(tasks)\n\nCreates an iterator function which calls the next function in the array,\nreturning a continuation to call the next one after that. It's also possible to\n'peek' the next iterator by doing iterator.next().\n\nThis function is used internally by the async module but can be useful when\nyou want to manually control the flow of functions in series.\n\n__Arguments__\n\n* tasks - An array of functions to run.\n\n__Example__\n\n```js\nvar iterator = async.iterator([\n function(){ sys.p('one'); },\n function(){ sys.p('two'); },\n function(){ sys.p('three'); }\n]);\n\nnode> var iterator2 = iterator();\n'one'\nnode> var iterator3 = iterator2();\n'two'\nnode> iterator3();\n'three'\nnode> var nextfn = iterator2.next();\nnode> nextfn();\n'three'\n```\n\n---------------------------------------\n\n\n### apply(function, arguments..)\n\nCreates a continuation function with some arguments already applied, a useful\nshorthand when combined with other control flow functions. Any arguments\npassed to the returned function are added to the arguments originally passed\nto apply.\n\n__Arguments__\n\n* function - The function you want to eventually apply all arguments to.\n* arguments... - Any number of arguments to automatically apply when the\n continuation is called.\n\n__Example__\n\n```js\n// using apply\n\nasync.parallel([\n async.apply(fs.writeFile, 'testfile1', 'test1'),\n async.apply(fs.writeFile, 'testfile2', 'test2'),\n]);\n\n\n// the same process without using apply\n\nasync.parallel([\n function(callback){\n fs.writeFile('testfile1', 'test1', callback);\n },\n function(callback){\n fs.writeFile('testfile2', 'test2', callback);\n }\n]);\n```\n\nIt's possible to pass any number of additional arguments when calling the\ncontinuation:\n\n```js\nnode> var fn = async.apply(sys.puts, 'one');\nnode> fn('two', 'three');\none\ntwo\nthree\n```\n\n---------------------------------------\n\n\n### nextTick(callback)\n\nCalls the callback on a later loop around the event loop. In node.js this just\ncalls process.nextTick, in the browser it falls back to setImmediate(callback)\nif available, otherwise setTimeout(callback, 0), which means other higher priority\nevents may precede the execution of the callback.\n\nThis is used internally for browser-compatibility purposes.\n\n__Arguments__\n\n* callback - The function to call on a later loop around the event loop.\n\n__Example__\n\n```js\nvar call_order = [];\nasync.nextTick(function(){\n call_order.push('two');\n // call_order now equals ['one','two']\n});\ncall_order.push('one')\n```\n\n\n### times(n, callback)\n\nCalls the callback n times and accumulates results in the same manner\nyou would use with async.map.\n\n__Arguments__\n\n* n - The number of times to run the function.\n* callback - The function to call n times.\n\n__Example__\n\n```js\n// Pretend this is some complicated async factory\nvar createUser = function(id, callback) {\n callback(null, {\n id: 'user' + id\n })\n}\n// generate 5 users\nasync.times(5, function(n, next){\n createUser(n, function(err, user) {\n next(err, user)\n })\n}, function(err, users) {\n // we should now have 5 users\n});\n```\n\n\n### timesSeries(n, callback)\n\nThe same as times only the iterator is applied to each item in the array in\nseries. The next iterator is only called once the current one has completed\nprocessing. The results array will be in the same order as the original.\n\n\n## Utils\n\n\n### memoize(fn, [hasher])\n\nCaches the results of an async function. When creating a hash to store function\nresults against, the callback is omitted from the hash and an optional hash\nfunction can be used.\n\nThe cache of results is exposed as the `memo` property of the function returned\nby `memoize`.\n\n__Arguments__\n\n* fn - the function you to proxy and cache results from.\n* hasher - an optional function for generating a custom hash for storing\n results, it has all the arguments applied to it apart from the callback, and\n must be synchronous.\n\n__Example__\n\n```js\nvar slow_fn = function (name, callback) {\n // do something\n callback(null, result);\n};\nvar fn = async.memoize(slow_fn);\n\n// fn can now be used as if it were slow_fn\nfn('some name', function () {\n // callback\n});\n```\n\n\n### unmemoize(fn)\n\nUndoes a memoized function, reverting it to the original, unmemoized\nform. Comes handy in tests.\n\n__Arguments__\n\n* fn - the memoized function\n\n\n### log(function, arguments)\n\nLogs the result of an async function to the console. Only works in node.js or\nin browsers that support console.log and console.error (such as FF and Chrome).\nIf multiple arguments are returned from the async function, console.log is\ncalled on each argument in order.\n\n__Arguments__\n\n* function - The function you want to eventually apply all arguments to.\n* arguments... - Any number of arguments to apply to the function.\n\n__Example__\n\n```js\nvar hello = function(name, callback){\n setTimeout(function(){\n callback(null, 'hello ' + name);\n }, 1000);\n};\n```\n```js\nnode> async.log(hello, 'world');\n'hello world'\n```\n\n---------------------------------------\n\n\n### dir(function, arguments)\n\nLogs the result of an async function to the console using console.dir to\ndisplay the properties of the resulting object. Only works in node.js or\nin browsers that support console.dir and console.error (such as FF and Chrome).\nIf multiple arguments are returned from the async function, console.dir is\ncalled on each argument in order.\n\n__Arguments__\n\n* function - The function you want to eventually apply all arguments to.\n* arguments... - Any number of arguments to apply to the function.\n\n__Example__\n\n```js\nvar hello = function(name, callback){\n setTimeout(function(){\n callback(null, {hello: name});\n }, 1000);\n};\n```\n```js\nnode> async.dir(hello, 'world');\n{hello: 'world'}\n```\n\n---------------------------------------\n\n\n### noConflict()\n\nChanges the value of async back to its original value, returning a reference to the\nasync object.\n", + "readmeFilename": "README.md", + "_id": "async@0.2.10", + "dist": { + "shasum": "b6bbe0b0674b9d719708ca38de8c237cb526c3d1" + }, + "_from": "async@0.2.10", + "_resolved": "https://registry.npmjs.org/async/-/async-0.2.10.tgz" +} diff --git a/src/node_modules/nedb/node_modules/binary-search-tree/.npmignore b/src/node_modules/nedb/node_modules/binary-search-tree/.npmignore new file mode 100644 index 0000000..699b5d4 --- /dev/null +++ b/src/node_modules/nedb/node_modules/binary-search-tree/.npmignore @@ -0,0 +1,16 @@ +lib-cov +*.seed +*.log +*.csv +*.dat +*.out +*.pid +*.gz + +pids +logs +results + +npm-debug.log + +node_modules diff --git a/src/node_modules/nedb/node_modules/binary-search-tree/Makefile b/src/node_modules/nedb/node_modules/binary-search-tree/Makefile new file mode 100644 index 0000000..a43b503 --- /dev/null +++ b/src/node_modules/nedb/node_modules/binary-search-tree/Makefile @@ -0,0 +1,7 @@ +test: + @echo "Launching tests" + @ ./node_modules/.bin/mocha --timeout 10000 --reporter spec + @echo "Tests finished" + +.PHONY: test + diff --git a/src/node_modules/nedb/node_modules/binary-search-tree/README.md b/src/node_modules/nedb/node_modules/binary-search-tree/README.md new file mode 100644 index 0000000..573eff8 --- /dev/null +++ b/src/node_modules/nedb/node_modules/binary-search-tree/README.md @@ -0,0 +1,123 @@ +# Binary search trees for Node.js + +Two implementations of binary search tree: basic and AVL (a kind of self-balancing binmary search tree). I wrote this module primarily to store indexes for NeDB (a javascript dependency-less database). + + +## Installation and tests +Package name is `binary-search-tree`. + +```bash +npm install binary-search-tree --save + +make test +``` + +## Usage +The API mainly provides 3 functions: `insert`, `search` and `delete`. If you do not create a unique-type binary search tree, you can store multiple pieces of data for the same key. Doing so with a unique-type BST will result in an error being thrown. Data is always returned as an array, and you can delete all data relating to a given key, or just one piece of data. + +```javascript +var BinarySearchTree = require('binary-search-tree').BinarySearchTree + , AVLTree = require('binary-search-tree').AVLTree // Same API as BinarySearchTree + +// Creating a binary search tree +var bst = new BinarySearchTree(); + +// Inserting some data +bst.insert(15, 'some data for key 15'); +bst.insert(12, 'something else'); +bst.insert(18, 'hello'); + +// You can insert multiple pieces of data for the same key +// if your tree doesn't enforce a unique constraint +bst.insert(18, 'world'); + +// Retrieving data (always returned as an array of all data stored for this key) +bst.search(15); // Equal to ['some data for key 15'] +bst.search(18); // Equal to ['hello', 'world'] +bst.search(1); // Equal to [] + +// Search between bounds with a MongoDB-like query +// Data is returned in key order +// Note the difference between $lt (less than) and $gte (less than OR EQUAL) +bst.betweenBounds({ $lt: 18, $gte: 12}); // Equal to ['something else', 'some data for key 15'] + +// Deleting all the data relating to a key +bst.delete(15); // bst.search(15) will now give [] +bst.delete(18, 'world'); // bst.search(18) will now give ['hello'] +``` + +There are three optional parameters you can pass the BST constructor, allowing you to enforce a key-uniqueness constraint, use a custom function to compare keys and use a custom function to check whether values are equal. These parameters are all passed in an object. + +### Uniqueness + +```javascript +var bst = new BinarySearchTree({ unique: true }); +bst.insert(10, 'hello'); +bst.insert(10, 'world'); // Will throw an error +``` + +### Custom key comparison + +```javascript +// Custom key comparison function +// It needs to return a negative number if a is less than b, +// a positive number if a is greater than b +// and 0 if they are equal +// If none is provided, the default one can compare numbers, dates and strings +// which are the most common usecases +function compareKeys (a, b) { + if (a.age < b.age) { return -1; } + if (a.age > b.age) { return 1; } + + return 0; +} + +// Now we can use objects with an 'age' property as keys +var bst = new BinarySearchTree({ compareKeys: compareKeys }); +bst.insert({ age: 23 }, 'Mark'); +bst.insert({ age: 47 }, 'Franck'); +``` + +### Custom value checking + +```javascript +// Custom value equality checking function used when we try to just delete one piece of data +// Returns true if a and b are considered the same, false otherwise +// The default function is able to compare numbers and strings +function checkValueEquality (a, b) { + return a.length === b.length; +} +var bst = new BinarySearchTree({ checkValueEquality: checkValueEquality }); +bst.insert(10, 'hello'); +bst.insert(10, 'world'); +bst.insert(10, 'howdoyoudo'); + +bst.delete(10, 'abcde'); +bst.search(10); // Returns ['howdoyoudo'] +``` + + +## License + +(The MIT License) + +Copyright (c) 2013 Louis Chatriot <louis.chatriot@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/src/node_modules/nedb/node_modules/binary-search-tree/index.js b/src/node_modules/nedb/node_modules/binary-search-tree/index.js new file mode 100644 index 0000000..bc7179a --- /dev/null +++ b/src/node_modules/nedb/node_modules/binary-search-tree/index.js @@ -0,0 +1,2 @@ +module.exports.BinarySearchTree = require('./lib/bst'); +module.exports.AVLTree = require('./lib/avltree'); diff --git a/src/node_modules/nedb/node_modules/binary-search-tree/lib/avltree.js b/src/node_modules/nedb/node_modules/binary-search-tree/lib/avltree.js new file mode 100644 index 0000000..91b2a10 --- /dev/null +++ b/src/node_modules/nedb/node_modules/binary-search-tree/lib/avltree.js @@ -0,0 +1,455 @@ +/** + * Self-balancing binary search tree using the AVL implementation + */ +var BinarySearchTree = require('./bst') + , customUtils = require('./customUtils') + , util = require('util') + , _ = require('underscore') + ; + + +/** + * Constructor + * We can't use a direct pointer to the root node (as in the simple binary search tree) + * as the root will change during tree rotations + * @param {Boolean} options.unique Whether to enforce a 'unique' constraint on the key or not + * @param {Function} options.compareKeys Initialize this BST's compareKeys + */ +function AVLTree (options) { + this.tree = new _AVLTree(options); +} + + +/** + * Constructor of the internal AVLTree + * @param {Object} options Optional + * @param {Boolean} options.unique Whether to enforce a 'unique' constraint on the key or not + * @param {Key} options.key Initialize this BST's key with key + * @param {Value} options.value Initialize this BST's data with [value] + * @param {Function} options.compareKeys Initialize this BST's compareKeys + */ +function _AVLTree (options) { + options = options || {}; + + this.left = null; + this.right = null; + this.parent = options.parent !== undefined ? options.parent : null; + if (options.hasOwnProperty('key')) { this.key = options.key; } + this.data = options.hasOwnProperty('value') ? [options.value] : []; + this.unique = options.unique || false; + + this.compareKeys = options.compareKeys || customUtils.defaultCompareKeysFunction; + this.checkValueEquality = options.checkValueEquality || customUtils.defaultCheckValueEquality; +} + + +/** + * Inherit basic functions from the basic binary search tree + */ +util.inherits(_AVLTree, BinarySearchTree); + +/** + * Keep a pointer to the internal tree constructor for testing purposes + */ +AVLTree._AVLTree = _AVLTree; + + +/** + * Check the recorded height is correct for every node + * Throws if one height doesn't match + */ +_AVLTree.prototype.checkHeightCorrect = function () { + var leftH, rightH; + + if (!this.hasOwnProperty('key')) { return; } // Empty tree + + if (this.left && this.left.height === undefined) { throw "Undefined height for node " + this.left.key; } + if (this.right && this.right.height === undefined) { throw "Undefined height for node " + this.right.key; } + if (this.height === undefined) { throw "Undefined height for node " + this.key; } + + leftH = this.left ? this.left.height : 0; + rightH = this.right ? this.right.height : 0; + + if (this.height !== 1 + Math.max(leftH, rightH)) { throw "Height constraint failed for node " + this.key; } + if (this.left) { this.left.checkHeightCorrect(); } + if (this.right) { this.right.checkHeightCorrect(); } +}; + + +/** + * Return the balance factor + */ +_AVLTree.prototype.balanceFactor = function () { + var leftH = this.left ? this.left.height : 0 + , rightH = this.right ? this.right.height : 0 + ; + return leftH - rightH; +}; + + +/** + * Check that the balance factors are all between -1 and 1 + */ +_AVLTree.prototype.checkBalanceFactors = function () { + if (Math.abs(this.balanceFactor()) > 1) { throw 'Tree is unbalanced at node ' + this.key; } + + if (this.left) { this.left.checkBalanceFactors(); } + if (this.right) { this.right.checkBalanceFactors(); } +}; + + +/** + * When checking if the BST conditions are met, also check that the heights are correct + * and the tree is balanced + */ +_AVLTree.prototype.checkIsAVLT = function () { + _AVLTree.super_.prototype.checkIsBST.call(this); + this.checkHeightCorrect(); + this.checkBalanceFactors(); +}; +AVLTree.prototype.checkIsAVLT = function () { this.tree.checkIsAVLT(); }; + + +/** + * Perform a right rotation of the tree if possible + * and return the root of the resulting tree + * The resulting tree's nodes' heights are also updated + */ +_AVLTree.prototype.rightRotation = function () { + var q = this + , p = this.left + , b + , ah, bh, ch; + + if (!p) { return this; } // No change + + b = p.right; + + // Alter tree structure + if (q.parent) { + p.parent = q.parent; + if (q.parent.left === q) { q.parent.left = p; } else { q.parent.right = p; } + } else { + p.parent = null; + } + p.right = q; + q.parent = p; + q.left = b; + if (b) { b.parent = q; } + + // Update heights + ah = p.left ? p.left.height : 0; + bh = b ? b.height : 0; + ch = q.right ? q.right.height : 0; + q.height = Math.max(bh, ch) + 1; + p.height = Math.max(ah, q.height) + 1; + + return p; +}; + + +/** + * Perform a left rotation of the tree if possible + * and return the root of the resulting tree + * The resulting tree's nodes' heights are also updated + */ +_AVLTree.prototype.leftRotation = function () { + var p = this + , q = this.right + , b + , ah, bh, ch; + + if (!q) { return this; } // No change + + b = q.left; + + // Alter tree structure + if (p.parent) { + q.parent = p.parent; + if (p.parent.left === p) { p.parent.left = q; } else { p.parent.right = q; } + } else { + q.parent = null; + } + q.left = p; + p.parent = q; + p.right = b; + if (b) { b.parent = p; } + + // Update heights + ah = p.left ? p.left.height : 0; + bh = b ? b.height : 0; + ch = q.right ? q.right.height : 0; + p.height = Math.max(ah, bh) + 1; + q.height = Math.max(ch, p.height) + 1; + + return q; +}; + + +/** + * Modify the tree if its right subtree is too small compared to the left + * Return the new root if any + */ +_AVLTree.prototype.rightTooSmall = function () { + if (this.balanceFactor() <= 1) { return this; } // Right is not too small, don't change + + if (this.left.balanceFactor() < 0) { + this.left.leftRotation(); + } + + return this.rightRotation(); +}; + + +/** + * Modify the tree if its left subtree is too small compared to the right + * Return the new root if any + */ +_AVLTree.prototype.leftTooSmall = function () { + if (this.balanceFactor() >= -1) { return this; } // Left is not too small, don't change + + if (this.right.balanceFactor() > 0) { + this.right.rightRotation(); + } + + return this.leftRotation(); +}; + + +/** + * Rebalance the tree along the given path. The path is given reversed (as he was calculated + * in the insert and delete functions). + * Returns the new root of the tree + * Of course, the first element of the path must be the root of the tree + */ +_AVLTree.prototype.rebalanceAlongPath = function (path) { + var newRoot = this + , rotated + , i; + + if (!this.hasOwnProperty('key')) { delete this.height; return this; } // Empty tree + + // Rebalance the tree and update all heights + for (i = path.length - 1; i >= 0; i -= 1) { + path[i].height = 1 + Math.max(path[i].left ? path[i].left.height : 0, path[i].right ? path[i].right.height : 0); + + if (path[i].balanceFactor() > 1) { + rotated = path[i].rightTooSmall(); + if (i === 0) { newRoot = rotated; } + } + + if (path[i].balanceFactor() < -1) { + rotated = path[i].leftTooSmall(); + if (i === 0) { newRoot = rotated; } + } + } + + return newRoot; +}; + + +/** + * Insert a key, value pair in the tree while maintaining the AVL tree height constraint + * Return a pointer to the root node, which may have changed + */ +_AVLTree.prototype.insert = function (key, value) { + var insertPath = [] + , currentNode = this + ; + + // Empty tree, insert as root + if (!this.hasOwnProperty('key')) { + this.key = key; + this.data.push(value); + this.height = 1; + return this; + } + + // Insert new leaf at the right place + while (true) { + // Same key: no change in the tree structure + if (currentNode.compareKeys(currentNode.key, key) === 0) { + if (currentNode.unique) { + throw { message: "Can't insert key " + key + ", it violates the unique constraint" + , key: key + , errorType: 'uniqueViolated' + }; + } else { + currentNode.data.push(value); + } + return this; + } + + insertPath.push(currentNode); + + if (currentNode.compareKeys(key, currentNode.key) < 0) { + if (!currentNode.left) { + insertPath.push(currentNode.createLeftChild({ key: key, value: value })); + break; + } else { + currentNode = currentNode.left; + } + } else { + if (!currentNode.right) { + insertPath.push(currentNode.createRightChild({ key: key, value: value })); + break; + } else { + currentNode = currentNode.right; + } + } + } + + return this.rebalanceAlongPath(insertPath); +}; + +// Insert in the internal tree, update the pointer to the root if needed +AVLTree.prototype.insert = function (key, value) { + var newTree = this.tree.insert(key, value); + + // If newTree is undefined, that means its structure was not modified + if (newTree) { this.tree = newTree; } +}; + + +/** + * Delete a key or just a value and return the new root of the tree + * @param {Key} key + * @param {Value} value Optional. If not set, the whole key is deleted. If set, only this value is deleted + */ +_AVLTree.prototype.delete = function (key, value) { + var newData = [], replaceWith + , self = this + , currentNode = this + , deletePath = [] + ; + + if (!this.hasOwnProperty('key')) { return this; } // Empty tree + + // Either no match is found and the function will return from within the loop + // Or a match is found and deletePath will contain the path from the root to the node to delete after the loop + while (true) { + if (currentNode.compareKeys(key, currentNode.key) === 0) { break; } + + deletePath.push(currentNode); + + if (currentNode.compareKeys(key, currentNode.key) < 0) { + if (currentNode.left) { + currentNode = currentNode.left; + } else { + return this; // Key not found, no modification + } + } else { + // currentNode.compareKeys(key, currentNode.key) is > 0 + if (currentNode.right) { + currentNode = currentNode.right; + } else { + return this; // Key not found, no modification + } + } + } + + // Delete only a value (no tree modification) + if (currentNode.data.length > 1 && value) { + currentNode.data.forEach(function (d) { + if (!currentNode.checkValueEquality(d, value)) { newData.push(d); } + }); + currentNode.data = newData; + return this; + } + + // Delete a whole node + + // Leaf + if (!currentNode.left && !currentNode.right) { + if (currentNode === this) { // This leaf is also the root + delete currentNode.key; + currentNode.data = []; + delete currentNode.height; + return this; + } else { + if (currentNode.parent.left === currentNode) { + currentNode.parent.left = null; + } else { + currentNode.parent.right = null; + } + return this.rebalanceAlongPath(deletePath); + } + } + + + // Node with only one child + if (!currentNode.left || !currentNode.right) { + replaceWith = currentNode.left ? currentNode.left : currentNode.right; + + if (currentNode === this) { // This node is also the root + replaceWith.parent = null; + return replaceWith; // height of replaceWith is necessarily 1 because the tree was balanced before deletion + } else { + if (currentNode.parent.left === currentNode) { + currentNode.parent.left = replaceWith; + replaceWith.parent = currentNode.parent; + } else { + currentNode.parent.right = replaceWith; + replaceWith.parent = currentNode.parent; + } + + return this.rebalanceAlongPath(deletePath); + } + } + + + // Node with two children + // Use the in-order predecessor (no need to randomize since we actively rebalance) + deletePath.push(currentNode); + replaceWith = currentNode.left; + + // Special case: the in-order predecessor is right below the node to delete + if (!replaceWith.right) { + currentNode.key = replaceWith.key; + currentNode.data = replaceWith.data; + currentNode.left = replaceWith.left; + if (replaceWith.left) { replaceWith.left.parent = currentNode; } + return this.rebalanceAlongPath(deletePath); + } + + // After this loop, replaceWith is the right-most leaf in the left subtree + // and deletePath the path from the root (inclusive) to replaceWith (exclusive) + while (true) { + if (replaceWith.right) { + deletePath.push(replaceWith); + replaceWith = replaceWith.right; + } else { + break; + } + } + + currentNode.key = replaceWith.key; + currentNode.data = replaceWith.data; + + replaceWith.parent.right = replaceWith.left; + if (replaceWith.left) { replaceWith.left.parent = replaceWith.parent; } + + return this.rebalanceAlongPath(deletePath); +}; + +// Delete a value +AVLTree.prototype.delete = function (key, value) { + var newTree = this.tree.delete(key, value); + + // If newTree is undefined, that means its structure was not modified + if (newTree) { this.tree = newTree; } +}; + + +/** + * Other functions we want to use on an AVLTree as if it were the internal _AVLTree + */ +['getNumberOfKeys', 'search', 'betweenBounds', 'prettyPrint', 'executeOnEveryNode'].forEach(function (fn) { + AVLTree.prototype[fn] = function () { + return this.tree[fn].apply(this.tree, arguments); + }; +}); + + +// Interface +module.exports = AVLTree; diff --git a/src/node_modules/nedb/node_modules/binary-search-tree/lib/bst.js b/src/node_modules/nedb/node_modules/binary-search-tree/lib/bst.js new file mode 100644 index 0000000..2276f67 --- /dev/null +++ b/src/node_modules/nedb/node_modules/binary-search-tree/lib/bst.js @@ -0,0 +1,543 @@ +/** + * Simple binary search tree + */ +var customUtils = require('./customUtils'); + + +/** + * Constructor + * @param {Object} options Optional + * @param {Boolean} options.unique Whether to enforce a 'unique' constraint on the key or not + * @param {Key} options.key Initialize this BST's key with key + * @param {Value} options.value Initialize this BST's data with [value] + * @param {Function} options.compareKeys Initialize this BST's compareKeys + */ +function BinarySearchTree (options) { + options = options || {}; + + this.left = null; + this.right = null; + this.parent = options.parent !== undefined ? options.parent : null; + if (options.hasOwnProperty('key')) { this.key = options.key; } + this.data = options.hasOwnProperty('value') ? [options.value] : []; + this.unique = options.unique || false; + + this.compareKeys = options.compareKeys || customUtils.defaultCompareKeysFunction; + this.checkValueEquality = options.checkValueEquality || customUtils.defaultCheckValueEquality; +} + + +// ================================ +// Methods used to test the tree +// ================================ + + +/** + * Get the descendant with max key + */ +BinarySearchTree.prototype.getMaxKeyDescendant = function () { + if (this.right) { + return this.right.getMaxKeyDescendant(); + } else { + return this; + } +}; + + +/** + * Get the maximum key + */ +BinarySearchTree.prototype.getMaxKey = function () { + return this.getMaxKeyDescendant().key; +}; + + +/** + * Get the descendant with min key + */ +BinarySearchTree.prototype.getMinKeyDescendant = function () { + if (this.left) { + return this.left.getMinKeyDescendant() + } else { + return this; + } +}; + + +/** + * Get the minimum key + */ +BinarySearchTree.prototype.getMinKey = function () { + return this.getMinKeyDescendant().key; +}; + + +/** + * Check that all nodes (incl. leaves) fullfil condition given by fn + * test is a function passed every (key, data) and which throws if the condition is not met + */ +BinarySearchTree.prototype.checkAllNodesFullfillCondition = function (test) { + if (!this.hasOwnProperty('key')) { return; } + + test(this.key, this.data); + if (this.left) { this.left.checkAllNodesFullfillCondition(test); } + if (this.right) { this.right.checkAllNodesFullfillCondition(test); } +}; + + +/** + * Check that the core BST properties on node ordering are verified + * Throw if they aren't + */ +BinarySearchTree.prototype.checkNodeOrdering = function () { + var self = this; + + if (!this.hasOwnProperty('key')) { return; } + + if (this.left) { + this.left.checkAllNodesFullfillCondition(function (k) { + if (self.compareKeys(k, self.key) >= 0) { + throw 'Tree with root ' + self.key + ' is not a binary search tree'; + } + }); + this.left.checkNodeOrdering(); + } + + if (this.right) { + this.right.checkAllNodesFullfillCondition(function (k) { + if (self.compareKeys(k, self.key) <= 0) { + throw 'Tree with root ' + self.key + ' is not a binary search tree'; + } + }); + this.right.checkNodeOrdering(); + } +}; + + +/** + * Check that all pointers are coherent in this tree + */ +BinarySearchTree.prototype.checkInternalPointers = function () { + if (this.left) { + if (this.left.parent !== this) { throw 'Parent pointer broken for key ' + this.key; } + this.left.checkInternalPointers(); + } + + if (this.right) { + if (this.right.parent !== this) { throw 'Parent pointer broken for key ' + this.key; } + this.right.checkInternalPointers(); + } +}; + + +/** + * Check that a tree is a BST as defined here (node ordering and pointer references) + */ +BinarySearchTree.prototype.checkIsBST = function () { + this.checkNodeOrdering(); + this.checkInternalPointers(); + if (this.parent) { throw "The root shouldn't have a parent"; } +}; + + +/** + * Get number of keys inserted + */ +BinarySearchTree.prototype.getNumberOfKeys = function () { + var res; + + if (!this.hasOwnProperty('key')) { return 0; } + + res = 1; + if (this.left) { res += this.left.getNumberOfKeys(); } + if (this.right) { res += this.right.getNumberOfKeys(); } + + return res; +}; + + + +// ============================================ +// Methods used to actually work on the tree +// ============================================ + +/** + * Create a BST similar (i.e. same options except for key and value) to the current one + * Use the same constructor (i.e. BinarySearchTree, AVLTree etc) + * @param {Object} options see constructor + */ +BinarySearchTree.prototype.createSimilar = function (options) { + options = options || {}; + options.unique = this.unique; + options.compareKeys = this.compareKeys; + options.checkValueEquality = this.checkValueEquality; + + return new this.constructor(options); +}; + + +/** + * Create the left child of this BST and return it + */ +BinarySearchTree.prototype.createLeftChild = function (options) { + var leftChild = this.createSimilar(options); + leftChild.parent = this; + this.left = leftChild; + + return leftChild; +}; + + +/** + * Create the right child of this BST and return it + */ +BinarySearchTree.prototype.createRightChild = function (options) { + var rightChild = this.createSimilar(options); + rightChild.parent = this; + this.right = rightChild; + + return rightChild; +}; + + +/** + * Insert a new element + */ +BinarySearchTree.prototype.insert = function (key, value) { + // Empty tree, insert as root + if (!this.hasOwnProperty('key')) { + this.key = key; + this.data.push(value); + return; + } + + // Same key as root + if (this.compareKeys(this.key, key) === 0) { + if (this.unique) { + throw { message: "Can't insert key " + key + ", it violates the unique constraint" + , key: key + , errorType: 'uniqueViolated' + }; + } else { + this.data.push(value); + } + return; + } + + if (this.compareKeys(key, this.key) < 0) { + // Insert in left subtree + if (this.left) { + this.left.insert(key, value); + } else { + this.createLeftChild({ key: key, value: value }); + } + } else { + // Insert in right subtree + if (this.right) { + this.right.insert(key, value); + } else { + this.createRightChild({ key: key, value: value }); + } + } +}; + + +/** + * Search for all data corresponding to a key + */ +BinarySearchTree.prototype.search = function (key) { + if (!this.hasOwnProperty('key')) { return []; } + + if (this.compareKeys(this.key, key) === 0) { return this.data; } + + if (this.compareKeys(key, this.key) < 0) { + if (this.left) { + return this.left.search(key); + } else { + return []; + } + } else { + if (this.right) { + return this.right.search(key); + } else { + return []; + } + } +}; + + +/** + * Return a function that tells whether a given key matches a lower bound + */ +BinarySearchTree.prototype.getLowerBoundMatcher = function (query) { + var self = this; + + // No lower bound + if (!query.hasOwnProperty('$gt') && !query.hasOwnProperty('$gte')) { + return function () { return true; }; + } + + if (query.hasOwnProperty('$gt') && query.hasOwnProperty('$gte')) { + if (self.compareKeys(query.$gte, query.$gt) === 0) { + return function (key) { return self.compareKeys(key, query.$gt) > 0; }; + } + + if (self.compareKeys(query.$gte, query.$gt) > 0) { + return function (key) { return self.compareKeys(key, query.$gte) >= 0; }; + } else { + return function (key) { return self.compareKeys(key, query.$gt) > 0; }; + } + } + + if (query.hasOwnProperty('$gt')) { + return function (key) { return self.compareKeys(key, query.$gt) > 0; }; + } else { + return function (key) { return self.compareKeys(key, query.$gte) >= 0; }; + } +}; + + +/** + * Return a function that tells whether a given key matches an upper bound + */ +BinarySearchTree.prototype.getUpperBoundMatcher = function (query) { + var self = this; + + // No lower bound + if (!query.hasOwnProperty('$lt') && !query.hasOwnProperty('$lte')) { + return function () { return true; }; + } + + if (query.hasOwnProperty('$lt') && query.hasOwnProperty('$lte')) { + if (self.compareKeys(query.$lte, query.$lt) === 0) { + return function (key) { return self.compareKeys(key, query.$lt) < 0; }; + } + + if (self.compareKeys(query.$lte, query.$lt) < 0) { + return function (key) { return self.compareKeys(key, query.$lte) <= 0; }; + } else { + return function (key) { return self.compareKeys(key, query.$lt) < 0; }; + } + } + + if (query.hasOwnProperty('$lt')) { + return function (key) { return self.compareKeys(key, query.$lt) < 0; }; + } else { + return function (key) { return self.compareKeys(key, query.$lte) <= 0; }; + } +}; + + +// 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 data for a key between bounds + * Return it in key order + * @param {Object} query Mongo-style query where keys are $lt, $lte, $gt or $gte (other keys are not considered) + * @param {Functions} lbm/ubm matching functions calculated at the first recursive step + */ +BinarySearchTree.prototype.betweenBounds = function (query, lbm, ubm) { + var res = []; + + if (!this.hasOwnProperty('key')) { return []; } // Empty tree + + lbm = lbm || this.getLowerBoundMatcher(query); + ubm = ubm || this.getUpperBoundMatcher(query); + + if (lbm(this.key) && this.left) { append(res, this.left.betweenBounds(query, lbm, ubm)); } + if (lbm(this.key) && ubm(this.key)) { append(res, this.data); } + if (ubm(this.key) && this.right) { append(res, this.right.betweenBounds(query, lbm, ubm)); } + + return res; +}; + + +/** + * Delete the current node if it is a leaf + * Return true if it was deleted + */ +BinarySearchTree.prototype.deleteIfLeaf = function () { + if (this.left || this.right) { return false; } + + // The leaf is itself a root + if (!this.parent) { + delete this.key; + this.data = []; + return true; + } + + if (this.parent.left === this) { + this.parent.left = null; + } else { + this.parent.right = null; + } + + return true; +}; + + +/** + * Delete the current node if it has only one child + * Return true if it was deleted + */ +BinarySearchTree.prototype.deleteIfOnlyOneChild = function () { + var child; + + if (this.left && !this.right) { child = this.left; } + if (!this.left && this.right) { child = this.right; } + if (!child) { return false; } + + // Root + if (!this.parent) { + this.key = child.key; + this.data = child.data; + + this.left = null; + if (child.left) { + this.left = child.left; + child.left.parent = this; + } + + this.right = null; + if (child.right) { + this.right = child.right; + child.right.parent = this; + } + + return true; + } + + if (this.parent.left === this) { + this.parent.left = child; + child.parent = this.parent; + } else { + this.parent.right = child; + child.parent = this.parent; + } + + return true; +}; + + +/** + * Delete a key or just a value + * @param {Key} key + * @param {Value} value Optional. If not set, the whole key is deleted. If set, only this value is deleted + */ +BinarySearchTree.prototype.delete = function (key, value) { + var newData = [], replaceWith + , self = this + ; + + if (!this.hasOwnProperty('key')) { return; } + + if (this.compareKeys(key, this.key) < 0) { + if (this.left) { this.left.delete(key, value); } + return; + } + + if (this.compareKeys(key, this.key) > 0) { + if (this.right) { this.right.delete(key, value); } + return; + } + + if (!this.compareKeys(key, this.key) === 0) { return; } + + // Delete only a value + if (this.data.length > 1 && value !== undefined) { + this.data.forEach(function (d) { + if (!self.checkValueEquality(d, value)) { newData.push(d); } + }); + self.data = newData; + return; + } + + // Delete the whole node + if (this.deleteIfLeaf()) { + return; + } + if (this.deleteIfOnlyOneChild()) { + return; + } + + // We are in the case where the node to delete has two children + if (Math.random() >= 0.5) { // Randomize replacement to avoid unbalancing the tree too much + // Use the in-order predecessor + replaceWith = this.left.getMaxKeyDescendant(); + + this.key = replaceWith.key; + this.data = replaceWith.data; + + if (this === replaceWith.parent) { // Special case + this.left = replaceWith.left; + if (replaceWith.left) { replaceWith.left.parent = replaceWith.parent; } + } else { + replaceWith.parent.right = replaceWith.left; + if (replaceWith.left) { replaceWith.left.parent = replaceWith.parent; } + } + } else { + // Use the in-order successor + replaceWith = this.right.getMinKeyDescendant(); + + this.key = replaceWith.key; + this.data = replaceWith.data; + + if (this === replaceWith.parent) { // Special case + this.right = replaceWith.right; + if (replaceWith.right) { replaceWith.right.parent = replaceWith.parent; } + } else { + replaceWith.parent.left = replaceWith.right; + if (replaceWith.right) { replaceWith.right.parent = replaceWith.parent; } + } + } +}; + + +/** + * Execute a function on every node of the tree, in key order + * @param {Function} fn Signature: node. Most useful will probably be node.key and node.data + */ +BinarySearchTree.prototype.executeOnEveryNode = function (fn) { + if (this.left) { this.left.executeOnEveryNode(fn); } + fn(this); + if (this.right) { this.right.executeOnEveryNode(fn); } +}; + + +/** + * Pretty print a tree + * @param {Boolean} printData To print the nodes' data along with the key + */ +BinarySearchTree.prototype.prettyPrint = function (printData, spacing) { + spacing = spacing || ""; + + console.log(spacing + "* " + this.key); + if (printData) { console.log(spacing + "* " + this.data); } + + if (!this.left && !this.right) { return; } + + if (this.left) { + this.left.prettyPrint(printData, spacing + " "); + } else { + console.log(spacing + " *"); + } + if (this.right) { + this.right.prettyPrint(printData, spacing + " "); + } else { + console.log(spacing + " *"); + } +}; + + + + +// Interface +module.exports = BinarySearchTree; diff --git a/src/node_modules/nedb/node_modules/binary-search-tree/lib/customUtils.js b/src/node_modules/nedb/node_modules/binary-search-tree/lib/customUtils.js new file mode 100644 index 0000000..742ff03 --- /dev/null +++ b/src/node_modules/nedb/node_modules/binary-search-tree/lib/customUtils.js @@ -0,0 +1,38 @@ +/** + * Return an array with the numbers from 0 to n-1, in a random order + */ +function getRandomArray (n) { + var res, next; + + if (n === 0) { return []; } + if (n === 1) { return [0]; } + + res = getRandomArray(n - 1); + next = Math.floor(Math.random() * n); + res.splice(next, 0, n - 1); // Add n-1 at a random position in the array + + return res; +}; +module.exports.getRandomArray = getRandomArray; + + +/* + * Default compareKeys function will work for numbers, strings and dates + */ +function defaultCompareKeysFunction (a, b) { + if (a < b) { return -1; } + if (a > b) { return 1; } + if (a === b) { return 0; } + + throw { message: "Couldn't compare elements", a: a, b: b }; +} +module.exports.defaultCompareKeysFunction = defaultCompareKeysFunction; + + +/** + * Check whether two values are equal (used in non-unique deletion) + */ +function defaultCheckValueEquality (a, b) { + return a === b; +} +module.exports.defaultCheckValueEquality = defaultCheckValueEquality; diff --git a/src/node_modules/nedb/node_modules/binary-search-tree/package.json b/src/node_modules/nedb/node_modules/binary-search-tree/package.json new file mode 100644 index 0000000..3226550 --- /dev/null +++ b/src/node_modules/nedb/node_modules/binary-search-tree/package.json @@ -0,0 +1,43 @@ +{ + "name": "binary-search-tree", + "version": "0.2.4", + "author": { + "name": "Louis Chatriot", + "email": "louis.chatriot@gmail.com" + }, + "description": "Different binary search tree implementations, including a self-balancing one (AVL)", + "keywords": [ + "AVL tree", + "binary search tree", + "self-balancing", + "AVL tree" + ], + "homepage": "https://github.com/louischatriot/node-binary-search-tree", + "repository": { + "type": "git", + "url": "git@github.com:louischatriot/node-binary-search-tree.git" + }, + "dependencies": { + "underscore": "~1.4.4" + }, + "devDependencies": { + "chai": "1.0.x", + "mocha": "1.4.x" + }, + "scripts": { + "test": "make test" + }, + "main": "index", + "licence": "MIT", + "readme": "# Binary search trees for Node.js\n\nTwo implementations of binary search tree: basic and AVL (a kind of self-balancing binmary search tree). I wrote this module primarily to store indexes for NeDB (a javascript dependency-less database).\n\n\n## Installation and tests\nPackage name is `binary-search-tree`.\n\n```bash\nnpm install binary-search-tree --save\n\nmake test\n```\n\n## Usage\nThe API mainly provides 3 functions: `insert`, `search` and `delete`. If you do not create a unique-type binary search tree, you can store multiple pieces of data for the same key. Doing so with a unique-type BST will result in an error being thrown. Data is always returned as an array, and you can delete all data relating to a given key, or just one piece of data.\n\n```javascript\nvar BinarySearchTree = require('binary-search-tree').BinarySearchTree\n , AVLTree = require('binary-search-tree').AVLTree // Same API as BinarySearchTree\n\n// Creating a binary search tree\nvar bst = new BinarySearchTree();\n\n// Inserting some data\nbst.insert(15, 'some data for key 15');\nbst.insert(12, 'something else');\nbst.insert(18, 'hello');\n\n// You can insert multiple pieces of data for the same key\n// if your tree doesn't enforce a unique constraint\nbst.insert(18, 'world');\n\n// Retrieving data (always returned as an array of all data stored for this key)\nbst.search(15); // Equal to ['some data for key 15']\nbst.search(18); // Equal to ['hello', 'world']\nbst.search(1); // Equal to []\n\n// Search between bounds with a MongoDB-like query\n// Data is returned in key order\n// Note the difference between $lt (less than) and $gte (less than OR EQUAL)\nbst.betweenBounds({ $lt: 18, $gte: 12}); // Equal to ['something else', 'some data for key 15']\n\n// Deleting all the data relating to a key\nbst.delete(15); // bst.search(15) will now give []\nbst.delete(18, 'world'); // bst.search(18) will now give ['hello']\n```\n\nThere are three optional parameters you can pass the BST constructor, allowing you to enforce a key-uniqueness constraint, use a custom function to compare keys and use a custom function to check whether values are equal. These parameters are all passed in an object.\n\n### Uniqueness\n\n```javascript\nvar bst = new BinarySearchTree({ unique: true });\nbst.insert(10, 'hello');\nbst.insert(10, 'world'); // Will throw an error\n```\n\n### Custom key comparison\n\n```javascript\n// Custom key comparison function\n// It needs to return a negative number if a is less than b,\n// a positive number if a is greater than b\n// and 0 if they are equal\n// If none is provided, the default one can compare numbers, dates and strings\n// which are the most common usecases\nfunction compareKeys (a, b) {\n if (a.age < b.age) { return -1; }\n if (a.age > b.age) { return 1; }\n \n return 0;\n}\n\n// Now we can use objects with an 'age' property as keys\nvar bst = new BinarySearchTree({ compareKeys: compareKeys });\nbst.insert({ age: 23 }, 'Mark');\nbst.insert({ age: 47 }, 'Franck');\n```\n\n### Custom value checking\n\n```javascript\n// Custom value equality checking function used when we try to just delete one piece of data\n// Returns true if a and b are considered the same, false otherwise\n// The default function is able to compare numbers and strings\nfunction checkValueEquality (a, b) {\n return a.length === b.length;\n}\nvar bst = new BinarySearchTree({ checkValueEquality: checkValueEquality });\nbst.insert(10, 'hello');\nbst.insert(10, 'world');\nbst.insert(10, 'howdoyoudo');\n\nbst.delete(10, 'abcde');\nbst.search(10); // Returns ['howdoyoudo']\n```\n\n\n## License \n\n(The MIT License)\n\nCopyright (c) 2013 Louis Chatriot <louis.chatriot@gmail.com>\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n'Software'), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\nIN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\nCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,\nTORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\nSOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n", + "readmeFilename": "README.md", + "bugs": { + "url": "https://github.com/louischatriot/node-binary-search-tree/issues" + }, + "_id": "binary-search-tree@0.2.4", + "dist": { + "shasum": "14fe106366a59ca8efb68c0ae30c36aaff0cd510" + }, + "_from": "binary-search-tree@0.2.4", + "_resolved": "https://registry.npmjs.org/binary-search-tree/-/binary-search-tree-0.2.4.tgz" +} diff --git a/src/node_modules/nedb/node_modules/binary-search-tree/test/avltree.test.js b/src/node_modules/nedb/node_modules/binary-search-tree/test/avltree.test.js new file mode 100644 index 0000000..be58101 --- /dev/null +++ b/src/node_modules/nedb/node_modules/binary-search-tree/test/avltree.test.js @@ -0,0 +1,1083 @@ +var should = require('chai').should() + , assert = require('chai').assert + , AVLTree = require('../index').AVLTree + , _ = require('underscore') + , customUtils = require('../lib/customUtils') + ; + + +describe('AVL tree', function () { + + describe('Sanity checks', function () { + + it('Checking that all nodes heights are correct', function () { + var _AVLTree = AVLTree._AVLTree + , avlt = new _AVLTree({ key: 10 }) + , l = new _AVLTree({ key: 5 }) + , r = new _AVLTree({ key: 15 }) + , ll = new _AVLTree({ key: 3 }) + , lr = new _AVLTree({ key: 8 }) + , rl = new _AVLTree({ key: 13 }) + , rr = new _AVLTree({ key: 18 }) + , lrl = new _AVLTree({ key: 7 }) + , lrll = new _AVLTree({ key: 6 }) + ; + + + // With a balanced tree + avlt.left = l; + avlt.right = r; + l.left = ll; + l.right = lr; + r.left = rl; + r.right = rr; + + (function () { avlt.checkHeightCorrect() }).should.throw(); + avlt.height = 1; + (function () { avlt.checkHeightCorrect() }).should.throw(); + l.height = 1; + (function () { avlt.checkHeightCorrect() }).should.throw(); + r.height = 1; + (function () { avlt.checkHeightCorrect() }).should.throw(); + ll.height = 1; + (function () { avlt.checkHeightCorrect() }).should.throw(); + lr.height = 1; + (function () { avlt.checkHeightCorrect() }).should.throw(); + rl.height = 1; + (function () { avlt.checkHeightCorrect() }).should.throw(); + rr.height = 1; + (function () { avlt.checkHeightCorrect() }).should.throw(); + avlt.height = 2; + (function () { avlt.checkHeightCorrect() }).should.throw(); + l.height = 2; + (function () { avlt.checkHeightCorrect() }).should.throw(); + r.height = 2; + (function () { avlt.checkHeightCorrect() }).should.throw(); + avlt.height = 3; + avlt.checkHeightCorrect(); // Correct + + // With an unbalanced tree + lr.left = lrl; + (function () { avlt.checkHeightCorrect() }).should.throw(); + lrl.left = lrll; + (function () { avlt.checkHeightCorrect() }).should.throw(); + lrl.height = 1; + (function () { avlt.checkHeightCorrect() }).should.throw(); + lrll.height = 1; + (function () { avlt.checkHeightCorrect() }).should.throw(); + lrl.height = 2; + (function () { avlt.checkHeightCorrect() }).should.throw(); + lr.height = 3; + (function () { avlt.checkHeightCorrect() }).should.throw(); + l.height = 4; + (function () { avlt.checkHeightCorrect() }).should.throw(); + avlt.height = 5; + avlt.checkHeightCorrect(); // Correct + }); + + it('Calculate the balance factor', function () { + var _AVLTree = AVLTree._AVLTree + , avlt = new _AVLTree({ key: 10 }) + , l = new _AVLTree({ key: 5 }) + , r = new _AVLTree({ key: 15 }) + , ll = new _AVLTree({ key: 3 }) + , lr = new _AVLTree({ key: 8 }) + , rl = new _AVLTree({ key: 13 }) + , rr = new _AVLTree({ key: 18 }) + , lrl = new _AVLTree({ key: 7 }) + , lrll = new _AVLTree({ key: 6 }) + ; + + + // With a balanced tree + avlt.left = l; + avlt.right = r; + l.left = ll; + l.right = lr; + r.left = rl; + r.right = rr; + + ll.height = 1; + rl.height = 1; + rr.height = 1; + avlt.height = 2; + r.height = 2; + lr.left = lrl; + lrl.left = lrll; + lrl.height = 1; + lrll.height = 1; + lrl.height = 2; + lr.height = 3; + l.height = 4; + avlt.height = 5; + avlt.checkHeightCorrect(); // Correct + + lrll.balanceFactor().should.equal(0); + lrl.balanceFactor().should.equal(1); + ll.balanceFactor().should.equal(0); + lr.balanceFactor().should.equal(2); + rl.balanceFactor().should.equal(0); + rr.balanceFactor().should.equal(0); + l.balanceFactor().should.equal(-2); + r.balanceFactor().should.equal(0); + avlt.balanceFactor().should.equal(2); + }); + + it('Can check that a tree is balanced', function () { + var _AVLTree = AVLTree._AVLTree + , avlt = new _AVLTree({ key: 10 }) + , l = new _AVLTree({ key: 5 }) + , r = new _AVLTree({ key: 15 }) + , ll = new _AVLTree({ key: 3 }) + , lr = new _AVLTree({ key: 8 }) + , rl = new _AVLTree({ key: 13 }) + , rr = new _AVLTree({ key: 18 }) + + avlt.left = l; + avlt.right = r; + l.left = ll; + l.right = lr; + r.left = rl; + r.right = rr; + + ll.height = 1; + lr.height = 1; + rl.height = 1; + rr.height = 1; + l.height = 2; + r.height = 2; + avlt.height = 3; + avlt.checkBalanceFactors(); + + r.height = 0; + (function () { avlt.checkBalanceFactors(); }).should.throw(); + r.height = 4; + (function () { avlt.checkBalanceFactors(); }).should.throw(); + r.height = 2; + avlt.checkBalanceFactors(); + + ll.height = -1; + (function () { avlt.checkBalanceFactors(); }).should.throw(); + ll.height = 3; + (function () { avlt.checkBalanceFactors(); }).should.throw(); + ll.height = 1; + avlt.checkBalanceFactors(); + + rl.height = -1; + (function () { avlt.checkBalanceFactors(); }).should.throw(); + rl.height = 3; + (function () { avlt.checkBalanceFactors(); }).should.throw(); + rl.height = 1; + avlt.checkBalanceFactors(); + }); + + }); // ==== End of 'Sanity checks' ==== // + + + describe('Insertion', function () { + + it('The root has a height of 1', function () { + var avlt = new AVLTree(); + + avlt.insert(10, 'root'); + avlt.tree.height.should.equal(1); + }); + + + it('Insert at the root if its the first insertion', function () { + var avlt = new AVLTree(); + + avlt.insert(10, 'some data'); + + avlt.checkIsAVLT(); + avlt.tree.key.should.equal(10); + _.isEqual(avlt.tree.data, ['some data']).should.equal(true); + assert.isNull(avlt.tree.left); + assert.isNull(avlt.tree.right); + }); + + it('If uniqueness constraint not enforced, we can insert different data for same key', function () { + var avlt = new AVLTree(); + + avlt.insert(10, 'some data'); + avlt.insert(3, 'hello'); + avlt.insert(3, 'world'); + + avlt.checkIsAVLT(); + _.isEqual(avlt.search(3), ['hello', 'world']).should.equal(true); + + avlt.insert(12, 'a'); + avlt.insert(12, 'b'); + + avlt.checkIsAVLT(); + _.isEqual(avlt.search(12), ['a', 'b']).should.equal(true); + }); + + it('If uniqueness constraint is enforced, we cannot insert different data for same key', function () { + var avlt = new AVLTree({ unique: true }); + + avlt.insert(10, 'some data'); + avlt.insert(3, 'hello'); + try { + avlt.insert(3, 'world'); + } catch (e) { + e.errorType.should.equal('uniqueViolated'); + e.key.should.equal(3); + } + + avlt.checkIsAVLT(); + _.isEqual(avlt.search(3), ['hello']).should.equal(true); + + avlt.insert(12, 'a'); + try { + avlt.insert(12, 'world'); + } catch (e) { + e.errorType.should.equal('uniqueViolated'); + e.key.should.equal(12); + } + + avlt.checkIsAVLT(); + _.isEqual(avlt.search(12), ['a']).should.equal(true); + }); + + it('Can insert 0 or the empty string', function () { + var avlt = new AVLTree(); + + avlt.insert(0, 'some data'); + + avlt.checkIsAVLT(); + avlt.tree.key.should.equal(0); + _.isEqual(avlt.tree.data, ['some data']).should.equal(true); + + avlt = new AVLTree(); + + avlt.insert('', 'some other data'); + + avlt.checkIsAVLT(); + avlt.tree.key.should.equal(''); + _.isEqual(avlt.tree.data, ['some other data']).should.equal(true); + }); + + it('Auto-balancing insertions', function () { + var avlt = new AVLTree() + , avlt2 = new AVLTree() + , avlt3 = new AVLTree() + ; + + // Balancing insertions on the left + avlt.tree.getNumberOfKeys().should.equal(0); + avlt.insert(18); + avlt.tree.getNumberOfKeys().should.equal(1); + avlt.tree.checkIsAVLT(); + avlt.insert(15); + avlt.tree.getNumberOfKeys().should.equal(2); + avlt.tree.checkIsAVLT(); + avlt.insert(13); + avlt.tree.getNumberOfKeys().should.equal(3); + avlt.tree.checkIsAVLT(); + avlt.insert(10); + avlt.tree.getNumberOfKeys().should.equal(4); + avlt.tree.checkIsAVLT(); + avlt.insert(8); + avlt.tree.getNumberOfKeys().should.equal(5); + avlt.tree.checkIsAVLT(); + avlt.insert(5); + avlt.tree.getNumberOfKeys().should.equal(6); + avlt.tree.checkIsAVLT(); + avlt.insert(3); + avlt.tree.getNumberOfKeys().should.equal(7); + avlt.tree.checkIsAVLT(); + + // Balancing insertions on the right + avlt2.tree.getNumberOfKeys().should.equal(0); + avlt2.insert(3); + avlt2.tree.getNumberOfKeys().should.equal(1); + avlt2.tree.checkIsAVLT(); + avlt2.insert(5); + avlt2.tree.getNumberOfKeys().should.equal(2); + avlt2.tree.checkIsAVLT(); + avlt2.insert(8); + avlt2.tree.getNumberOfKeys().should.equal(3); + avlt2.tree.checkIsAVLT(); + avlt2.insert(10); + avlt2.tree.getNumberOfKeys().should.equal(4); + avlt2.tree.checkIsAVLT(); + avlt2.insert(13); + avlt2.tree.getNumberOfKeys().should.equal(5); + avlt2.tree.checkIsAVLT(); + avlt2.insert(15); + avlt2.tree.getNumberOfKeys().should.equal(6); + avlt2.tree.checkIsAVLT(); + avlt2.insert(18); + avlt2.tree.getNumberOfKeys().should.equal(7); + avlt2.tree.checkIsAVLT(); + + // Balancing already-balanced insertions + avlt3.tree.getNumberOfKeys().should.equal(0); + avlt3.insert(10); + avlt3.tree.getNumberOfKeys().should.equal(1); + avlt3.tree.checkIsAVLT(); + avlt3.insert(5); + avlt3.tree.getNumberOfKeys().should.equal(2); + avlt3.tree.checkIsAVLT(); + avlt3.insert(15); + avlt3.tree.getNumberOfKeys().should.equal(3); + avlt3.tree.checkIsAVLT(); + avlt3.insert(3); + avlt3.tree.getNumberOfKeys().should.equal(4); + avlt3.tree.checkIsAVLT(); + avlt3.insert(8); + avlt3.tree.getNumberOfKeys().should.equal(5); + avlt3.tree.checkIsAVLT(); + avlt3.insert(13); + avlt3.tree.getNumberOfKeys().should.equal(6); + avlt3.tree.checkIsAVLT(); + avlt3.insert(18); + avlt3.tree.getNumberOfKeys().should.equal(7); + avlt3.tree.checkIsAVLT(); + }); + + it('Can insert a lot of keys and still get an AVLT (sanity check)', function () { + var avlt = new AVLTree({ unique: true }); + + customUtils.getRandomArray(1000).forEach(function (n) { + avlt.insert(n, 'some data'); + avlt.checkIsAVLT(); + }); + + }); + + }); // ==== End of 'Insertion' ==== // + + + describe('Search', function () { + + it('Can find data in an AVLT', function () { + var avlt = new AVLTree() + , i; + + customUtils.getRandomArray(100).forEach(function (n) { + avlt.insert(n, 'some data for ' + n); + }); + + avlt.checkIsAVLT(); + + for (i = 0; i < 100; i += 1) { + _.isEqual(avlt.search(i), ['some data for ' + i]).should.equal(true); + } + }); + + it('If no data can be found, return an empty array', function () { + var avlt = new AVLTree(); + + customUtils.getRandomArray(100).forEach(function (n) { + if (n !== 63) { + avlt.insert(n, 'some data for ' + n); + } + }); + + avlt.checkIsAVLT(); + + avlt.search(-2).length.should.equal(0); + avlt.search(100).length.should.equal(0); + avlt.search(101).length.should.equal(0); + avlt.search(63).length.should.equal(0); + }); + + it('Can search for data between two bounds', function () { + var avlt = new AVLTree(); + + [10, 5, 15, 3, 8, 13, 18].forEach(function (k) { + avlt.insert(k, 'data ' + k); + }); + + assert.deepEqual(avlt.betweenBounds({ $gte: 8, $lte: 15 }), ['data 8', 'data 10', 'data 13', 'data 15']); + assert.deepEqual(avlt.betweenBounds({ $gt: 8, $lt: 15 }), ['data 10', 'data 13']); + }); + + it('Bounded search can handle cases where query contains both $lt and $lte, or both $gt and $gte', function () { + var avlt = new AVLTree(); + + [10, 5, 15, 3, 8, 13, 18].forEach(function (k) { + avlt.insert(k, 'data ' + k); + }); + + assert.deepEqual(avlt.betweenBounds({ $gt:8, $gte: 8, $lte: 15 }), ['data 10', 'data 13', 'data 15']); + assert.deepEqual(avlt.betweenBounds({ $gt:5, $gte: 8, $lte: 15 }), ['data 8', 'data 10', 'data 13', 'data 15']); + assert.deepEqual(avlt.betweenBounds({ $gt:8, $gte: 5, $lte: 15 }), ['data 10', 'data 13', 'data 15']); + + assert.deepEqual(avlt.betweenBounds({ $gte: 8, $lte: 15, $lt: 15 }), ['data 8', 'data 10', 'data 13']); + assert.deepEqual(avlt.betweenBounds({ $gte: 8, $lte: 18, $lt: 15 }), ['data 8', 'data 10', 'data 13']); + assert.deepEqual(avlt.betweenBounds({ $gte: 8, $lte: 15, $lt: 18 }), ['data 8', 'data 10', 'data 13', 'data 15']); + }); + + it('Bounded search can work when one or both boundaries are missing', function () { + var avlt = new AVLTree(); + + [10, 5, 15, 3, 8, 13, 18].forEach(function (k) { + avlt.insert(k, 'data ' + k); + }); + + assert.deepEqual(avlt.betweenBounds({ $gte: 11 }), ['data 13', 'data 15', 'data 18']); + assert.deepEqual(avlt.betweenBounds({ $lte: 9 }), ['data 3', 'data 5', 'data 8']); + }); + + }); /// ==== End of 'Search' ==== // + + + describe('Deletion', function () { + + it('Deletion does nothing on an empty tree', function () { + var avlt = new AVLTree() + , avltu = new AVLTree({ unique: true }); + + avlt.getNumberOfKeys().should.equal(0); + avltu.getNumberOfKeys().should.equal(0); + + avlt.delete(5); + avltu.delete(5); + + avlt.tree.hasOwnProperty('key').should.equal(false); + avltu.tree.hasOwnProperty('key').should.equal(false); + + avlt.tree.data.length.should.equal(0); + avltu.tree.data.length.should.equal(0); + + avlt.getNumberOfKeys().should.equal(0); + avltu.getNumberOfKeys().should.equal(0); + }); + + it('Deleting a non-existent key doesnt have any effect', function () { + var avlt = new AVLTree(); + + [10, 5, 3, 8, 15, 12, 37].forEach(function (k) { + avlt.insert(k, 'some ' + k); + }); + + function checkavlt () { + [10, 5, 3, 8, 15, 12, 37].forEach(function (k) { + _.isEqual(avlt.search(k), ['some ' + k]).should.equal(true); + }); + } + + checkavlt(); + avlt.getNumberOfKeys().should.equal(7); + + avlt.delete(2); + checkavlt(); avlt.checkIsAVLT(); avlt.getNumberOfKeys().should.equal(7); + avlt.delete(4); + checkavlt(); avlt.checkIsAVLT(); avlt.getNumberOfKeys().should.equal(7); + avlt.delete(9); + checkavlt(); avlt.checkIsAVLT(); avlt.getNumberOfKeys().should.equal(7); + avlt.delete(6); + checkavlt(); avlt.checkIsAVLT(); avlt.getNumberOfKeys().should.equal(7); + avlt.delete(11); + checkavlt(); avlt.checkIsAVLT(); avlt.getNumberOfKeys().should.equal(7); + avlt.delete(14); + checkavlt(); avlt.checkIsAVLT(); avlt.getNumberOfKeys().should.equal(7); + avlt.delete(20); + checkavlt(); avlt.checkIsAVLT(); avlt.getNumberOfKeys().should.equal(7); + avlt.delete(200); + checkavlt(); avlt.checkIsAVLT(); avlt.getNumberOfKeys().should.equal(7); + }); + + it('Able to delete the root if it is also a leaf', function () { + var avlt = new AVLTree(); + + avlt.insert(10, 'hello'); + avlt.tree.key.should.equal(10); + _.isEqual(avlt.tree.data, ['hello']).should.equal(true); + avlt.getNumberOfKeys().should.equal(1); + + avlt.delete(10); + avlt.tree.hasOwnProperty('key').should.equal(false); + avlt.tree.data.length.should.equal(0); + avlt.getNumberOfKeys().should.equal(0); + }); + + it('Able to delete leaf nodes that are non-root', function () { + var avlt; + + // This will create an AVL tree with leaves 3, 8, 12, 37 + // (do a pretty print to see this) + function recreateavlt () { + avlt = new AVLTree(); + + [10, 5, 3, 8, 15, 12, 37].forEach(function (k) { + avlt.insert(k, 'some ' + k); + }); + + avlt.getNumberOfKeys().should.equal(7); + } + + // Check that only keys in array theRemoved were removed + function checkRemoved (theRemoved) { + [10, 5, 3, 8, 15, 12, 37].forEach(function (k) { + if (theRemoved.indexOf(k) !== -1) { + avlt.search(k).length.should.equal(0); + } else { + _.isEqual(avlt.search(k), ['some ' + k]).should.equal(true); + } + }); + + avlt.getNumberOfKeys().should.equal(7 - theRemoved.length); + } + + recreateavlt(); + avlt.delete(3); + avlt.checkIsAVLT(); + checkRemoved([3]); + + recreateavlt(); + avlt.delete(8); + avlt.checkIsAVLT(); + checkRemoved([8]); + + recreateavlt(); + avlt.delete(12); + avlt.checkIsAVLT(); + checkRemoved([12]); + + // Delete all leaves in a way that makes the tree unbalanced + recreateavlt(); + avlt.delete(37); + avlt.checkIsAVLT(); + checkRemoved([37]); + + avlt.delete(12); + avlt.checkIsAVLT(); + checkRemoved([12, 37]); + + avlt.delete(15); + avlt.checkIsAVLT(); + checkRemoved([12, 15, 37]); + + avlt.delete(3); + avlt.checkIsAVLT(); + checkRemoved([3, 12, 15, 37]); + + avlt.delete(5); + avlt.checkIsAVLT(); + checkRemoved([3, 5, 12, 15, 37]); + + avlt.delete(10); + avlt.checkIsAVLT(); + checkRemoved([3, 5, 10, 12, 15, 37]); + + avlt.delete(8); + avlt.checkIsAVLT(); + checkRemoved([3, 5, 8, 10, 12, 15, 37]); + }); + + it('Able to delete the root if it has only one child', function () { + var avlt; + + // Root has only one child, on the left + avlt = new AVLTree(); + [10, 5].forEach(function (k) { + avlt.insert(k, 'some ' + k); + }); + avlt.getNumberOfKeys().should.equal(2); + avlt.delete(10); + avlt.checkIsAVLT(); + avlt.getNumberOfKeys().should.equal(1); + _.isEqual(avlt.search(5), ['some 5']).should.equal(true); + avlt.search(10).length.should.equal(0); + + // Root has only one child, on the right + avlt = new AVLTree(); + [10, 15].forEach(function (k) { + avlt.insert(k, 'some ' + k); + }); + avlt.getNumberOfKeys().should.equal(2); + avlt.delete(10); + avlt.checkIsAVLT(); + avlt.getNumberOfKeys().should.equal(1); + _.isEqual(avlt.search(15), ['some 15']).should.equal(true); + avlt.search(10).length.should.equal(0); + }); + + it('Able to delete non root nodes that have only one child', function () { + var avlt = new AVLTree() + , firstSet = [10, 5, 15, 3, 1, 4, 20] + , secondSet = [10, 5, 15, 3, 1, 4, 20, 17, 25] + ; + + // Check that only keys in array theRemoved were removed + function checkRemoved (set, theRemoved) { + set.forEach(function (k) { + if (theRemoved.indexOf(k) !== -1) { + avlt.search(k).length.should.equal(0); + } else { + _.isEqual(avlt.search(k), ['some ' + k]).should.equal(true); + } + }); + + avlt.getNumberOfKeys().should.equal(set.length - theRemoved.length); + } + + // First set: no rebalancing necessary + firstSet.forEach(function (k) { + avlt.insert(k, 'some ' + k); + }); + + avlt.getNumberOfKeys().should.equal(7); + avlt.checkIsAVLT(); + + avlt.delete(4); // Leaf + avlt.checkIsAVLT(); + checkRemoved(firstSet, [4]); + + avlt.delete(3); // Node with only one child (on the left) + avlt.checkIsAVLT(); + checkRemoved(firstSet, [3, 4]); + + avlt.delete(10); // Leaf + avlt.checkIsAVLT(); + checkRemoved(firstSet, [3, 4, 10]); + + avlt.delete(15); // Node with only one child (on the right) + avlt.checkIsAVLT(); + checkRemoved(firstSet, [3, 4, 10, 15]); + + // Second set: some rebalancing necessary + avlt = new AVLTree(); + secondSet.forEach(function (k) { + avlt.insert(k, 'some ' + k); + }); + + avlt.delete(4); // Leaf + avlt.checkIsAVLT(); + checkRemoved(secondSet, [4]); + + avlt.delete(3); // Node with only one child (on the left), causes rebalancing + avlt.checkIsAVLT(); + checkRemoved(secondSet, [3, 4]); + }); + + it('Can delete the root if it has 2 children', function () { + var avlt = new AVLTree(); + + // No rebalancing needed + [10, 5, 15, 3, 8, 12, 37].forEach(function (k) { + avlt.insert(k, 'some ' + k); + }); + avlt.getNumberOfKeys().should.equal(7); + avlt.delete(10); + avlt.checkIsAVLT(); + avlt.getNumberOfKeys().should.equal(6); + [5, 3, 8, 15, 12, 37].forEach(function (k) { + _.isEqual(avlt.search(k), ['some ' + k]).should.equal(true); + }); + avlt.search(10).length.should.equal(0); + + // Rebalancing needed + avlt = new AVLTree(); + [10, 5, 15, 8, 12, 37, 42].forEach(function (k) { + avlt.insert(k, 'some ' + k); + }); + avlt.getNumberOfKeys().should.equal(7); + avlt.delete(10); + avlt.checkIsAVLT(); + avlt.getNumberOfKeys().should.equal(6); + [5, 8, 15, 12, 37, 42].forEach(function (k) { + _.isEqual(avlt.search(k), ['some ' + k]).should.equal(true); + }); + avlt.search(10).length.should.equal(0); + }); + + it('Can delete a non-root node that has two children', function () { + var avlt; + + // On the left + avlt = new AVLTree(); + [10, 5, 15, 3, 8, 12, 20, 1, 4, 6, 9, 11, 13, 19, 42, 3.5].forEach(function (k) { + avlt.insert(k, 'some ' + k); + }); + avlt.getNumberOfKeys().should.equal(16); + avlt.delete(5); + avlt.checkIsAVLT(); + avlt.getNumberOfKeys().should.equal(15); + [10, 3, 1, 4, 8, 6, 9, 15, 12, 11, 13, 20, 19, 42, 3.5].forEach(function (k) { + _.isEqual(avlt.search(k), ['some ' + k]).should.equal(true); + }); + avlt.search(5).length.should.equal(0); + + // On the right + avlt = new AVLTree(); + [10, 5, 15, 3, 8, 12, 20, 1, 4, 6, 9, 11, 13, 19, 42, 12.5].forEach(function (k) { + avlt.insert(k, 'some ' + k); + }); + avlt.getNumberOfKeys().should.equal(16); + avlt.delete(15); + avlt.checkIsAVLT(); + avlt.getNumberOfKeys().should.equal(15); + [10, 3, 1, 4, 8, 6, 9, 5, 12, 11, 13, 20, 19, 42, 12.5].forEach(function (k) { + _.isEqual(avlt.search(k), ['some ' + k]).should.equal(true); + }); + avlt.search(15).length.should.equal(0); + }); + + it('If no value is provided, it will delete the entire node even if there are multiple pieces of data', function () { + var avlt = new AVLTree(); + + avlt.insert(10, 'yes'); + avlt.insert(5, 'hello'); + avlt.insert(3, 'yes'); + avlt.insert(5, 'world'); + avlt.insert(8, 'yes'); + + assert.deepEqual(avlt.search(5), ['hello', 'world']); + avlt.getNumberOfKeys().should.equal(4); + + avlt.delete(5); + avlt.checkIsAVLT(); + avlt.search(5).length.should.equal(0); + avlt.getNumberOfKeys().should.equal(3); + }); + + it('Can remove only one value from an array', function () { + var avlt = new AVLTree(); + + avlt.insert(10, 'yes'); + avlt.insert(5, 'hello'); + avlt.insert(3, 'yes'); + avlt.insert(5, 'world'); + avlt.insert(8, 'yes'); + + assert.deepEqual(avlt.search(5), ['hello', 'world']); + avlt.getNumberOfKeys().should.equal(4); + + avlt.delete(5, 'hello'); + avlt.checkIsAVLT(); + assert.deepEqual(avlt.search(5), ['world']); + avlt.getNumberOfKeys().should.equal(4); + }); + + it('Removes nothing if value doesnt match', function () { + var avlt = new AVLTree(); + + avlt.insert(10, 'yes'); + avlt.insert(5, 'hello'); + avlt.insert(3, 'yes'); + avlt.insert(5, 'world'); + avlt.insert(8, 'yes'); + + assert.deepEqual(avlt.search(5), ['hello', 'world']); + avlt.getNumberOfKeys().should.equal(4); + + avlt.delete(5, 'nope'); + avlt.checkIsAVLT(); + assert.deepEqual(avlt.search(5), ['hello', 'world']); + avlt.getNumberOfKeys().should.equal(4); + }); + + it('If value provided but node contains only one value, remove entire node', function () { + var avlt = new AVLTree(); + + avlt.insert(10, 'yes'); + avlt.insert(5, 'hello'); + avlt.insert(3, 'yes2'); + avlt.insert(5, 'world'); + avlt.insert(8, 'yes3'); + + assert.deepEqual(avlt.search(3), ['yes2']); + avlt.getNumberOfKeys().should.equal(4); + + avlt.delete(3, 'yes2'); + avlt.checkIsAVLT(); + avlt.search(3).length.should.equal(0); + avlt.getNumberOfKeys().should.equal(3); + }); + + it('Can remove the root from a tree with height 2 when the root has two children (special case)', function () { + var avlt = new AVLTree(); + + avlt.insert(10, 'maybe'); + avlt.insert(5, 'no'); + avlt.insert(15, 'yes'); + avlt.getNumberOfKeys().should.equal(3); + + avlt.delete(10); + avlt.checkIsAVLT(); + avlt.getNumberOfKeys().should.equal(2); + assert.deepEqual(avlt.search(5), ['no']); + assert.deepEqual(avlt.search(15), ['yes']); + }); + + it('Can remove the root from a tree with height 3 when the root has two children (special case where the two children themselves have children)', function () { + var avlt = new AVLTree(); + + avlt.insert(10, 'maybe'); + avlt.insert(5, 'no'); + avlt.insert(15, 'yes'); + avlt.insert(2, 'no'); + avlt.insert(35, 'yes'); + avlt.getNumberOfKeys().should.equal(5); + + avlt.delete(10); + avlt.checkIsAVLT(); + avlt.getNumberOfKeys().should.equal(4); + assert.deepEqual(avlt.search(5), ['no']); + assert.deepEqual(avlt.search(15), ['yes']); + }); + + }); // ==== End of 'Deletion' ==== // + + + it('Can use undefined as key and value', function () { + function compareKeys (a, b) { + if (a === undefined && b === undefined) { return 0; } + if (a === undefined) { return -1; } + if (b === undefined) { return 1; } + + if (a < b) { return -1; } + if (a > b) { return 1; } + if (a === b) { return 0; } + } + + var avlt = new AVLTree({ compareKeys: compareKeys }); + + avlt.insert(2, undefined); + avlt.checkIsAVLT(); + avlt.getNumberOfKeys().should.equal(1); + assert.deepEqual(avlt.search(2), [undefined]); + assert.deepEqual(avlt.search(undefined), []); + + avlt.insert(undefined, 'hello'); + avlt.checkIsAVLT(); + avlt.getNumberOfKeys().should.equal(2); + assert.deepEqual(avlt.search(2), [undefined]); + assert.deepEqual(avlt.search(undefined), ['hello']); + + avlt.insert(undefined, 'world'); + avlt.checkIsAVLT(); + avlt.getNumberOfKeys().should.equal(2); + assert.deepEqual(avlt.search(2), [undefined]); + assert.deepEqual(avlt.search(undefined), ['hello', 'world']); + + avlt.insert(4, undefined); + avlt.checkIsAVLT(); + avlt.getNumberOfKeys().should.equal(3); + assert.deepEqual(avlt.search(2), [undefined]); + assert.deepEqual(avlt.search(4), [undefined]); + assert.deepEqual(avlt.search(undefined), ['hello', 'world']); + + avlt.delete(undefined, 'hello'); + avlt.checkIsAVLT(); + avlt.getNumberOfKeys().should.equal(3); + assert.deepEqual(avlt.search(2), [undefined]); + assert.deepEqual(avlt.search(4), [undefined]); + assert.deepEqual(avlt.search(undefined), ['world']); + + avlt.delete(undefined); + avlt.checkIsAVLT(); + avlt.getNumberOfKeys().should.equal(2); + assert.deepEqual(avlt.search(2), [undefined]); + assert.deepEqual(avlt.search(4), [undefined]); + assert.deepEqual(avlt.search(undefined), []); + + avlt.delete(2, undefined); + avlt.checkIsAVLT(); + avlt.getNumberOfKeys().should.equal(1); + assert.deepEqual(avlt.search(2), []); + assert.deepEqual(avlt.search(4), [undefined]); + assert.deepEqual(avlt.search(undefined), []); + + avlt.delete(4); + avlt.checkIsAVLT(); + avlt.getNumberOfKeys().should.equal(0); + assert.deepEqual(avlt.search(2), []); + assert.deepEqual(avlt.search(4), []); + assert.deepEqual(avlt.search(undefined), []); + }); + + + it('Can use null as key and value', function () { + function compareKeys (a, b) { + if (a === null && b === null) { return 0; } + if (a === null) { return -1; } + if (b === null) { return 1; } + + if (a < b) { return -1; } + if (a > b) { return 1; } + if (a === b) { return 0; } + } + + var avlt = new AVLTree({ compareKeys: compareKeys }); + + avlt.insert(2, null); + avlt.checkIsAVLT(); + avlt.getNumberOfKeys().should.equal(1); + assert.deepEqual(avlt.search(2), [null]); + assert.deepEqual(avlt.search(null), []); + + avlt.insert(null, 'hello'); + avlt.checkIsAVLT(); + avlt.getNumberOfKeys().should.equal(2); + assert.deepEqual(avlt.search(2), [null]); + assert.deepEqual(avlt.search(null), ['hello']); + + avlt.insert(null, 'world'); + avlt.checkIsAVLT(); + avlt.getNumberOfKeys().should.equal(2); + assert.deepEqual(avlt.search(2), [null]); + assert.deepEqual(avlt.search(null), ['hello', 'world']); + + avlt.insert(4, null); + avlt.checkIsAVLT(); + avlt.getNumberOfKeys().should.equal(3); + assert.deepEqual(avlt.search(2), [null]); + assert.deepEqual(avlt.search(4), [null]); + assert.deepEqual(avlt.search(null), ['hello', 'world']); + + avlt.delete(null, 'hello'); + avlt.checkIsAVLT(); + avlt.getNumberOfKeys().should.equal(3); + assert.deepEqual(avlt.search(2), [null]); + assert.deepEqual(avlt.search(4), [null]); + assert.deepEqual(avlt.search(null), ['world']); + + avlt.delete(null); + avlt.checkIsAVLT(); + avlt.getNumberOfKeys().should.equal(2); + assert.deepEqual(avlt.search(2), [null]); + assert.deepEqual(avlt.search(4), [null]); + assert.deepEqual(avlt.search(null), []); + + avlt.delete(2, null); + avlt.checkIsAVLT(); + avlt.getNumberOfKeys().should.equal(1); + assert.deepEqual(avlt.search(2), []); + assert.deepEqual(avlt.search(4), [null]); + assert.deepEqual(avlt.search(null), []); + + avlt.delete(4); + avlt.checkIsAVLT(); + avlt.getNumberOfKeys().should.equal(0); + assert.deepEqual(avlt.search(2), []); + assert.deepEqual(avlt.search(4), []); + assert.deepEqual(avlt.search(null), []); + }); + + + describe('Execute on every node (=tree traversal)', function () { + + it('Can execute a function on every node', function () { + var avlt = new AVLTree() + , keys = [] + , executed = 0 + ; + + avlt.insert(10, 'yes'); + avlt.insert(5, 'hello'); + avlt.insert(3, 'yes2'); + avlt.insert(8, 'yes3'); + avlt.insert(15, 'yes3'); + avlt.insert(159, 'yes3'); + avlt.insert(11, 'yes3'); + + avlt.executeOnEveryNode(function (node) { + keys.push(node.key); + executed += 1; + }); + + assert.deepEqual(keys, [3, 5, 8, 10, 11, 15, 159]); + executed.should.equal(7); + }); + + }); // ==== End of 'Execute on every node' ==== // + + + // This test performs several inserts and deletes at random, always checking the content + // of the tree are as expected and the binary search tree constraint is respected + // This test is important because it can catch bugs other tests can't + // By their nature, BSTs can be hard to test (many possible cases, bug at one operation whose + // effect begins to be felt only after several operations etc.) + describe('Randomized test (takes much longer than the rest of the test suite)', function () { + var avlt = new AVLTree() + , data = {}; + + // Check a avlt against a simple key => [data] object + function checkDataIsTheSame (avlt, data) { + var avltDataElems = []; + + // avltDataElems is a simple array containing every piece of data in the tree + avlt.executeOnEveryNode(function (node) { + var i; + for (i = 0; i < node.data.length; i += 1) { + avltDataElems.push(node.data[i]); + } + }); + + // Number of key and number of pieces of data match + avlt.getNumberOfKeys().should.equal(Object.keys(data).length); + _.reduce(_.map(data, function (d) { return d.length; }), function (memo, n) { return memo + n; }, 0).should.equal(avltDataElems.length); + + // Compare data + Object.keys(data).forEach(function (key) { + checkDataEquality(avlt.search(key), data[key]); + }); + } + + // Check two pieces of data coming from the avlt and data are the same + function checkDataEquality (fromavlt, fromData) { + if (fromavlt.length === 0) { + if (fromData) { fromData.length.should.equal(0); } + } + + assert.deepEqual(fromavlt, fromData); + } + + // Tests the tree structure (deletions concern the whole tree, deletion of some data in a node is well tested above) + it('Inserting and deleting entire nodes', function () { + // You can skew to be more insertive or deletive, to test all cases + function launchRandomTest (nTests, proba) { + var i, key, dataPiece, possibleKeys; + + for (i = 0; i < nTests; i += 1) { + if (Math.random() > proba) { // Deletion + possibleKeys = Object.keys(data); + + if (possibleKeys.length > 0) { + key = possibleKeys[Math.floor(possibleKeys.length * Math.random()).toString()]; + } else { + key = Math.floor(70 * Math.random()).toString(); + } + + delete data[key]; + avlt.delete(key); + } else { // Insertion + key = Math.floor(70 * Math.random()).toString(); + dataPiece = Math.random().toString().substring(0, 6); + + avlt.insert(key, dataPiece); + if (data[key]) { + data[key].push(dataPiece); + } else { + data[key] = [dataPiece]; + } + } + + // Check the avlt constraint are still met and the data is correct + avlt.checkIsAVLT(); + checkDataIsTheSame(avlt, data); + } + } + + launchRandomTest(1000, 0.65); + launchRandomTest(2000, 0.35); + }); + + }); // ==== End of 'Randomized test' ==== // + +}); + + + + + + + + + diff --git a/src/node_modules/nedb/node_modules/binary-search-tree/test/bst.test.js b/src/node_modules/nedb/node_modules/binary-search-tree/test/bst.test.js new file mode 100644 index 0000000..ceb20a9 --- /dev/null +++ b/src/node_modules/nedb/node_modules/binary-search-tree/test/bst.test.js @@ -0,0 +1,1043 @@ +var should = require('chai').should() + , assert = require('chai').assert + , BinarySearchTree = require('../index').BinarySearchTree + , _ = require('underscore') + , customUtils = require('../lib/customUtils') + ; + + +describe('Binary search tree', function () { + + it('Upon creation, left, right are null, key and data can be set', function () { + var bst = new BinarySearchTree(); + assert.isNull(bst.left); + assert.isNull(bst.right); + bst.hasOwnProperty('key').should.equal(false); + bst.data.length.should.equal(0); + + bst = new BinarySearchTree({ key: 6, value: 'ggg' }); + assert.isNull(bst.left); + assert.isNull(bst.right); + bst.key.should.equal(6); + bst.data.length.should.equal(1); + bst.data[0].should.equal('ggg'); + }); + + describe('Sanity checks', function () { + + it('Can get maxkey and minkey descendants', function () { + var t = new BinarySearchTree({ key: 10 }) + , l = new BinarySearchTree({ key: 5 }) + , r = new BinarySearchTree({ key: 15 }) + , ll = new BinarySearchTree({ key: 3 }) + , lr = new BinarySearchTree({ key: 8 }) + , rl = new BinarySearchTree({ key: 11 }) + , rr = new BinarySearchTree({ key: 42 }) + ; + + t.left = l; t.right = r; + l.left = ll; l.right = lr; + r.left = rl; r.right = rr; + + // Getting min and max key descendants + t.getMinKeyDescendant().key.should.equal(3); + t.getMaxKeyDescendant().key.should.equal(42); + + t.left.getMinKeyDescendant().key.should.equal(3); + t.left.getMaxKeyDescendant().key.should.equal(8); + + t.right.getMinKeyDescendant().key.should.equal(11); + t.right.getMaxKeyDescendant().key.should.equal(42); + + t.right.left.getMinKeyDescendant().key.should.equal(11); + t.right.left.getMaxKeyDescendant().key.should.equal(11); + + // Getting min and max keys + t.getMinKey().should.equal(3); + t.getMaxKey().should.equal(42); + + t.left.getMinKey().should.equal(3); + t.left.getMaxKey().should.equal(8); + + t.right.getMinKey().should.equal(11); + t.right.getMaxKey().should.equal(42); + + t.right.left.getMinKey().should.equal(11); + t.right.left.getMaxKey().should.equal(11); + }); + + it('Can check a condition against every node in a tree', function () { + var t = new BinarySearchTree({ key: 10 }) + , l = new BinarySearchTree({ key: 6 }) + , r = new BinarySearchTree({ key: 16 }) + , ll = new BinarySearchTree({ key: 4 }) + , lr = new BinarySearchTree({ key: 8 }) + , rl = new BinarySearchTree({ key: 12 }) + , rr = new BinarySearchTree({ key: 42 }) + ; + + t.left = l; t.right = r; + l.left = ll; l.right = lr; + r.left = rl; r.right = rr; + + function test (k, v) { if (k % 2 !== 0) { throw 'Key is not even'; } } + + t.checkAllNodesFullfillCondition(test); + + [l, r, ll, lr, rl, rr].forEach(function (node) { + node.key += 1; + (function () { t.checkAllNodesFullfillCondition(test); }).should.throw(); + node.key -= 1; + }); + + t.checkAllNodesFullfillCondition(test); + }); + + it('Can check that a tree verifies node ordering', function () { + var t = new BinarySearchTree({ key: 10 }) + , l = new BinarySearchTree({ key: 5 }) + , r = new BinarySearchTree({ key: 15 }) + , ll = new BinarySearchTree({ key: 3 }) + , lr = new BinarySearchTree({ key: 8 }) + , rl = new BinarySearchTree({ key: 11 }) + , rr = new BinarySearchTree({ key: 42 }) + ; + + t.left = l; t.right = r; + l.left = ll; l.right = lr; + r.left = rl; r.right = rr; + + t.checkNodeOrdering(); + + // Let's be paranoid and check all cases... + l.key = 12; + (function () { t.checkNodeOrdering(); }).should.throw(); + l.key = 5; + + r.key = 9; + (function () { t.checkNodeOrdering(); }).should.throw(); + r.key = 15; + + ll.key = 6; + (function () { t.checkNodeOrdering(); }).should.throw(); + ll.key = 11; + (function () { t.checkNodeOrdering(); }).should.throw(); + ll.key = 3; + + lr.key = 4; + (function () { t.checkNodeOrdering(); }).should.throw(); + lr.key = 11; + (function () { t.checkNodeOrdering(); }).should.throw(); + lr.key = 8; + + rl.key = 16; + (function () { t.checkNodeOrdering(); }).should.throw(); + rl.key = 9; + (function () { t.checkNodeOrdering(); }).should.throw(); + rl.key = 11; + + rr.key = 12; + (function () { t.checkNodeOrdering(); }).should.throw(); + rr.key = 7; + (function () { t.checkNodeOrdering(); }).should.throw(); + rr.key = 10.5; + (function () { t.checkNodeOrdering(); }).should.throw(); + rr.key = 42; + + t.checkNodeOrdering(); + }); + + it('Checking if a tree\'s internal pointers (i.e. parents) are correct', function () { + var t = new BinarySearchTree({ key: 10 }) + , l = new BinarySearchTree({ key: 5 }) + , r = new BinarySearchTree({ key: 15 }) + , ll = new BinarySearchTree({ key: 3 }) + , lr = new BinarySearchTree({ key: 8 }) + , rl = new BinarySearchTree({ key: 11 }) + , rr = new BinarySearchTree({ key: 42 }) + ; + + t.left = l; t.right = r; + l.left = ll; l.right = lr; + r.left = rl; r.right = rr; + + (function () { t.checkInternalPointers(); }).should.throw(); + l.parent = t; + (function () { t.checkInternalPointers(); }).should.throw(); + r.parent = t; + (function () { t.checkInternalPointers(); }).should.throw(); + ll.parent = l; + (function () { t.checkInternalPointers(); }).should.throw(); + lr.parent = l; + (function () { t.checkInternalPointers(); }).should.throw(); + rl.parent = r; + (function () { t.checkInternalPointers(); }).should.throw(); + rr.parent = r; + + t.checkInternalPointers(); + }); + + it('Can get the number of inserted keys', function () { + var bst = new BinarySearchTree(); + + bst.getNumberOfKeys().should.equal(0); + bst.insert(10); + bst.getNumberOfKeys().should.equal(1); + bst.insert(5); + bst.getNumberOfKeys().should.equal(2); + bst.insert(3); + bst.getNumberOfKeys().should.equal(3); + bst.insert(8); + bst.getNumberOfKeys().should.equal(4); + bst.insert(15); + bst.getNumberOfKeys().should.equal(5); + bst.insert(12); + bst.getNumberOfKeys().should.equal(6); + bst.insert(37); + bst.getNumberOfKeys().should.equal(7); + }); + + }); + + describe('Insertion', function () { + + it('Insert at the root if its the first insertion', function () { + var bst = new BinarySearchTree(); + + bst.insert(10, 'some data'); + + bst.checkIsBST(); + bst.key.should.equal(10); + _.isEqual(bst.data, ['some data']).should.equal(true); + assert.isNull(bst.left); + assert.isNull(bst.right); + }); + + it("Insert on the left if key is less than the root's", function () { + var bst = new BinarySearchTree(); + + bst.insert(10, 'some data'); + bst.insert(7, 'some other data'); + + bst.checkIsBST(); + assert.isNull(bst.right); + bst.left.key.should.equal(7); + _.isEqual(bst.left.data, ['some other data']).should.equal(true); + assert.isNull(bst.left.left); + assert.isNull(bst.left.right); + }); + + it("Insert on the right if key is greater than the root's", function () { + var bst = new BinarySearchTree(); + + bst.insert(10, 'some data'); + bst.insert(14, 'some other data'); + + bst.checkIsBST(); + assert.isNull(bst.left); + bst.right.key.should.equal(14); + _.isEqual(bst.right.data, ['some other data']).should.equal(true); + assert.isNull(bst.right.left); + assert.isNull(bst.right.right); + }); + + it("Recursive insertion on the left works", function () { + var bst = new BinarySearchTree(); + + bst.insert(10, 'some data'); + bst.insert(7, 'some other data'); + bst.insert(1, 'hello'); + bst.insert(9, 'world'); + + bst.checkIsBST(); + assert.isNull(bst.right); + bst.left.key.should.equal(7); + _.isEqual(bst.left.data, ['some other data']).should.equal(true); + + bst.left.left.key.should.equal(1); + _.isEqual(bst.left.left.data, ['hello']).should.equal(true); + + bst.left.right.key.should.equal(9); + _.isEqual(bst.left.right.data, ['world']).should.equal(true); + }); + + it("Recursive insertion on the right works", function () { + var bst = new BinarySearchTree(); + + bst.insert(10, 'some data'); + bst.insert(17, 'some other data'); + bst.insert(11, 'hello'); + bst.insert(19, 'world'); + + bst.checkIsBST(); + assert.isNull(bst.left); + bst.right.key.should.equal(17); + _.isEqual(bst.right.data, ['some other data']).should.equal(true); + + bst.right.left.key.should.equal(11); + _.isEqual(bst.right.left.data, ['hello']).should.equal(true); + + bst.right.right.key.should.equal(19); + _.isEqual(bst.right.right.data, ['world']).should.equal(true); + }); + + it('If uniqueness constraint not enforced, we can insert different data for same key', function () { + var bst = new BinarySearchTree(); + + bst.insert(10, 'some data'); + bst.insert(3, 'hello'); + bst.insert(3, 'world'); + + bst.checkIsBST(); + bst.left.key.should.equal(3); + _.isEqual(bst.left.data, ['hello', 'world']).should.equal(true); + + bst.insert(12, 'a'); + bst.insert(12, 'b'); + + bst.checkIsBST(); + bst.right.key.should.equal(12); + _.isEqual(bst.right.data, ['a', 'b']).should.equal(true); + }); + + it('If uniqueness constraint is enforced, we cannot insert different data for same key', function () { + var bst = new BinarySearchTree({ unique: true }); + + bst.insert(10, 'some data'); + bst.insert(3, 'hello'); + try { + bst.insert(3, 'world'); + } catch (e) { + e.errorType.should.equal('uniqueViolated'); + e.key.should.equal(3); + } + + bst.checkIsBST(); + bst.left.key.should.equal(3); + _.isEqual(bst.left.data, ['hello']).should.equal(true); + + bst.insert(12, 'a'); + try { + bst.insert(12, 'world'); + } catch (e) { + e.errorType.should.equal('uniqueViolated'); + e.key.should.equal(12); + } + + bst.checkIsBST(); + bst.right.key.should.equal(12); + _.isEqual(bst.right.data, ['a']).should.equal(true); + }); + + it('Can insert 0 or the empty string', function () { + var bst = new BinarySearchTree(); + + bst.insert(0, 'some data'); + + bst.checkIsBST(); + bst.key.should.equal(0); + _.isEqual(bst.data, ['some data']).should.equal(true); + assert.isNull(bst.left); + assert.isNull(bst.right); + + bst = new BinarySearchTree(); + + bst.insert('', 'some other data'); + + bst.checkIsBST(); + bst.key.should.equal(''); + _.isEqual(bst.data, ['some other data']).should.equal(true); + assert.isNull(bst.left); + assert.isNull(bst.right); + }); + + it('Can insert a lot of keys and still get a BST (sanity check)', function () { + var bst = new BinarySearchTree({ unique: true }); + + customUtils.getRandomArray(100).forEach(function (n) { + bst.insert(n, 'some data'); + }); + + bst.checkIsBST(); + }); + + it('All children get a pointer to their parent, the root doesnt', function () { + var bst = new BinarySearchTree(); + + bst.insert(10, 'root'); + bst.insert(5, 'yes'); + bst.insert(15, 'no'); + + bst.checkIsBST(); + + assert.isNull(bst.parent); + bst.left.parent.should.equal(bst); + bst.right.parent.should.equal(bst); + }); + + }); // ==== End of 'Insertion' ==== // + + + describe('Search', function () { + + it('Can find data in a BST', function () { + var bst = new BinarySearchTree() + , i; + + customUtils.getRandomArray(100).forEach(function (n) { + bst.insert(n, 'some data for ' + n); + }); + + bst.checkIsBST(); + + for (i = 0; i < 100; i += 1) { + _.isEqual(bst.search(i), ['some data for ' + i]).should.equal(true); + } + }); + + it('If no data can be found, return an empty array', function () { + var bst = new BinarySearchTree(); + + customUtils.getRandomArray(100).forEach(function (n) { + if (n !== 63) { + bst.insert(n, 'some data for ' + n); + } + }); + + bst.checkIsBST(); + + bst.search(-2).length.should.equal(0); + bst.search(100).length.should.equal(0); + bst.search(101).length.should.equal(0); + bst.search(63).length.should.equal(0); + }); + + it('Can search for data between two bounds', function () { + var bst = new BinarySearchTree(); + + [10, 5, 15, 3, 8, 13, 18].forEach(function (k) { + bst.insert(k, 'data ' + k); + }); + + assert.deepEqual(bst.betweenBounds({ $gte: 8, $lte: 15 }), ['data 8', 'data 10', 'data 13', 'data 15']); + assert.deepEqual(bst.betweenBounds({ $gt: 8, $lt: 15 }), ['data 10', 'data 13']); + }); + + it('Bounded search can handle cases where query contains both $lt and $lte, or both $gt and $gte', function () { + var bst = new BinarySearchTree(); + + [10, 5, 15, 3, 8, 13, 18].forEach(function (k) { + bst.insert(k, 'data ' + k); + }); + + assert.deepEqual(bst.betweenBounds({ $gt:8, $gte: 8, $lte: 15 }), ['data 10', 'data 13', 'data 15']); + assert.deepEqual(bst.betweenBounds({ $gt:5, $gte: 8, $lte: 15 }), ['data 8', 'data 10', 'data 13', 'data 15']); + assert.deepEqual(bst.betweenBounds({ $gt:8, $gte: 5, $lte: 15 }), ['data 10', 'data 13', 'data 15']); + + assert.deepEqual(bst.betweenBounds({ $gte: 8, $lte: 15, $lt: 15 }), ['data 8', 'data 10', 'data 13']); + assert.deepEqual(bst.betweenBounds({ $gte: 8, $lte: 18, $lt: 15 }), ['data 8', 'data 10', 'data 13']); + assert.deepEqual(bst.betweenBounds({ $gte: 8, $lte: 15, $lt: 18 }), ['data 8', 'data 10', 'data 13', 'data 15']); + }); + + it('Bounded search can work when one or both boundaries are missing', function () { + var bst = new BinarySearchTree(); + + [10, 5, 15, 3, 8, 13, 18].forEach(function (k) { + bst.insert(k, 'data ' + k); + }); + + assert.deepEqual(bst.betweenBounds({ $gte: 11 }), ['data 13', 'data 15', 'data 18']); + assert.deepEqual(bst.betweenBounds({ $lte: 9 }), ['data 3', 'data 5', 'data 8']); + }); + + }); /// ==== End of 'Search' ==== // + + + describe('Deletion', function () { + + it('Deletion does nothing on an empty tree', function () { + var bst = new BinarySearchTree() + , bstu = new BinarySearchTree({ unique: true }); + + bst.getNumberOfKeys().should.equal(0); + bstu.getNumberOfKeys().should.equal(0); + + bst.delete(5); + bstu.delete(5); + + bst.hasOwnProperty('key').should.equal(false); + bstu.hasOwnProperty('key').should.equal(false); + + bst.data.length.should.equal(0); + bstu.data.length.should.equal(0); + + bst.getNumberOfKeys().should.equal(0); + bstu.getNumberOfKeys().should.equal(0); + }); + + it('Deleting a non-existent key doesnt have any effect', function () { + var bst = new BinarySearchTree(); + + [10, 5, 3, 8, 15, 12, 37].forEach(function (k) { + bst.insert(k, 'some ' + k); + }); + + function checkBst () { + [10, 5, 3, 8, 15, 12, 37].forEach(function (k) { + _.isEqual(bst.search(k), ['some ' + k]).should.equal(true); + }); + } + + checkBst(); + bst.getNumberOfKeys().should.equal(7); + + bst.delete(2); + checkBst(); bst.checkIsBST(); bst.getNumberOfKeys().should.equal(7); + bst.delete(4); + checkBst(); bst.checkIsBST(); bst.getNumberOfKeys().should.equal(7); + bst.delete(9); + checkBst(); bst.checkIsBST(); bst.getNumberOfKeys().should.equal(7); + bst.delete(6); + checkBst(); bst.checkIsBST(); bst.getNumberOfKeys().should.equal(7); + bst.delete(11); + checkBst(); bst.checkIsBST(); bst.getNumberOfKeys().should.equal(7); + bst.delete(14); + checkBst(); bst.checkIsBST(); bst.getNumberOfKeys().should.equal(7); + bst.delete(20); + checkBst(); bst.checkIsBST(); bst.getNumberOfKeys().should.equal(7); + bst.delete(200); + checkBst(); bst.checkIsBST(); bst.getNumberOfKeys().should.equal(7); + }); + + it('Able to delete the root if it is also a leaf', function () { + var bst = new BinarySearchTree(); + + bst.insert(10, 'hello'); + bst.key.should.equal(10); + _.isEqual(bst.data, ['hello']).should.equal(true); + bst.getNumberOfKeys().should.equal(1); + + bst.delete(10); + bst.hasOwnProperty('key').should.equal(false); + bst.data.length.should.equal(0); + bst.getNumberOfKeys().should.equal(0); + }); + + it('Able to delete leaf nodes that are non-root', function () { + var bst; + + function recreateBst () { + bst = new BinarySearchTree(); + + // With this insertion order the tree is well balanced + // So we know the leaves are 3, 8, 12, 37 + [10, 5, 3, 8, 15, 12, 37].forEach(function (k) { + bst.insert(k, 'some ' + k); + }); + + bst.getNumberOfKeys().should.equal(7); + } + + function checkOnlyOneWasRemoved (theRemoved) { + [10, 5, 3, 8, 15, 12, 37].forEach(function (k) { + if (k === theRemoved) { + bst.search(k).length.should.equal(0); + } else { + _.isEqual(bst.search(k), ['some ' + k]).should.equal(true); + } + }); + + bst.getNumberOfKeys().should.equal(6); + } + + recreateBst(); + bst.delete(3); + bst.checkIsBST(); + checkOnlyOneWasRemoved(3); + assert.isNull(bst.left.left); + + recreateBst(); + bst.delete(8); + bst.checkIsBST(); + checkOnlyOneWasRemoved(8); + assert.isNull(bst.left.right); + + recreateBst(); + bst.delete(12); + bst.checkIsBST(); + checkOnlyOneWasRemoved(12); + assert.isNull(bst.right.left); + + recreateBst(); + bst.delete(37); + bst.checkIsBST(); + checkOnlyOneWasRemoved(37); + assert.isNull(bst.right.right); + }); + + it('Able to delete the root if it has only one child', function () { + var bst; + + // Root has only one child, on the left + bst = new BinarySearchTree(); + [10, 5, 3, 6].forEach(function (k) { + bst.insert(k, 'some ' + k); + }); + bst.getNumberOfKeys().should.equal(4); + bst.delete(10); + bst.checkIsBST(); + bst.getNumberOfKeys().should.equal(3); + [5, 3, 6].forEach(function (k) { + _.isEqual(bst.search(k), ['some ' + k]).should.equal(true); + }); + bst.search(10).length.should.equal(0); + + // Root has only one child, on the right + bst = new BinarySearchTree(); + [10, 15, 13, 16].forEach(function (k) { + bst.insert(k, 'some ' + k); + }); + bst.getNumberOfKeys().should.equal(4); + bst.delete(10); + bst.checkIsBST(); + bst.getNumberOfKeys().should.equal(3); + [15, 13, 16].forEach(function (k) { + _.isEqual(bst.search(k), ['some ' + k]).should.equal(true); + }); + bst.search(10).length.should.equal(0); + }); + + it('Able to delete non root nodes that have only one child', function () { + var bst; + + function recreateBst () { + bst = new BinarySearchTree(); + + [10, 5, 15, 3, 1, 4, 20, 17, 25].forEach(function (k) { + bst.insert(k, 'some ' + k); + }); + + bst.getNumberOfKeys().should.equal(9); + } + + function checkOnlyOneWasRemoved (theRemoved) { + [10, 5, 15, 3, 1, 4, 20, 17, 25].forEach(function (k) { + if (k === theRemoved) { + bst.search(k).length.should.equal(0); + } else { + _.isEqual(bst.search(k), ['some ' + k]).should.equal(true); + } + }); + + bst.getNumberOfKeys().should.equal(8); + } + + recreateBst(); + bst.delete(5); + bst.checkIsBST(); + checkOnlyOneWasRemoved(5); + + recreateBst(); + bst.delete(15); + bst.checkIsBST(); + checkOnlyOneWasRemoved(15); + }); + + it('Can delete the root if it has 2 children', function () { + var bst; + + bst = new BinarySearchTree(); + [10, 5, 3, 8, 15, 12, 37].forEach(function (k) { + bst.insert(k, 'some ' + k); + }); + bst.getNumberOfKeys().should.equal(7); + bst.delete(10); + bst.checkIsBST(); + bst.getNumberOfKeys().should.equal(6); + [5, 3, 8, 15, 12, 37].forEach(function (k) { + _.isEqual(bst.search(k), ['some ' + k]).should.equal(true); + }); + bst.search(10).length.should.equal(0); + }); + + it('Can delete a non-root node that has two children', function () { + var bst; + + bst = new BinarySearchTree(); + [10, 5, 3, 1, 4, 8, 6, 9, 15, 12, 11, 13, 20, 19, 42].forEach(function (k) { + bst.insert(k, 'some ' + k); + }); + bst.getNumberOfKeys().should.equal(15); + bst.delete(5); + bst.checkIsBST(); + bst.getNumberOfKeys().should.equal(14); + [10, 3, 1, 4, 8, 6, 9, 15, 12, 11, 13, 20, 19, 42].forEach(function (k) { + _.isEqual(bst.search(k), ['some ' + k]).should.equal(true); + }); + bst.search(5).length.should.equal(0); + + bst = new BinarySearchTree(); + [10, 5, 3, 1, 4, 8, 6, 9, 15, 12, 11, 13, 20, 19, 42].forEach(function (k) { + bst.insert(k, 'some ' + k); + }); + bst.getNumberOfKeys().should.equal(15); + bst.delete(15); + bst.checkIsBST(); + bst.getNumberOfKeys().should.equal(14); + [10, 5, 3, 1, 4, 8, 6, 9, 12, 11, 13, 20, 19, 42].forEach(function (k) { + _.isEqual(bst.search(k), ['some ' + k]).should.equal(true); + }); + bst.search(15).length.should.equal(0); + }); + + it('If no value is provided, it will delete the entire node even if there are multiple pieces of data', function () { + var bst = new BinarySearchTree(); + + bst.insert(10, 'yes'); + bst.insert(5, 'hello'); + bst.insert(3, 'yes'); + bst.insert(5, 'world'); + bst.insert(8, 'yes'); + + assert.deepEqual(bst.search(5), ['hello', 'world']); + bst.getNumberOfKeys().should.equal(4); + + bst.delete(5); + bst.search(5).length.should.equal(0); + bst.getNumberOfKeys().should.equal(3); + }); + + it('Can remove only one value from an array', function () { + var bst = new BinarySearchTree(); + + bst.insert(10, 'yes'); + bst.insert(5, 'hello'); + bst.insert(3, 'yes'); + bst.insert(5, 'world'); + bst.insert(8, 'yes'); + + assert.deepEqual(bst.search(5), ['hello', 'world']); + bst.getNumberOfKeys().should.equal(4); + + bst.delete(5, 'hello'); + assert.deepEqual(bst.search(5), ['world']); + bst.getNumberOfKeys().should.equal(4); + }); + + it('Removes nothing if value doesnt match', function () { + var bst = new BinarySearchTree(); + + bst.insert(10, 'yes'); + bst.insert(5, 'hello'); + bst.insert(3, 'yes'); + bst.insert(5, 'world'); + bst.insert(8, 'yes'); + + assert.deepEqual(bst.search(5), ['hello', 'world']); + bst.getNumberOfKeys().should.equal(4); + + bst.delete(5, 'nope'); + assert.deepEqual(bst.search(5), ['hello', 'world']); + bst.getNumberOfKeys().should.equal(4); + }); + + it('If value provided but node contains only one value, remove entire node', function () { + var bst = new BinarySearchTree(); + + bst.insert(10, 'yes'); + bst.insert(5, 'hello'); + bst.insert(3, 'yes2'); + bst.insert(5, 'world'); + bst.insert(8, 'yes3'); + + assert.deepEqual(bst.search(3), ['yes2']); + bst.getNumberOfKeys().should.equal(4); + + bst.delete(3, 'yes2'); + bst.search(3).length.should.equal(0); + bst.getNumberOfKeys().should.equal(3); + }); + + it('Can remove the root from a tree with height 2 when the root has two children (special case)', function () { + var bst = new BinarySearchTree(); + + bst.insert(10, 'maybe'); + bst.insert(5, 'no'); + bst.insert(15, 'yes'); + bst.getNumberOfKeys().should.equal(3); + + bst.delete(10); + bst.checkIsBST(); + bst.getNumberOfKeys().should.equal(2); + assert.deepEqual(bst.search(5), ['no']); + assert.deepEqual(bst.search(15), ['yes']); + }); + + it('Can remove the root from a tree with height 3 when the root has two children (special case where the two children themselves have children)', function () { + var bst = new BinarySearchTree(); + + bst.insert(10, 'maybe'); + bst.insert(5, 'no'); + bst.insert(15, 'yes'); + bst.insert(2, 'no'); + bst.insert(35, 'yes'); + bst.getNumberOfKeys().should.equal(5); + + bst.delete(10); + bst.checkIsBST(); + bst.getNumberOfKeys().should.equal(4); + assert.deepEqual(bst.search(5), ['no']); + assert.deepEqual(bst.search(15), ['yes']); + }); + + }); // ==== End of 'Deletion' ==== // + + + it('Can use undefined as key and value', function () { + function compareKeys (a, b) { + if (a === undefined && b === undefined) { return 0; } + if (a === undefined) { return -1; } + if (b === undefined) { return 1; } + + if (a < b) { return -1; } + if (a > b) { return 1; } + if (a === b) { return 0; } + } + + var bst = new BinarySearchTree({ compareKeys: compareKeys }); + + bst.insert(2, undefined); + bst.checkIsBST(); + bst.getNumberOfKeys().should.equal(1); + assert.deepEqual(bst.search(2), [undefined]); + assert.deepEqual(bst.search(undefined), []); + + bst.insert(undefined, 'hello'); + bst.checkIsBST(); + bst.getNumberOfKeys().should.equal(2); + assert.deepEqual(bst.search(2), [undefined]); + assert.deepEqual(bst.search(undefined), ['hello']); + + bst.insert(undefined, 'world'); + bst.checkIsBST(); + bst.getNumberOfKeys().should.equal(2); + assert.deepEqual(bst.search(2), [undefined]); + assert.deepEqual(bst.search(undefined), ['hello', 'world']); + + bst.insert(4, undefined); + bst.checkIsBST(); + bst.getNumberOfKeys().should.equal(3); + assert.deepEqual(bst.search(2), [undefined]); + assert.deepEqual(bst.search(4), [undefined]); + assert.deepEqual(bst.search(undefined), ['hello', 'world']); + + bst.delete(undefined, 'hello'); + bst.checkIsBST(); + bst.getNumberOfKeys().should.equal(3); + assert.deepEqual(bst.search(2), [undefined]); + assert.deepEqual(bst.search(4), [undefined]); + assert.deepEqual(bst.search(undefined), ['world']); + + bst.delete(undefined); + bst.checkIsBST(); + bst.getNumberOfKeys().should.equal(2); + assert.deepEqual(bst.search(2), [undefined]); + assert.deepEqual(bst.search(4), [undefined]); + assert.deepEqual(bst.search(undefined), []); + + bst.delete(2, undefined); + bst.checkIsBST(); + bst.getNumberOfKeys().should.equal(1); + assert.deepEqual(bst.search(2), []); + assert.deepEqual(bst.search(4), [undefined]); + assert.deepEqual(bst.search(undefined), []); + + bst.delete(4); + bst.checkIsBST(); + bst.getNumberOfKeys().should.equal(0); + assert.deepEqual(bst.search(2), []); + assert.deepEqual(bst.search(4), []); + assert.deepEqual(bst.search(undefined), []); + }); + + + it('Can use null as key and value', function () { + function compareKeys (a, b) { + if (a === null && b === null) { return 0; } + if (a === null) { return -1; } + if (b === null) { return 1; } + + if (a < b) { return -1; } + if (a > b) { return 1; } + if (a === b) { return 0; } + } + + var bst = new BinarySearchTree({ compareKeys: compareKeys }); + + bst.insert(2, null); + bst.checkIsBST(); + bst.getNumberOfKeys().should.equal(1); + assert.deepEqual(bst.search(2), [null]); + assert.deepEqual(bst.search(null), []); + + bst.insert(null, 'hello'); + bst.checkIsBST(); + bst.getNumberOfKeys().should.equal(2); + assert.deepEqual(bst.search(2), [null]); + assert.deepEqual(bst.search(null), ['hello']); + + bst.insert(null, 'world'); + bst.checkIsBST(); + bst.getNumberOfKeys().should.equal(2); + assert.deepEqual(bst.search(2), [null]); + assert.deepEqual(bst.search(null), ['hello', 'world']); + + bst.insert(4, null); + bst.checkIsBST(); + bst.getNumberOfKeys().should.equal(3); + assert.deepEqual(bst.search(2), [null]); + assert.deepEqual(bst.search(4), [null]); + assert.deepEqual(bst.search(null), ['hello', 'world']); + + bst.delete(null, 'hello'); + bst.checkIsBST(); + bst.getNumberOfKeys().should.equal(3); + assert.deepEqual(bst.search(2), [null]); + assert.deepEqual(bst.search(4), [null]); + assert.deepEqual(bst.search(null), ['world']); + + bst.delete(null); + bst.checkIsBST(); + bst.getNumberOfKeys().should.equal(2); + assert.deepEqual(bst.search(2), [null]); + assert.deepEqual(bst.search(4), [null]); + assert.deepEqual(bst.search(null), []); + + bst.delete(2, null); + bst.checkIsBST(); + bst.getNumberOfKeys().should.equal(1); + assert.deepEqual(bst.search(2), []); + assert.deepEqual(bst.search(4), [null]); + assert.deepEqual(bst.search(null), []); + + bst.delete(4); + bst.checkIsBST(); + bst.getNumberOfKeys().should.equal(0); + assert.deepEqual(bst.search(2), []); + assert.deepEqual(bst.search(4), []); + assert.deepEqual(bst.search(null), []); + }); + + + describe('Execute on every node (=tree traversal)', function () { + + it('Can execute a function on every node', function () { + var bst = new BinarySearchTree() + , keys = [] + , executed = 0 + ; + + bst.insert(10, 'yes'); + bst.insert(5, 'hello'); + bst.insert(3, 'yes2'); + bst.insert(8, 'yes3'); + bst.insert(15, 'yes3'); + bst.insert(159, 'yes3'); + bst.insert(11, 'yes3'); + + bst.executeOnEveryNode(function (node) { + keys.push(node.key); + executed += 1; + }); + + assert.deepEqual(keys, [3, 5, 8, 10, 11, 15, 159]); + executed.should.equal(7); + }); + + }); // ==== End of 'Execute on every node' ==== // + + + // This test performs several inserts and deletes at random, always checking the content + // of the tree are as expected and the binary search tree constraint is respected + // This test is important because it can catch bugs other tests can't + // By their nature, BSTs can be hard to test (many possible cases, bug at one operation whose + // effect begins to be felt only after several operations etc.) + describe('Randomized test (takes much longer than the rest of the test suite)', function () { + var bst = new BinarySearchTree() + , data = {}; + + // Check a bst against a simple key => [data] object + function checkDataIsTheSame (bst, data) { + var bstDataElems = []; + + // bstDataElems is a simple array containing every piece of data in the tree + bst.executeOnEveryNode(function (node) { + var i; + for (i = 0; i < node.data.length; i += 1) { + bstDataElems.push(node.data[i]); + } + }); + + // Number of key and number of pieces of data match + bst.getNumberOfKeys().should.equal(Object.keys(data).length); + _.reduce(_.map(data, function (d) { return d.length; }), function (memo, n) { return memo + n; }, 0).should.equal(bstDataElems.length); + + // Compare data + Object.keys(data).forEach(function (key) { + checkDataEquality(bst.search(key), data[key]); + }); + } + + // Check two pieces of data coming from the bst and data are the same + function checkDataEquality (fromBst, fromData) { + if (fromBst.length === 0) { + if (fromData) { fromData.length.should.equal(0); } + } + + assert.deepEqual(fromBst, fromData); + } + + // Tests the tree structure (deletions concern the whole tree, deletion of some data in a node is well tested above) + it('Inserting and deleting entire nodes', function () { + // You can skew to be more insertive or deletive, to test all cases + function launchRandomTest (nTests, proba) { + var i, key, dataPiece, possibleKeys; + + for (i = 0; i < nTests; i += 1) { + if (Math.random() > proba) { // Deletion + possibleKeys = Object.keys(data); + + if (possibleKeys.length > 0) { + key = possibleKeys[Math.floor(possibleKeys.length * Math.random()).toString()]; + } else { + key = Math.floor(70 * Math.random()).toString(); + } + + delete data[key]; + bst.delete(key); + } else { // Insertion + key = Math.floor(70 * Math.random()).toString(); + dataPiece = Math.random().toString().substring(0, 6); + bst.insert(key, dataPiece); + if (data[key]) { + data[key].push(dataPiece); + } else { + data[key] = [dataPiece]; + } + } + + // Check the bst constraint are still met and the data is correct + bst.checkIsBST(); + checkDataIsTheSame(bst, data); + } + } + + launchRandomTest(1000, 0.65); + launchRandomTest(2000, 0.35); + }); + + }); // ==== End of 'Randomized test' ==== // + + + +}); diff --git a/src/node_modules/nedb/node_modules/mkdirp/.npmignore b/src/node_modules/nedb/node_modules/mkdirp/.npmignore new file mode 100644 index 0000000..9303c34 --- /dev/null +++ b/src/node_modules/nedb/node_modules/mkdirp/.npmignore @@ -0,0 +1,2 @@ +node_modules/ +npm-debug.log \ No newline at end of file diff --git a/src/node_modules/nedb/node_modules/mkdirp/.travis.yml b/src/node_modules/nedb/node_modules/mkdirp/.travis.yml new file mode 100644 index 0000000..84fd7ca --- /dev/null +++ b/src/node_modules/nedb/node_modules/mkdirp/.travis.yml @@ -0,0 +1,5 @@ +language: node_js +node_js: + - 0.6 + - 0.8 + - 0.9 diff --git a/src/node_modules/nedb/node_modules/mkdirp/LICENSE b/src/node_modules/nedb/node_modules/mkdirp/LICENSE new file mode 100644 index 0000000..432d1ae --- /dev/null +++ b/src/node_modules/nedb/node_modules/mkdirp/LICENSE @@ -0,0 +1,21 @@ +Copyright 2010 James Halliday (mail@substack.net) + +This project is free software released under the MIT/X11 license: + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/src/node_modules/nedb/node_modules/mkdirp/examples/pow.js b/src/node_modules/nedb/node_modules/mkdirp/examples/pow.js new file mode 100644 index 0000000..e692421 --- /dev/null +++ b/src/node_modules/nedb/node_modules/mkdirp/examples/pow.js @@ -0,0 +1,6 @@ +var mkdirp = require('mkdirp'); + +mkdirp('/tmp/foo/bar/baz', function (err) { + if (err) console.error(err) + else console.log('pow!') +}); diff --git a/src/node_modules/nedb/node_modules/mkdirp/index.js b/src/node_modules/nedb/node_modules/mkdirp/index.js new file mode 100644 index 0000000..fda6de8 --- /dev/null +++ b/src/node_modules/nedb/node_modules/mkdirp/index.js @@ -0,0 +1,82 @@ +var path = require('path'); +var fs = require('fs'); + +module.exports = mkdirP.mkdirp = mkdirP.mkdirP = mkdirP; + +function mkdirP (p, mode, f, made) { + if (typeof mode === 'function' || mode === undefined) { + f = mode; + mode = 0777 & (~process.umask()); + } + if (!made) made = null; + + var cb = f || function () {}; + if (typeof mode === 'string') mode = parseInt(mode, 8); + p = path.resolve(p); + + fs.mkdir(p, mode, function (er) { + if (!er) { + made = made || p; + return cb(null, made); + } + switch (er.code) { + case 'ENOENT': + mkdirP(path.dirname(p), mode, function (er, made) { + if (er) cb(er, made); + else mkdirP(p, mode, cb, made); + }); + break; + + // In the case of any other error, just see if there's a dir + // there already. If so, then hooray! If not, then something + // is borked. + default: + fs.stat(p, function (er2, stat) { + // if the stat fails, then that's super weird. + // let the original error be the failure reason. + if (er2 || !stat.isDirectory()) cb(er, made) + else cb(null, made); + }); + break; + } + }); +} + +mkdirP.sync = function sync (p, mode, made) { + if (mode === undefined) { + mode = 0777 & (~process.umask()); + } + if (!made) made = null; + + if (typeof mode === 'string') mode = parseInt(mode, 8); + p = path.resolve(p); + + try { + fs.mkdirSync(p, mode); + made = made || p; + } + catch (err0) { + switch (err0.code) { + case 'ENOENT' : + made = sync(path.dirname(p), mode, made); + sync(p, mode, made); + break; + + // In the case of any other error, just see if there's a dir + // there already. If so, then hooray! If not, then something + // is borked. + default: + var stat; + try { + stat = fs.statSync(p); + } + catch (err1) { + throw err0; + } + if (!stat.isDirectory()) throw err0; + break; + } + } + + return made; +}; diff --git a/src/node_modules/nedb/node_modules/mkdirp/package.json b/src/node_modules/nedb/node_modules/mkdirp/package.json new file mode 100644 index 0000000..0c9f5bf --- /dev/null +++ b/src/node_modules/nedb/node_modules/mkdirp/package.json @@ -0,0 +1,37 @@ +{ + "name": "mkdirp", + "description": "Recursively mkdir, like `mkdir -p`", + "version": "0.3.5", + "author": { + "name": "James Halliday", + "email": "mail@substack.net", + "url": "http://substack.net" + }, + "main": "./index", + "keywords": [ + "mkdir", + "directory" + ], + "repository": { + "type": "git", + "url": "http://github.com/substack/node-mkdirp.git" + }, + "scripts": { + "test": "tap test/*.js" + }, + "devDependencies": { + "tap": "~0.4.0" + }, + "license": "MIT", + "readme": "# mkdirp\n\nLike `mkdir -p`, but in node.js!\n\n[![build status](https://secure.travis-ci.org/substack/node-mkdirp.png)](http://travis-ci.org/substack/node-mkdirp)\n\n# example\n\n## pow.js\n\n```js\nvar mkdirp = require('mkdirp');\n \nmkdirp('/tmp/foo/bar/baz', function (err) {\n if (err) console.error(err)\n else console.log('pow!')\n});\n```\n\nOutput\n\n```\npow!\n```\n\nAnd now /tmp/foo/bar/baz exists, huzzah!\n\n# methods\n\n```js\nvar mkdirp = require('mkdirp');\n```\n\n## mkdirp(dir, mode, cb)\n\nCreate a new directory and any necessary subdirectories at `dir` with octal\npermission string `mode`.\n\nIf `mode` isn't specified, it defaults to `0777 & (~process.umask())`.\n\n`cb(err, made)` fires with the error or the first directory `made`\nthat had to be created, if any.\n\n## mkdirp.sync(dir, mode)\n\nSynchronously create a new directory and any necessary subdirectories at `dir`\nwith octal permission string `mode`.\n\nIf `mode` isn't specified, it defaults to `0777 & (~process.umask())`.\n\nReturns the first directory that had to be created, if any.\n\n# install\n\nWith [npm](http://npmjs.org) do:\n\n```\nnpm install mkdirp\n```\n\n# license\n\nMIT\n", + "readmeFilename": "readme.markdown", + "bugs": { + "url": "https://github.com/substack/node-mkdirp/issues" + }, + "_id": "mkdirp@0.3.5", + "dist": { + "shasum": "67e5c12bd18b5d68b5f56518ff1ea28068eecb4f" + }, + "_from": "mkdirp@~0.3.5", + "_resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz" +} diff --git a/src/node_modules/nedb/node_modules/mkdirp/readme.markdown b/src/node_modules/nedb/node_modules/mkdirp/readme.markdown new file mode 100644 index 0000000..83b0216 --- /dev/null +++ b/src/node_modules/nedb/node_modules/mkdirp/readme.markdown @@ -0,0 +1,63 @@ +# mkdirp + +Like `mkdir -p`, but in node.js! + +[![build status](https://secure.travis-ci.org/substack/node-mkdirp.png)](http://travis-ci.org/substack/node-mkdirp) + +# example + +## pow.js + +```js +var mkdirp = require('mkdirp'); + +mkdirp('/tmp/foo/bar/baz', function (err) { + if (err) console.error(err) + else console.log('pow!') +}); +``` + +Output + +``` +pow! +``` + +And now /tmp/foo/bar/baz exists, huzzah! + +# methods + +```js +var mkdirp = require('mkdirp'); +``` + +## mkdirp(dir, mode, cb) + +Create a new directory and any necessary subdirectories at `dir` with octal +permission string `mode`. + +If `mode` isn't specified, it defaults to `0777 & (~process.umask())`. + +`cb(err, made)` fires with the error or the first directory `made` +that had to be created, if any. + +## mkdirp.sync(dir, mode) + +Synchronously create a new directory and any necessary subdirectories at `dir` +with octal permission string `mode`. + +If `mode` isn't specified, it defaults to `0777 & (~process.umask())`. + +Returns the first directory that had to be created, if any. + +# install + +With [npm](http://npmjs.org) do: + +``` +npm install mkdirp +``` + +# license + +MIT diff --git a/src/node_modules/nedb/node_modules/mkdirp/test/chmod.js b/src/node_modules/nedb/node_modules/mkdirp/test/chmod.js new file mode 100644 index 0000000..520dcb8 --- /dev/null +++ b/src/node_modules/nedb/node_modules/mkdirp/test/chmod.js @@ -0,0 +1,38 @@ +var mkdirp = require('../').mkdirp; +var path = require('path'); +var fs = require('fs'); +var test = require('tap').test; + +var ps = [ '', 'tmp' ]; + +for (var i = 0; i < 25; i++) { + var dir = Math.floor(Math.random() * Math.pow(16,4)).toString(16); + ps.push(dir); +} + +var file = ps.join('/'); + +test('chmod-pre', function (t) { + var mode = 0744 + mkdirp(file, mode, function (er) { + t.ifError(er, 'should not error'); + fs.stat(file, function (er, stat) { + t.ifError(er, 'should exist'); + t.ok(stat && stat.isDirectory(), 'should be directory'); + t.equal(stat && stat.mode & 0777, mode, 'should be 0744'); + t.end(); + }); + }); +}); + +test('chmod', function (t) { + var mode = 0755 + mkdirp(file, mode, function (er) { + t.ifError(er, 'should not error'); + fs.stat(file, function (er, stat) { + t.ifError(er, 'should exist'); + t.ok(stat && stat.isDirectory(), 'should be directory'); + t.end(); + }); + }); +}); diff --git a/src/node_modules/nedb/node_modules/mkdirp/test/clobber.js b/src/node_modules/nedb/node_modules/mkdirp/test/clobber.js new file mode 100644 index 0000000..0eb7099 --- /dev/null +++ b/src/node_modules/nedb/node_modules/mkdirp/test/clobber.js @@ -0,0 +1,37 @@ +var mkdirp = require('../').mkdirp; +var path = require('path'); +var fs = require('fs'); +var test = require('tap').test; + +var ps = [ '', 'tmp' ]; + +for (var i = 0; i < 25; i++) { + var dir = Math.floor(Math.random() * Math.pow(16,4)).toString(16); + ps.push(dir); +} + +var file = ps.join('/'); + +// a file in the way +var itw = ps.slice(0, 3).join('/'); + + +test('clobber-pre', function (t) { + console.error("about to write to "+itw) + fs.writeFileSync(itw, 'I AM IN THE WAY, THE TRUTH, AND THE LIGHT.'); + + fs.stat(itw, function (er, stat) { + t.ifError(er) + t.ok(stat && stat.isFile(), 'should be file') + t.end() + }) +}) + +test('clobber', function (t) { + t.plan(2); + mkdirp(file, 0755, function (err) { + t.ok(err); + t.equal(err.code, 'ENOTDIR'); + t.end(); + }); +}); diff --git a/src/node_modules/nedb/node_modules/mkdirp/test/mkdirp.js b/src/node_modules/nedb/node_modules/mkdirp/test/mkdirp.js new file mode 100644 index 0000000..b07cd70 --- /dev/null +++ b/src/node_modules/nedb/node_modules/mkdirp/test/mkdirp.js @@ -0,0 +1,28 @@ +var mkdirp = require('../'); +var path = require('path'); +var fs = require('fs'); +var test = require('tap').test; + +test('woo', function (t) { + t.plan(2); + var x = Math.floor(Math.random() * Math.pow(16,4)).toString(16); + var y = Math.floor(Math.random() * Math.pow(16,4)).toString(16); + var z = Math.floor(Math.random() * Math.pow(16,4)).toString(16); + + var file = '/tmp/' + [x,y,z].join('/'); + + mkdirp(file, 0755, function (err) { + if (err) t.fail(err); + else path.exists(file, function (ex) { + if (!ex) t.fail('file not created') + else fs.stat(file, function (err, stat) { + if (err) t.fail(err) + else { + t.equal(stat.mode & 0777, 0755); + t.ok(stat.isDirectory(), 'target not a directory'); + t.end(); + } + }) + }) + }); +}); diff --git a/src/node_modules/nedb/node_modules/mkdirp/test/perm.js b/src/node_modules/nedb/node_modules/mkdirp/test/perm.js new file mode 100644 index 0000000..23a7abb --- /dev/null +++ b/src/node_modules/nedb/node_modules/mkdirp/test/perm.js @@ -0,0 +1,32 @@ +var mkdirp = require('../'); +var path = require('path'); +var fs = require('fs'); +var test = require('tap').test; + +test('async perm', function (t) { + t.plan(2); + var file = '/tmp/' + (Math.random() * (1<<30)).toString(16); + + mkdirp(file, 0755, function (err) { + if (err) t.fail(err); + else path.exists(file, function (ex) { + if (!ex) t.fail('file not created') + else fs.stat(file, function (err, stat) { + if (err) t.fail(err) + else { + t.equal(stat.mode & 0777, 0755); + t.ok(stat.isDirectory(), 'target not a directory'); + t.end(); + } + }) + }) + }); +}); + +test('async root perm', function (t) { + mkdirp('/tmp', 0755, function (err) { + if (err) t.fail(err); + t.end(); + }); + t.end(); +}); diff --git a/src/node_modules/nedb/node_modules/mkdirp/test/perm_sync.js b/src/node_modules/nedb/node_modules/mkdirp/test/perm_sync.js new file mode 100644 index 0000000..f685f60 --- /dev/null +++ b/src/node_modules/nedb/node_modules/mkdirp/test/perm_sync.js @@ -0,0 +1,39 @@ +var mkdirp = require('../'); +var path = require('path'); +var fs = require('fs'); +var test = require('tap').test; + +test('sync perm', function (t) { + t.plan(2); + var file = '/tmp/' + (Math.random() * (1<<30)).toString(16) + '.json'; + + mkdirp.sync(file, 0755); + path.exists(file, function (ex) { + if (!ex) t.fail('file not created') + else fs.stat(file, function (err, stat) { + if (err) t.fail(err) + else { + t.equal(stat.mode & 0777, 0755); + t.ok(stat.isDirectory(), 'target not a directory'); + t.end(); + } + }) + }); +}); + +test('sync root perm', function (t) { + t.plan(1); + + var file = '/tmp'; + mkdirp.sync(file, 0755); + path.exists(file, function (ex) { + if (!ex) t.fail('file not created') + else fs.stat(file, function (err, stat) { + if (err) t.fail(err) + else { + t.ok(stat.isDirectory(), 'target not a directory'); + t.end(); + } + }) + }); +}); diff --git a/src/node_modules/nedb/node_modules/mkdirp/test/race.js b/src/node_modules/nedb/node_modules/mkdirp/test/race.js new file mode 100644 index 0000000..96a0447 --- /dev/null +++ b/src/node_modules/nedb/node_modules/mkdirp/test/race.js @@ -0,0 +1,41 @@ +var mkdirp = require('../').mkdirp; +var path = require('path'); +var fs = require('fs'); +var test = require('tap').test; + +test('race', function (t) { + t.plan(4); + var ps = [ '', 'tmp' ]; + + for (var i = 0; i < 25; i++) { + var dir = Math.floor(Math.random() * Math.pow(16,4)).toString(16); + ps.push(dir); + } + var file = ps.join('/'); + + var res = 2; + mk(file, function () { + if (--res === 0) t.end(); + }); + + mk(file, function () { + if (--res === 0) t.end(); + }); + + function mk (file, cb) { + mkdirp(file, 0755, function (err) { + if (err) t.fail(err); + else path.exists(file, function (ex) { + if (!ex) t.fail('file not created') + else fs.stat(file, function (err, stat) { + if (err) t.fail(err) + else { + t.equal(stat.mode & 0777, 0755); + t.ok(stat.isDirectory(), 'target not a directory'); + if (cb) cb(); + } + }) + }) + }); + } +}); diff --git a/src/node_modules/nedb/node_modules/mkdirp/test/rel.js b/src/node_modules/nedb/node_modules/mkdirp/test/rel.js new file mode 100644 index 0000000..7985824 --- /dev/null +++ b/src/node_modules/nedb/node_modules/mkdirp/test/rel.js @@ -0,0 +1,32 @@ +var mkdirp = require('../'); +var path = require('path'); +var fs = require('fs'); +var test = require('tap').test; + +test('rel', function (t) { + t.plan(2); + var x = Math.floor(Math.random() * Math.pow(16,4)).toString(16); + var y = Math.floor(Math.random() * Math.pow(16,4)).toString(16); + var z = Math.floor(Math.random() * Math.pow(16,4)).toString(16); + + var cwd = process.cwd(); + process.chdir('/tmp'); + + var file = [x,y,z].join('/'); + + mkdirp(file, 0755, function (err) { + if (err) t.fail(err); + else path.exists(file, function (ex) { + if (!ex) t.fail('file not created') + else fs.stat(file, function (err, stat) { + if (err) t.fail(err) + else { + process.chdir(cwd); + t.equal(stat.mode & 0777, 0755); + t.ok(stat.isDirectory(), 'target not a directory'); + t.end(); + } + }) + }) + }); +}); diff --git a/src/node_modules/nedb/node_modules/mkdirp/test/return.js b/src/node_modules/nedb/node_modules/mkdirp/test/return.js new file mode 100644 index 0000000..bce68e5 --- /dev/null +++ b/src/node_modules/nedb/node_modules/mkdirp/test/return.js @@ -0,0 +1,25 @@ +var mkdirp = require('../'); +var path = require('path'); +var fs = require('fs'); +var test = require('tap').test; + +test('return value', function (t) { + t.plan(4); + var x = Math.floor(Math.random() * Math.pow(16,4)).toString(16); + var y = Math.floor(Math.random() * Math.pow(16,4)).toString(16); + var z = Math.floor(Math.random() * Math.pow(16,4)).toString(16); + + var file = '/tmp/' + [x,y,z].join('/'); + + // should return the first dir created. + // By this point, it would be profoundly surprising if /tmp didn't + // already exist, since every other test makes things in there. + mkdirp(file, function (err, made) { + t.ifError(err); + t.equal(made, '/tmp/' + x); + mkdirp(file, function (err, made) { + t.ifError(err); + t.equal(made, null); + }); + }); +}); diff --git a/src/node_modules/nedb/node_modules/mkdirp/test/return_sync.js b/src/node_modules/nedb/node_modules/mkdirp/test/return_sync.js new file mode 100644 index 0000000..7c222d3 --- /dev/null +++ b/src/node_modules/nedb/node_modules/mkdirp/test/return_sync.js @@ -0,0 +1,24 @@ +var mkdirp = require('../'); +var path = require('path'); +var fs = require('fs'); +var test = require('tap').test; + +test('return value', function (t) { + t.plan(2); + var x = Math.floor(Math.random() * Math.pow(16,4)).toString(16); + var y = Math.floor(Math.random() * Math.pow(16,4)).toString(16); + var z = Math.floor(Math.random() * Math.pow(16,4)).toString(16); + + var file = '/tmp/' + [x,y,z].join('/'); + + // should return the first dir created. + // By this point, it would be profoundly surprising if /tmp didn't + // already exist, since every other test makes things in there. + // Note that this will throw on failure, which will fail the test. + var made = mkdirp.sync(file); + t.equal(made, '/tmp/' + x); + + // making the same file again should have no effect. + made = mkdirp.sync(file); + t.equal(made, null); +}); diff --git a/src/node_modules/nedb/node_modules/mkdirp/test/root.js b/src/node_modules/nedb/node_modules/mkdirp/test/root.js new file mode 100644 index 0000000..97ad7a2 --- /dev/null +++ b/src/node_modules/nedb/node_modules/mkdirp/test/root.js @@ -0,0 +1,18 @@ +var mkdirp = require('../'); +var path = require('path'); +var fs = require('fs'); +var test = require('tap').test; + +test('root', function (t) { + // '/' on unix, 'c:/' on windows. + var file = path.resolve('/'); + + mkdirp(file, 0755, function (err) { + if (err) throw err + fs.stat(file, function (er, stat) { + if (er) throw er + t.ok(stat.isDirectory(), 'target is a directory'); + t.end(); + }) + }); +}); diff --git a/src/node_modules/nedb/node_modules/mkdirp/test/sync.js b/src/node_modules/nedb/node_modules/mkdirp/test/sync.js new file mode 100644 index 0000000..7530cad --- /dev/null +++ b/src/node_modules/nedb/node_modules/mkdirp/test/sync.js @@ -0,0 +1,32 @@ +var mkdirp = require('../'); +var path = require('path'); +var fs = require('fs'); +var test = require('tap').test; + +test('sync', function (t) { + t.plan(2); + var x = Math.floor(Math.random() * Math.pow(16,4)).toString(16); + var y = Math.floor(Math.random() * Math.pow(16,4)).toString(16); + var z = Math.floor(Math.random() * Math.pow(16,4)).toString(16); + + var file = '/tmp/' + [x,y,z].join('/'); + + try { + mkdirp.sync(file, 0755); + } catch (err) { + t.fail(err); + return t.end(); + } + + path.exists(file, function (ex) { + if (!ex) t.fail('file not created') + else fs.stat(file, function (err, stat) { + if (err) t.fail(err) + else { + t.equal(stat.mode & 0777, 0755); + t.ok(stat.isDirectory(), 'target not a directory'); + t.end(); + } + }); + }); +}); diff --git a/src/node_modules/nedb/node_modules/mkdirp/test/umask.js b/src/node_modules/nedb/node_modules/mkdirp/test/umask.js new file mode 100644 index 0000000..64ccafe --- /dev/null +++ b/src/node_modules/nedb/node_modules/mkdirp/test/umask.js @@ -0,0 +1,28 @@ +var mkdirp = require('../'); +var path = require('path'); +var fs = require('fs'); +var test = require('tap').test; + +test('implicit mode from umask', function (t) { + t.plan(2); + var x = Math.floor(Math.random() * Math.pow(16,4)).toString(16); + var y = Math.floor(Math.random() * Math.pow(16,4)).toString(16); + var z = Math.floor(Math.random() * Math.pow(16,4)).toString(16); + + var file = '/tmp/' + [x,y,z].join('/'); + + mkdirp(file, function (err) { + if (err) t.fail(err); + else path.exists(file, function (ex) { + if (!ex) t.fail('file not created') + else fs.stat(file, function (err, stat) { + if (err) t.fail(err) + else { + t.equal(stat.mode & 0777, 0777 & (~process.umask())); + t.ok(stat.isDirectory(), 'target not a directory'); + t.end(); + } + }) + }) + }); +}); diff --git a/src/node_modules/nedb/node_modules/mkdirp/test/umask_sync.js b/src/node_modules/nedb/node_modules/mkdirp/test/umask_sync.js new file mode 100644 index 0000000..35bd5cb --- /dev/null +++ b/src/node_modules/nedb/node_modules/mkdirp/test/umask_sync.js @@ -0,0 +1,32 @@ +var mkdirp = require('../'); +var path = require('path'); +var fs = require('fs'); +var test = require('tap').test; + +test('umask sync modes', function (t) { + t.plan(2); + var x = Math.floor(Math.random() * Math.pow(16,4)).toString(16); + var y = Math.floor(Math.random() * Math.pow(16,4)).toString(16); + var z = Math.floor(Math.random() * Math.pow(16,4)).toString(16); + + var file = '/tmp/' + [x,y,z].join('/'); + + try { + mkdirp.sync(file); + } catch (err) { + t.fail(err); + return t.end(); + } + + path.exists(file, function (ex) { + if (!ex) t.fail('file not created') + else fs.stat(file, function (err, stat) { + if (err) t.fail(err) + else { + t.equal(stat.mode & 0777, (0777 & (~process.umask()))); + t.ok(stat.isDirectory(), 'target not a directory'); + t.end(); + } + }); + }); +}); diff --git a/src/node_modules/nedb/node_modules/underscore/.npmignore b/src/node_modules/nedb/node_modules/underscore/.npmignore new file mode 100644 index 0000000..4e5886d --- /dev/null +++ b/src/node_modules/nedb/node_modules/underscore/.npmignore @@ -0,0 +1,4 @@ +test/ +Rakefile +docs/ +raw/ diff --git a/src/node_modules/nedb/node_modules/underscore/.travis.yml b/src/node_modules/nedb/node_modules/underscore/.travis.yml new file mode 100644 index 0000000..99dc771 --- /dev/null +++ b/src/node_modules/nedb/node_modules/underscore/.travis.yml @@ -0,0 +1,5 @@ +language: node_js +node_js: + - 0.8 +notifications: + email: false diff --git a/src/node_modules/nedb/node_modules/underscore/CNAME b/src/node_modules/nedb/node_modules/underscore/CNAME new file mode 100644 index 0000000..a007e65 --- /dev/null +++ b/src/node_modules/nedb/node_modules/underscore/CNAME @@ -0,0 +1 @@ +underscorejs.org diff --git a/src/node_modules/nedb/node_modules/underscore/CONTRIBUTING.md b/src/node_modules/nedb/node_modules/underscore/CONTRIBUTING.md new file mode 100644 index 0000000..de5d562 --- /dev/null +++ b/src/node_modules/nedb/node_modules/underscore/CONTRIBUTING.md @@ -0,0 +1,9 @@ +## How to contribute to Underscore.js + +* Before you open a ticket or send a pull request, [search](https://github.com/documentcloud/underscore/issues) for previous discussions about the same feature or issue. Add to the earlier ticket if you find one. + +* Before sending a pull request for a feature, be sure to have [tests](http://underscorejs.org/test/). + +* Use the same coding style as the rest of the [codebase](https://github.com/documentcloud/underscore/blob/master/underscore.js). + +* In your pull request, do not add documentation or re-build the minified `underscore-min.js` file. We'll do those things before cutting a new release. diff --git a/src/node_modules/nedb/node_modules/underscore/LICENSE b/src/node_modules/nedb/node_modules/underscore/LICENSE new file mode 100644 index 0000000..0d8dbe4 --- /dev/null +++ b/src/node_modules/nedb/node_modules/underscore/LICENSE @@ -0,0 +1,22 @@ +Copyright (c) 2009-2013 Jeremy Ashkenas, DocumentCloud + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. diff --git a/src/node_modules/nedb/node_modules/underscore/README.md b/src/node_modules/nedb/node_modules/underscore/README.md new file mode 100644 index 0000000..b1f3e50 --- /dev/null +++ b/src/node_modules/nedb/node_modules/underscore/README.md @@ -0,0 +1,19 @@ + __ + /\ \ __ + __ __ ___ \_\ \ __ _ __ ____ ___ ___ _ __ __ /\_\ ____ + /\ \/\ \ /' _ `\ /'_ \ /'__`\/\ __\/ ,__\ / ___\ / __`\/\ __\/'__`\ \/\ \ /',__\ + \ \ \_\ \/\ \/\ \/\ \ \ \/\ __/\ \ \//\__, `\/\ \__//\ \ \ \ \ \//\ __/ __ \ \ \/\__, `\ + \ \____/\ \_\ \_\ \___,_\ \____\\ \_\\/\____/\ \____\ \____/\ \_\\ \____\/\_\ _\ \ \/\____/ + \/___/ \/_/\/_/\/__,_ /\/____/ \/_/ \/___/ \/____/\/___/ \/_/ \/____/\/_//\ \_\ \/___/ + \ \____/ + \/___/ + +Underscore.js is a utility-belt library for JavaScript that provides +support for the usual functional suspects (each, map, reduce, filter...) +without extending any core JavaScript objects. + +For Docs, License, Tests, and pre-packed downloads, see: +http://underscorejs.org + +Many thanks to our contributors: +https://github.com/documentcloud/underscore/contributors diff --git a/src/node_modules/nedb/node_modules/underscore/favicon.ico b/src/node_modules/nedb/node_modules/underscore/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..03049683875ee7207b4ee62241fc6977956723c7 GIT binary patch literal 1406 zcmZQzU<5(|0R|w+!H~hqz#zuJz@P!dKp_SNAO?x!16vCj7#y|msTh?T4S~@R7;Yi( a|NnmmMkru{0(Aax^D{300E2>o91H*{%ptM> literal 0 HcmV?d00001 diff --git a/src/node_modules/nedb/node_modules/underscore/index.html b/src/node_modules/nedb/node_modules/underscore/index.html new file mode 100644 index 0000000..8c5793a --- /dev/null +++ b/src/node_modules/nedb/node_modules/underscore/index.html @@ -0,0 +1,2467 @@ + + + + + + + + + Underscore.js + + + + + + +
    + +

    + +

    + +

    + Underscore is a + utility-belt library for JavaScript that provides a lot of the + functional programming support that you would expect in + Prototype.js + (or Ruby), + but without extending any of the built-in JavaScript objects. It's the + tie to go along with jQuery's tux, + and Backbone.js's suspenders. +

    + +

    + Underscore provides 80-odd functions that support both the usual + functional suspects: map, select, invoke — + as well as more specialized helpers: function binding, javascript + templating, deep equality testing, and so on. It delegates to built-in + functions, if present, so modern browsers will use the + native implementations of forEach, map, reduce, + filter, every, some and indexOf. +

    + +

    + A complete Test & Benchmark Suite + is included for your perusal. +

    + +

    + You may also read through the annotated source code. +

    + +

    + The project is + hosted on GitHub. + You can report bugs and discuss features on the + issues page, + on Freenode in the #documentcloud channel, + or send tweets to @documentcloud. +

    + +

    + Underscore is an open-source component of DocumentCloud. +

    + +

    Downloads (Right-click, and use "Save As")

    + + + + + + + + + + + + + + + + + +
    Development Version (1.4.4)40kb, Uncompressed with Plentiful Comments
    Production Version (1.4.4)4kb, Minified and Gzipped
    Edge VersionUnreleased, current master, use at your own risk
    + +
    + +

    Collection Functions (Arrays or Objects)

    + +

    + each_.each(list, iterator, [context]) + Alias: forEach +
    + Iterates over a list of elements, yielding each in turn to an iterator + function. The iterator is bound to the context object, if one is + passed. Each invocation of iterator is called with three arguments: + (element, index, list). If list is a JavaScript object, iterator's + arguments will be (value, key, list). Delegates to the native + forEach function if it exists. +

    +
    +_.each([1, 2, 3], alert);
    +=> alerts each number in turn...
    +_.each({one : 1, two : 2, three : 3}, alert);
    +=> alerts each number value in turn...
    + +

    + map_.map(list, iterator, [context]) + Alias: collect +
    + Produces a new array of values by mapping each value in list + through a transformation function (iterator). If the native map method + exists, it will be used instead. If list is a JavaScript object, + iterator's arguments will be (value, key, list). +

    +
    +_.map([1, 2, 3], function(num){ return num * 3; });
    +=> [3, 6, 9]
    +_.map({one : 1, two : 2, three : 3}, function(num, key){ return num * 3; });
    +=> [3, 6, 9]
    + +

    + reduce_.reduce(list, iterator, memo, [context]) + Aliases: inject, foldl +
    + Also known as inject and foldl, reduce boils down a + list of values into a single value. Memo is the initial state + of the reduction, and each successive step of it should be returned by + iterator. The iterator is passed four arguments: the memo, + then the value and index (or key) of the iteration, + and finally a reference to the entire list. +

    +
    +var sum = _.reduce([1, 2, 3], function(memo, num){ return memo + num; }, 0);
    +=> 6
    +
    + +

    + reduceRight_.reduceRight(list, iterator, memo, [context]) + Alias: foldr +
    + The right-associative version of reduce. Delegates to the + JavaScript 1.8 version of reduceRight, if it exists. Foldr + is not as useful in JavaScript as it would be in a language with lazy + evaluation. +

    +
    +var list = [[0, 1], [2, 3], [4, 5]];
    +var flat = _.reduceRight(list, function(a, b) { return a.concat(b); }, []);
    +=> [4, 5, 2, 3, 0, 1]
    +
    + +

    + find_.find(list, iterator, [context]) + Alias: detect +
    + Looks through each value in the list, returning the first one that + passes a truth test (iterator). The function returns as + soon as it finds an acceptable element, and doesn't traverse the + entire list. +

    +
    +var even = _.find([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; });
    +=> 2
    +
    + +

    + filter_.filter(list, iterator, [context]) + Alias: select +
    + Looks through each value in the list, returning an array of all + the values that pass a truth test (iterator). Delegates to the + native filter method, if it exists. +

    +
    +var evens = _.filter([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; });
    +=> [2, 4, 6]
    +
    + +

    + where_.where(list, properties) +
    + Looks through each value in the list, returning an array of all + the values that contain all of the key-value pairs listed in properties. +

    +
    +_.where(listOfPlays, {author: "Shakespeare", year: 1611});
    +=> [{title: "Cymbeline", author: "Shakespeare", year: 1611},
    +    {title: "The Tempest", author: "Shakespeare", year: 1611}]
    +
    + +

    + findWhere_.findWhere(list, properties) +
    + Looks through the list and returns the first value that matches + all of the key-value pairs listed in properties. +

    +
    +_.findWhere(publicServicePulitzers, {newsroom: "The New York Times"});
    +=> {year: 1918, newsroom: "The New York Times",
    +  reason: "For its public service in publishing in full so many official reports,
    +  documents and speeches by European statesmen relating to the progress and
    +  conduct of the war."}
    +
    + +

    + reject_.reject(list, iterator, [context]) +
    + Returns the values in list without the elements that the truth + test (iterator) passes. The opposite of filter. +

    +
    +var odds = _.reject([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; });
    +=> [1, 3, 5]
    +
    + +

    + every_.every(list, iterator, [context]) + Alias: all +
    + Returns true if all of the values in the list pass the iterator + truth test. Delegates to the native method every, if present. +

    +
    +_.every([true, 1, null, 'yes'], _.identity);
    +=> false
    +
    + +

    + some_.some(list, [iterator], [context]) + Alias: any +
    + Returns true if any of the values in the list pass the + iterator truth test. Short-circuits and stops traversing the list + if a true element is found. Delegates to the native method some, + if present. +

    +
    +_.some([null, 0, 'yes', false]);
    +=> true
    +
    + +

    + contains_.contains(list, value) + Alias: include +
    + Returns true if the value is present in the list. + Uses indexOf internally, if list is an Array. +

    +
    +_.contains([1, 2, 3], 3);
    +=> true
    +
    + +

    + invoke_.invoke(list, methodName, [*arguments]) +
    + Calls the method named by methodName on each value in the list. + Any extra arguments passed to invoke will be forwarded on to the + method invocation. +

    +
    +_.invoke([[5, 1, 7], [3, 2, 1]], 'sort');
    +=> [[1, 5, 7], [1, 2, 3]]
    +
    + +

    + pluck_.pluck(list, propertyName) +
    + A convenient version of what is perhaps the most common use-case for + map: extracting a list of property values. +

    +
    +var stooges = [{name : 'moe', age : 40}, {name : 'larry', age : 50}, {name : 'curly', age : 60}];
    +_.pluck(stooges, 'name');
    +=> ["moe", "larry", "curly"]
    +
    + +

    + max_.max(list, [iterator], [context]) +
    + Returns the maximum value in list. If iterator is passed, + it will be used on each value to generate the criterion by which the + value is ranked. +

    +
    +var stooges = [{name : 'moe', age : 40}, {name : 'larry', age : 50}, {name : 'curly', age : 60}];
    +_.max(stooges, function(stooge){ return stooge.age; });
    +=> {name : 'curly', age : 60};
    +
    + +

    + min_.min(list, [iterator], [context]) +
    + Returns the minimum value in list. If iterator is passed, + it will be used on each value to generate the criterion by which the + value is ranked. +

    +
    +var numbers = [10, 5, 100, 2, 1000];
    +_.min(numbers);
    +=> 2
    +
    + +

    + sortBy_.sortBy(list, iterator, [context]) +
    + Returns a sorted copy of list, ranked in ascending order by the + results of running each value through iterator. Iterator may + also be the string name of the property to sort by (eg. length). +

    +
    +_.sortBy([1, 2, 3, 4, 5, 6], function(num){ return Math.sin(num); });
    +=> [5, 4, 6, 3, 1, 2]
    +
    + +

    + groupBy_.groupBy(list, iterator, [context]) +
    + Splits a collection into sets, grouped by the result of running each + value through iterator. If iterator is a string instead of + a function, groups by the property named by iterator on each of + the values. +

    +
    +_.groupBy([1.3, 2.1, 2.4], function(num){ return Math.floor(num); });
    +=> {1: [1.3], 2: [2.1, 2.4]}
    +
    +_.groupBy(['one', 'two', 'three'], 'length');
    +=> {3: ["one", "two"], 5: ["three"]}
    +
    + +

    + countBy_.countBy(list, iterator, [context]) +
    + Sorts a list into groups and returns a count for the number of objects + in each group. + Similar to groupBy, but instead of returning a list of values, + returns a count for the number of values in that group. +

    +
    +_.countBy([1, 2, 3, 4, 5], function(num) {
    +  return num % 2 == 0 ? 'even' : 'odd';
    +});
    +=> {odd: 3, even: 2}
    +
    + +

    + shuffle_.shuffle(list) +
    + Returns a shuffled copy of the list, using a version of the + Fisher-Yates shuffle. +

    +
    +_.shuffle([1, 2, 3, 4, 5, 6]);
    +=> [4, 1, 6, 3, 5, 2]
    +
    + +

    + toArray_.toArray(list) +
    + Converts the list (anything that can be iterated over), into a + real Array. Useful for transmuting the arguments object. +

    +
    +(function(){ return _.toArray(arguments).slice(1); })(1, 2, 3, 4);
    +=> [2, 3, 4]
    +
    + +

    + size_.size(list) +
    + Return the number of values in the list. +

    +
    +_.size({one : 1, two : 2, three : 3});
    +=> 3
    +
    + +

    Array Functions

    + +

    + + Note: All array functions will also work on the arguments object. + However, Underscore functions are not designed to work on "sparse" arrays. + +

    + +

    + first_.first(array, [n]) + Alias: head, take +
    + Returns the first element of an array. Passing n will + return the first n elements of the array. +

    +
    +_.first([5, 4, 3, 2, 1]);
    +=> 5
    +
    + +

    + initial_.initial(array, [n]) +
    + Returns everything but the last entry of the array. Especially useful on + the arguments object. Pass n to exclude the last n elements + from the result. +

    +
    +_.initial([5, 4, 3, 2, 1]);
    +=> [5, 4, 3, 2]
    +
    + +

    + last_.last(array, [n]) +
    + Returns the last element of an array. Passing n will return + the last n elements of the array. +

    +
    +_.last([5, 4, 3, 2, 1]);
    +=> 1
    +
    + +

    + rest_.rest(array, [index]) + Alias: tail, drop +
    + Returns the rest of the elements in an array. Pass an index + to return the values of the array from that index onward. +

    +
    +_.rest([5, 4, 3, 2, 1]);
    +=> [4, 3, 2, 1]
    +
    + +

    + compact_.compact(array) +
    + Returns a copy of the array with all falsy values removed. + In JavaScript, false, null, 0, "", + undefined and NaN are all falsy. +

    +
    +_.compact([0, 1, false, 2, '', 3]);
    +=> [1, 2, 3]
    +
    + +

    + flatten_.flatten(array, [shallow]) +
    + Flattens a nested array (the nesting can be to any depth). If you + pass shallow, the array will only be flattened a single level. +

    +
    +_.flatten([1, [2], [3, [[4]]]]);
    +=> [1, 2, 3, 4];
    +
    +_.flatten([1, [2], [3, [[4]]]], true);
    +=> [1, 2, 3, [[4]]];
    +
    + +

    + without_.without(array, [*values]) +
    + Returns a copy of the array with all instances of the values + removed. +

    +
    +_.without([1, 2, 1, 0, 3, 1, 4], 0, 1);
    +=> [2, 3, 4]
    +
    + +

    + union_.union(*arrays) +
    + Computes the union of the passed-in arrays: the list of unique items, + in order, that are present in one or more of the arrays. +

    +
    +_.union([1, 2, 3], [101, 2, 1, 10], [2, 1]);
    +=> [1, 2, 3, 101, 10]
    +
    + +

    + intersection_.intersection(*arrays) +
    + Computes the list of values that are the intersection of all the arrays. + Each value in the result is present in each of the arrays. +

    +
    +_.intersection([1, 2, 3], [101, 2, 1, 10], [2, 1]);
    +=> [1, 2]
    +
    + +

    + difference_.difference(array, *others) +
    + Similar to without, but returns the values from array that + are not present in the other arrays. +

    +
    +_.difference([1, 2, 3, 4, 5], [5, 2, 10]);
    +=> [1, 3, 4]
    +
    + +

    + uniq_.uniq(array, [isSorted], [iterator]) + Alias: unique +
    + Produces a duplicate-free version of the array, using === to test + object equality. If you know in advance that the array is sorted, + passing true for isSorted will run a much faster algorithm. + If you want to compute unique items based on a transformation, pass an + iterator function. +

    +
    +_.uniq([1, 2, 1, 3, 1, 4]);
    +=> [1, 2, 3, 4]
    +
    + +

    + zip_.zip(*arrays) +
    + Merges together the values of each of the arrays with the + values at the corresponding position. Useful when you have separate + data sources that are coordinated through matching array indexes. + If you're working with a matrix of nested arrays, zip.apply + can transpose the matrix in a similar fashion. +

    +
    +_.zip(['moe', 'larry', 'curly'], [30, 40, 50], [true, false, false]);
    +=> [["moe", 30, true], ["larry", 40, false], ["curly", 50, false]]
    +
    + +

    + object_.object(list, [values]) +
    + Converts arrays into objects. Pass either a single list of + [key, value] pairs, or a list of keys, and a list of values. +

    +
    +_.object(['moe', 'larry', 'curly'], [30, 40, 50]);
    +=> {moe: 30, larry: 40, curly: 50}
    +
    +_.object([['moe', 30], ['larry', 40], ['curly', 50]]);
    +=> {moe: 30, larry: 40, curly: 50}
    +
    + +

    + indexOf_.indexOf(array, value, [isSorted]) +
    + Returns the index at which value can be found in the array, + or -1 if value is not present in the array. Uses the native + indexOf function unless it's missing. If you're working with a + large array, and you know that the array is already sorted, pass true + for isSorted to use a faster binary search ... or, pass a number as + the third argument in order to look for the first matching value in the + array after the given index. +

    +
    +_.indexOf([1, 2, 3], 2);
    +=> 1
    +
    + +

    + lastIndexOf_.lastIndexOf(array, value, [fromIndex]) +
    + Returns the index of the last occurrence of value in the array, + or -1 if value is not present. Uses the native lastIndexOf + function if possible. Pass fromIndex to start your search at a + given index. +

    +
    +_.lastIndexOf([1, 2, 3, 1, 2, 3], 2);
    +=> 4
    +
    + +

    + sortedIndex_.sortedIndex(list, value, [iterator], [context]) +
    + Uses a binary search to determine the index at which the value + should be inserted into the list in order to maintain the list's + sorted order. If an iterator is passed, it will be used to compute + the sort ranking of each value, including the value you pass. +

    +
    +_.sortedIndex([10, 20, 30, 40, 50], 35);
    +=> 3
    +
    + +

    + range_.range([start], stop, [step]) +
    + A function to create flexibly-numbered lists of integers, handy for + each and map loops. start, if omitted, defaults + to 0; step defaults to 1. Returns a list of integers + from start to stop, incremented (or decremented) by step, + exclusive. +

    +
    +_.range(10);
    +=> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    +_.range(1, 11);
    +=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    +_.range(0, 30, 5);
    +=> [0, 5, 10, 15, 20, 25]
    +_.range(0, -10, -1);
    +=> [0, -1, -2, -3, -4, -5, -6, -7, -8, -9]
    +_.range(0);
    +=> []
    +
    + +

    Function (uh, ahem) Functions

    + +

    + bind_.bind(function, object, [*arguments]) +
    + Bind a function to an object, meaning that whenever + the function is called, the value of this will be the object. + Optionally, pass arguments to the function to pre-fill them, + also known as partial application. +

    +
    +var func = function(greeting){ return greeting + ': ' + this.name };
    +func = _.bind(func, {name : 'moe'}, 'hi');
    +func();
    +=> 'hi: moe'
    +
    + +

    + bindAll_.bindAll(object, [*methodNames]) +
    + Binds a number of methods on the object, specified by + methodNames, to be run in the context of that object whenever they + are invoked. Very handy for binding functions that are going to be used + as event handlers, which would otherwise be invoked with a fairly useless + this. If no methodNames are provided, all of the object's + function properties will be bound to it. +

    +
    +var buttonView = {
    +  label   : 'underscore',
    +  onClick : function(){ alert('clicked: ' + this.label); },
    +  onHover : function(){ console.log('hovering: ' + this.label); }
    +};
    +_.bindAll(buttonView);
    +jQuery('#underscore_button').bind('click', buttonView.onClick);
    +=> When the button is clicked, this.label will have the correct value...
    +
    + +

    + partial_.partial(function, [*arguments]) +
    + Partially apply a function by filling in any number of its arguments, + without changing its dynamic this value. A close cousin + of bind. +

    +
    +var add = function(a, b) { return a + b; };
    +add5 = _.partial(add, 5);
    +add5(10);
    +=> 15
    +
    + +

    + memoize_.memoize(function, [hashFunction]) +
    + Memoizes a given function by caching the computed result. Useful + for speeding up slow-running computations. If passed an optional + hashFunction, it will be used to compute the hash key for storing + the result, based on the arguments to the original function. The default + hashFunction just uses the first argument to the memoized function + as the key. +

    +
    +var fibonacci = _.memoize(function(n) {
    +  return n < 2 ? n : fibonacci(n - 1) + fibonacci(n - 2);
    +});
    +
    + +

    + delay_.delay(function, wait, [*arguments]) +
    + Much like setTimeout, invokes function after wait + milliseconds. If you pass the optional arguments, they will be + forwarded on to the function when it is invoked. +

    +
    +var log = _.bind(console.log, console);
    +_.delay(log, 1000, 'logged later');
    +=> 'logged later' // Appears after one second.
    +
    + +

    + defer_.defer(function, [*arguments]) +
    + Defers invoking the function until the current call stack has cleared, + similar to using setTimeout with a delay of 0. Useful for performing + expensive computations or HTML rendering in chunks without blocking the UI thread + from updating. If you pass the optional arguments, they will be + forwarded on to the function when it is invoked. +

    +
    +_.defer(function(){ alert('deferred'); });
    +// Returns from the function before the alert runs.
    +
    + +

    + throttle_.throttle(function, wait) +
    + Creates and returns a new, throttled version of the passed function, + that, when invoked repeatedly, will only actually call the original function + at most once per every wait + milliseconds. Useful for rate-limiting events that occur faster than you + can keep up with. +

    +
    +var throttled = _.throttle(updatePosition, 100);
    +$(window).scroll(throttled);
    +
    + +

    + debounce_.debounce(function, wait, [immediate]) +
    + Creates and returns a new debounced version of the passed function that + will postpone its execution until after + wait milliseconds have elapsed since the last time it + was invoked. Useful for implementing behavior that should only happen + after the input has stopped arriving. For example: rendering a + preview of a Markdown comment, recalculating a layout after the window + has stopped being resized, and so on. +

    + +

    + Pass true for the immediate parameter to cause + debounce to trigger the function on the leading instead of the + trailing edge of the wait interval. Useful in circumstances like + preventing accidental double-clicks on a "submit" button from firing a + second time. +

    + +
    +var lazyLayout = _.debounce(calculateLayout, 300);
    +$(window).resize(lazyLayout);
    +
    + +

    + once_.once(function) +
    + Creates a version of the function that can only be called one time. + Repeated calls to the modified function will have no effect, returning + the value from the original call. Useful for initialization functions, + instead of having to set a boolean flag and then check it later. +

    +
    +var initialize = _.once(createApplication);
    +initialize();
    +initialize();
    +// Application is only created once.
    +
    + +

    + after_.after(count, function) +
    + Creates a version of the function that will only be run after first + being called count times. Useful for grouping asynchronous responses, + where you want to be sure that all the async calls have finished, before + proceeding. +

    +
    +var renderNotes = _.after(notes.length, render);
    +_.each(notes, function(note) {
    +  note.asyncSave({success: renderNotes});
    +});
    +// renderNotes is run once, after all notes have saved.
    +
    + +

    + wrap_.wrap(function, wrapper) +
    + Wraps the first function inside of the wrapper function, + passing it as the first argument. This allows the wrapper to + execute code before and after the function runs, adjust the arguments, + and execute it conditionally. +

    +
    +var hello = function(name) { return "hello: " + name; };
    +hello = _.wrap(hello, function(func) {
    +  return "before, " + func("moe") + ", after";
    +});
    +hello();
    +=> 'before, hello: moe, after'
    +
    + +

    + compose_.compose(*functions) +
    + Returns the composition of a list of functions, where each function + consumes the return value of the function that follows. In math terms, + composing the functions f(), g(), and h() produces + f(g(h())). +

    +
    +var greet    = function(name){ return "hi: " + name; };
    +var exclaim  = function(statement){ return statement + "!"; };
    +var welcome = _.compose(exclaim, greet);
    +welcome('moe');
    +=> 'hi: moe!'
    +
    + +

    Object Functions

    + +

    + keys_.keys(object) +
    + Retrieve all the names of the object's properties. +

    +
    +_.keys({one : 1, two : 2, three : 3});
    +=> ["one", "two", "three"]
    +
    + +

    + values_.values(object) +
    + Return all of the values of the object's properties. +

    +
    +_.values({one : 1, two : 2, three : 3});
    +=> [1, 2, 3]
    +
    + +

    + pairs_.pairs(object) +
    + Convert an object into a list of [key, value] pairs. +

    +
    +_.pairs({one: 1, two: 2, three: 3});
    +=> [["one", 1], ["two", 2], ["three", 3]]
    +
    + +

    + invert_.invert(object) +
    + Returns a copy of the object where the keys have become the values + and the values the keys. For this to work, all of your object's values + should be unique and string serializable. +

    +
    +_.invert({Moe: "Moses", Larry: "Louis", Curly: "Jerome"});
    +=> {Moses: "Moe", Louis: "Larry", Jerome: "Curly"};
    +
    + +

    + functions_.functions(object) + Alias: methods +
    + Returns a sorted list of the names of every method in an object — + that is to say, the name of every function property of the object. +

    +
    +_.functions(_);
    +=> ["all", "any", "bind", "bindAll", "clone", "compact", "compose" ...
    +
    + +

    + extend_.extend(destination, *sources) +
    + Copy all of the properties in the source objects over to the + destination object, and return the destination object. + It's in-order, so the last source will override properties of the same + name in previous arguments. +

    +
    +_.extend({name : 'moe'}, {age : 50});
    +=> {name : 'moe', age : 50}
    +
    + +

    + pick_.pick(object, *keys) +
    + Return a copy of the object, filtered to only have values for + the whitelisted keys (or array of valid keys). +

    +
    +_.pick({name : 'moe', age: 50, userid : 'moe1'}, 'name', 'age');
    +=> {name : 'moe', age : 50}
    +
    + +

    + omit_.omit(object, *keys) +
    + Return a copy of the object, filtered to omit the blacklisted + keys (or array of keys). +

    +
    +_.omit({name : 'moe', age : 50, userid : 'moe1'}, 'userid');
    +=> {name : 'moe', age : 50}
    +
    + +

    + defaults_.defaults(object, *defaults) +
    + Fill in null and undefined properties in object with values from the + defaults objects, and return the object. As soon as the + property is filled, further defaults will have no effect. +

    +
    +var iceCream = {flavor : "chocolate"};
    +_.defaults(iceCream, {flavor : "vanilla", sprinkles : "lots"});
    +=> {flavor : "chocolate", sprinkles : "lots"}
    +
    + +

    + clone_.clone(object) +
    + Create a shallow-copied clone of the object. Any nested objects + or arrays will be copied by reference, not duplicated. +

    +
    +_.clone({name : 'moe'});
    +=> {name : 'moe'};
    +
    + +

    + tap_.tap(object, interceptor) +
    + Invokes interceptor with the object, and then returns object. + The primary purpose of this method is to "tap into" a method chain, in order to perform operations on intermediate results within the chain. +

    +
    +_.chain([1,2,3,200])
    +  .filter(function(num) { return num % 2 == 0; })
    +  .tap(alert)
    +  .map(function(num) { return num * num })
    +  .value();
    +=> // [2, 200] (alerted)
    +=> [4, 40000]
    +
    + +

    + has_.has(object, key) +
    + Does the object contain the given key? Identical to + object.hasOwnProperty(key), but uses a safe reference to the + hasOwnProperty function, in case it's been + overridden accidentally. +

    +
    +_.has({a: 1, b: 2, c: 3}, "b");
    +=> true
    +
    + +

    + isEqual_.isEqual(object, other) +
    + Performs an optimized deep comparison between the two objects, to determine + if they should be considered equal. +

    +
    +var moe   = {name : 'moe', luckyNumbers : [13, 27, 34]};
    +var clone = {name : 'moe', luckyNumbers : [13, 27, 34]};
    +moe == clone;
    +=> false
    +_.isEqual(moe, clone);
    +=> true
    +
    + +

    + isEmpty_.isEmpty(object) +
    + Returns true if object contains no values. +

    +
    +_.isEmpty([1, 2, 3]);
    +=> false
    +_.isEmpty({});
    +=> true
    +
    + +

    + isElement_.isElement(object) +
    + Returns true if object is a DOM element. +

    +
    +_.isElement(jQuery('body')[0]);
    +=> true
    +
    + +

    + isArray_.isArray(object) +
    + Returns true if object is an Array. +

    +
    +(function(){ return _.isArray(arguments); })();
    +=> false
    +_.isArray([1,2,3]);
    +=> true
    +
    + +

    + isObject_.isObject(value) +
    + Returns true if value is an Object. Note that JavaScript + arrays and functions are objects, while (normal) strings and numbers are not. +

    +
    +_.isObject({});
    +=> true
    +_.isObject(1);
    +=> false
    +
    + +

    + isArguments_.isArguments(object) +
    + Returns true if object is an Arguments object. +

    +
    +(function(){ return _.isArguments(arguments); })(1, 2, 3);
    +=> true
    +_.isArguments([1,2,3]);
    +=> false
    +
    + +

    + isFunction_.isFunction(object) +
    + Returns true if object is a Function. +

    +
    +_.isFunction(alert);
    +=> true
    +
    + +

    + isString_.isString(object) +
    + Returns true if object is a String. +

    +
    +_.isString("moe");
    +=> true
    +
    + +

    + isNumber_.isNumber(object) +
    + Returns true if object is a Number (including NaN). +

    +
    +_.isNumber(8.4 * 5);
    +=> true
    +
    + +

    + isFinite_.isFinite(object) +
    + Returns true if object is a finite Number. +

    +
    +_.isFinite(-101);
    +=> true
    +
    +_.isFinite(-Infinity);
    +=> false
    +
    + +

    + isBoolean_.isBoolean(object) +
    + Returns true if object is either true or false. +

    +
    +_.isBoolean(null);
    +=> false
    +
    + +

    + isDate_.isDate(object) +
    + Returns true if object is a Date. +

    +
    +_.isDate(new Date());
    +=> true
    +
    + +

    + isRegExp_.isRegExp(object) +
    + Returns true if object is a RegExp. +

    +
    +_.isRegExp(/moe/);
    +=> true
    +
    + +

    + isNaN_.isNaN(object) +
    + Returns true if object is NaN.
    Note: this is not + the same as the native isNaN function, which will also return + true if the variable is undefined. +

    +
    +_.isNaN(NaN);
    +=> true
    +isNaN(undefined);
    +=> true
    +_.isNaN(undefined);
    +=> false
    +
    + +

    + isNull_.isNull(object) +
    + Returns true if the value of object is null. +

    +
    +_.isNull(null);
    +=> true
    +_.isNull(undefined);
    +=> false
    +
    + +

    + isUndefined_.isUndefined(value) +
    + Returns true if value is undefined. +

    +
    +_.isUndefined(window.missingVariable);
    +=> true
    +
    + +

    Utility Functions

    + +

    + noConflict_.noConflict() +
    + Give control of the "_" variable back to its previous owner. Returns + a reference to the Underscore object. +

    +
    +var underscore = _.noConflict();
    + +

    + identity_.identity(value) +
    + Returns the same value that is used as the argument. In math: + f(x) = x
    + This function looks useless, but is used throughout Underscore as + a default iterator. +

    +
    +var moe = {name : 'moe'};
    +moe === _.identity(moe);
    +=> true
    + +

    + times_.times(n, iterator, [context]) +
    + Invokes the given iterator function n times. Each invocation of + iterator is called with an index argument. +
    + Note: this example uses the chaining syntax. +

    +
    +_(3).times(function(n){ genie.grantWishNumber(n); });
    + +

    + random_.random(min, max) +
    + Returns a random integer between min and max, inclusive. + If you only pass one argument, it will return a number between 0 + and that number. +

    +
    +_.random(0, 100);
    +=> 42
    + +

    + mixin_.mixin(object) +
    + Allows you to extend Underscore with your own utility functions. Pass + a hash of {name: function} definitions to have your functions + added to the Underscore object, as well as the OOP wrapper. +

    +
    +_.mixin({
    +  capitalize : function(string) {
    +    return string.charAt(0).toUpperCase() + string.substring(1).toLowerCase();
    +  }
    +});
    +_("fabio").capitalize();
    +=> "Fabio"
    +
    + +

    + uniqueId_.uniqueId([prefix]) +
    + Generate a globally-unique id for client-side models or DOM elements + that need one. If prefix is passed, the id will be appended to it. +

    +
    +_.uniqueId('contact_');
    +=> 'contact_104'
    + +

    + escape_.escape(string) +
    + Escapes a string for insertion into HTML, replacing + &, <, >, ", ', and / characters. +

    +
    +_.escape('Curly, Larry & Moe');
    +=> "Curly, Larry &amp; Moe"
    + +

    + unescape_.unescape(string) +
    + The opposite of escape, replaces + &amp;, &lt;, &gt;, + &quot;, &#x27;, and &#x2F; + with their unescaped counterparts. +

    +
    +_.unescape('Curly, Larry &amp; Moe');
    +=> "Curly, Larry & Moe"
    + +

    + result_.result(object, property) +
    + If the value of the named property is a function then invoke it; otherwise, return it. +

    +
    +var object = {cheese: 'crumpets', stuff: function(){ return 'nonsense'; }};
    +_.result(object, 'cheese');
    +=> "crumpets"
    +_.result(object, 'stuff');
    +=> "nonsense"
    + +

    + template_.template(templateString, [data], [settings]) +
    + Compiles JavaScript templates into functions that can be evaluated + for rendering. Useful for rendering complicated bits of HTML from JSON + data sources. Template functions can both interpolate variables, using + <%= … %>, as well as execute arbitrary JavaScript code, with + <% … %>. If you wish to interpolate a value, and have + it be HTML-escaped, use <%- … %> When you evaluate a template function, pass in a + data object that has properties corresponding to the template's free + variables. If you're writing a one-off, you can pass the data + object as the second parameter to template in order to render + immediately instead of returning a template function. The settings argument + should be a hash containing any _.templateSettings that should be overridden. +

    + +
    +var compiled = _.template("hello: <%= name %>");
    +compiled({name : 'moe'});
    +=> "hello: moe"
    +
    +var list = "<% _.each(people, function(name) { %> <li><%= name %></li> <% }); %>";
    +_.template(list, {people : ['moe', 'curly', 'larry']});
    +=> "<li>moe</li><li>curly</li><li>larry</li>"
    +
    +var template = _.template("<b><%- value %></b>");
    +template({value : '<script>'});
    +=> "<b>&lt;script&gt;</b>"
    + +

    + You can also use print from within JavaScript code. This is + sometimes more convenient than using <%= ... %>. +

    + +
    +var compiled = _.template("<% print('Hello ' + epithet); %>");
    +compiled({epithet: "stooge"});
    +=> "Hello stooge."
    + +

    + If ERB-style delimiters aren't your cup of tea, you can change Underscore's + template settings to use different symbols to set off interpolated code. + Define an interpolate regex to match expressions that should be + interpolated verbatim, an escape regex to match expressions that should + be inserted after being HTML escaped, and an evaluate regex to match + expressions that should be evaluated without insertion into the resulting + string. You may define or omit any combination of the three. + For example, to perform + Mustache.js + style templating: +

    + +
    +_.templateSettings = {
    +  interpolate : /\{\{(.+?)\}\}/g
    +};
    +
    +var template = _.template("Hello {{ name }}!");
    +template({name : "Mustache"});
    +=> "Hello Mustache!"
    + +

    + By default, template places the values from your data in the local scope + via the with statement. However, you can specify a single variable name + with the variable setting. This can significantly improve the speed + at which a template is able to render. +

    + +
    +_.template("Using 'with': <%= data.answer %>", {answer: 'no'}, {variable: 'data'});
    +=> "Using 'with': no"
    + +

    + Precompiling your templates can be a big help when debugging errors you can't + reproduce. This is because precompiled templates can provide line numbers and + a stack trace, something that is not possible when compiling templates on the client. + The source property is available on the compiled template + function for easy precompilation. +

    + +
    <script>
    +  JST.project = <%= _.template(jstText).source %>;
    +</script>
    + + +

    Chaining

    + +

    + You can use Underscore in either an object-oriented or a functional style, + depending on your preference. The following two lines of code are + identical ways to double a list of numbers. +

    + +
    +_.map([1, 2, 3], function(n){ return n * 2; });
    +_([1, 2, 3]).map(function(n){ return n * 2; });
    + +

    + Calling chain will cause all future method calls to return + wrapped objects. When you've finished the computation, use + value to retrieve the final value. Here's an example of chaining + together a map/flatten/reduce, in order to get the word count of + every word in a song. +

    + +
    +var lyrics = [
    +  {line : 1, words : "I'm a lumberjack and I'm okay"},
    +  {line : 2, words : "I sleep all night and I work all day"},
    +  {line : 3, words : "He's a lumberjack and he's okay"},
    +  {line : 4, words : "He sleeps all night and he works all day"}
    +];
    +
    +_.chain(lyrics)
    +  .map(function(line) { return line.words.split(' '); })
    +  .flatten()
    +  .reduce(function(counts, word) {
    +    counts[word] = (counts[word] || 0) + 1;
    +    return counts;
    +  }, {})
    +  .value();
    +
    +=> {lumberjack : 2, all : 4, night : 2 ... }
    + +

    + In addition, the + Array prototype's methods + are proxied through the chained Underscore object, so you can slip a + reverse or a push into your chain, and continue to + modify the array. +

    + +

    + chain_.chain(obj) +
    + Returns a wrapped object. Calling methods on this object will continue + to return wrapped objects until value is used. +

    +
    +var stooges = [{name : 'curly', age : 25}, {name : 'moe', age : 21}, {name : 'larry', age : 23}];
    +var youngest = _.chain(stooges)
    +  .sortBy(function(stooge){ return stooge.age; })
    +  .map(function(stooge){ return stooge.name + ' is ' + stooge.age; })
    +  .first()
    +  .value();
    +=> "moe is 21"
    +
    + +

    + value_(obj).value() +
    + Extracts the value of a wrapped object. +

    +
    +_([1, 2, 3]).value();
    +=> [1, 2, 3]
    +
    + + + +

    + The Underscore documentation is also available in + Simplified Chinese. +

    + +

    + Underscore.lua, + a Lua port of the functions that are applicable in both languages. + Includes OOP-wrapping and chaining. + (source) +

    + +

    + Underscore.m, an Objective-C port + of many of the Underscore.js functions, using a syntax that encourages + chaining. + (source) +

    + +

    + _.m, an alternative + Objective-C port that tries to stick a little closer to the original + Underscore.js API. + (source) +

    + +

    + Underscore.php, + a PHP port of the functions that are applicable in both languages. + Includes OOP-wrapping and chaining. + (source) +

    + +

    + Underscore-perl, + a Perl port of many of the Underscore.js functions, + aimed at on Perl hashes and arrays. + (source) +

    + +

    + Underscore.cfc, + a Coldfusion port of many of the Underscore.js functions. + (source) +

    + +

    + Underscore.string, + an Underscore extension that adds functions for string-manipulation: + trim, startsWith, contains, capitalize, + reverse, sprintf, and more. +

    + +

    + Ruby's Enumerable module. +

    + +

    + Prototype.js, which provides + JavaScript with collection functions in the manner closest to Ruby's Enumerable. +

    + +

    + Oliver Steele's + Functional JavaScript, + which includes comprehensive higher-order function support as well as string lambdas. +

    + +

    + Michael Aufreiter's Data.js, + a data manipulation + persistence library for JavaScript. +

    + +

    + Python's itertools. +

    + +

    Change Log

    + +

    + 1.4.4Jan. 30, 2013Diff
    +

      +
    • + Added _.findWhere, for finding the first element in a list + that matches a particular set of keys and values. +
    • +
    • + Added _.partial, for partially applying a function without + changing its dynamic reference to this. +
    • +
    • + Simplified bind by removing some edge cases involving + constructor functions. In short: don't _.bind your + constructors. +
    • +
    • + A minor optimization to invoke. +
    • +
    • + Fix bug in the minified version due to the minifier incorrectly + optimizing-away isFunction. +
    • +
    +

    + +

    + 1.4.3Dec. 4, 2012Diff
    +

      +
    • + Improved Underscore compatibility with Adobe's JS engine that can be + used to script Illustrator, Photoshop, and friends. +
    • +
    • + Added a default _.identity iterator to countBy and + groupBy. +
    • +
    • + The uniq function can now take array, iterator, context + as the argument list. +
    • +
    • + The times function now returns the mapped array of iterator + results. +
    • +
    • + Simplified and fixed bugs in throttle. +
    • +
    +

    + +

    + 1.4.2Oct. 1, 2012Diff
    +

      +
    • + For backwards compatibility, returned to pre-1.4.0 behavior when + passing null to iteration functions. They now become no-ops + again. +
    • +
    +

    + +

    + 1.4.1Oct. 1, 2012Diff
    +

      +
    • + Fixed a 1.4.0 regression in the lastIndexOf function. +
    • +
    +

    + +

    + 1.4.0Sept. 27, 2012Diff
    +

      +
    • + Added a pairs function, for turning a JavaScript object + into [key, value] pairs ... as well as an object + function, for converting an array of [key, value] pairs + into an object. +
    • +
    • + Added a countBy function, for counting the number of objects + in a list that match a certain criteria. +
    • +
    • + Added an invert function, for performing a simple inversion + of the keys and values in an object. +
    • +
    • + Added a where function, for easy cases of filtering a list + for objects with specific values. +
    • +
    • + Added an omit function, for filtering an object to remove + certain keys. +
    • +
    • + Added a random function, to return a random number in a + given range. +
    • +
    • + _.debounce'd functions now return their last updated value, + just like _.throttle'd functions do. +
    • +
    • + The sortBy function now runs a stable sort algorithm. +
    • +
    • + Added the optional fromIndex option to indexOf and + lastIndexOf. +
    • +
    • + "Sparse" arrays are no longer supported in Underscore iteration + functions. Use a for loop instead (or better yet, an object). +
    • +
    • + The min and max functions may now be called on + very large arrays. +
    • +
    • + Interpolation in templates now represents null and + undefined as the empty string. +
    • +
    • + Underscore iteration functions no longer accept null values + as a no-op argument. You'll get an early error instead. +
    • +
    • + A number of edge-cases fixes and tweaks, which you can spot in the + diff. + Depending on how you're using Underscore, 1.4.0 may be more + backwards-incompatible than usual — please test when you upgrade. +
    • +
    +

    + +

    + 1.3.3April 10, 2012
    +

      +
    • + Many improvements to _.template, which now provides the + source of the template function as a property, for potentially + even more efficient pre-compilation on the server-side. You may now + also set the variable option when creating a template, + which will cause your passed-in data to be made available under the + variable you named, instead of using a with statement — + significantly improving the speed of rendering the template. +
    • +
    • + Added the pick function, which allows you to filter an + object literal with a whitelist of allowed property names. +
    • +
    • + Added the result function, for convenience when working + with APIs that allow either functions or raw properties. +
    • +
    • + Added the isFinite function, because sometimes knowing that + a value is a number just ain't quite enough. +
    • +
    • + The sortBy function may now also be passed the string name + of a property to use as the sort order on each object. +
    • +
    • + Fixed uniq to work with sparse arrays. +
    • +
    • + The difference function now performs a shallow flatten + instead of a deep one when computing array differences. +
    • +
    • + The debounce function now takes an immediate + parameter, which will cause the callback to fire on the leading + instead of the trailing edge. +
    • +
    +

    + +

    + 1.3.1Jan. 23, 2012
    +

      +
    • + Added an _.has function, as a safer way to use hasOwnProperty. +
    • +
    • + Added _.collect as an alias for _.map. Smalltalkers, rejoice. +
    • +
    • + Reverted an old change so that _.extend will correctly copy + over keys with undefined values again. +
    • +
    • + Bugfix to stop escaping slashes within interpolations in _.template. +
    • +
    +

    + +

    + 1.3.0Jan. 11, 2012
    +

      +
    • + Removed AMD (RequireJS) support from Underscore. If you'd like to use + Underscore with RequireJS, you can load it as a normal script, wrap + or patch your copy, or download a forked version. +
    • +
    +

    + +

    + 1.2.4Jan. 4, 2012
    +

      +
    • + You now can (and probably should, as it's simpler) + write _.chain(list) + instead of _(list).chain(). +
    • +
    • + Fix for escaped characters in Underscore templates, and for supporting + customizations of _.templateSettings that only define one or + two of the required regexes. +
    • +
    • + Fix for passing an array as the first argument to an _.wrap'd function. +
    • +
    • + Improved compatibility with ClojureScript, which adds a call + function to String.prototype. +
    • +
    +

    + +

    + 1.2.3Dec. 7, 2011
    +

      +
    • + Dynamic scope is now preserved for compiled _.template functions, + so you can use the value of this if you like. +
    • +
    • + Sparse array support of _.indexOf, _.lastIndexOf. +
    • +
    • + Both _.reduce and _.reduceRight can now be passed an + explicitly undefined value. (There's no reason why you'd + want to do this.) +
    • +
    +

    + +

    + 1.2.2Nov. 14, 2011
    +

      +
    • + Continued tweaks to _.isEqual semantics. Now JS primitives are + considered equivalent to their wrapped versions, and arrays are compared + by their numeric properties only (#351). +
    • +
    • + _.escape no longer tries to be smart about not double-escaping + already-escaped HTML entities. Now it just escapes regardless (#350). +
    • +
    • + In _.template, you may now leave semicolons out of evaluated + statements if you wish: <% }) %> (#369). +
    • +
    • + _.after(callback, 0) will now trigger the callback immediately, + making "after" easier to use with asynchronous APIs (#366). +
    • +
    +

    + +

    + 1.2.1Oct. 24, 2011
    +

      +
    • + Several important bug fixes for _.isEqual, which should now + do better on mutated Arrays, and on non-Array objects with + length properties. (#329) +
    • +
    • + jrburke contributed Underscore exporting for AMD module loaders, + and tonylukasavage for Appcelerator Titanium. + (#335, #338) +
    • +
    • + You can now _.groupBy(list, 'property') as a shortcut for + grouping values by a particular common property. +
    • +
    • + _.throttle'd functions now fire immediately upon invocation, + and are rate-limited thereafter (#170, #266). +
    • +
    • + Most of the _.is[Type] checks no longer ducktype. +
    • +
    • + The _.bind function now also works on constructors, a-la + ES5 ... but you would never want to use _.bind on a + constructor function. +
    • +
    • + _.clone no longer wraps non-object types in Objects. +
    • +
    • + _.find and _.filter are now the preferred names for + _.detect and _.select. +
    • +
    +

    + +

    + 1.2.0Oct. 5, 2011
    +

      +
    • + The _.isEqual function now + supports true deep equality comparisons, with checks for cyclic structures, + thanks to Kit Cambridge. +
    • +
    • + Underscore templates now support HTML escaping interpolations, using + <%- ... %> syntax. +
    • +
    • + Ryan Tenney contributed _.shuffle, which uses a modified + Fisher-Yates to give you a shuffled copy of an array. +
    • +
    • + _.uniq can now be passed an optional iterator, to determine by + what criteria an object should be considered unique. +
    • +
    • + _.last now takes an optional argument which will return the last + N elements of the list. +
    • +
    • + A new _.initial function was added, as a mirror of _.rest, + which returns all the initial values of a list (except the last N). +
    • +
    +

    + +

    + 1.1.7July 13, 2011
    + Added _.groupBy, which aggregates a collection into groups of like items. + Added _.union and _.difference, to complement the + (re-named) _.intersection. + Various improvements for support of sparse arrays. + _.toArray now returns a clone, if directly passed an array. + _.functions now also returns the names of functions that are present + in the prototype chain. +

    + +

    + 1.1.6April 18, 2011
    + Added _.after, which will return a function that only runs after + first being called a specified number of times. + _.invoke can now take a direct function reference. + _.every now requires an iterator function to be passed, which + mirrors the ECMA5 API. + _.extend no longer copies keys when the value is undefined. + _.bind now errors when trying to bind an undefined value. +

    + +

    + 1.1.5Mar 20, 2011
    + Added an _.defaults function, for use merging together JS objects + representing default options. + Added an _.once function, for manufacturing functions that should + only ever execute a single time. + _.bind now delegates to the native ECMAScript 5 version, + where available. + _.keys now throws an error when used on non-Object values, as in + ECMAScript 5. + Fixed a bug with _.keys when used over sparse arrays. +

    + +

    + 1.1.4Jan 9, 2011
    + Improved compliance with ES5's Array methods when passing null + as a value. _.wrap now correctly sets this for the + wrapped function. _.indexOf now takes an optional flag for + finding the insertion index in an array that is guaranteed to already + be sorted. Avoiding the use of .callee, to allow _.isArray + to work properly in ES5's strict mode. +

    + +

    + 1.1.3Dec 1, 2010
    + In CommonJS, Underscore may now be required with just:
    + var _ = require("underscore"). + Added _.throttle and _.debounce functions. + Removed _.breakLoop, in favor of an ECMA5-style un-break-able + each implementation — this removes the try/catch, and you'll now have + better stack traces for exceptions that are thrown within an Underscore iterator. + Improved the isType family of functions for better interoperability + with Internet Explorer host objects. + _.template now correctly escapes backslashes in templates. + Improved _.reduce compatibility with the ECMA5 version: + if you don't pass an initial value, the first item in the collection is used. + _.each no longer returns the iterated collection, for improved + consistency with ES5's forEach. +

    + +

    + 1.1.2
    + Fixed _.contains, which was mistakenly pointing at + _.intersect instead of _.include, like it should + have been. Added _.unique as an alias for _.uniq. +

    + +

    + 1.1.1
    + Improved the speed of _.template, and its handling of multiline + interpolations. Ryan Tenney contributed optimizations to many Underscore + functions. An annotated version of the source code is now available. +

    + +

    + 1.1.0
    + The method signature of _.reduce has been changed to match + the ECMAScript 5 signature, instead of the Ruby/Prototype.js version. + This is a backwards-incompatible change. _.template may now be + called with no arguments, and preserves whitespace. _.contains + is a new alias for _.include. +

    + +

    + 1.0.4
    + Andri Möll contributed the _.memoize + function, which can be used to speed up expensive repeated computations + by caching the results. +

    + +

    + 1.0.3
    + Patch that makes _.isEqual return false if any property + of the compared object has a NaN value. Technically the correct + thing to do, but of questionable semantics. Watch out for NaN comparisons. +

    + +

    + 1.0.2
    + Fixes _.isArguments in recent versions of Opera, which have + arguments objects as real Arrays. +

    + +

    + 1.0.1
    + Bugfix for _.isEqual, when comparing two objects with the same + number of undefined keys, but with different names. +

    + +

    + 1.0.0
    + Things have been stable for many months now, so Underscore is now + considered to be out of beta, at 1.0. Improvements since 0.6 + include _.isBoolean, and the ability to have _.extend + take multiple source objects. +

    + +

    + 0.6.0
    + Major release. Incorporates a number of + Mile Frawley's refactors for + safer duck-typing on collection functions, and cleaner internals. A new + _.mixin method that allows you to extend Underscore with utility + functions of your own. Added _.times, which works the same as in + Ruby or Prototype.js. Native support for ECMAScript 5's Array.isArray, + and Object.keys. +

    + +

    + 0.5.8
    + Fixed Underscore's collection functions to work on + NodeLists and + HTMLCollections + once more, thanks to + Justin Tulloss. +

    + +

    + 0.5.7
    + A safer implementation of _.isArguments, and a + faster _.isNumber,
    thanks to + Jed Schmidt. +

    + +

    + 0.5.6
    + Customizable delimiters for _.template, contributed by + Noah Sloan. +

    + +

    + 0.5.5
    + Fix for a bug in MobileSafari's OOP-wrapper, with the arguments object. +

    + +

    + 0.5.4
    + Fix for multiple single quotes within a template string for + _.template. See: + Rick Strahl's blog post. +

    + +

    + 0.5.2
    + New implementations of isArray, isDate, isFunction, + isNumber, isRegExp, and isString, thanks to + a suggestion from + Robert Kieffer. + Instead of doing Object#toString + comparisons, they now check for expected properties, which is less safe, + but more than an order of magnitude faster. Most other Underscore + functions saw minor speed improvements as a result. + Evgeniy Dolzhenko + contributed _.tap, + similar to Ruby 1.9's, + which is handy for injecting side effects (like logging) into chained calls. +

    + +

    + 0.5.1
    + Added an _.isArguments function. Lots of little safety checks + and optimizations contributed by + Noah Sloan and + Andri Möll. +

    + +

    + 0.5.0
    + [API Changes] _.bindAll now takes the context object as + its first parameter. If no method names are passed, all of the context + object's methods are bound to it, enabling chaining and easier binding. + _.functions now takes a single argument and returns the names + of its Function properties. Calling _.functions(_) will get you + the previous behavior. + Added _.isRegExp so that isEqual can now test for RegExp equality. + All of the "is" functions have been shrunk down into a single definition. + Karl Guertin contributed patches. +

    + +

    + 0.4.7
    + Added isDate, isNaN, and isNull, for completeness. + Optimizations for isEqual when checking equality between Arrays + or Dates. _.keys is now 25%–2X faster (depending on your + browser) which speeds up the functions that rely on it, such as _.each. +

    + +

    + 0.4.6
    + Added the range function, a port of the + Python + function of the same name, for generating flexibly-numbered lists + of integers. Original patch contributed by + Kirill Ishanov. +

    + +

    + 0.4.5
    + Added rest for Arrays and arguments objects, and aliased + first as head, and rest as tail, + thanks to Luke Sutton's patches. + Added tests ensuring that all Underscore Array functions also work on + arguments objects. +

    + +

    + 0.4.4
    + Added isString, and isNumber, for consistency. Fixed + _.isEqual(NaN, NaN) to return true (which is debatable). +

    + +

    + 0.4.3
    + Started using the native StopIteration object in browsers that support it. + Fixed Underscore setup for CommonJS environments. +

    + +

    + 0.4.2
    + Renamed the unwrapping function to value, for clarity. +

    + +

    + 0.4.1
    + Chained Underscore objects now support the Array prototype methods, so + that you can perform the full range of operations on a wrapped array + without having to break your chain. Added a breakLoop method + to break in the middle of any Underscore iteration. Added an + isEmpty function that works on arrays and objects. +

    + +

    + 0.4.0
    + All Underscore functions can now be called in an object-oriented style, + like so: _([1, 2, 3]).map(...);. Original patch provided by + Marc-André Cournoyer. + Wrapped objects can be chained through multiple + method invocations. A functions method + was added, providing a sorted list of all the functions in Underscore. +

    + +

    + 0.3.3
    + Added the JavaScript 1.8 function reduceRight. Aliased it + as foldr, and aliased reduce as foldl. +

    + +

    + 0.3.2
    + Now runs on stock Rhino + interpreters with: load("underscore.js"). + Added identity as a utility function. +

    + +

    + 0.3.1
    + All iterators are now passed in the original collection as their third + argument, the same as JavaScript 1.6's forEach. Iterating over + objects is now called with (value, key, collection), for details + see _.each. +

    + +

    + 0.3.0
    + Added Dmitry Baranovskiy's + comprehensive optimizations, merged in + Kris Kowal's patches to make Underscore + CommonJS and + Narwhal compliant. +

    + +

    + 0.2.0
    + Added compose and lastIndexOf, renamed inject to + reduce, added aliases for inject, filter, + every, some, and forEach. +

    + +

    + 0.1.1
    + Added noConflict, so that the "Underscore" object can be assigned to + other variables. +

    + +

    + 0.1.0
    + Initial release of Underscore.js. +

    + +

    + + A DocumentCloud Project + +

    + +
    + +
    + + + + + + diff --git a/src/node_modules/nedb/node_modules/underscore/index.js b/src/node_modules/nedb/node_modules/underscore/index.js new file mode 100644 index 0000000..2cf0ca5 --- /dev/null +++ b/src/node_modules/nedb/node_modules/underscore/index.js @@ -0,0 +1 @@ +module.exports = require('./underscore'); diff --git a/src/node_modules/nedb/node_modules/underscore/package.json b/src/node_modules/nedb/node_modules/underscore/package.json new file mode 100644 index 0000000..d12da68 --- /dev/null +++ b/src/node_modules/nedb/node_modules/underscore/package.json @@ -0,0 +1,39 @@ +{ + "name": "underscore", + "description": "JavaScript's functional programming helper library.", + "homepage": "http://underscorejs.org", + "keywords": [ + "util", + "functional", + "server", + "client", + "browser" + ], + "author": { + "name": "Jeremy Ashkenas", + "email": "jeremy@documentcloud.org" + }, + "repository": { + "type": "git", + "url": "git://github.com/documentcloud/underscore.git" + }, + "main": "underscore.js", + "version": "1.4.4", + "devDependencies": { + "phantomjs": "0.2.2" + }, + "scripts": { + "test": "phantomjs test/vendor/runner.js test/index.html?noglobals=true" + }, + "readme": " __\n /\\ \\ __\n __ __ ___ \\_\\ \\ __ _ __ ____ ___ ___ _ __ __ /\\_\\ ____\n /\\ \\/\\ \\ /' _ `\\ /'_ \\ /'__`\\/\\ __\\/ ,__\\ / ___\\ / __`\\/\\ __\\/'__`\\ \\/\\ \\ /',__\\\n \\ \\ \\_\\ \\/\\ \\/\\ \\/\\ \\ \\ \\/\\ __/\\ \\ \\//\\__, `\\/\\ \\__//\\ \\ \\ \\ \\ \\//\\ __/ __ \\ \\ \\/\\__, `\\\n \\ \\____/\\ \\_\\ \\_\\ \\___,_\\ \\____\\\\ \\_\\\\/\\____/\\ \\____\\ \\____/\\ \\_\\\\ \\____\\/\\_\\ _\\ \\ \\/\\____/\n \\/___/ \\/_/\\/_/\\/__,_ /\\/____/ \\/_/ \\/___/ \\/____/\\/___/ \\/_/ \\/____/\\/_//\\ \\_\\ \\/___/\n \\ \\____/\n \\/___/\n\nUnderscore.js is a utility-belt library for JavaScript that provides\nsupport for the usual functional suspects (each, map, reduce, filter...)\nwithout extending any core JavaScript objects.\n\nFor Docs, License, Tests, and pre-packed downloads, see:\nhttp://underscorejs.org\n\nMany thanks to our contributors:\nhttps://github.com/documentcloud/underscore/contributors\n", + "readmeFilename": "README.md", + "bugs": { + "url": "https://github.com/documentcloud/underscore/issues" + }, + "_id": "underscore@1.4.4", + "dist": { + "shasum": "f5e657a6e6c96f20810ac45ff0021b99644c35bb" + }, + "_from": "underscore@~1.4.4", + "_resolved": "https://registry.npmjs.org/underscore/-/underscore-1.4.4.tgz" +} diff --git a/src/node_modules/nedb/node_modules/underscore/underscore-min.js b/src/node_modules/nedb/node_modules/underscore/underscore-min.js new file mode 100644 index 0000000..c1d9d3a --- /dev/null +++ b/src/node_modules/nedb/node_modules/underscore/underscore-min.js @@ -0,0 +1 @@ +(function(){var n=this,t=n._,r={},e=Array.prototype,u=Object.prototype,i=Function.prototype,a=e.push,o=e.slice,c=e.concat,l=u.toString,f=u.hasOwnProperty,s=e.forEach,p=e.map,h=e.reduce,v=e.reduceRight,d=e.filter,g=e.every,m=e.some,y=e.indexOf,b=e.lastIndexOf,x=Array.isArray,_=Object.keys,j=i.bind,w=function(n){return n instanceof w?n:this instanceof w?(this._wrapped=n,void 0):new w(n)};"undefined"!=typeof exports?("undefined"!=typeof module&&module.exports&&(exports=module.exports=w),exports._=w):n._=w,w.VERSION="1.4.4";var A=w.each=w.forEach=function(n,t,e){if(null!=n)if(s&&n.forEach===s)n.forEach(t,e);else if(n.length===+n.length){for(var u=0,i=n.length;i>u;u++)if(t.call(e,n[u],u,n)===r)return}else for(var a in n)if(w.has(n,a)&&t.call(e,n[a],a,n)===r)return};w.map=w.collect=function(n,t,r){var e=[];return null==n?e:p&&n.map===p?n.map(t,r):(A(n,function(n,u,i){e[e.length]=t.call(r,n,u,i)}),e)};var O="Reduce of empty array with no initial value";w.reduce=w.foldl=w.inject=function(n,t,r,e){var u=arguments.length>2;if(null==n&&(n=[]),h&&n.reduce===h)return e&&(t=w.bind(t,e)),u?n.reduce(t,r):n.reduce(t);if(A(n,function(n,i,a){u?r=t.call(e,r,n,i,a):(r=n,u=!0)}),!u)throw new TypeError(O);return r},w.reduceRight=w.foldr=function(n,t,r,e){var u=arguments.length>2;if(null==n&&(n=[]),v&&n.reduceRight===v)return e&&(t=w.bind(t,e)),u?n.reduceRight(t,r):n.reduceRight(t);var i=n.length;if(i!==+i){var a=w.keys(n);i=a.length}if(A(n,function(o,c,l){c=a?a[--i]:--i,u?r=t.call(e,r,n[c],c,l):(r=n[c],u=!0)}),!u)throw new TypeError(O);return r},w.find=w.detect=function(n,t,r){var e;return E(n,function(n,u,i){return t.call(r,n,u,i)?(e=n,!0):void 0}),e},w.filter=w.select=function(n,t,r){var e=[];return null==n?e:d&&n.filter===d?n.filter(t,r):(A(n,function(n,u,i){t.call(r,n,u,i)&&(e[e.length]=n)}),e)},w.reject=function(n,t,r){return w.filter(n,function(n,e,u){return!t.call(r,n,e,u)},r)},w.every=w.all=function(n,t,e){t||(t=w.identity);var u=!0;return null==n?u:g&&n.every===g?n.every(t,e):(A(n,function(n,i,a){return(u=u&&t.call(e,n,i,a))?void 0:r}),!!u)};var E=w.some=w.any=function(n,t,e){t||(t=w.identity);var u=!1;return null==n?u:m&&n.some===m?n.some(t,e):(A(n,function(n,i,a){return u||(u=t.call(e,n,i,a))?r:void 0}),!!u)};w.contains=w.include=function(n,t){return null==n?!1:y&&n.indexOf===y?n.indexOf(t)!=-1:E(n,function(n){return n===t})},w.invoke=function(n,t){var r=o.call(arguments,2),e=w.isFunction(t);return w.map(n,function(n){return(e?t:n[t]).apply(n,r)})},w.pluck=function(n,t){return w.map(n,function(n){return n[t]})},w.where=function(n,t,r){return w.isEmpty(t)?r?null:[]:w[r?"find":"filter"](n,function(n){for(var r in t)if(t[r]!==n[r])return!1;return!0})},w.findWhere=function(n,t){return w.where(n,t,!0)},w.max=function(n,t,r){if(!t&&w.isArray(n)&&n[0]===+n[0]&&65535>n.length)return Math.max.apply(Math,n);if(!t&&w.isEmpty(n))return-1/0;var e={computed:-1/0,value:-1/0};return A(n,function(n,u,i){var a=t?t.call(r,n,u,i):n;a>=e.computed&&(e={value:n,computed:a})}),e.value},w.min=function(n,t,r){if(!t&&w.isArray(n)&&n[0]===+n[0]&&65535>n.length)return Math.min.apply(Math,n);if(!t&&w.isEmpty(n))return 1/0;var e={computed:1/0,value:1/0};return A(n,function(n,u,i){var a=t?t.call(r,n,u,i):n;e.computed>a&&(e={value:n,computed:a})}),e.value},w.shuffle=function(n){var t,r=0,e=[];return A(n,function(n){t=w.random(r++),e[r-1]=e[t],e[t]=n}),e};var k=function(n){return w.isFunction(n)?n:function(t){return t[n]}};w.sortBy=function(n,t,r){var e=k(t);return w.pluck(w.map(n,function(n,t,u){return{value:n,index:t,criteria:e.call(r,n,t,u)}}).sort(function(n,t){var r=n.criteria,e=t.criteria;if(r!==e){if(r>e||r===void 0)return 1;if(e>r||e===void 0)return-1}return n.indexi;){var o=i+a>>>1;u>r.call(e,n[o])?i=o+1:a=o}return i},w.toArray=function(n){return n?w.isArray(n)?o.call(n):n.length===+n.length?w.map(n,w.identity):w.values(n):[]},w.size=function(n){return null==n?0:n.length===+n.length?n.length:w.keys(n).length},w.first=w.head=w.take=function(n,t,r){return null==n?void 0:null==t||r?n[0]:o.call(n,0,t)},w.initial=function(n,t,r){return o.call(n,0,n.length-(null==t||r?1:t))},w.last=function(n,t,r){return null==n?void 0:null==t||r?n[n.length-1]:o.call(n,Math.max(n.length-t,0))},w.rest=w.tail=w.drop=function(n,t,r){return o.call(n,null==t||r?1:t)},w.compact=function(n){return w.filter(n,w.identity)};var R=function(n,t,r){return A(n,function(n){w.isArray(n)?t?a.apply(r,n):R(n,t,r):r.push(n)}),r};w.flatten=function(n,t){return R(n,t,[])},w.without=function(n){return w.difference(n,o.call(arguments,1))},w.uniq=w.unique=function(n,t,r,e){w.isFunction(t)&&(e=r,r=t,t=!1);var u=r?w.map(n,r,e):n,i=[],a=[];return A(u,function(r,e){(t?e&&a[a.length-1]===r:w.contains(a,r))||(a.push(r),i.push(n[e]))}),i},w.union=function(){return w.uniq(c.apply(e,arguments))},w.intersection=function(n){var t=o.call(arguments,1);return w.filter(w.uniq(n),function(n){return w.every(t,function(t){return w.indexOf(t,n)>=0})})},w.difference=function(n){var t=c.apply(e,o.call(arguments,1));return w.filter(n,function(n){return!w.contains(t,n)})},w.zip=function(){for(var n=o.call(arguments),t=w.max(w.pluck(n,"length")),r=Array(t),e=0;t>e;e++)r[e]=w.pluck(n,""+e);return r},w.object=function(n,t){if(null==n)return{};for(var r={},e=0,u=n.length;u>e;e++)t?r[n[e]]=t[e]:r[n[e][0]]=n[e][1];return r},w.indexOf=function(n,t,r){if(null==n)return-1;var e=0,u=n.length;if(r){if("number"!=typeof r)return e=w.sortedIndex(n,t),n[e]===t?e:-1;e=0>r?Math.max(0,u+r):r}if(y&&n.indexOf===y)return n.indexOf(t,r);for(;u>e;e++)if(n[e]===t)return e;return-1},w.lastIndexOf=function(n,t,r){if(null==n)return-1;var e=null!=r;if(b&&n.lastIndexOf===b)return e?n.lastIndexOf(t,r):n.lastIndexOf(t);for(var u=e?r:n.length;u--;)if(n[u]===t)return u;return-1},w.range=function(n,t,r){1>=arguments.length&&(t=n||0,n=0),r=arguments[2]||1;for(var e=Math.max(Math.ceil((t-n)/r),0),u=0,i=Array(e);e>u;)i[u++]=n,n+=r;return i},w.bind=function(n,t){if(n.bind===j&&j)return j.apply(n,o.call(arguments,1));var r=o.call(arguments,2);return function(){return n.apply(t,r.concat(o.call(arguments)))}},w.partial=function(n){var t=o.call(arguments,1);return function(){return n.apply(this,t.concat(o.call(arguments)))}},w.bindAll=function(n){var t=o.call(arguments,1);return 0===t.length&&(t=w.functions(n)),A(t,function(t){n[t]=w.bind(n[t],n)}),n},w.memoize=function(n,t){var r={};return t||(t=w.identity),function(){var e=t.apply(this,arguments);return w.has(r,e)?r[e]:r[e]=n.apply(this,arguments)}},w.delay=function(n,t){var r=o.call(arguments,2);return setTimeout(function(){return n.apply(null,r)},t)},w.defer=function(n){return w.delay.apply(w,[n,1].concat(o.call(arguments,1)))},w.throttle=function(n,t){var r,e,u,i,a=0,o=function(){a=new Date,u=null,i=n.apply(r,e)};return function(){var c=new Date,l=t-(c-a);return r=this,e=arguments,0>=l?(clearTimeout(u),u=null,a=c,i=n.apply(r,e)):u||(u=setTimeout(o,l)),i}},w.debounce=function(n,t,r){var e,u;return function(){var i=this,a=arguments,o=function(){e=null,r||(u=n.apply(i,a))},c=r&&!e;return clearTimeout(e),e=setTimeout(o,t),c&&(u=n.apply(i,a)),u}},w.once=function(n){var t,r=!1;return function(){return r?t:(r=!0,t=n.apply(this,arguments),n=null,t)}},w.wrap=function(n,t){return function(){var r=[n];return a.apply(r,arguments),t.apply(this,r)}},w.compose=function(){var n=arguments;return function(){for(var t=arguments,r=n.length-1;r>=0;r--)t=[n[r].apply(this,t)];return t[0]}},w.after=function(n,t){return 0>=n?t():function(){return 1>--n?t.apply(this,arguments):void 0}},w.keys=_||function(n){if(n!==Object(n))throw new TypeError("Invalid object");var t=[];for(var r in n)w.has(n,r)&&(t[t.length]=r);return t},w.values=function(n){var t=[];for(var r in n)w.has(n,r)&&t.push(n[r]);return t},w.pairs=function(n){var t=[];for(var r in n)w.has(n,r)&&t.push([r,n[r]]);return t},w.invert=function(n){var t={};for(var r in n)w.has(n,r)&&(t[n[r]]=r);return t},w.functions=w.methods=function(n){var t=[];for(var r in n)w.isFunction(n[r])&&t.push(r);return t.sort()},w.extend=function(n){return A(o.call(arguments,1),function(t){if(t)for(var r in t)n[r]=t[r]}),n},w.pick=function(n){var t={},r=c.apply(e,o.call(arguments,1));return A(r,function(r){r in n&&(t[r]=n[r])}),t},w.omit=function(n){var t={},r=c.apply(e,o.call(arguments,1));for(var u in n)w.contains(r,u)||(t[u]=n[u]);return t},w.defaults=function(n){return A(o.call(arguments,1),function(t){if(t)for(var r in t)null==n[r]&&(n[r]=t[r])}),n},w.clone=function(n){return w.isObject(n)?w.isArray(n)?n.slice():w.extend({},n):n},w.tap=function(n,t){return t(n),n};var I=function(n,t,r,e){if(n===t)return 0!==n||1/n==1/t;if(null==n||null==t)return n===t;n instanceof w&&(n=n._wrapped),t instanceof w&&(t=t._wrapped);var u=l.call(n);if(u!=l.call(t))return!1;switch(u){case"[object String]":return n==t+"";case"[object Number]":return n!=+n?t!=+t:0==n?1/n==1/t:n==+t;case"[object Date]":case"[object Boolean]":return+n==+t;case"[object RegExp]":return n.source==t.source&&n.global==t.global&&n.multiline==t.multiline&&n.ignoreCase==t.ignoreCase}if("object"!=typeof n||"object"!=typeof t)return!1;for(var i=r.length;i--;)if(r[i]==n)return e[i]==t;r.push(n),e.push(t);var a=0,o=!0;if("[object Array]"==u){if(a=n.length,o=a==t.length)for(;a--&&(o=I(n[a],t[a],r,e)););}else{var c=n.constructor,f=t.constructor;if(c!==f&&!(w.isFunction(c)&&c instanceof c&&w.isFunction(f)&&f instanceof f))return!1;for(var s in n)if(w.has(n,s)&&(a++,!(o=w.has(t,s)&&I(n[s],t[s],r,e))))break;if(o){for(s in t)if(w.has(t,s)&&!a--)break;o=!a}}return r.pop(),e.pop(),o};w.isEqual=function(n,t){return I(n,t,[],[])},w.isEmpty=function(n){if(null==n)return!0;if(w.isArray(n)||w.isString(n))return 0===n.length;for(var t in n)if(w.has(n,t))return!1;return!0},w.isElement=function(n){return!(!n||1!==n.nodeType)},w.isArray=x||function(n){return"[object Array]"==l.call(n)},w.isObject=function(n){return n===Object(n)},A(["Arguments","Function","String","Number","Date","RegExp"],function(n){w["is"+n]=function(t){return l.call(t)=="[object "+n+"]"}}),w.isArguments(arguments)||(w.isArguments=function(n){return!(!n||!w.has(n,"callee"))}),"function"!=typeof/./&&(w.isFunction=function(n){return"function"==typeof n}),w.isFinite=function(n){return isFinite(n)&&!isNaN(parseFloat(n))},w.isNaN=function(n){return w.isNumber(n)&&n!=+n},w.isBoolean=function(n){return n===!0||n===!1||"[object Boolean]"==l.call(n)},w.isNull=function(n){return null===n},w.isUndefined=function(n){return n===void 0},w.has=function(n,t){return f.call(n,t)},w.noConflict=function(){return n._=t,this},w.identity=function(n){return n},w.times=function(n,t,r){for(var e=Array(n),u=0;n>u;u++)e[u]=t.call(r,u);return e},w.random=function(n,t){return null==t&&(t=n,n=0),n+Math.floor(Math.random()*(t-n+1))};var M={escape:{"&":"&","<":"<",">":">",'"':""","'":"'","/":"/"}};M.unescape=w.invert(M.escape);var S={escape:RegExp("["+w.keys(M.escape).join("")+"]","g"),unescape:RegExp("("+w.keys(M.unescape).join("|")+")","g")};w.each(["escape","unescape"],function(n){w[n]=function(t){return null==t?"":(""+t).replace(S[n],function(t){return M[n][t]})}}),w.result=function(n,t){if(null==n)return null;var r=n[t];return w.isFunction(r)?r.call(n):r},w.mixin=function(n){A(w.functions(n),function(t){var r=w[t]=n[t];w.prototype[t]=function(){var n=[this._wrapped];return a.apply(n,arguments),D.call(this,r.apply(w,n))}})};var N=0;w.uniqueId=function(n){var t=++N+"";return n?n+t:t},w.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var T=/(.)^/,q={"'":"'","\\":"\\","\r":"r","\n":"n"," ":"t","\u2028":"u2028","\u2029":"u2029"},B=/\\|'|\r|\n|\t|\u2028|\u2029/g;w.template=function(n,t,r){var e;r=w.defaults({},r,w.templateSettings);var u=RegExp([(r.escape||T).source,(r.interpolate||T).source,(r.evaluate||T).source].join("|")+"|$","g"),i=0,a="__p+='";n.replace(u,function(t,r,e,u,o){return a+=n.slice(i,o).replace(B,function(n){return"\\"+q[n]}),r&&(a+="'+\n((__t=("+r+"))==null?'':_.escape(__t))+\n'"),e&&(a+="'+\n((__t=("+e+"))==null?'':__t)+\n'"),u&&(a+="';\n"+u+"\n__p+='"),i=o+t.length,t}),a+="';\n",r.variable||(a="with(obj||{}){\n"+a+"}\n"),a="var __t,__p='',__j=Array.prototype.join,"+"print=function(){__p+=__j.call(arguments,'');};\n"+a+"return __p;\n";try{e=Function(r.variable||"obj","_",a)}catch(o){throw o.source=a,o}if(t)return e(t,w);var c=function(n){return e.call(this,n,w)};return c.source="function("+(r.variable||"obj")+"){\n"+a+"}",c},w.chain=function(n){return w(n).chain()};var D=function(n){return this._chain?w(n).chain():n};w.mixin(w),A(["pop","push","reverse","shift","sort","splice","unshift"],function(n){var t=e[n];w.prototype[n]=function(){var r=this._wrapped;return t.apply(r,arguments),"shift"!=n&&"splice"!=n||0!==r.length||delete r[0],D.call(this,r)}}),A(["concat","join","slice"],function(n){var t=e[n];w.prototype[n]=function(){return D.call(this,t.apply(this._wrapped,arguments))}}),w.extend(w.prototype,{chain:function(){return this._chain=!0,this},value:function(){return this._wrapped}})}).call(this); \ No newline at end of file diff --git a/src/node_modules/nedb/node_modules/underscore/underscore.js b/src/node_modules/nedb/node_modules/underscore/underscore.js new file mode 100644 index 0000000..a12f0d9 --- /dev/null +++ b/src/node_modules/nedb/node_modules/underscore/underscore.js @@ -0,0 +1,1226 @@ +// Underscore.js 1.4.4 +// http://underscorejs.org +// (c) 2009-2013 Jeremy Ashkenas, DocumentCloud Inc. +// Underscore may be freely distributed under the MIT license. + +(function() { + + // Baseline setup + // -------------- + + // Establish the root object, `window` in the browser, or `global` on the server. + var root = this; + + // Save the previous value of the `_` variable. + var previousUnderscore = root._; + + // Establish the object that gets returned to break out of a loop iteration. + var breaker = {}; + + // Save bytes in the minified (but not gzipped) version: + var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype; + + // Create quick reference variables for speed access to core prototypes. + var push = ArrayProto.push, + slice = ArrayProto.slice, + concat = ArrayProto.concat, + toString = ObjProto.toString, + hasOwnProperty = ObjProto.hasOwnProperty; + + // All **ECMAScript 5** native function implementations that we hope to use + // are declared here. + var + nativeForEach = ArrayProto.forEach, + nativeMap = ArrayProto.map, + nativeReduce = ArrayProto.reduce, + nativeReduceRight = ArrayProto.reduceRight, + nativeFilter = ArrayProto.filter, + nativeEvery = ArrayProto.every, + nativeSome = ArrayProto.some, + nativeIndexOf = ArrayProto.indexOf, + nativeLastIndexOf = ArrayProto.lastIndexOf, + nativeIsArray = Array.isArray, + nativeKeys = Object.keys, + nativeBind = FuncProto.bind; + + // Create a safe reference to the Underscore object for use below. + var _ = function(obj) { + if (obj instanceof _) return obj; + if (!(this instanceof _)) return new _(obj); + this._wrapped = obj; + }; + + // Export the Underscore object for **Node.js**, with + // backwards-compatibility for the old `require()` API. If we're in + // the browser, add `_` as a global object via a string identifier, + // for Closure Compiler "advanced" mode. + if (typeof exports !== 'undefined') { + if (typeof module !== 'undefined' && module.exports) { + exports = module.exports = _; + } + exports._ = _; + } else { + root._ = _; + } + + // Current version. + _.VERSION = '1.4.4'; + + // Collection Functions + // -------------------- + + // The cornerstone, an `each` implementation, aka `forEach`. + // Handles objects with the built-in `forEach`, arrays, and raw objects. + // Delegates to **ECMAScript 5**'s native `forEach` if available. + var each = _.each = _.forEach = function(obj, iterator, context) { + if (obj == null) return; + if (nativeForEach && obj.forEach === nativeForEach) { + obj.forEach(iterator, context); + } else if (obj.length === +obj.length) { + for (var i = 0, l = obj.length; i < l; i++) { + if (iterator.call(context, obj[i], i, obj) === breaker) return; + } + } else { + for (var key in obj) { + if (_.has(obj, key)) { + if (iterator.call(context, obj[key], key, obj) === breaker) return; + } + } + } + }; + + // Return the results of applying the iterator to each element. + // Delegates to **ECMAScript 5**'s native `map` if available. + _.map = _.collect = function(obj, iterator, context) { + var results = []; + if (obj == null) return results; + if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context); + each(obj, function(value, index, list) { + results[results.length] = iterator.call(context, value, index, list); + }); + return results; + }; + + var reduceError = 'Reduce of empty array with no initial value'; + + // **Reduce** builds up a single result from a list of values, aka `inject`, + // or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available. + _.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) { + var initial = arguments.length > 2; + if (obj == null) obj = []; + if (nativeReduce && obj.reduce === nativeReduce) { + if (context) iterator = _.bind(iterator, context); + return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator); + } + each(obj, function(value, index, list) { + if (!initial) { + memo = value; + initial = true; + } else { + memo = iterator.call(context, memo, value, index, list); + } + }); + if (!initial) throw new TypeError(reduceError); + return memo; + }; + + // The right-associative version of reduce, also known as `foldr`. + // Delegates to **ECMAScript 5**'s native `reduceRight` if available. + _.reduceRight = _.foldr = function(obj, iterator, memo, context) { + var initial = arguments.length > 2; + if (obj == null) obj = []; + if (nativeReduceRight && obj.reduceRight === nativeReduceRight) { + if (context) iterator = _.bind(iterator, context); + return initial ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator); + } + var length = obj.length; + if (length !== +length) { + var keys = _.keys(obj); + length = keys.length; + } + each(obj, function(value, index, list) { + index = keys ? keys[--length] : --length; + if (!initial) { + memo = obj[index]; + initial = true; + } else { + memo = iterator.call(context, memo, obj[index], index, list); + } + }); + if (!initial) throw new TypeError(reduceError); + return memo; + }; + + // Return the first value which passes a truth test. Aliased as `detect`. + _.find = _.detect = function(obj, iterator, context) { + var result; + any(obj, function(value, index, list) { + if (iterator.call(context, value, index, list)) { + result = value; + return true; + } + }); + return result; + }; + + // Return all the elements that pass a truth test. + // Delegates to **ECMAScript 5**'s native `filter` if available. + // Aliased as `select`. + _.filter = _.select = function(obj, iterator, context) { + var results = []; + if (obj == null) return results; + if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context); + each(obj, function(value, index, list) { + if (iterator.call(context, value, index, list)) results[results.length] = value; + }); + return results; + }; + + // Return all the elements for which a truth test fails. + _.reject = function(obj, iterator, context) { + return _.filter(obj, function(value, index, list) { + return !iterator.call(context, value, index, list); + }, context); + }; + + // Determine whether all of the elements match a truth test. + // Delegates to **ECMAScript 5**'s native `every` if available. + // Aliased as `all`. + _.every = _.all = function(obj, iterator, context) { + iterator || (iterator = _.identity); + var result = true; + if (obj == null) return result; + if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context); + each(obj, function(value, index, list) { + if (!(result = result && iterator.call(context, value, index, list))) return breaker; + }); + return !!result; + }; + + // Determine if at least one element in the object matches a truth test. + // Delegates to **ECMAScript 5**'s native `some` if available. + // Aliased as `any`. + var any = _.some = _.any = function(obj, iterator, context) { + iterator || (iterator = _.identity); + var result = false; + if (obj == null) return result; + if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context); + each(obj, function(value, index, list) { + if (result || (result = iterator.call(context, value, index, list))) return breaker; + }); + return !!result; + }; + + // Determine if the array or object contains a given value (using `===`). + // Aliased as `include`. + _.contains = _.include = function(obj, target) { + if (obj == null) return false; + if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1; + return any(obj, function(value) { + return value === target; + }); + }; + + // Invoke a method (with arguments) on every item in a collection. + _.invoke = function(obj, method) { + var args = slice.call(arguments, 2); + var isFunc = _.isFunction(method); + return _.map(obj, function(value) { + return (isFunc ? method : value[method]).apply(value, args); + }); + }; + + // Convenience version of a common use case of `map`: fetching a property. + _.pluck = function(obj, key) { + return _.map(obj, function(value){ return value[key]; }); + }; + + // Convenience version of a common use case of `filter`: selecting only objects + // containing specific `key:value` pairs. + _.where = function(obj, attrs, first) { + if (_.isEmpty(attrs)) return first ? null : []; + return _[first ? 'find' : 'filter'](obj, function(value) { + for (var key in attrs) { + if (attrs[key] !== value[key]) return false; + } + return true; + }); + }; + + // Convenience version of a common use case of `find`: getting the first object + // containing specific `key:value` pairs. + _.findWhere = function(obj, attrs) { + return _.where(obj, attrs, true); + }; + + // Return the maximum element or (element-based computation). + // Can't optimize arrays of integers longer than 65,535 elements. + // See: https://bugs.webkit.org/show_bug.cgi?id=80797 + _.max = function(obj, iterator, context) { + if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) { + return Math.max.apply(Math, obj); + } + if (!iterator && _.isEmpty(obj)) return -Infinity; + var result = {computed : -Infinity, value: -Infinity}; + each(obj, function(value, index, list) { + var computed = iterator ? iterator.call(context, value, index, list) : value; + computed >= result.computed && (result = {value : value, computed : computed}); + }); + return result.value; + }; + + // Return the minimum element (or element-based computation). + _.min = function(obj, iterator, context) { + if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) { + return Math.min.apply(Math, obj); + } + if (!iterator && _.isEmpty(obj)) return Infinity; + var result = {computed : Infinity, value: Infinity}; + each(obj, function(value, index, list) { + var computed = iterator ? iterator.call(context, value, index, list) : value; + computed < result.computed && (result = {value : value, computed : computed}); + }); + return result.value; + }; + + // Shuffle an array. + _.shuffle = function(obj) { + var rand; + var index = 0; + var shuffled = []; + each(obj, function(value) { + rand = _.random(index++); + shuffled[index - 1] = shuffled[rand]; + shuffled[rand] = value; + }); + return shuffled; + }; + + // An internal function to generate lookup iterators. + var lookupIterator = function(value) { + return _.isFunction(value) ? value : function(obj){ return obj[value]; }; + }; + + // Sort the object's values by a criterion produced by an iterator. + _.sortBy = function(obj, value, context) { + var iterator = lookupIterator(value); + return _.pluck(_.map(obj, function(value, index, list) { + return { + value : value, + index : index, + criteria : iterator.call(context, value, index, list) + }; + }).sort(function(left, right) { + var a = left.criteria; + var b = right.criteria; + if (a !== b) { + if (a > b || a === void 0) return 1; + if (a < b || b === void 0) return -1; + } + return left.index < right.index ? -1 : 1; + }), 'value'); + }; + + // An internal function used for aggregate "group by" operations. + var group = function(obj, value, context, behavior) { + var result = {}; + var iterator = lookupIterator(value || _.identity); + each(obj, function(value, index) { + var key = iterator.call(context, value, index, obj); + behavior(result, key, value); + }); + return result; + }; + + // Groups the object's values by a criterion. Pass either a string attribute + // to group by, or a function that returns the criterion. + _.groupBy = function(obj, value, context) { + return group(obj, value, context, function(result, key, value) { + (_.has(result, key) ? result[key] : (result[key] = [])).push(value); + }); + }; + + // Counts instances of an object that group by a certain criterion. Pass + // either a string attribute to count by, or a function that returns the + // criterion. + _.countBy = function(obj, value, context) { + return group(obj, value, context, function(result, key) { + if (!_.has(result, key)) result[key] = 0; + result[key]++; + }); + }; + + // Use a comparator function to figure out the smallest index at which + // an object should be inserted so as to maintain order. Uses binary search. + _.sortedIndex = function(array, obj, iterator, context) { + iterator = iterator == null ? _.identity : lookupIterator(iterator); + var value = iterator.call(context, obj); + var low = 0, high = array.length; + while (low < high) { + var mid = (low + high) >>> 1; + iterator.call(context, array[mid]) < value ? low = mid + 1 : high = mid; + } + return low; + }; + + // Safely convert anything iterable into a real, live array. + _.toArray = function(obj) { + if (!obj) return []; + if (_.isArray(obj)) return slice.call(obj); + if (obj.length === +obj.length) return _.map(obj, _.identity); + return _.values(obj); + }; + + // Return the number of elements in an object. + _.size = function(obj) { + if (obj == null) return 0; + return (obj.length === +obj.length) ? obj.length : _.keys(obj).length; + }; + + // Array Functions + // --------------- + + // Get the first element of an array. Passing **n** will return the first N + // values in the array. Aliased as `head` and `take`. The **guard** check + // allows it to work with `_.map`. + _.first = _.head = _.take = function(array, n, guard) { + if (array == null) return void 0; + return (n != null) && !guard ? slice.call(array, 0, n) : array[0]; + }; + + // Returns everything but the last entry of the array. Especially useful on + // the arguments object. Passing **n** will return all the values in + // the array, excluding the last N. The **guard** check allows it to work with + // `_.map`. + _.initial = function(array, n, guard) { + return slice.call(array, 0, array.length - ((n == null) || guard ? 1 : n)); + }; + + // Get the last element of an array. Passing **n** will return the last N + // values in the array. The **guard** check allows it to work with `_.map`. + _.last = function(array, n, guard) { + if (array == null) return void 0; + if ((n != null) && !guard) { + return slice.call(array, Math.max(array.length - n, 0)); + } else { + return array[array.length - 1]; + } + }; + + // Returns everything but the first entry of the array. Aliased as `tail` and `drop`. + // Especially useful on the arguments object. Passing an **n** will return + // the rest N values in the array. The **guard** + // check allows it to work with `_.map`. + _.rest = _.tail = _.drop = function(array, n, guard) { + return slice.call(array, (n == null) || guard ? 1 : n); + }; + + // Trim out all falsy values from an array. + _.compact = function(array) { + return _.filter(array, _.identity); + }; + + // Internal implementation of a recursive `flatten` function. + var flatten = function(input, shallow, output) { + each(input, function(value) { + if (_.isArray(value)) { + shallow ? push.apply(output, value) : flatten(value, shallow, output); + } else { + output.push(value); + } + }); + return output; + }; + + // Return a completely flattened version of an array. + _.flatten = function(array, shallow) { + return flatten(array, shallow, []); + }; + + // Return a version of the array that does not contain the specified value(s). + _.without = function(array) { + return _.difference(array, slice.call(arguments, 1)); + }; + + // Produce a duplicate-free version of the array. If the array has already + // been sorted, you have the option of using a faster algorithm. + // Aliased as `unique`. + _.uniq = _.unique = function(array, isSorted, iterator, context) { + if (_.isFunction(isSorted)) { + context = iterator; + iterator = isSorted; + isSorted = false; + } + var initial = iterator ? _.map(array, iterator, context) : array; + var results = []; + var seen = []; + each(initial, function(value, index) { + if (isSorted ? (!index || seen[seen.length - 1] !== value) : !_.contains(seen, value)) { + seen.push(value); + results.push(array[index]); + } + }); + return results; + }; + + // Produce an array that contains the union: each distinct element from all of + // the passed-in arrays. + _.union = function() { + return _.uniq(concat.apply(ArrayProto, arguments)); + }; + + // Produce an array that contains every item shared between all the + // passed-in arrays. + _.intersection = function(array) { + var rest = slice.call(arguments, 1); + return _.filter(_.uniq(array), function(item) { + return _.every(rest, function(other) { + return _.indexOf(other, item) >= 0; + }); + }); + }; + + // Take the difference between one array and a number of other arrays. + // Only the elements present in just the first array will remain. + _.difference = function(array) { + var rest = concat.apply(ArrayProto, slice.call(arguments, 1)); + return _.filter(array, function(value){ return !_.contains(rest, value); }); + }; + + // Zip together multiple lists into a single array -- elements that share + // an index go together. + _.zip = function() { + var args = slice.call(arguments); + var length = _.max(_.pluck(args, 'length')); + var results = new Array(length); + for (var i = 0; i < length; i++) { + results[i] = _.pluck(args, "" + i); + } + return results; + }; + + // Converts lists into objects. Pass either a single array of `[key, value]` + // pairs, or two parallel arrays of the same length -- one of keys, and one of + // the corresponding values. + _.object = function(list, values) { + if (list == null) return {}; + var result = {}; + for (var i = 0, l = list.length; i < l; i++) { + if (values) { + result[list[i]] = values[i]; + } else { + result[list[i][0]] = list[i][1]; + } + } + return result; + }; + + // If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**), + // we need this function. Return the position of the first occurrence of an + // item in an array, or -1 if the item is not included in the array. + // Delegates to **ECMAScript 5**'s native `indexOf` if available. + // If the array is large and already in sort order, pass `true` + // for **isSorted** to use binary search. + _.indexOf = function(array, item, isSorted) { + if (array == null) return -1; + var i = 0, l = array.length; + if (isSorted) { + if (typeof isSorted == 'number') { + i = (isSorted < 0 ? Math.max(0, l + isSorted) : isSorted); + } else { + i = _.sortedIndex(array, item); + return array[i] === item ? i : -1; + } + } + if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item, isSorted); + for (; i < l; i++) if (array[i] === item) return i; + return -1; + }; + + // Delegates to **ECMAScript 5**'s native `lastIndexOf` if available. + _.lastIndexOf = function(array, item, from) { + if (array == null) return -1; + var hasIndex = from != null; + if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) { + return hasIndex ? array.lastIndexOf(item, from) : array.lastIndexOf(item); + } + var i = (hasIndex ? from : array.length); + while (i--) if (array[i] === item) return i; + return -1; + }; + + // Generate an integer Array containing an arithmetic progression. A port of + // the native Python `range()` function. See + // [the Python documentation](http://docs.python.org/library/functions.html#range). + _.range = function(start, stop, step) { + if (arguments.length <= 1) { + stop = start || 0; + start = 0; + } + step = arguments[2] || 1; + + var len = Math.max(Math.ceil((stop - start) / step), 0); + var idx = 0; + var range = new Array(len); + + while(idx < len) { + range[idx++] = start; + start += step; + } + + return range; + }; + + // Function (ahem) Functions + // ------------------ + + // Create a function bound to a given object (assigning `this`, and arguments, + // optionally). Delegates to **ECMAScript 5**'s native `Function.bind` if + // available. + _.bind = function(func, context) { + if (func.bind === nativeBind && nativeBind) return nativeBind.apply(func, slice.call(arguments, 1)); + var args = slice.call(arguments, 2); + return function() { + return func.apply(context, args.concat(slice.call(arguments))); + }; + }; + + // Partially apply a function by creating a version that has had some of its + // arguments pre-filled, without changing its dynamic `this` context. + _.partial = function(func) { + var args = slice.call(arguments, 1); + return function() { + return func.apply(this, args.concat(slice.call(arguments))); + }; + }; + + // Bind all of an object's methods to that object. Useful for ensuring that + // all callbacks defined on an object belong to it. + _.bindAll = function(obj) { + var funcs = slice.call(arguments, 1); + if (funcs.length === 0) funcs = _.functions(obj); + each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); }); + return obj; + }; + + // Memoize an expensive function by storing its results. + _.memoize = function(func, hasher) { + var memo = {}; + hasher || (hasher = _.identity); + return function() { + var key = hasher.apply(this, arguments); + return _.has(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments)); + }; + }; + + // Delays a function for the given number of milliseconds, and then calls + // it with the arguments supplied. + _.delay = function(func, wait) { + var args = slice.call(arguments, 2); + return setTimeout(function(){ return func.apply(null, args); }, wait); + }; + + // Defers a function, scheduling it to run after the current call stack has + // cleared. + _.defer = function(func) { + return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1))); + }; + + // Returns a function, that, when invoked, will only be triggered at most once + // during a given window of time. + _.throttle = function(func, wait) { + var context, args, timeout, result; + var previous = 0; + var later = function() { + previous = new Date; + timeout = null; + result = func.apply(context, args); + }; + return function() { + var now = new Date; + var remaining = wait - (now - previous); + context = this; + args = arguments; + if (remaining <= 0) { + clearTimeout(timeout); + timeout = null; + previous = now; + result = func.apply(context, args); + } else if (!timeout) { + timeout = setTimeout(later, remaining); + } + return result; + }; + }; + + // Returns a function, that, as long as it continues to be invoked, will not + // be triggered. The function will be called after it stops being called for + // N milliseconds. If `immediate` is passed, trigger the function on the + // leading edge, instead of the trailing. + _.debounce = function(func, wait, immediate) { + var timeout, result; + return function() { + var context = this, args = arguments; + var later = function() { + timeout = null; + if (!immediate) result = func.apply(context, args); + }; + var callNow = immediate && !timeout; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + if (callNow) result = func.apply(context, args); + return result; + }; + }; + + // Returns a function that will be executed at most one time, no matter how + // often you call it. Useful for lazy initialization. + _.once = function(func) { + var ran = false, memo; + return function() { + if (ran) return memo; + ran = true; + memo = func.apply(this, arguments); + func = null; + return memo; + }; + }; + + // Returns the first function passed as an argument to the second, + // allowing you to adjust arguments, run code before and after, and + // conditionally execute the original function. + _.wrap = function(func, wrapper) { + return function() { + var args = [func]; + push.apply(args, arguments); + return wrapper.apply(this, args); + }; + }; + + // Returns a function that is the composition of a list of functions, each + // consuming the return value of the function that follows. + _.compose = function() { + var funcs = arguments; + return function() { + var args = arguments; + for (var i = funcs.length - 1; i >= 0; i--) { + args = [funcs[i].apply(this, args)]; + } + return args[0]; + }; + }; + + // Returns a function that will only be executed after being called N times. + _.after = function(times, func) { + if (times <= 0) return func(); + return function() { + if (--times < 1) { + return func.apply(this, arguments); + } + }; + }; + + // Object Functions + // ---------------- + + // Retrieve the names of an object's properties. + // Delegates to **ECMAScript 5**'s native `Object.keys` + _.keys = nativeKeys || function(obj) { + if (obj !== Object(obj)) throw new TypeError('Invalid object'); + var keys = []; + for (var key in obj) if (_.has(obj, key)) keys[keys.length] = key; + return keys; + }; + + // Retrieve the values of an object's properties. + _.values = function(obj) { + var values = []; + for (var key in obj) if (_.has(obj, key)) values.push(obj[key]); + return values; + }; + + // Convert an object into a list of `[key, value]` pairs. + _.pairs = function(obj) { + var pairs = []; + for (var key in obj) if (_.has(obj, key)) pairs.push([key, obj[key]]); + return pairs; + }; + + // Invert the keys and values of an object. The values must be serializable. + _.invert = function(obj) { + var result = {}; + for (var key in obj) if (_.has(obj, key)) result[obj[key]] = key; + return result; + }; + + // Return a sorted list of the function names available on the object. + // Aliased as `methods` + _.functions = _.methods = function(obj) { + var names = []; + for (var key in obj) { + if (_.isFunction(obj[key])) names.push(key); + } + return names.sort(); + }; + + // Extend a given object with all the properties in passed-in object(s). + _.extend = function(obj) { + each(slice.call(arguments, 1), function(source) { + if (source) { + for (var prop in source) { + obj[prop] = source[prop]; + } + } + }); + return obj; + }; + + // Return a copy of the object only containing the whitelisted properties. + _.pick = function(obj) { + var copy = {}; + var keys = concat.apply(ArrayProto, slice.call(arguments, 1)); + each(keys, function(key) { + if (key in obj) copy[key] = obj[key]; + }); + return copy; + }; + + // Return a copy of the object without the blacklisted properties. + _.omit = function(obj) { + var copy = {}; + var keys = concat.apply(ArrayProto, slice.call(arguments, 1)); + for (var key in obj) { + if (!_.contains(keys, key)) copy[key] = obj[key]; + } + return copy; + }; + + // Fill in a given object with default properties. + _.defaults = function(obj) { + each(slice.call(arguments, 1), function(source) { + if (source) { + for (var prop in source) { + if (obj[prop] == null) obj[prop] = source[prop]; + } + } + }); + return obj; + }; + + // Create a (shallow-cloned) duplicate of an object. + _.clone = function(obj) { + if (!_.isObject(obj)) return obj; + return _.isArray(obj) ? obj.slice() : _.extend({}, obj); + }; + + // Invokes interceptor with the obj, and then returns obj. + // The primary purpose of this method is to "tap into" a method chain, in + // order to perform operations on intermediate results within the chain. + _.tap = function(obj, interceptor) { + interceptor(obj); + return obj; + }; + + // Internal recursive comparison function for `isEqual`. + var eq = function(a, b, aStack, bStack) { + // Identical objects are equal. `0 === -0`, but they aren't identical. + // See the Harmony `egal` proposal: http://wiki.ecmascript.org/doku.php?id=harmony:egal. + if (a === b) return a !== 0 || 1 / a == 1 / b; + // A strict comparison is necessary because `null == undefined`. + if (a == null || b == null) return a === b; + // Unwrap any wrapped objects. + if (a instanceof _) a = a._wrapped; + if (b instanceof _) b = b._wrapped; + // Compare `[[Class]]` names. + var className = toString.call(a); + if (className != toString.call(b)) return false; + switch (className) { + // Strings, numbers, dates, and booleans are compared by value. + case '[object String]': + // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is + // equivalent to `new String("5")`. + return a == String(b); + case '[object Number]': + // `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for + // other numeric values. + return a != +a ? b != +b : (a == 0 ? 1 / a == 1 / b : a == +b); + case '[object Date]': + case '[object Boolean]': + // Coerce dates and booleans to numeric primitive values. Dates are compared by their + // millisecond representations. Note that invalid dates with millisecond representations + // of `NaN` are not equivalent. + return +a == +b; + // RegExps are compared by their source patterns and flags. + case '[object RegExp]': + return a.source == b.source && + a.global == b.global && + a.multiline == b.multiline && + a.ignoreCase == b.ignoreCase; + } + if (typeof a != 'object' || typeof b != 'object') return false; + // Assume equality for cyclic structures. The algorithm for detecting cyclic + // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`. + var length = aStack.length; + while (length--) { + // Linear search. Performance is inversely proportional to the number of + // unique nested structures. + if (aStack[length] == a) return bStack[length] == b; + } + // Add the first object to the stack of traversed objects. + aStack.push(a); + bStack.push(b); + var size = 0, result = true; + // Recursively compare objects and arrays. + if (className == '[object Array]') { + // Compare array lengths to determine if a deep comparison is necessary. + size = a.length; + result = size == b.length; + if (result) { + // Deep compare the contents, ignoring non-numeric properties. + while (size--) { + if (!(result = eq(a[size], b[size], aStack, bStack))) break; + } + } + } else { + // Objects with different constructors are not equivalent, but `Object`s + // from different frames are. + var aCtor = a.constructor, bCtor = b.constructor; + if (aCtor !== bCtor && !(_.isFunction(aCtor) && (aCtor instanceof aCtor) && + _.isFunction(bCtor) && (bCtor instanceof bCtor))) { + return false; + } + // Deep compare objects. + for (var key in a) { + if (_.has(a, key)) { + // Count the expected number of properties. + size++; + // Deep compare each member. + if (!(result = _.has(b, key) && eq(a[key], b[key], aStack, bStack))) break; + } + } + // Ensure that both objects contain the same number of properties. + if (result) { + for (key in b) { + if (_.has(b, key) && !(size--)) break; + } + result = !size; + } + } + // Remove the first object from the stack of traversed objects. + aStack.pop(); + bStack.pop(); + return result; + }; + + // Perform a deep comparison to check if two objects are equal. + _.isEqual = function(a, b) { + return eq(a, b, [], []); + }; + + // Is a given array, string, or object empty? + // An "empty" object has no enumerable own-properties. + _.isEmpty = function(obj) { + if (obj == null) return true; + if (_.isArray(obj) || _.isString(obj)) return obj.length === 0; + for (var key in obj) if (_.has(obj, key)) return false; + return true; + }; + + // Is a given value a DOM element? + _.isElement = function(obj) { + return !!(obj && obj.nodeType === 1); + }; + + // Is a given value an array? + // Delegates to ECMA5's native Array.isArray + _.isArray = nativeIsArray || function(obj) { + return toString.call(obj) == '[object Array]'; + }; + + // Is a given variable an object? + _.isObject = function(obj) { + return obj === Object(obj); + }; + + // Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp. + each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp'], function(name) { + _['is' + name] = function(obj) { + return toString.call(obj) == '[object ' + name + ']'; + }; + }); + + // Define a fallback version of the method in browsers (ahem, IE), where + // there isn't any inspectable "Arguments" type. + if (!_.isArguments(arguments)) { + _.isArguments = function(obj) { + return !!(obj && _.has(obj, 'callee')); + }; + } + + // Optimize `isFunction` if appropriate. + if (typeof (/./) !== 'function') { + _.isFunction = function(obj) { + return typeof obj === 'function'; + }; + } + + // Is a given object a finite number? + _.isFinite = function(obj) { + return isFinite(obj) && !isNaN(parseFloat(obj)); + }; + + // Is the given value `NaN`? (NaN is the only number which does not equal itself). + _.isNaN = function(obj) { + return _.isNumber(obj) && obj != +obj; + }; + + // Is a given value a boolean? + _.isBoolean = function(obj) { + return obj === true || obj === false || toString.call(obj) == '[object Boolean]'; + }; + + // Is a given value equal to null? + _.isNull = function(obj) { + return obj === null; + }; + + // Is a given variable undefined? + _.isUndefined = function(obj) { + return obj === void 0; + }; + + // Shortcut function for checking if an object has a given property directly + // on itself (in other words, not on a prototype). + _.has = function(obj, key) { + return hasOwnProperty.call(obj, key); + }; + + // Utility Functions + // ----------------- + + // Run Underscore.js in *noConflict* mode, returning the `_` variable to its + // previous owner. Returns a reference to the Underscore object. + _.noConflict = function() { + root._ = previousUnderscore; + return this; + }; + + // Keep the identity function around for default iterators. + _.identity = function(value) { + return value; + }; + + // Run a function **n** times. + _.times = function(n, iterator, context) { + var accum = Array(n); + for (var i = 0; i < n; i++) accum[i] = iterator.call(context, i); + return accum; + }; + + // Return a random integer between min and max (inclusive). + _.random = function(min, max) { + if (max == null) { + max = min; + min = 0; + } + return min + Math.floor(Math.random() * (max - min + 1)); + }; + + // List of HTML entities for escaping. + var entityMap = { + escape: { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''', + '/': '/' + } + }; + entityMap.unescape = _.invert(entityMap.escape); + + // Regexes containing the keys and values listed immediately above. + var entityRegexes = { + escape: new RegExp('[' + _.keys(entityMap.escape).join('') + ']', 'g'), + unescape: new RegExp('(' + _.keys(entityMap.unescape).join('|') + ')', 'g') + }; + + // Functions for escaping and unescaping strings to/from HTML interpolation. + _.each(['escape', 'unescape'], function(method) { + _[method] = function(string) { + if (string == null) return ''; + return ('' + string).replace(entityRegexes[method], function(match) { + return entityMap[method][match]; + }); + }; + }); + + // If the value of the named property is a function then invoke it; + // otherwise, return it. + _.result = function(object, property) { + if (object == null) return null; + var value = object[property]; + return _.isFunction(value) ? value.call(object) : value; + }; + + // Add your own custom functions to the Underscore object. + _.mixin = function(obj) { + each(_.functions(obj), function(name){ + var func = _[name] = obj[name]; + _.prototype[name] = function() { + var args = [this._wrapped]; + push.apply(args, arguments); + return result.call(this, func.apply(_, args)); + }; + }); + }; + + // Generate a unique integer id (unique within the entire client session). + // Useful for temporary DOM ids. + var idCounter = 0; + _.uniqueId = function(prefix) { + var id = ++idCounter + ''; + return prefix ? prefix + id : id; + }; + + // By default, Underscore uses ERB-style template delimiters, change the + // following template settings to use alternative delimiters. + _.templateSettings = { + evaluate : /<%([\s\S]+?)%>/g, + interpolate : /<%=([\s\S]+?)%>/g, + escape : /<%-([\s\S]+?)%>/g + }; + + // When customizing `templateSettings`, if you don't want to define an + // interpolation, evaluation or escaping regex, we need one that is + // guaranteed not to match. + var noMatch = /(.)^/; + + // Certain characters need to be escaped so that they can be put into a + // string literal. + var escapes = { + "'": "'", + '\\': '\\', + '\r': 'r', + '\n': 'n', + '\t': 't', + '\u2028': 'u2028', + '\u2029': 'u2029' + }; + + var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g; + + // JavaScript micro-templating, similar to John Resig's implementation. + // Underscore templating handles arbitrary delimiters, preserves whitespace, + // and correctly escapes quotes within interpolated code. + _.template = function(text, data, settings) { + var render; + settings = _.defaults({}, settings, _.templateSettings); + + // Combine delimiters into one regular expression via alternation. + var matcher = new RegExp([ + (settings.escape || noMatch).source, + (settings.interpolate || noMatch).source, + (settings.evaluate || noMatch).source + ].join('|') + '|$', 'g'); + + // Compile the template source, escaping string literals appropriately. + var index = 0; + var source = "__p+='"; + text.replace(matcher, function(match, escape, interpolate, evaluate, offset) { + source += text.slice(index, offset) + .replace(escaper, function(match) { return '\\' + escapes[match]; }); + + if (escape) { + source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'"; + } + if (interpolate) { + source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'"; + } + if (evaluate) { + source += "';\n" + evaluate + "\n__p+='"; + } + index = offset + match.length; + return match; + }); + source += "';\n"; + + // If a variable is not specified, place data values in local scope. + if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n'; + + source = "var __t,__p='',__j=Array.prototype.join," + + "print=function(){__p+=__j.call(arguments,'');};\n" + + source + "return __p;\n"; + + try { + render = new Function(settings.variable || 'obj', '_', source); + } catch (e) { + e.source = source; + throw e; + } + + if (data) return render(data, _); + var template = function(data) { + return render.call(this, data, _); + }; + + // Provide the compiled function source as a convenience for precompilation. + template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}'; + + return template; + }; + + // Add a "chain" function, which will delegate to the wrapper. + _.chain = function(obj) { + return _(obj).chain(); + }; + + // OOP + // --------------- + // If Underscore is called as a function, it returns a wrapped object that + // can be used OO-style. This wrapper holds altered versions of all the + // underscore functions. Wrapped objects may be chained. + + // Helper function to continue chaining intermediate results. + var result = function(obj) { + return this._chain ? _(obj).chain() : obj; + }; + + // Add all of the Underscore functions to the wrapper object. + _.mixin(_); + + // Add all mutator Array functions to the wrapper. + each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) { + var method = ArrayProto[name]; + _.prototype[name] = function() { + var obj = this._wrapped; + method.apply(obj, arguments); + if ((name == 'shift' || name == 'splice') && obj.length === 0) delete obj[0]; + return result.call(this, obj); + }; + }); + + // Add all accessor Array functions to the wrapper. + each(['concat', 'join', 'slice'], function(name) { + var method = ArrayProto[name]; + _.prototype[name] = function() { + return result.call(this, method.apply(this._wrapped, arguments)); + }; + }); + + _.extend(_.prototype, { + + // Start chaining a wrapped Underscore object. + chain: function() { + this._chain = true; + return this; + }, + + // Extracts the result from a wrapped and chained object. + value: function() { + return this._wrapped; + } + + }); + +}).call(this); diff --git a/src/node_modules/nedb/package.json b/src/node_modules/nedb/package.json new file mode 100644 index 0000000..e617dd4 --- /dev/null +++ b/src/node_modules/nedb/package.json @@ -0,0 +1,54 @@ +{ + "name": "nedb", + "version": "0.11.1", + "author": { + "name": "Louis Chatriot", + "email": "louis.chatriot@gmail.com" + }, + "contributors": [ + { + "name": "Louis Chatriot" + } + ], + "description": "File-based embedded data store for node.js", + "keywords": [ + "database", + "datastore", + "embedded" + ], + "homepage": "https://github.com/louischatriot/nedb", + "repository": { + "type": "git", + "url": "git@github.com:louischatriot/nedb.git" + }, + "dependencies": { + "async": "0.2.10", + "underscore": "~1.4.4", + "binary-search-tree": "0.2.4", + "mkdirp": "~0.3.5" + }, + "devDependencies": { + "chai": "1.0.x", + "mocha": "1.4.x", + "request": "2.9.x", + "sinon": "1.3.x", + "exec-time": "0.0.2", + "commander": "1.1.1" + }, + "scripts": { + "test": "./node_modules/.bin/mocha --reporter spec --timeout 10000" + }, + "main": "index", + "licence": "MIT", + "readme": "# NeDB (Node embedded database)\r\n\r\n\r\n\r\n**Embedded persistent database for Node.js, written in Javascript, with no dependency** (except npm\r\nmodules of course). You can **think of it as a SQLite for Node.js projects**, which\r\ncan be used with a simple `require` statement. The API is a subset of MongoDB's. You can use it as a persistent or an in-memory only datastore.\r\n\r\nNeDB is not intended to be a replacement of large-scale databases such as MongoDB! Its goal is to provide you with a clean and easy way to query data and persist it to disk, for web applications that do not need lots of concurrent connections, for example a continuous integration and deployment server and desktop applications built with Node Webkit.\r\n\r\nNeDB was benchmarked against the popular client-side database TaffyDB and NeDB is much, much faster. That's why there is now a browser version, which can also provide persistence.\r\n\r\nCheck the change log in the wiki if you think nedb doesn't behave as the documentation describes! Most of the issues I get are due to non-latest version NeDBs.\r\n\r\nYou want to help out? You can contribute time or bitcoins, check out how!\r\n\r\n\r\n## Installation, tests\r\nModule name on npm is `nedb`.\r\n```javascript\r\nnpm install nedb --save // Put latest version in your package.json\r\n\r\nnpm test // You'll need the dev dependencies to test it\r\n```\r\n\r\n## API\r\nIt's a subset of MongoDB's API (the most used operations). The current API will not change, but I will add operations as they are needed. Summary of the API: \r\n\r\n* Creating/loading a database\r\n* Compacting the database\r\n* Inserting documents\r\n* Finding documents\r\n * Basic Querying\r\n * Operators ($lt, $lte, $gt, $gte, $in, $nin, $ne, $exists, $regex)\r\n * Array fields\r\n * Logical operators $or, $and, $not, $where\r\n * Sorting and paginating\r\n * Projections\r\n* Counting documents\r\n* Updating documents\r\n* Removing documents\r\n* Indexing\r\n* Browser version\r\n\r\n### Creating/loading a database\r\nYou can use NeDB as an in-memory only datastore or as a persistent datastore. One datastore is the equivalent of a MongoDB collection. The constructor is used as follows `new Datastore(options)` where `options` is an object with the following fields: \r\n\r\n* `filename` (optional): path to the file where the data is persisted. If left blank, the datastore is automatically considered in-memory only. It cannot end with a `~` which is used in the temporary files NeDB uses to perform crash-safe writes\r\n* `inMemoryOnly` (optional, defaults to false): as the name implies.\r\n* `autoload` (optional, defaults to false): if used, the database will\r\n automatically be loaded from the datafile upon creation (you don't\r\nneed to call `loadDatabase`). Any command\r\nissued before load is finished is buffered and will be executed when\r\nload is done.\r\n* `onload` (optional): if you use autoloading, this is the handler called after the `loadDatabase`. It takes one `error` argument. If you use autoloading without specifying this handler, and an error happens during load, an error will be thrown.\r\n* `nodeWebkitAppName` (optional, **DEPRECATED**): if you are using NeDB from whithin a Node Webkit app, specify its name (the same one you use in the `package.json`) in this field and the `filename` will be relative to the directory Node Webkit uses to store the rest of the application's data (local storage etc.). It works on Linux, OS X and Windows. Now that you can use `require('nw.gui').App.dataPath` in Node Webkit to get the path to the data directory for your application, you should not use this option anymore and it will be removed.\r\n\r\nIf you use a persistent datastore without the `autoload` option, you need to call `loadDatabase` manually.\r\nThis function fetches the data from datafile and prepares the database. **Don't forget it!** If you use a\r\npersistent datastore, no command (insert, find, update, remove) will be executed before `loadDatabase`\r\nis called, so make sure to call it yourself or use the `autoload`\r\noption.\r\n\r\n```javascript\r\n// Type 1: In-memory only datastore (no need to load the database)\r\nvar Datastore = require('nedb')\r\n , db = new Datastore();\r\n\r\n\r\n// Type 2: Persistent datastore with manual loading\r\nvar Datastore = require('nedb')\r\n , db = new Datastore({ filename: 'path/to/datafile' });\r\ndb.loadDatabase(function (err) { // Callback is optional\r\n // Now commands will be executed\r\n});\r\n\r\n\r\n// Type 3: Persistent datastore with automatic loading\r\nvar Datastore = require('nedb')\r\n , db = new Datastore({ filename: 'path/to/datafile', autoload: true });\r\n// You can issue commands right away\r\n\r\n\r\n// Type 4: Persistent datastore for a Node Webkit app called 'nwtest'\r\n// For example on Linux, the datafile will be ~/.config/nwtest/nedb-data/something.db\r\nvar Datastore = require('nedb')\r\n , path = require('path')\r\n , db = new Datastore({ filename: path.join(require('nw.gui').App.dataPath, 'something.db') });\r\n\r\n\r\n// Of course you can create multiple datastores if you need several\r\n// collections. In this case it's usually a good idea to use autoload for all collections.\r\ndb = {};\r\ndb.users = new Datastore('path/to/users.db');\r\ndb.robots = new Datastore('path/to/robots.db');\r\n\r\n// You need to load each database (here we do it asynchronously)\r\ndb.users.loadDatabase();\r\ndb.robots.loadDatabase();\r\n```\r\n\r\n### Compacting the database\r\nUnder the hood, NeDB's persistence uses an append-only format, meaning that all updates and deletes actually result in lines added at the end of the datafile. The reason for this is that disk space is very cheap and appends are much faster than rewrites since they don't do a seek. The database is automatically compacted (i.e. put back in the one-line-per-document format) everytime your application restarts.\r\n\r\nYou can manually call the compaction function with `yourDatabase.persistence.compactDatafile` which takes no argument. It queues a compaction of the datafile in the executor, to be executed sequentially after all pending operations.\r\n\r\nYou can also set automatic compaction at regular intervals with `yourDatabase.persistence.setAutocompactionInterval(interval)`, `interval` in milliseconds (a minimum of 5s is enforced), and stop automatic compaction with `yourDatabase.persistence.stopAutocompaction()`.\r\n\r\nKeep in mind that compaction takes a bit of time (not too much: 130ms for 50k records on my slow machine) and no other operation can happen when it does, so most projects actually don't need to use it.\r\n\r\n\r\n### Inserting documents\r\nThe native types are `String`, `Number`, `Boolean`, `Date` and `null`. You can also use\r\narrays and subdocuments (objects). If a field is `undefined`, it will not be saved (this is different from \r\nMongoDB which transforms `undefined` in `null`, something I find counter-intuitive). \r\n\r\nIf the document does not contain an `_id` field, NeDB will automatically generated one for you (a 16-characters alphanumerical string). The `_id` of a document, once set, cannot be modified.\r\n\r\nField names cannot begin by '$' or contain a '.'.\r\n\r\n```javascript\r\nvar doc = { hello: 'world'\r\n , n: 5\r\n , today: new Date()\r\n , nedbIsAwesome: true\r\n , notthere: null\r\n , notToBeSaved: undefined // Will not be saved\r\n , fruits: [ 'apple', 'orange', 'pear' ]\r\n , infos: { name: 'nedb' }\r\n };\r\n\r\ndb.insert(doc, function (err, newDoc) { // Callback is optional\r\n // newDoc is the newly inserted document, including its _id\r\n // newDoc has no key called notToBeSaved since its value was undefined\r\n});\r\n```\r\n\r\nYou can also bulk-insert an array of documents. This operation is atomic, meaning that if one insert fails due to a unique constraint being violated, all changes are rolled back.\r\n```javascript\r\ndb.insert([{ a: 5 }, { a: 42 }], function (err, newDocs) {\r\n // Two documents were inserted in the database\r\n // newDocs is an array with these documents, augmented with their _id\r\n});\r\n\r\n// If there is a unique constraint on field 'a', this will fail\r\ndb.insert([{ a: 5 }, { a: 42 }, { a: 5 }], function (err) {\r\n // err is a 'uniqueViolated' error\r\n // The database was not modified\r\n});\r\n```\r\n\r\n### Finding documents\r\nUse `find` to look for multiple documents matching you query, or `findOne` to look for one specific document. You can select documents based on field equality or use comparison operators (`$lt`, `$lte`, `$gt`, `$gte`, `$in`, `$nin`, `$ne`). You can also use logical operators `$or`, `$and`, `$not` and `$where`. See below for the syntax.\r\n\r\nYou can use regular expressions in two ways: in basic querying in place of a string, or with the `$regex` operator.\r\n\r\nYou can sort and paginate results using the cursor API (see below).\r\n\r\nYou can use standard projections to restrict the fields to appear in the results (see below).\r\n\r\n#### Basic querying\r\nBasic querying means are looking for documents whose fields match the ones you specify. You can use regular expression to match strings.\r\nYou can use the dot notation to navigate inside nested documents, arrays, arrays of subdocuments and to match a specific element of an array.\r\n\r\n```javascript\r\n// Let's say our datastore contains the following collection\r\n// { _id: 'id1', planet: 'Mars', system: 'solar', inhabited: false, satellites: ['Phobos', 'Deimos'] }\r\n// { _id: 'id2', planet: 'Earth', system: 'solar', inhabited: true, humans: { genders: 2, eyes: true } }\r\n// { _id: 'id3', planet: 'Jupiter', system: 'solar', inhabited: false }\r\n// { _id: 'id4', planet: 'Omicron Persei 8', system: 'futurama', inhabited: true, humans: { genders: 7 } }\r\n// { _id: 'id5', completeData: { planets: [ { name: 'Earth', number: 3 }, { name: 'Mars', number: 2 }, { name: 'Pluton', number: 9 } ] } }\r\n\r\n// Finding all planets in the solar system\r\ndb.find({ system: 'solar' }, function (err, docs) {\r\n // docs is an array containing documents Mars, Earth, Jupiter\r\n // If no document is found, docs is equal to []\r\n});\r\n\r\n// Finding all planets whose name contain the substring 'ar' using a regular expression\r\ndb.find({ planet: /ar/ }, function (err, docs) {\r\n // docs contains Mars and Earth\r\n});\r\n\r\n// Finding all inhabited planets in the solar system\r\ndb.find({ system: 'solar', inhabited: true }, function (err, docs) {\r\n // docs is an array containing document Earth only\r\n});\r\n\r\n// Use the dot-notation to match fields in subdocuments\r\ndb.find({ \"humans.genders\": 2 }, function (err, docs) {\r\n // docs contains Earth\r\n});\r\n\r\n// Use the dot-notation to navigate arrays of subdocuments\r\ndb.find({ \"completeData.planets.name\": \"Mars\" }, function (err, docs) {\r\n // docs contains document 5\r\n});\r\n\r\ndb.find({ \"completeData.planets.name\": \"Jupiter\" }, function (err, docs) {\r\n // docs is empty\r\n});\r\n\r\ndb.find({ \"completeData.planets.0.name\": \"Earth\" }, function (err, docs) {\r\n // docs contains document 5\r\n // If we had tested against \"Mars\" docs would be empty because we are matching against a specific array element\r\n});\r\n\r\n\r\n// You can also deep-compare objects. Don't confuse this with dot-notation!\r\ndb.find({ humans: { genders: 2 } }, function (err, docs) {\r\n // docs is empty, because { genders: 2 } is not equal to { genders: 2, eyes: true }\r\n});\r\n\r\n// Find all documents in the collection\r\ndb.find({}, function (err, docs) {\r\n});\r\n\r\n// The same rules apply when you want to only find one document\r\ndb.findOne({ _id: 'id1' }, function (err, doc) {\r\n // doc is the document Mars\r\n // If no document is found, doc is null\r\n});\r\n```\r\n\r\n#### Operators ($lt, $lte, $gt, $gte, $in, $nin, $ne, $exists, $regex)\r\nThe syntax is `{ field: { $op: value } }` where `$op` is any comparison operator: \r\n\r\n* `$lt`, `$lte`: less than, less than or equal\r\n* `$gt`, `$gte`: greater than, greater than or equal\r\n* `$in`: member of. `value` must be an array of values\r\n* `$ne`, `$nin`: not equal, not a member of\r\n* `$exists`: checks whether the document posses the property `field`. `value` should be true or false\r\n* `$regex`: checks whether a string is matched by the regular expression. Contrary to MongoDB, the use of `$options` with `$regex` is not supported, because it doesn't give you more power than regex flags. Basic queries are more readable so only use the `$regex` operator when you need to use another operator with it (see example below)\r\n\r\n```javascript\r\n// $lt, $lte, $gt and $gte work on numbers and strings\r\ndb.find({ \"humans.genders\": { $gt: 5 } }, function (err, docs) {\r\n // docs contains Omicron Persei 8, whose humans have more than 5 genders (7).\r\n});\r\n\r\n// When used with strings, lexicographical order is used\r\ndb.find({ planet: { $gt: 'Mercury' }}, function (err, docs) {\r\n // docs contains Omicron Persei 8\r\n})\r\n\r\n// Using $in. $nin is used in the same way\r\ndb.find({ planet: { $in: ['Earth', 'Jupiter'] }}, function (err, docs) {\r\n // docs contains Earth and Jupiter\r\n});\r\n\r\n// Using $exists\r\ndb.find({ satellites: { $exists: true } }, function (err, docs) {\r\n // docs contains only Mars\r\n});\r\n\r\n// Using $regex with another operator\r\ndb.find({ planet: { $regex: /ar/, $nin: ['Jupiter', 'Earth'] } }, function (err, docs) {\r\n // docs only contains Mars because Earth was excluded from the match by $nin\r\n});\r\n```\r\n\r\n#### Array fields\r\nWhen a field in a document is an array, NeDB first tries to see if there is an array-specific comparison function (for now there is only `$size`) being used\r\nand tries it first. If there isn't, the query is treated as a query on every element and there is a match if at least one element matches.\r\n\r\n```javascript\r\n// Using an array-specific comparison function\r\n// Note: you can't use nested comparison functions, e.g. { $size: { $lt: 5 } } will throw an error\r\ndb.find({ satellites: { $size: 2 } }, function (err, docs) {\r\n // docs contains Mars\r\n});\r\n\r\ndb.find({ satellites: { $size: 1 } }, function (err, docs) {\r\n // docs is empty\r\n});\r\n\r\n// If a document's field is an array, matching it means matching any element of the array\r\ndb.find({ satellites: 'Phobos' }, function (err, docs) {\r\n // docs contains Mars. Result would have been the same if query had been { satellites: 'Deimos' }\r\n});\r\n\r\n// This also works for queries that use comparison operators\r\ndb.find({ satellites: { $lt: 'Amos' } }, function (err, docs) {\r\n // docs is empty since Phobos and Deimos are after Amos in lexicographical order\r\n});\r\n\r\n// This also works with the $in and $nin operator\r\ndb.find({ satellites: { $in: ['Moon', 'Deimos'] } }, function (err, docs) {\r\n // docs contains Mars (the Earth document is not complete!)\r\n});\r\n```\r\n\r\n#### Logical operators $or, $and, $not, $where\r\nYou can combine queries using logical operators: \r\n\r\n* For `$or` and `$and`, the syntax is `{ $op: [query1, query2, ...] }`.\r\n* For `$not`, the syntax is `{ $not: query }`\r\n* For `$where`, the syntax is `{ $where: function () { /* object is \"this\", return a boolean */ } }`\r\n\r\n```javascript\r\ndb.find({ $or: [{ planet: 'Earth' }, { planet: 'Mars' }] }, function (err, docs) {\r\n // docs contains Earth and Mars\r\n});\r\n\r\ndb.find({ $not: { planet: 'Earth' } }, function (err, docs) {\r\n // docs contains Mars, Jupiter, Omicron Persei 8\r\n});\r\n\r\ndb.find({ $where: function () { return Object.keys(this) > 6; } }, function (err, docs) {\r\n // docs with more than 6 properties\r\n});\r\n\r\n// You can mix normal queries, comparison queries and logical operators\r\ndb.find({ $or: [{ planet: 'Earth' }, { planet: 'Mars' }], inhabited: true }, function (err, docs) {\r\n // docs contains Earth\r\n});\r\n\r\n```\r\n\r\n#### Sorting and paginating\r\nIf you don't specify a callback to `find`, `findOne` or `count`, a `Cursor` object is returned. You can modify the cursor with `sort`, `skip` and `limit` and then execute it with `exec(callback)`.\r\n\r\n```javascript\r\n// Let's say the database contains these 4 documents\r\n// doc1 = { _id: 'id1', planet: 'Mars', system: 'solar', inhabited: false, satellites: ['Phobos', 'Deimos'] }\r\n// doc2 = { _id: 'id2', planet: 'Earth', system: 'solar', inhabited: true, humans: { genders: 2, eyes: true } }\r\n// doc3 = { _id: 'id3', planet: 'Jupiter', system: 'solar', inhabited: false }\r\n// doc4 = { _id: 'id4', planet: 'Omicron Persei 8', system: 'futurama', inhabited: true, humans: { genders: 7 } }\r\n\r\n// No query used means all results are returned (before the Cursor modifiers)\r\ndb.find({}).sort({ planet: 1 }).skip(1).limit(2).exec(function (err, docs) {\r\n // docs is [doc3, doc1]\r\n});\r\n\r\n// You can sort in reverse order like this\r\ndb.find({ system: 'solar' }).sort({ planet: -1 }).exec(function (err, docs) {\r\n // docs is [doc1, doc3, doc2]\r\n});\r\n\r\n// You can sort on one field, then another, and so on like this:\r\ndb.find({}).sort({ firstField: 1, secondField: -1 }) ... // You understand how this works!\r\n```\r\n\r\n#### Projections\r\nYou can give `find` and `findOne` an optional second argument, `projections`. The syntax is the same as MongoDB: `{ a: 1, b: 1 }` to return only the `a` and `b` fields, `{ a: 0, b: 0 }` to omit these two fields. You cannot use both modes at the time, except for `_id` which is by default always returned and which you can choose to omit.\r\n\r\n```javascript\r\n// Same database as above\r\n\r\n// Keeping only the given fields\r\ndb.find({ planet: 'Mars' }, { planet: 1, system: 1 }, function (err, docs) {\r\n // docs is [{ planet: 'Mars', system: 'solar', _id: 'id1' }]\r\n});\r\n\r\n// Keeping only the given fields but removing _id\r\ndb.find({ planet: 'Mars' }, { planet: 1, system: 1, _id: 0 }, function (err, docs) {\r\n // docs is [{ planet: 'Mars', system: 'solar' }]\r\n});\r\n\r\n// Omitting only the given fields and removing _id\r\ndb.find({ planet: 'Mars' }, { planet: 0, system: 0, _id: 0 }, function (err, docs) {\r\n // docs is [{ inhabited: false, satellites: ['Phobos', 'Deimos'] }]\r\n});\r\n\r\n// Failure: using both modes at the same time\r\ndb.find({ planet: 'Mars' }, { planet: 0, system: 1 }, function (err, docs) {\r\n // err is the error message, docs is undefined\r\n});\r\n\r\n// You can also use it in a Cursor way but this syntax is not compatible with MongoDB\r\n// If upstream compatibility is important don't use this method\r\ndb.find({ planet: 'Mars' }).projection({ planet: 1, system: 1 }).exec(function (err, docs) {\r\n // docs is [{ planet: 'Mars', system: 'solar', _id: 'id1' }]\r\n});\r\n```\r\n\r\n\r\n\r\n### Counting documents\r\nYou can use `count` to count documents. It has the same syntax as `find`. For example:\r\n\r\n```javascript\r\n// Count all planets in the solar system\r\ndb.count({ system: 'solar' }, function (err, count) {\r\n // count equals to 3\r\n});\r\n\r\n// Count all documents in the datastore\r\ndb.count({}, function (err, count) {\r\n // count equals to 4\r\n});\r\n```\r\n\r\n\r\n### Updating documents\r\n`db.update(query, update, options, callback)` will update all documents matching `query` according to the `update` rules: \r\n* `query` is the same kind of finding query you use with `find` and `findOne`\r\n* `update` specifies how the documents should be modified. It is either a new document or a set of modifiers (you cannot use both together, it doesn't make sense!)\r\n * A new document will replace the matched docs\r\n * The modifiers create the fields they need to modify if they don't exist, and you can apply them to subdocs. Available field modifiers are `$set` to change a field's value, `$unset` to delete a field and `$inc` to increment a field's value. To work on arrays, you have `$push`, `$pop`, `$addToSet`, `$pull`, and the special `$each`. See examples below for the syntax.\r\n* `options` is an object with two possible parameters\r\n * `multi` (defaults to `false`) which allows the modification of several documents if set to true\r\n * `upsert` (defaults to `false`) if you want to insert a new document corresponding to the `update` rules if your `query` doesn't match anything\r\n* `callback` (optional) signature: `err`, `numReplaced`, `newDoc`\r\n * `numReplaced` is the number of documents replaced\r\n * `newDoc` is the created document if the upsert mode was chosen and a document was inserted\r\n\r\n**Note**: you can't change a document's _id.\r\n\r\n```javascript\r\n// Let's use the same example collection as in the \"finding document\" part\r\n// { _id: 'id1', planet: 'Mars', system: 'solar', inhabited: false }\r\n// { _id: 'id2', planet: 'Earth', system: 'solar', inhabited: true }\r\n// { _id: 'id3', planet: 'Jupiter', system: 'solar', inhabited: false }\r\n// { _id: 'id4', planet: 'Omicron Persia 8', system: 'futurama', inhabited: true }\r\n\r\n// Replace a document by another\r\ndb.update({ planet: 'Jupiter' }, { planet: 'Pluton'}, {}, function (err, numReplaced) {\r\n // numReplaced = 1\r\n // The doc #3 has been replaced by { _id: 'id3', planet: 'Pluton' }\r\n // Note that the _id is kept unchanged, and the document has been replaced\r\n // (the 'system' and inhabited fields are not here anymore)\r\n});\r\n\r\n// Set an existing field's value\r\ndb.update({ system: 'solar' }, { $set: { system: 'solar system' } }, { multi: true }, function (err, numReplaced) {\r\n // numReplaced = 3\r\n // Field 'system' on Mars, Earth, Jupiter now has value 'solar system'\r\n});\r\n\r\n// Setting the value of a non-existing field in a subdocument by using the dot-notation\r\ndb.update({ planet: 'Mars' }, { $set: { \"data.satellites\": 2, \"data.red\": true } }, {}, function () {\r\n // Mars document now is { _id: 'id1', system: 'solar', inhabited: false\r\n // , data: { satellites: 2, red: true }\r\n // }\r\n // Not that to set fields in subdocuments, you HAVE to use dot-notation\r\n // Using object-notation will just replace the top-level field\r\n db.update({ planet: 'Mars' }, { $set: { data: { satellites: 3 } } }, {}, function () {\r\n // Mars document now is { _id: 'id1', system: 'solar', inhabited: false\r\n // , data: { satellites: 3 }\r\n // }\r\n // You lost the \"data.red\" field which is probably not the intended behavior\r\n });\r\n});\r\n\r\n// Deleting a field\r\ndb.update({ planet: 'Mars' }, { $unset: { planet: true } }, {}, function () {\r\n // Now the document for Mars doesn't contain the planet field\r\n // You can unset nested fields with the dot notation of course\r\n});\r\n\r\n// Upserting a document\r\ndb.update({ planet: 'Pluton' }, { planet: 'Pluton', inhabited: false }, { upsert: true }, function (err, numReplaced, upsert) {\r\n // numReplaced = 1, upsert = { _id: 'id5', planet: 'Pluton', inhabited: false }\r\n // A new document { _id: 'id5', planet: 'Pluton', inhabited: false } has been added to the collection\r\n});\r\n\r\n// If you upsert with a modifier, the upserted doc is the query modified by the modifier\r\n// This is simpler than it sounds :)\r\ndb.update({ planet: 'Pluton' }, { $inc: { distance: 38 } }, { upsert: true }, function () {\r\n // A new document { _id: 'id5', planet: 'Pluton', distance: 38 } has been added to the collection \r\n});\r\n\r\n// If we insert a new document { _id: 'id6', fruits: ['apple', 'orange', 'pear'] } in the collection,\r\n// let's see how we can modify the array field atomically\r\n\r\n// $push inserts new elements at the end of the array\r\ndb.update({ _id: 'id6' }, { $push: { fruits: 'banana' } }, {}, function () {\r\n // Now the fruits array is ['apple', 'orange', 'pear', 'banana']\r\n});\r\n\r\n// $pop removes an element from the end (if used with 1) or the front (if used with -1) of the array\r\ndb.update({ _id: 'id6' }, { $pop: { fruits: 1 } }, {}, function () {\r\n // Now the fruits array is ['apple', 'orange']\r\n // With { $pop: { fruits: -1 } }, it would have been ['orange', 'pear']\r\n});\r\n\r\n// $addToSet adds an element to an array only if it isn't already in it\r\n// Equality is deep-checked (i.e. $addToSet will not insert an object in an array already containing the same object)\r\n// Note that it doesn't check whether the array contained duplicates before or not\r\ndb.update({ _id: 'id6' }, { $addToSet: { fruits: 'apple' } }, {}, function () {\r\n // The fruits array didn't change\r\n // If we had used a fruit not in the array, e.g. 'banana', it would have been added to the array\r\n});\r\n\r\n// $pull removes all values matching a value or even any NeDB query from the array\r\ndb.update({ _id: 'id6' }, { $pull: { fruits: 'apple' } }, {}, function () {\r\n // Now the fruits array is ['orange', 'pear']\r\n});\r\ndb.update({ _id: 'id6' }, { $pull: { fruits: $in: ['apple', 'pear'] } }, {}, function () {\r\n // Now the fruits array is ['orange']\r\n});\r\n\r\n\r\n\r\n// $each can be used to $push or $addToSet multiple values at once\r\n// This example works the same way with $addToSet\r\ndb.update({ _id: 'id6' }, { $push: { fruits: {$each: ['banana', 'orange'] } } }, {}, function () {\r\n // Now the fruits array is ['apple', 'orange', 'pear', 'banana', 'orange']\r\n});\r\n```\r\n\r\n### Removing documents\r\n`db.remove(query, options, callback)` will remove all documents matching `query` according to `options` \r\n* `query` is the same as the ones used for finding and updating\r\n* `options` only one option for now: `multi` which allows the removal of multiple documents if set to true. Default is false\r\n* `callback` is optional, signature: err, numRemoved\r\n\r\n```javascript\r\n// Let's use the same example collection as in the \"finding document\" part\r\n// { _id: 'id1', planet: 'Mars', system: 'solar', inhabited: false }\r\n// { _id: 'id2', planet: 'Earth', system: 'solar', inhabited: true }\r\n// { _id: 'id3', planet: 'Jupiter', system: 'solar', inhabited: false }\r\n// { _id: 'id4', planet: 'Omicron Persia 8', system: 'futurama', inhabited: true }\r\n\r\n// Remove one document from the collection\r\n// options set to {} since the default for multi is false\r\ndb.remove({ _id: 'id2' }, {}, function (err, numRemoved) {\r\n // numRemoved = 1\r\n});\r\n\r\n// Remove multiple documents\r\ndb.remove({ system: 'solar' }, { multi: true }, function (err, numRemoved) {\r\n // numRemoved = 3\r\n // All planets from the solar system were removed\r\n});\r\n```\r\n\r\n### Indexing\r\nNeDB supports indexing. It gives a very nice speed boost and can be used to enforce a unique constraint on a field. You can index any field, including fields in nested documents using the dot notation. For now, indexes are only used to speed up basic queries and queries using `$in`, `$lt`, `$lte`, `$gt` and `$gte`.\r\n\r\nTo create an index, use `datastore.ensureIndex(options, cb)`, where callback is optional and get passed an error if any (usually a unique constraint that was violated). `ensureIndex` can be called when you want, even after some data was inserted, though it's best to call it at application startup. The options are: \r\n\r\n* **fieldName** (required): name of the field to index. Use the dot notation to index a field in a nested document.\r\n* **unique** (optional, defaults to `false`): enforce field uniqueness. Note that a unique index will raise an error if you try to index two documents for which the field is not defined.\r\n* **sparse** (optional, defaults to `false`): don't index documents for which the field is not defined. Use this option along with \"unique\" if you want to accept multiple documents for which it is not defined.\r\n\r\nNote: the `_id` is automatically indexed with a unique constraint, no need to call `ensureIndex` on it.\r\n\r\nYou can remove a previously created index with `datastore.removeIndex(fieldName, cb)`.\r\n\r\nIf your datastore is persistent, the indexes you created are persisted in the datafile, when you load the database a second time they are automatically created for you. No need to remove any `ensureIndex` though, if it is called on a database that already has the index, nothing happens.\r\n\r\n```javascript\r\ndb.ensureIndex({ fieldName: 'somefield' }, function (err) {\r\n // If there was an error, err is not null\r\n});\r\n\r\n// Using a unique constraint with the index\r\ndb.ensureIndex({ fieldName: 'somefield', unique: true }, function (err) {\r\n});\r\n\r\n// Using a sparse unique index\r\ndb.ensureIndex({ fieldName: 'somefield', unique: true, sparse: true }, function (err) {\r\n});\r\n\r\n\r\n// Format of the error message when the unique constraint is not met\r\ndb.insert({ somefield: 'nedb' }, function (err) {\r\n // err is null\r\n db.insert({ somefield: 'nedb' }, function (err) {\r\n // err is { errorType: 'uniqueViolated'\r\n // , key: 'name'\r\n // , message: 'Unique constraint violated for key name' }\r\n });\r\n});\r\n\r\n// Remove index on field somefield\r\ndb.removeIndex('somefield', function (err) {\r\n});\r\n```\r\n\r\n**Note:** the `ensureIndex` function creates the index synchronously, so it's best to use it at application startup. It's quite fast so it doesn't increase startup time much (35 ms for a collection containing 10,000 documents).\r\n\r\n\r\n## Browser version\r\nAs of v0.8.0, you can use NeDB in the browser! You can find it and its minified version in the repository, in the `browser-version/out` directory. You only need to require `nedb.js` or `nedb.min.js` in your HTML file and the global object `Nedb` can be used right away, with the same API as the server version:\r\n\r\n```\r\n\r\n\r\n```\r\n\r\nIt has been tested and is compatible with Chrome, Safari, Firefox, IE 10, IE 9. Please open an issue if you need compatibility with IE 8/IE 7, I think it will need some work and am not sure it is needed, since most complex webapplications - the ones that would need NeDB - only work on modern browsers anyway. To launch the tests, simply open the file `browser-version/test/index.html` in a browser and you'll see the results of the tests for this browser.\r\n\r\nIf you fork and modify nedb, you can build the browser version from the sources, the build script is `browser-version/build.js`.\r\n\r\nAs of v0.11, NeDB is also persistent on the browser. To use this, simply create the collection with the `filename` option which will be the name of the `localStorage` variable storing data. Persistence should work on all browsers where NeDB works.\r\n\r\n**Browser persistence is still young! It has been tested on most major browsers but please report any bugs you find**\r\n\r\n\r\n## Performance\r\n### Speed\r\nNeDB is not intended to be a replacement of large-scale databases such as MongoDB, and as such was not designed for speed. That said, it is still pretty fast on the expected datasets, especially if you use indexing. On my machine (3 years old, no SSD), with a collection containing 10,000 documents, with indexing: \r\n* Insert: **5,950 ops/s**\r\n* Find: **25,440 ops/s**\r\n* Update: **4,490 ops/s**\r\n* Remove: **6,620 ops/s** \r\n\r\nYou can run the simple benchmarks I use by executing the scripts in the `benchmarks` folder. Run them with the `--help` flag to see how they work.\r\n\r\n### Memory footprint\r\nA copy of the whole database is kept in memory. This is not much on the\r\nexpected kind of datasets (20MB for 10,000 2KB documents). If requested, I'll introduce an\r\noption to not use this cache to decrease memory footprint (at the cost\r\nof a lower speed).\r\n\r\n\r\n## Use in other services\r\n* connect-nedb-session is a session store for\r\nConnect and Express, backed by nedb\r\n* If you mostly use NeDB for logging purposes and don't want the memory footprint of your application to grow too large, you can use NeDB Logger to insert documents in a NeDB-readable database\r\n* If you've outgrown NeDB, switching to MongoDB won't be too hard as it is the same API. Use this utility to transfer the data from a NeDB database to a MongoDB collection\r\n\r\n\r\n## Contribute!\r\nYou want to help? You can contribute time or bitcoins.\r\n\r\n### Helping on the codebase\r\nIssues reporting and pull requests are always appreciated. For issues, make sure to always include a code snippet and describe the expected vs actual behavior. If you send a pull request, make sure to stick to NeDB's coding style and always test all the code you submit. You can look at the current tests to see how to do it\r\n\r\n### Bitcoins\r\nYou don't have time? You can support NeDB by sending bitcoins to this adress: 1dDZLnWpBbodPiN8sizzYrgaz5iahFyb1\r\n\r\n\r\n## License \r\n\r\nSee [License](LICENSE)\r\n", + "readmeFilename": "README.md", + "bugs": { + "url": "https://github.com/louischatriot/nedb/issues" + }, + "_id": "nedb@0.11.1", + "dist": { + "shasum": "d622c8eb3817fc2cc0db8cf5b99b00684e166c14" + }, + "_from": "nedb@", + "_resolved": "https://registry.npmjs.org/nedb/-/nedb-0.11.1.tgz" +} diff --git a/src/node_modules/nedb/test/.db.test.js.swo b/src/node_modules/nedb/test/.db.test.js.swo new file mode 100644 index 0000000000000000000000000000000000000000..fb62a5388d043bfede6e16686cf791aa5c21bba6 GIT binary patch literal 118784 zcmeI534k0$x%fv>L{v}_Z=_wKyTP!@ZURC$6he@I2}lx9L*&iQ^k#?5&a5-D*=%B- z`V{f0c;khlKF}wM7d}M=@j^WCcu)O%A|5E-Hz?1G|L?2n>aOapp6S_a5PduF%g)hV zUFTQzef3q<*s?R%tPLJJyrRVGzNOOXr~H1sygm7VQt64!=~`Pp^msk-w2f=luR3k* z={>HitXz6}t9fo%>9m(N!s^)4PT1}&t&R=L-{EtMJgUbN7JgpHfrT8%&w>3)%ST6+ z9WwC9;DGyY%)e~mmxUZy$bp3%Sjd5e99YPKg&bJOfrT7c$bo;#9Oz6vtaLjmyDPNd zE)fsoZyiJ_m+$TdgYezx^TYYO687dt`Tg$jR5&*Jyf^PB;MLLRd+=VuyFL265AUn6 zl?TiH_k<>VDf+yx{07JErGLK{@5kY?=<~gKUxWRLnB0FKI1{!M4#`+`zOIKqtEx}{aJ7v36bkDhm^{2Vf6U{ygwWM5Pd$7_nY7h z5-Qg}5LUxGqt6fG{W|zv^!dTOe=F@+N9v%-5 zh6CZV=+Vc(fv^w!4L$pha2=cv&w{hyXm}v(4Szv*zXg5`9|qC=zXYFv55jxlSuhJH z!4Uiod%_RkTksWlF6@F0a6i}!{)~;{f8qD=JNOca-QomzDBOzje+yg#FNbkB2@ZlE zVlR0gJRi=7Rq!B?^1L}Q6a>zzIy_!$REKu%S`v&;H!7W4vk?r1t=5vD+N`t}2Rlpl z<<;SOvs^u~+$oQh+u@LNNBY{OOM~ObJ7Fs*2dz+U3px{FQ03lWqTCL~!mtrknynT- zAobZ`Dr~iD?M_%NIS(wi+hMCSTx*{g63?(YB(d7GZ1eCrwd&%dolDIx?dD`CPZ@4c zG^gv;;qd9x<@(TIbL(L0hSSEx|73V;INLVCE*`FjjV+xC_ukRejq)V#g(Im81n*{N8AcHOOVdz7+j*sio{W8u)?8kHr^bzW7K^{&_!2B)0FQsB9Y%!Qtyz54T``i^0@JGy=@ZbB*)Ey zLFt?&rACnDCNp)VD%fVDO&(V085jvnPL(^gv0A;>nGI%Yorz%Evf(2R>sCCQWpqb3 zX(VRSGbFNrQj~soy2U7MY$(1|zOD{$2|LHv>(V<7`G-u&s6lRU3)vk96&X_6{d;9B z>1Fcb?WQ6Kb_F4Y66|ym6eM>`9D@LvG2N)w8e50N30b-vo@KH~UxH*E3b!*(O^uAX zRev4d8h4ML>raoyx{Miy!$y0$6`oj=VK8lFwF-QQ5}g>X)>_ivM?o&OGa<=>TBK8Jk<@yq;Z|9lD^ZSAGtJQ+q3BBy$C7=tT}Cjg z%asXBVKBWAzpI*mbSV)sQm!mtSlrVEynod;Lzak@X~s1Y^hNu$lyU4 z1T9GhQl6_tE~-IRbgk;YJ=dx4qia+a+t;Wsq94z(f0(8(Z?ze(o2W?H+84;@=*IE3 z{lj=W`Z~%@XZ#}HMdKI7BuY!N@2yOfYlDl2^$#V<;b`P)Wma^J{$XfwN%a2^eev1o zlA`~gz@Hn@@m~ck7=_=V$G;o4!eihk=iIUMDP9>y7o3$1_!|<=+zZi0wwq$I`kAQ zf*a76KMk*jT@b=)a5OB2hrqqycG4?)^{?Pc_yC*_&x7+o^5a>s@LI@$J(>d|E=5%H zHwXe$sAG8rt5G|fN`v}s=~6A){dYt{GJ6}L^B8%R)W4CS&c0W1 ziLUYeD&J`#uYWcF`o8mDnHPc%$sdmXO=;y?Q#X&5za{&FdGGyFenc&aa#Soz(YPjO zOk*oLCSdc|u_eV}F5Wb<+3iCI%9su&!d7A%Fd-y=^TNXF<(RYvi18f4@mj9BrLEiO zEs3?fBR6eb-|FsV%$KqyCb#y`^`tjPeb288iKS6zF5>}Zh7o&XRJuXA6$b52tzH+4 z1n-Ax!{M;JpH)9iwL;YWZCE9$(=BC>LeWt}Yh@BOmXsvUITdZbYE)hw=)XSYh16ed z$_b^U3#&8Z&kUM))Mkf}X(&55gr7PodakoWT;MRIbuZwZnYPCw5z^JduGdC1IoJ zq_jXCPBs*+75CM9Sb@y3|H&z)|li3x16q;AZ$JY=Qk?FZge40h?e2`~^F}HE0p$6B%hv7Z&Hn;>Xf`j2&Yz7~J z%>!B+51I1gr_2@QBGEQ4FI5xgCq3a7&wcm(VRcZ0p)2J8bD z!@oiW9tTU{uh>(53qOMY2Va2C!=-Qu{0GdyHaHZ1bC*)-YIrFm1!m3ZcE(o*N4N!S z53EyHu5hoUD_s(t!%WUdat{;a$^_}DP=lRu|E#gZ%uFy$V_0RLp(g#e%=scalh> z$V=bEo05Ezm#a;Trjx) zw6&{))lXiv`t%KJPFpu<-xe@uJG-hm&7AmSjtxfapTx)<|0ouKWl3o<)>QLor!gbF zGbjg4My99rmFEU`*zt)XXVngw#&j!(%3ytyojH-39fiPL@pgBNlul%vIq@BK<}$Be z3ES=AW@Ct%1PEX&gctosH?APNq`S(E~TtHpdUZV$b}?Pz}51 zvZI4qa7@p;BS9<|OQt8;+ACVIm3{4+b*p6xAh|V4rU^k0M423UqIk0~)!p_blr+TI zlN9}upca4IJ1a=Gd1$@sv!XD?2%J=_V43<_TJf53HA`PH^<7oowlr!}LdwxW6_J`a z>I4;O&N6$5hX-yN^g@j)RdNXdF&O86q(Vzgv@<`26}}~98)qWkHrhuckk_G0RZN0I zgLTag-VKxGSzIfom9C(}ciLfTqVtCG`xER<`x&Iq(R8>g9Ej?+p(#5E&syC$JJuT?6`NX}VFYbEjo zDRPQDey`qni+#?$*^_t{EoWfLzlY6S7i1U!T+|=#p zd8%-zt*vsSEhVWeKC04cmlVmJCP`L-&}%kZaY?RXzO`N>!->gQI8okKLkNjUX*f7R zx;mc3G;C7PGEqYu%0c|9#P6hWP-jDQk1a#YAxfT~Wc*(AtWWIjVu`&Vm{SnRW7Rt~ z?Z(uXO!lp6QX3i_nSRpVD|E@R_e(z!qhFTUI|);_S5nxaA?U)}jwb=ty_U35Vp7bl z%<8%+kG{Q?;+{|3SZq;bSrWAA18~SKG%Nz#)gt>S*BA->~ARnWL2gk{1$SylITHb)rRL_omZ_Olm zm3k9-;aX*9y_LA)BAjkHe70i1r(qJtXm!P?JibT?_Zjfv-f%rdC^79>6jlO_BuqwP zMP*VSVr7n^!D{;A&J{I!-j!P|dRImTq*nRX(#{#eh|7a5-|V55N^(*&l6Sg&i~f(A zTzW3Lr|AE(hW{$``xnA#@W1Hv;^Y4kcrldWU=Th3OK>H;1ulg1VJAEtYA^whfFGjg zUkT5KRd7$Z8a;m;?i=a*e@4ImBD@Grfi*A!2gCkw7q}JO{^xKL`~Y47PXX~8_%(@m zJ50eN;d*rbX*d`@i@yJU_%ApgR>NNM89l!RPlu<#6F~I;#c&WD0QZ7@L3|1>fTzGA zaN}O+^RN+aM~A-*E`(RY7I+lgM4tWtJ_jF!x54Y+#UT0p9FSL_Ug?t2-^#!CFTT&& zE95F8uS@d5enn;=hE-Fmm76@3X~h!c3Rb+8QnOabv?v25tPV$I9xfsILZEj`LKsa# zz;5A)OdXT1g0rNrvHwk4Y4WYoc~?YoRb&-X)@cQ{q>>0JNhOvLl|T_Jo+qhLN9Pi% zO0+jrBWnm%O*bdj4)pmJwjG_>JA*}Z4`lk1V9^$NL*8X08H=BtriAQ7$JU+|iMFb< zEOj3xUT!e(P&3n6i;p%LEq4P+1(7mI#g87f?@HZs2N|c7Wr|Ud7z@;lZmZTtgHaZ# zl#R&S_JuHFM`*-~kSG7h zt-OKN3L>mZN^(AV-NEu<`CR0KDh0265NAci(mYF@DAbX;#8nlLH?9R_85Rw?4y0KP z0ka9=hCn-f^MWi>>v`gto1TVtOmtX!lqADs(vY=B7?9|gqw&AkIA$iWB*aes-a%kV zT1g_Kl6UsE?ltl${j2|*KIK~8Affm$BTtO%;bF!V_!EclswvZIUS5#3BY^D*azwCg_OWw)q1Wh%%gz}p~%A=nqh4)7LuG5j0sfDyPA{r~Im9(XNW0O!FpOhO&* z3O~a(@Kv}NPJ#c$Ch&9kE_@wc2~UQVa1XcvJHVCjCU_;h2v)(};m6nnJ_}dCTj64O zF+2~>gCpSq@O$h9KZT!w*bnNk4ju^s+>G7eI(RF*1)c`Sz`bB!kaGHHWH+FwGLs%sC~SKKF)a6|?6z`tgF7ECl`tCg*V^87?;@)6 z;-6C7gS?C-p?eVJfaOfyrMi4~<{ZRCWXjytVG%~t;#o;TK>uZcRh?J`e z3u~mTTTojpw1XiQ^#`Y{Kdiw@MJ8=KEHiH}E;2;j&6SF7)~73o`>R_$?1HiOQD`)$ zLZ5{ss-3!c?7FK~E&|hS3B6iXQ=;X%Qs2DKiIR~Q zL0={&`ImTJB#T6)tkTOwrPetu89xoNM(Djel*LTXlxwFhU5%xkM-{TcHYf2G9!%Je zCG>FaPT8YIS^p2vr@BqB#$G+_gQT)I z+LJ|jPv&-;iL>-cjJ2J%D)K>eH^ww{0G|R+g45yNa6LNzzd;p-;V}3f`u@k@P4Gro53=vy{;(h1hQ9w3_!h_> zfY-v+a1~60*agHM@G@8rBd|Ao7QJ751YQW@Bk(A=4W0i(k^Vo+-(|2bd>bAATktWs z99{=6fM(jU^CnozKadu^{^Apf-_-%_yxNEr{Oxd0xpHuz>DE|a2^~8H)034 z99{@7fHOgS35MVnGWmM=9K0OL@Go#O908Ap2gAMLuJ9*p0kS{A3t&Aw1ipd(e-W$$ zSvT-k%1o78#ty)UksWm{)C;J(1mNC2ie=5*9!9%>*<-=v(W(2A+YyH#)F-wg+&uIXdFb1__WHq%Q!_-l!Ryl#IF0(YpG|VafaUS{O zT|7)3>A6=$@gG#$-8uIvwxAad23lLm^R`%PQ3I6qR#NnvRN3jiE9ZGu@NN+k+Z0Ylg`e# z;hPqmZ?RJek_cK4a$?hVq7%p1ADOt0_7x9nq-RwCBfLx+e8Nkl&>70dR1A8?IbKVi zc(}sY3y*g+3$-Z19g`S2Q<4lw$}D+jf9qZ&pVGhjzwuoqS9|WO&#Mv@dZt!0pIZ`k|%IhwZpn^C1bEA*Eh+A$>70FhQDTWQ*a=}yw zkwEd7$K>Ls!6+N?rmX~~* zx(|^uNP3ZhEv+z=eUDjuyM;Am12TGz*N7y8K~tDgmZhXRq~%dII^84FEh-anT$*~t zO3&Ev^x;%`QX6PTX>+oyTMUcEFQ@4w_1>zaCVtX)>nvwRG|qCj1(^Ws5qP!PIgDA`Y_)o#va>xKmBUZX3k=>Mq7rFTa9KOfbc z|GUuZUk@(=IRl^$WAIcE{r|V<_#cL6!QmFk*#qDq@Nsnci=YM*a5BhV z{@1|2!}DPa>;>OMpMN*J3tkG_LG}w=2gkq?cm&)Z_JI=IP6FNwPlsXn8M^%E;LRX9 zeh3eR+tB5|0564?fav%4h99B3Uj-kA8ay5z00+STqPzbHWbgh@!u#Pecq6xeU4bsDjWMSSVHW+$inlS}P(ZImpf#V?C>Vr3J^F$H9#poAGe zx>qdY3%kX!SPXsrt&tiMC;h?zx2i02yQ0F6VH#Eo;^JjGdZMf&OD$a49;eA!mLWdV z=@EOTERr<(4jkxS0K{WuuuM!cMYBJ8TEEfk9?5PcV4~P7|1H5U)vSl1#&ojrctr9k z9&bqiaqYBLY-YQ~c$)^h<6w!toH9YTAxmJ5ZoCA|xHZXqudGR!ZebFWwbFl&Uc@IU zy7Vn^-K@2p_U0yS0ydA+vLdn z$^vH6{Zl9jKFpV=;eq@U(%6vzF6iid$6eR`h6Nx=j)3aPt zqT)^_YwqlV#xH*I1}oBva)-UTiMhq?AN=~R%<60Us!X4W32+Mse~jR#D}7~ZHbZRm zT_!WKg9QB)#KO8JUNr6LdM7;?&{vQWudQ6qJY)s}{UkBf;}%HZNNyYVCy?YFkC#wc zO=0-bmzO#nmYh20f-Gn7@)7tpS$ISLSc#i@cQOPQ3L_n3b$-1uUEl zUFFoA5=)DeKI=r@m23aB(`>Q7o0(@*Gi)kb3Qe%dL`SbVHkGf5q)Xl@Ij5_RmG}1V z@?-k5@0&y}>fn-pseoFc6n;{V^|&+pLhZwB!L_$qt_ z-U*k3>E@x!(k8}1_!`*(f$7mUIk(kI0wWJ;8ZvomcuCg9DBf5 z;3IH8EQcRp3-~I020jM6;BoLskUau_hTbn{{mDLo7r^N-3St-dGd6%2)(CtC-T`lie}yF=W%xC1D@dw>yi>Cd_IeqrhdsurBEgM2XU2<4wzL||ECr2r zwy+nM_7NyvPpJ^in9OU|P7fj*Zk6uG;ZvWS$4J)lUyNNoSuGLHoY<^jC7hM*_KD2S zV3O{+W_JFub4{s`jimFlf7>G-;Axp)Noy-LiQAhUA?YlWLjGrRTpn0s&rJHAZD{~z z7CQ2nV85UmX56Z?>$D6rr94YW*7z8~jq0pLM@^5ep*#XTut`cLd6$;ho-u6P?RU{w z<7@KfhAXn4Bo%sLa@Y}ic0nXRcnZP%D1Bx(xni!13nWKZmg}ncVCzybGk0^I7S2X% zt~0WkDD^!hTO=;Mvd5D(3sa&yOHym*eM%HNkrHWU%~zjGi?YStPfu&Ao7FYN9c|W* zYMIUKZflS9qd6<7vdMK@TDO{=)M_iaISYsq9}p>Y8J#8B(JQ?R>muf1U4)2cwO1k~ z9=WvnmpM6|zBM?oyY_i+WBoY#M{O*B&|I*FEqMCt1JIMfe5y&7bVhbrJ?E)ZyW!@) zN`kYh-;g!r%zF8sB#Q#FzWqAR_CUyfuPd?W|D&SwNBaL(-d~1J|5}iJ|HbG30dN3( z9-Y1h2f@QZ_WQpT{r#u#efS^vEPMuJAHXpvgXsACz>VnjuLN2DFPjeh4n6+2@HzM_ zTnIH-1t-9Ma1;9cM?m%fcs^`~$HEaHdjlQ-_X61uK=ggl^<^!$89$G{@E7Cry1@bA!qad;XW4szz;PtfbX2S)dQBkwPUGvFvV67C7#MUNLB zfvxaJ_&Peg>zMEN9DkmG{c#a#9lpJ}omIpwOR zs@?198%JfPn!n4{X$VGR9uC&4gYVhyTINAyV{l4ESx{wfc#dUaad30e)C-A(FLTtc zF;?ba5=zpf{4!PETai-ZGtQ{p!mbp~nRwD;TG#30V&%E|rN&$Kj%{5HRgxCj(Y97y zo-$GOYe!2$Qk&zJOnjolZ#j^|+L^6joJO2D^){O6XU2_P8tm!Kc1PPZ){8@aFr>zu zQS6u?P&*c|-@bC!56a`R`MtK9sWR5b?3w%)GhnSsS=OpFG|4j+5Sq*YZZPJ)Xs7pN z4MDO9RaO?Ld5Anij4e;nV=XxZSC+Gj@9C^MGC7qvvzJ98oO+x*EyrG~c_pjiXU6AI zUd`d@ZvX3uMqN_!Z_2vaj}qduZYEktN}BGjc{9p}ByZi+xu5J}G)Ya;@0-e_GSNOn z{K$3|MupY)=2$+JsoM4HR7P3}BpKT2AgHi9c)e|FloK4}sFF>`!gaVI79;Ue zb@$T2^m6c2W-td|EE;~tpM{RnmgPn?Sf;1AIsKhFN;TKSItmecY0JcJ^^|$7IxVF0 zTDgA=rv|tPXSHj%64NYLwe|(Ud0w_gu=-Lh5q#r8HBL8SskZEfrDS^-@#{2ne{_8t z6KwT)AWco8i^Jj)0#x;=$HvYHIx+n zUsOor|3A+AYti-J4zj*~1&FWzSK$5d7I-zh5S{}o;hX68SHZ<#&H!lh{uB`1{}1T; zzXDkU@IrVF{2RzRfbXH>Ukh)8=fZQ~DX=fx6P}A+UxgDv^!)Fm&wmfDfOFswcmQ03 z4*wdMfJ5OHboL*C`1@Z4?|?Ue%49}A+xUkz8mQ{iw}0pCDh ze=ocTUIVhX-!_;4@%cXkP60V9@OJe0-@WC{UC`mKji12@*GUCZdz87? z#%rpxHCx7P?!~Ds$$}u&xB0ua)G)$YPNif8%9_{s8JWv;@)=UM+_<)4B6yj*8pKsL$^nVnCD z5~71fz6kc7Vr7kU4`XF5>3Z~WY;m6y830=D z5sf)!@#2-!zEYQ0_8X~%y%s+C=f|T4kSawMg>P5K=c&S!&R(62SfHq^JT#Rnu0HuO zNY|pMmc$QU5=_>%hxDU^t(IOL+M~u~qR}%FsVIy!o0C#e2gb@`MPOHQmH=`%WdB*v zo)knp+1lcNaBWnV3d8n~4p=vD2CkZ3R=V z#CWw<+Y6#dYD*>>+~t-9v*o=Jp7P@9}; zw%fI_I_KaFa_~Co*RzytmnzGqw4jP3$Vu2%h0{OkoXdCJYq3y5`TM1$sT8qS&t_=U zcQN%*L~-#X?qE6zR^g=c*B-6K_h<69?|jwyl4RXZ&zOSji=r3JHJBxEiIA06#}YUW zvfvEQu+SzUn^Qh3y{)XeNH$<198HMLEfOS;! z*i_ohD4aW5l?Fq$^RkDUy_{EKnXBf|3T}Ix9t@^G+cS&{W@L3dsO#O_-Ha^y|H0_| z#{a*@``gj^KLn@1Fo=);kJ0n5f(zl5a4xKZd&76o@!t*4gY!Ui|1Gc?Ho^w@8M^)@ z@b7Re><`~TzkerO4zGrj-~sSG^nA0&Uz_)3coM7vnfpHsWZ%C=bDbh@xck= zBg{*Z6Nw;7-r3)}*T|>zul{ek=(=X?nX#?pj^pGJ=C0I9w(iu3T$nM*hzj60%lK~2 z9X7e0l{}}vHsnoomHR zV>%FfblJC|(Nm^zz9l)E(XcFGlUy~|{O`6ZM{i~86-H7NY3)iXQQTK{D-zSaN;5j+ zA7C3tPdVu-=qW8k;P6@RBqCEczG%ik#ab+`@?5MH@+>j8X3IYvAZDSnT20P} zaT1dx#50K_)-gw5s8K+abDo5&>f|O}_aeoTos;ANT~hKqjLQT1A-z&eYFFu_=>wT_ zYMQfLtjeFx%CUNR3iq*S&PCF$AL6$JpEVIO*U=Ump%Fz0b&*BNtcz_)_Iis7=S=P32yVnbOYJObjjbxX{izSvG$0E$t@Y9N zMo!>`hbXD(xje&}Jp$}wc#_GCq{p?Q8+}>TYZ^60|Js+mQpAlhxak|M!BG%yqmru{8`p7vRTcdob+z<#=9p#u|C#*x82bK& za3p*a{r(&9GN^#;^>-YI-YWJuYogRv3y3C-val5z2PJ1?Q)ji7La}YM&TFe>oUJDd-?qqz5D`r z5lq2T;UriAi{OEpq~9s>KrJwSB!*O9l9zf+ROQf|_fhmH1hD_p}C)!VVxs5zo_9JNQK zC?^gxQou5l(u>fIP#Q1S>TU76W==F7591os3b)mo)0`xsRxGyzEfnP3;8wFPfDvZp;c{?TFg9Ekhe4lY>@6Eg$pg*1 zUd>;w>oGl7GGUp&=nku8;G-9{-jtet_GLD{4=%3{+in{a|4RU_uT)}_Lq z{~DKi!2H*?zG~X;*0R29Sg+dUb}q_9wldg6;~U)^DX5vTdeR*AXuaFMKz8@>&(H;} zl|7K+apm8Ch9pN0MZ?UDT*$QN`EPHTNAzxSM)@n+h2*w4-{5yXBzxbj&N7{56k{V> z`;+POCDBvjaK6hw75w~`eIMoCznm5Q{{+<1KZtIM{y)K=|3>e>7{=jd^!%N0CTxUJ zxLx#pcqS|c*}Lzfa1Dg;ICu#B8r@#z_-}yg-~%9j{;!9Z!wW$6|9>dRI{){m~~;NRitun7(ZIRoGw*bH6*PlRE( zFUSFlzr%KLGu#Llg3R?F3ikmy58!9;1CaUtH$w=UVFfILhr`1_%IQ`qtKE7@W29** zv$;4Sx5astJ;_^PqcV%JvppR~3jz~K%b-}93(N?xw_{c$hyMxOty~G)ZE#T?ZiSQ0 zZA=#Axm$G`H!$y$3>|Dk&4O!{sdiFzEsb0pKxGb5>}I`|h}juxoIxyK5()WEyh7I~ zDM@om+NMN3@l`}gE6F$dC4GWSAefwT71qp(Q*$|*se2_^<7j_{=Ln?nluT@*afxpg zs~vprp@$AIR$BXAusIzsc+TI#K&^_=%~=~@ot2q+hSX%|$TO5AaITZqEtx^lkpM(6 zTTL7rtY)8VoEru=>a8aAmrIxGTuVwhd6%}Z*uO}|cr7aFi?}RfNX?pCs~Icm3b#Dv zd3NKJ&+~89B*b~PXeuiyZG*)GY@ZZvAKnsnj<45;%$^aF4jI*l<;uj6NF1&SnJJ8` z+>rVzk=ObL%5>F4SlNozq1?flK?m;apklWMYshS7{18n}LqptYi#~k{a?U8L96EqG zbFe&C8O-RcmU%Q@NTlaQ?fA=sVn<$yJ#jw^3nNiyzEiD+St6UpRBK4sPr9Zc z$BXhSU`m1GO_VXRRb`tHD@yc!OivARlfFSNs+ZfcT}&9vOw`y!M@?gtbgnrvo!GMy zDHT&0eN-%IZS?_Mk57y5`qBBP{sr^SZIYNH!w zTk2$tItMjgKgY0$Lov0n;O{gA<8A$Ek)iWFJK>@#lw1GQrA1@~x$Kn|ri{&B>KQF9 z&LnGgialf+mHKp5ui$B7&{oZe8Yt$vPOG+M3-yf5rtUUdZbQE##)XhQIDUrb;7D^HtcZzDdSmN<=u7vAH#ARoI`Vnz|Um=$kbE>iVMOXe5 z)VvBu(f=RBkmP-l{y)b1=c4zY0SCiR(D&t>zn8#^;c0Lf{2smk&5;dYEq@;Y2f<$O zYjpqbz!yO50`G&%;LY$RSPuj674-fq;H7Xb$liZv!KrXO90fBPvR)tXSt477Bpbc1_l zy2`ab?tUisk->duZm*2B3@X;(mzkR7@u;_(Ma3Q9I)ldLiP!dIvy*r3ZN41-JGcVq zc^Ivd-WgV^*mB3TlsxItpPXiVh#yITSFu)NhLbEb9ce=eb#ECJjTg4HuPio3jIeaW6# zg`(4g*{I9eFrGyX|A*F|_&YF9nHdR{+qivW&EAdE1grmcZ)f@%YdkbnFE?a6D*ZR3 zM$$^HNdaY5v{VN3WW86YGIDmB+G3qp(uQ=e^>?F}`! zzTBB^H3m1QO)-pj^#jz#(~^_yJ}rYcl7ZiHeKu`4$j$7uH(Ng$ZH)m<2AUYVS!k!& zcautP-YvcxR+n3yiIm5wd5B)DB{PR+o9+sBL~M&{a4QoJo!TTeSnRD+<(B+ZSKBe; z=---E8-YazXExfv`Ln6KzSaCgm306Z+r~m?B0{RTJX`etQ&2LMuF3rWM&7>-y}t%> zzTXeg_1^=c``6(ZxEUS)rLYnn4F|w4(dRz}Z-f_v`2AO51DpgaVHpg;Al!~#|2Oz6 zh#la|uoHH`?dbPEfakyx_!_$X=isyOIuPH0&2R+l1$Tu{qU(PGUIDVdzw80{2Xy@p zKn)%aW*z^B(eW>bH^Hkw=KLQDH=*0h-2PiY=Jk&OSk zKp9SlU!u>y1)d4V!JjNW{=I%(-lN|4{K{>G^2wQsGcy&7sZj0AMOBCgMEhK-A%_oCXPGDu8MZ9E#IWkZI}59xN?DLhZy#&G(6gg z>>0zXO*lTz3GQmrA=-$Q7J~_zNg~={w3}3CrdA21Ny_YovVoS|9Q9VnD>^low6@^(j(GjXG_2WR&CtcAflU`txsj^R!#42Rp87MaT(Tt2uslUdcM z9g6jCXfewSQd3~LfsOVKY@~2tBfSG-M-F#fm5CW5YTfkYSlBvk{M2x^J(RMLWXDcM zAnh1#Gqz}xQFb*_n?QAWrk*(M@1A(Mn|s^f4tLe6v=XcGrPj&q(Cg$%s>($FL_#Im zvccb0*u-IwUTjeE(z)NQ2Hv*e=3lPO*9`0=V&m=MnO3b6o>Z%c>X?cX$ChwR%8XPt zrtqgb<3~EJLGF#CRUk^T30!=A)*C~jATO4JhILv&t5&Ypc8H6bT7EK6sZ>}#y@Ms% z%lX^DA5gX;-N`7jTAp1=i=7dlc-JpNhuAIDv9Ylz)KUJXSG+C`9uo|n&HAgyl#-z0 zOhKKt;u z>WCg0YE+}?1r-sd$(^vHij#!8QLS^28hG|n;;_r>iKO+kIi4w&6lhjb`@Tsm!}gu> zzLfqV>#vaYSh8Gd5(7)>BnTaD)>|Rz`0OWVqTz32|2S?2%J*rW-3?=XIg_U|C&L2w5CE}xOk_U0g}a_I@M}<|86e^75#r0mGgOb;UDz> zC-LWU^nN-2@0qX_o&sy&Xjlr5f<^F7Yys<_1h-%V_&r<;p9DDv;9uY{SOQ`n_yt@F zvJb!`;q&PF=Yj0|zZUKbUq#=SJpfLJ(?I6>FGjy#2BP189L|R)!L8`@uYeJF5IhjX zkN*d-9S(+T(ceD_qSIdhGf;si!SS#h2H_xhILP^Y&w)ko1$6ou*b3*u(XbNsha1uF z-vzPblzzRs@)mh*kv{^IyTz=rJ;IEi6fevh%sfC?UC1 zkNmaSeaCQ-5kua+g+>qV&6Z|gz=CD&&4|L@mMn8?^4D59q6D$BE#nRl?MbM_v69Ln zD@A{Dy#eg8p$XKQf0iIeb&$DcZhf1SoXnY!{+zzX7Gzp=QztW63qJfIrMs4#aMVJO zl1YR7okRXQ_ZJxTxiboP#0Z>*#li|PD`NHat+CrpOJNV2UYtSFl|=T<`8VZtx7KMp zod_h=aZuBpwO2}b$*;O2i3f;LcjB1H97~-$ZgWR^?kD$3scXJ({NE*`I(?u0Z^{0k z-zO5W;ACPt2sQsN{{QDk`u|zHm-&A=`|t7aIJh_53$91cmvjG4g?qui@CS7JpTG_9 zW%wU>7F6JF@Lu$M+51oY0L1Tq5gY=yq3eGEUIrUs13Vahi@yI_I39kA&i_sL2D}9> zg(@rp@%6tHE`bU>3cip2|0=i;o(;0re+$Uo{%6BCu?JiYn?d#g{1E#7J3;3Bcfm7Y z72E^%hRw^0crS=u;8>8o03QsWCVwT5 zm7OJP7f6>&APby^q1oCnJC&M|8PpxvnbGpI^qh%=)lGjbQ^|D*X0=;NXhv3KH%^hf z^d6F@uk7~BG5QzJQ|w1reFWA#)QPc`!E*O5w?A@kK(*n96{&@T_8so^`T?nRgURJ0 zWYo@!I;UiR1Z(A=>`~nc+f%p*gw-X1)x&FNn`kYzS*NCij@s9{6*BL@M=gHlN7@vd z{OmF2vaK;vDh0Z?^es|jw}!LslpdC-)Tnu`6+5^8B*3KHc6+kT49=rD<36&jTm9dW zX>+!gSunFpNps25L-S_Mk}1Au)@*)f_h_*3Ubdd8H(AxV$A$xXd1IHEy~52nJ+-FW zvb0+qz_&(?SPv^AmpYoUb_KdaT5{QOV{l8*`W((-7SA;CNE*8e9 zHwNkXv`h?ow6BaYCJCU3Q%AQfNZs#M)w?A>b-&kjgOlEKv#*$d@nk^HL4M|*>~y8F zR@rN!`=4T1o}OY5>6Ed8^&jqNHr)T)QzB7Y?a@-~-nd&&WbH031=j7-w1dqZ*-eQj z3cZRexKM?8vWqFUT|9-d#&WC9T;Vd{6j)#@74}1X|z`Nm6*a|1W zW8t3gQ}q9Lf!G8770!jT;dD3&R>M!R1^fhV0y!V>Kj0i#0b(QgBYX&CZNTBM0)CHe zK=uke7izE(o&ZO~2rL6xD^?5xfKH@MyRJo4~i>0$2<8hEHG@n1O9D z1{>gHI1Y}5A7U2}yMeJ69LM_-d>GrnhhRG#2|vabAp8A&8RQIs55l|PoiGT$!X|J9 zh^?Rvhr_QagI|G^#rq-ul?Xsrsm1*b`)V}H=FzuH$&DHNr#-TwuZibCRA`IwnADRY z?j(qPQ;|u3dpP7q9sNBw>f)3*bE29+OJ*4QeRRcie)08 z>xy=$u@1kgjk6(=e2%vbl6jQaP{|LXY0#0?m}BU2RIY0{bWM-$r^J|OmAl9CZRcc{ zk~gK$+c&z$P{pnUG6IR`2I!x=)!;Y+qV8KsTByo7Cy7J@xTNyxF@~tD$r3zvDU(X5 zy!SERs58gQ)I|7;+riy)3JkcWXfP7i!wE_OHe!9kd%usmSnew5B6}<%PRX>s-cVfi;SWD^sm)EnA3Ji*lZ$d$r#m z#5?P6*B6l!QS-~0%QGGxDyN4ALM$=Ko~NnFmcWiv5^R$dXl$jLR4(^4%08m*HdrgW zl84EYBwC5`-OZ>KcAJTm0lwv7-*ui&GBQp+HLIjP^N;;L{VDs|_l^I%oZ-BFY(}E` z^VlqWW7XN1TKy*a|3N64Zx($M|Npc3a~*p98{qXY3rE3k(dT~yUxZh|BpeBUMTh?j z+zelX&%o7i1-usyhrQu?^!eArMIh$`i2uKw0r*DP3}?eW@I`d@FTmTN3IlK>y8CzF zWpD!Aitc_3d=I`1*TL0r6&wzKLXVd-0OVZ2U2rVO*?_-+55O~EJ)8k|hr7Ym=C?bxAoM;b+rQ=QvKNxeyKo}HbIroeJ; zWc|Ny159-MTxu$}Qo@ilRF&o72H~ zt2s$!7KtU*x--*MYcAOQJ!Hf$orm(J76mlU-XPpw38%1vZ*+pI*h=hjgh0 z*ubVoU9%BJ4IBk>jN!iSC?3DlYTTg2g+#W%pIdMew-GnMtih#ZnxXtnYKcx)k$RF= z9Fj03IlC8vPQhHlP{J}Ab%0Arn1p8)53Lh(rRxrLaa@!mQ+8VX4txH^^VUz1!tLJ9j(&$g_iT-~Ey62Zg2Sxwi#Gg;1 z?_UV#!r`z2eul1p89V__0@3-ef~UZf;RG0kMetzQ5AFl9AK*C1xq!0o-*3?I#qVF% z{$C7jI1Y{lnb*Gp%-a9`cz+-GBRc<&LDm9%0X_+zfL-tmI1(NUKep@u&*1%W@MCoU zkAkcN*dP9it}kc)eG5JaSHg?nc(@In|Bvt@7=wM_UFi3(gV(||JPA&RL*T3E`p<*& zU=lXLscuD=fV<@xqiv>|LLat=h+0hes5Py+Q9nSr0gE4PY^04 zKUxy|pGKQhGXZLgKAl{)z7)xd3^21DL(Nt)IbyzTFIIz4b@ppsW+CJR;+8O61&bGrdEGtYDQx*Wx_OSrV;{ zXx3urzCw--KAChVXIAN$j%7%0cd)4|leL?>q^uUX=iV%d7jd%cMHikqxLQsA8HL== z3wj26aJ5(>^(Wk%viLVr7Kx_uu-sz zxg9CBYmZg0JyW@gRjs6AiHGfos4mHpV7^&lm6LB(uvLvDi}b6H?nE^_(U1K1_OedO z?Ym*yR%a}VL4sh|e_Cx2GDo{(GAS7c8jS<#ZZT?_{w3;so#A|}A0n{H2RdqJPErjA zC6>9foO7Ri%ytnu{ZOJZx^6h5h1M2J=_Vo2+#PF$6ryxko>e2U$;5r?hTqnH~~Nz$0STydUzS8{{AlWJJ#(%w@AB|GFu64%;!hptJ&lj(%0)%m6q z=H67LkmHK365C}v&+M|)*hFkJZR3&VF2*$elwxd%tU}lnwZ4b9i+!MAG-1Ww=T2kX z^SX7{TBb&B#T$ZJX%**eRf2X1}E~;p*wxL2quePhZ-* z*_#@m^F3+O$-pgRoz$adX_}({AAml2bfo{U=KW>p`{MU6KL1r1fN!AlzZB#gz_oB! zxDx&T1JD7{{eOdge>t2CH=)lz8y*f1gTJE3zZ@EH3akOy+wVi@?G2cO#qfP}cG=(W zFjxXVL1(`d{sj(!@1UoPfBya9Khe+M3NHp(yI+T=!5JX)_)mZ@pr^kQHo*Wq626O0 zei3YhCGctV@i)LK_$m4D64(hl;24m+SqbS^lJWXof0mb2n~@-n_I#&`%3ea!ml~oD z&g!P%jLucTiKoo-;%x6!)mNP7Q*}}s1V5UC!5u z?UjPHJ3%R`=p0cw#kScPuh%Lp5|*QrY1hk3>i6g$x0_wAw>4l-GoHU(Ki+u`I+g_C zKF$q8x9d?I#%OLBJski>bO_{vM?Um7%C69zVp{Lts6IB=8;gtKH{n)4WK>2zZ_{Ec4k7cohGheS=-(@oMAFu*=qTDsvL>k zCbV6O>2kH%etZsrG(Y^th7feRPGtMN-49vUgKWr2b@s;9JH+*6pdC(;LXd*&w{1x+ zb{*Dm!nYHhGAf3`~oA z;2Col=1Dxq=GYx1Arx;aZoQfZNt{F?Ri>D8RDSy!zhCs`24$*;FDSnpv4biiAw@vs zwmG*R>E_^zO0QJ8;h6g93#nJ4ikEjPQE9=L?c#fh#Y8CXwgq4Gf7I~OnWEn^|G$nu z-$dWP95%te@F8^l4}zTkHv~7M>t70EP=0+2D8uK{=B~)z5jez1t-9vAbb3Y zpZ}kD=&#_1a1+Q{ep$PJ8#aKC!Zl##_J4;R;Ips>4u(%*2iOYt12dn04fcSKfSl)7 zg9pQ>(f_x>G%SW6q5EF~WjGif0pCLZe>H4?`@wzT^Vk8N4<|wiZYOVk4F3;41y{m5 z;LY$RI142IHbV50ChoWF7n>@WPwRePibc|BD~`P$UDYGGvm1X%!t=sFSM`SB8yVz< zfwt-mBio4FcoY#po_&+-KwR0n+#e4KL-N3WC24Vk81)CyJv-bWM*Kl^&y8YzU60&w z1?h5cp>)p-Hw51ZC%gTYWNVV0ZU}0EC^KH}o)yJ}xGx=a+qlNXUVZg&yn_iOnsrd7 z1Ep3e*Jpe24AVX7UM`-Uc9yYVDpK?BX_JvP3$LtuULOtLJiI%hpV*JA09EfO{Q(=1%YDWVZ6E?F@&A7~ z$XS1Ncq+&`09osQKe#W*etxfkQ{fc&Jv#rV;eBugya>kOv2X;4pMcl}{(ueOX7~nt z9zFpdhqFQK1ABw`4SX8=zzbjv916d|F7SDH8~i88nE(%gFJUA2BD?@(AAnzB7x*Py z1|d8WzKv}_&IP$zYd%6Lrf5oBN=YQlyR^m7 zXCa7{NXLubgkb)fy+BA0u)=s`{n|bcHEy=!+|hFEIa%g&Vh`8C^1s)cj3K%hyDF(dnHwH>7^2V(f$!Fa1oy z(jw7k=Ib?$B2lqVkk(5f(w<*t$&ATT{g>(&t*nd0p#8Jtdg6zv4R~7mc|5I(xqJPi zZ}hTTOY?LV-=RzM(!&*9yxj&KSjD`~BmVJ4y8ik^oWDyi*)>hS&q|`=mb!E!P(Bve z>aZ3sk8O2D#tE5t`E1axQCqhOOV?4t$7?S31?C#GrG;2Q<(sl|EzeR0nN*|;s;HpJ z1e7hLJ>}Y~y}Re%vA5XrvUG(iE>-&yn+4rubL6Jn-P1ufe|xq5ykj%_tDO-}Hn-iu zYtXa|&8UK+|34CCv?e+!`u|$~yb)c$4x8aga5DTEeP8?l-UHj=@gV#B-;S>TeRvx@ zA9lhKa2q=RhhZxm2ERbJe>+?RWmpX21MoF?6`TosfvoL+790ad!>7^VFMzY*2)G3u z{tA#Y{T>EiLw|oMJR8n}RWJgg*B=1)fWM;G%i8`991o-LXgCP|8y)|}a2~9M13}LD zlePUHfL*W|P6yeuU-tED!6EP?^6XNOyql2x>-GwqS9L5N#YZPMYWHb(`m)oSb~nzH zFtWGmMZ|@jP^|!D0NCf<&c2dyDi&`V+3d!BK&Ok--<*VNYen)^UU19O!4cK;NpeAu zbGNr2PWP~6L+)lTCi1RVd$QTtc)8vVGh;bpS~00BnK$yc`(RX+Ni<~C8OPbWYs;;+ zI$qaVA>clxTd>|hSC?CziSD7s@y^L!RM=EmrwS+MK5wzu{b~H*>KMHjOJCIvF6~aU z6{?-+IMJ3}3RVVFn7B?H!+C$Y)0ACg%(S3oBVY%yq4D-GTLrf}?e(*b%8r1rqAi5CWYvqU^x$jSP*d9@~1gf%eWknqySq)^9HQ6Jr z-JE1w@3ElCy?UGCiSpDGzZvgs$sE~*DXBnasbjz^iikbbwcls0Z|<)WMD`p1cZe-* zvy+?bSIPbmN6G%u5?7I^%sGSGG+L_`B;jO3=~M1WP*xywkem`iPRl4PJ-s=U7ih0$ zDiPAJoDjSXHZ4bW2;Jn6c?#K=v*(j{ftBQU0WsjMgQ(}nk8^H44M*?3)e7a&YNd=> zqpwywsP+G}HC8?h)rP}ixnfqW1Y$2k1oAf8V|BVEgEf&hmF6U^TH55|tg2vDk2%-W zY#4plQ!9u$)zX~Gusd}$FUX=bH0S=p^|N>F?6YppwPq&u5^?UWk+GC7nP#K^raoGQ zJjc404WZAPW(6>ZdX`h9g=<+VKk|YrR>N}cE>yqBhMwcdg`pz*LO*@FTpt=(7%B$y zI8(f=Qe-nv2bSM>i=_;W4#zMTIj=l+TQ ze>yw>zJVnluYoh*p73#W`JFHdKSOty_5T$J;6`-z>){O`x_lF4&HoxW z3LXRE^Dp}Sec>bM^Dls%(1dY#2z(vA{)-^{^*s$X!a*SO_}9T(;k6+7wH|ygTW~K( z2g!{b`MRJ;>Z&q-iX@xMvShd2lclN z{wjB8zwv*U_ig_7MKwu@MM?h2zN~@ESiLztHLN!ocWoM8+nWh16N1lrr;J2iWmJN( zf`|rizF71dhA$rdrKIa1&_9>uPfpi>d`|v4bFF;oIjYPLB~RdtrY%>h0%Q$~IxhKw zE8p5#GPk;a^CDDid}_kV3B@~3&Ci_P+K_%8MbV$WF1O89Gt!St^rJgaZ`aX|4f4HY zf{v;EZ^`~(-dhz}hv;jY-P*SmKyL|Qo|^nEC2x~=*)V;v>LM|$0CcLlHuF4rVTBSU zt!prORoMz=&axt>Np0#+Qsjlwr<}+OrXMMh6GmS$qL_pr`*StlK~+0YIduJ68K|0` z%%DgW>!}s%dh4mkR>Ajf8inrfs~Kb;KZojT1v1CVn^V$-O(@<0QeL3Nt6)wD-U|2+ zW=!sFw98bs-c3nWg!J6sDbrI-P_?>G)Op*7Rj&%P4blHmpG%(=T^9Yn&Yv65_1_IG zI2QJU`@pT}{5Qc@;8P&}0PlwXgtx-kumbjn{oote1TKQ7!7$tfK8{V`8E_QH8Gy1+ z!0p%uehWW>Z^KvM%Ww{?hCgB}5Zl2o;L~ss?1GIj4EKN^U_-bJo(UUa1ISr{N5B$T z1c!jE4cG>!fb1pk7wigOfX~Bua26a2cZaWHXZQ+S0O!CGcnll}a&DlU74UQTK713t z0ndb!U=SVz_kp{^-QaoH9Uc!4fd|8{u{T@?pMrmbQ{Z@53WtE$Bg~!zvOnP_I0}x0 zz2V!GnUvdSr0jHEkbjMnn*B{>SREDUsyKtpQr0=@Iq^)=n@yg^rek{R?~H_2u0=8f zT{59FOM7B73ZB`T!Dt2apb6A<>cM48MMMsPQ4jY#L5FCjxn-JkZL?Y%pH)k#(POo5 zWmx60K{=Y_iM}N5VVm=(bR^VlPo+6E%i-oSkHb-gq6drr*I2pTWeeCudrjV@HPUo0 z#~Cu$v&^iFqCX&mWNSA%P&I$m?o5x5vo-fiDGaVRW;EU0j;2e8uMu%Gh4zS)5Hq@NB%-cnK-vz|mYXenjO-ed>mu*Y2 zH23YQY~{itnVMJU`B9LVW+_Q;$w@1cgtM8S915hXwoaT*l0H;OpOiKhZmBgI(hT%g zeZiujUE9)SL&78_$-8ttwttb>q(3KlP?DdM7anwNG2oTm`ZE}(+!icyTR%J5sukR4 zgollXor*dYQKgt8_^NDB*PO0bgV|chztWx)$BAv_`gE9xCiPHcQ#yubPFR&sCYEx8 z2r~C7BhGaCNqZ*LoOD|vHS1YUI7ZItfYE=wsYWFCm2h7K-2_>}K#I6$qJ1HHlUzK( zR(s*t0juJ;O~vF;A|RS$=Z0*!s^t-aT^%(jRpG=FL~z9CYU!yERfo*E_sv#~MpUjd z2}4~md5X1kyvZqq{fY;-v6wEQq{~wN#fM&kR}9m`%x9~O$| z-iF#F8-%(cSJeUy{fkq|@`!Rqv0dSg!SzLHy>)PWJR6B}#(qB-hMvp3BY#(?kL9Xo zBfzc|is+j9ArdB9cR_yJPEV10Nn9>TW2So@JD1k3hC7=i`Z$1*)+Kbcn;Gno%`5pA zy;T+BN_`<`XX`_ADoFL%PfWW{8E6DrWL7E!*v1sHz^xpT0t_ zGL>B3+pl=XPPzs1P(*_av&^aKNxJv0k}e3n$x*TG`5l#~Yi#x}H^B2SCNdG?YU3BylLG}TVbN&`y3pucm1AXRz5jN62Mx(KvK?mE#5pIu@ zjM_3+u5hoUJ7irSqEVeC6v@3VGY_3{f9EE5n(jzU2DnNnSKt6ADjAT8@J(XhNA#1M z?uYhp7__UVGP+tFSqn}wR(u}mN`5<6^yUq#i3enQn&qM)a=M+ zgypkV2`0-x5y{>d7Ir6>22NeI#Os$gAICll1tj*e4k~_^yo!k)&u0L(< z>R|PgSFJvM!ZVxvbLxb#I zTAtoA(OJE{5~^iKly_yi)nb&sG5yoYREjz)n>N)p#}5dwp|n^)I-?i{Bs}O9nQ3+dqqj4awLzk+U1cwIenqvK!EYD&d zn{KNmFw$inHp%j3Oc2dReKuGZV!fDA7Fn^=bDcyqM98rCrroSh%UYQzKojozW`H4I zmyAAuN7u3-&bKAO6i3uFr#Zi7Moj-TQl};WqIN$cdwaGphf}8HBDqD?o8)aA0`;&_ zsSgKho6H3bVk1(D_OOXU6lBOELlGlQHHSbF$)G6jB-g|wA3ZUeJCak912qf69D_Vp zmg04Sdb2qd4B>Xk351mCQdJ(>`a?cOaiG+qh81w&P_-@@IvL7TRjoZqzLJ!2)*(g4 zA60TXMjX{+=0Z%G$>lh$6hvGz;bG|RcQl8 ze@GQ_M?BqCAnKdABv*;$tVV_tld*83%xRD<<``Qn&$)ixowkjlw3oEGJ#17m13gGF~Vth9;!B@x{^eamwKL{{c_ez number of results + cursor.exec(function (err, docs) { + assert.isNull(err); + docs.length.should.equal(2); + // No way to predict which results are returned of course ... + done(); + }); + }); + + }); // ===== End of 'Without sorting' ===== + + + describe('Sorting of the results', function () { + + beforeEach(function (done) { + // We don't know the order in which docs wil be inserted but we ensure correctness by testing both sort orders + d.insert({ age: 5 }, function (err) { + d.insert({ age: 57 }, function (err) { + d.insert({ age: 52 }, function (err) { + d.insert({ age: 23 }, function (err) { + d.insert({ age: 89 }, function (err) { + return done(); + }); + }); + }); + }); + }); + }); + + it('Using one sort', function (done) { + var cursor, i; + + cursor = new Cursor(d, {}); + cursor.sort({ age: 1 }); + cursor.exec(function (err, docs) { + assert.isNull(err); + // Results are in ascending order + for (i = 0; i < docs.length - 1; i += 1) { + assert(docs[i].age < docs[i + 1].age) + } + + cursor.sort({ age: -1 }); + cursor.exec(function (err, docs) { + assert.isNull(err); + // Results are in descending order + for (i = 0; i < docs.length - 1; i += 1) { + assert(docs[i].age > docs[i + 1].age) + } + + done(); + }); + }); + }); + + it('With an empty collection', function (done) { + async.waterfall([ + function (cb) { + d.remove({}, { multi: true }, function(err) { return cb(err); }) + } + , function (cb) { + var cursor = new Cursor(d); + cursor.sort({ age: 1 }); + cursor.exec(function (err, docs) { + assert.isNull(err); + docs.length.should.equal(0); + cb(); + }); + } + ], done); + }); + + it('Ability to chain sorting and exec', function (done) { + var i; + async.waterfall([ + function (cb) { + var cursor = new Cursor(d); + cursor.sort({ age: 1 }).exec(function (err, docs) { + assert.isNull(err); + // Results are in ascending order + for (i = 0; i < docs.length - 1; i += 1) { + assert(docs[i].age < docs[i + 1].age) + } + cb(); + }); + } + , function (cb) { + var cursor = new Cursor(d); + cursor.sort({ age: -1 }).exec(function (err, docs) { + assert.isNull(err); + // Results are in descending order + for (i = 0; i < docs.length - 1; i += 1) { + assert(docs[i].age > docs[i + 1].age) + } + cb(); + }); + } + ], done); + }); + + it('Using limit and sort', function (done) { + var i; + async.waterfall([ + function (cb) { + var cursor = new Cursor(d); + cursor.sort({ age: 1 }).limit(3).exec(function (err, docs) { + assert.isNull(err); + docs.length.should.equal(3); + docs[0].age.should.equal(5); + docs[1].age.should.equal(23); + docs[2].age.should.equal(52); + cb(); + }); + } + , function (cb) { + var cursor = new Cursor(d); + cursor.sort({ age: -1 }).limit(2).exec(function (err, docs) { + assert.isNull(err); + docs.length.should.equal(2); + docs[0].age.should.equal(89); + docs[1].age.should.equal(57); + cb(); + }); + } + ], done); + }); + + it('Using a limit higher than total number of docs shouldnt cause an error', function (done) { + var i; + async.waterfall([ + function (cb) { + var cursor = new Cursor(d); + cursor.sort({ age: 1 }).limit(7).exec(function (err, docs) { + assert.isNull(err); + docs.length.should.equal(5); + docs[0].age.should.equal(5); + docs[1].age.should.equal(23); + docs[2].age.should.equal(52); + docs[3].age.should.equal(57); + docs[4].age.should.equal(89); + cb(); + }); + } + ], done); + }); + + it('Using limit and skip with sort', function (done) { + var i; + async.waterfall([ + function (cb) { + var cursor = new Cursor(d); + cursor.sort({ age: 1 }).limit(1).skip(2).exec(function (err, docs) { + assert.isNull(err); + docs.length.should.equal(1); + docs[0].age.should.equal(52); + cb(); + }); + } + , function (cb) { + var cursor = new Cursor(d); + cursor.sort({ age: 1 }).limit(3).skip(1).exec(function (err, docs) { + assert.isNull(err); + docs.length.should.equal(3); + docs[0].age.should.equal(23); + docs[1].age.should.equal(52); + docs[2].age.should.equal(57); + cb(); + }); + } + , function (cb) { + var cursor = new Cursor(d); + cursor.sort({ age: -1 }).limit(2).skip(2).exec(function (err, docs) { + assert.isNull(err); + docs.length.should.equal(2); + docs[0].age.should.equal(52); + docs[1].age.should.equal(23); + cb(); + }); + } + ], done); + }); + + it('Using too big a limit and a skip with sort', function (done) { + var i; + async.waterfall([ + function (cb) { + var cursor = new Cursor(d); + cursor.sort({ age: 1 }).limit(8).skip(2).exec(function (err, docs) { + assert.isNull(err); + docs.length.should.equal(3); + docs[0].age.should.equal(52); + docs[1].age.should.equal(57); + docs[2].age.should.equal(89); + cb(); + }); + } + ], done); + }); + + it('Using too big a skip with sort should return no result', function (done) { + var i; + async.waterfall([ + function (cb) { + var cursor = new Cursor(d); + cursor.sort({ age: 1 }).skip(5).exec(function (err, docs) { + assert.isNull(err); + docs.length.should.equal(0); + cb(); + }); + } + , function (cb) { + var cursor = new Cursor(d); + cursor.sort({ age: 1 }).skip(7).exec(function (err, docs) { + assert.isNull(err); + docs.length.should.equal(0); + cb(); + }); + } + , function (cb) { + var cursor = new Cursor(d); + cursor.sort({ age: 1 }).limit(3).skip(7).exec(function (err, docs) { + assert.isNull(err); + docs.length.should.equal(0); + cb(); + }); + } + , function (cb) { + var cursor = new Cursor(d); + cursor.sort({ age: 1 }).limit(6).skip(7).exec(function (err, docs) { + assert.isNull(err); + docs.length.should.equal(0); + cb(); + }); + } + ], done); + }); + + it('Sorting strings', function (done) { + async.waterfall([ + function (cb) { + d.remove({}, { multi: true }, function (err) { + if (err) { return cb(err); } + + d.insert({ name: 'jako'}, function () { + d.insert({ name: 'jakeb' }, function () { + d.insert({ name: 'sue' }, function () { + return cb(); + }); + }); + }); + }); + } + , function (cb) { + var cursor = new Cursor(d, {}); + cursor.sort({ name: 1 }).exec(function (err, docs) { + docs.length.should.equal(3); + docs[0].name.should.equal('jakeb'); + docs[1].name.should.equal('jako'); + docs[2].name.should.equal('sue'); + return cb(); + }); + } + , function (cb) { + var cursor = new Cursor(d, {}); + cursor.sort({ name: -1 }).exec(function (err, docs) { + docs.length.should.equal(3); + docs[0].name.should.equal('sue'); + docs[1].name.should.equal('jako'); + docs[2].name.should.equal('jakeb'); + return cb(); + }); + } + ], done); + }); + + it('Sorting nested fields with dates', function (done) { + var doc1, doc2, doc3; + + async.waterfall([ + function (cb) { + d.remove({}, { multi: true }, function (err) { + if (err) { return cb(err); } + + d.insert({ event: { recorded: new Date(400) } }, function (err, _doc1) { + doc1 = _doc1; + d.insert({ event: { recorded: new Date(60000) } }, function (err, _doc2) { + doc2 = _doc2; + d.insert({ event: { recorded: new Date(32) } }, function (err, _doc3) { + doc3 = _doc3; + return cb(); + }); + }); + }); + }); + } + , function (cb) { + var cursor = new Cursor(d, {}); + cursor.sort({ "event.recorded": 1 }).exec(function (err, docs) { + docs.length.should.equal(3); + docs[0]._id.should.equal(doc3._id); + docs[1]._id.should.equal(doc1._id); + docs[2]._id.should.equal(doc2._id); + return cb(); + }); + } + , function (cb) { + var cursor = new Cursor(d, {}); + cursor.sort({ "event.recorded": -1 }).exec(function (err, docs) { + docs.length.should.equal(3); + docs[0]._id.should.equal(doc2._id); + docs[1]._id.should.equal(doc1._id); + docs[2]._id.should.equal(doc3._id); + return cb(); + }); + } + ], done); + }); + + it('Sorting when some fields are undefined', function (done) { + async.waterfall([ + function (cb) { + d.remove({}, { multi: true }, function (err) { + if (err) { return cb(err); } + + d.insert({ name: 'jako', other: 2 }, function () { + d.insert({ name: 'jakeb', other: 3 }, function () { + d.insert({ name: 'sue' }, function () { + d.insert({ name: 'henry', other: 4 }, function () { + return cb(); + }); + }); + }); + }); + }); + } + , function (cb) { + var cursor = new Cursor(d, {}); + cursor.sort({ other: 1 }).exec(function (err, docs) { + docs.length.should.equal(4); + docs[0].name.should.equal('sue'); + assert.isUndefined(docs[0].other); + docs[1].name.should.equal('jako'); + docs[1].other.should.equal(2); + docs[2].name.should.equal('jakeb'); + docs[2].other.should.equal(3); + docs[3].name.should.equal('henry'); + docs[3].other.should.equal(4); + return cb(); + }); + } + , function (cb) { + var cursor = new Cursor(d, { name: { $in: [ 'suzy', 'jakeb', 'jako' ] } }); + cursor.sort({ other: -1 }).exec(function (err, docs) { + docs.length.should.equal(2); + docs[0].name.should.equal('jakeb'); + docs[0].other.should.equal(3); + docs[1].name.should.equal('jako'); + docs[1].other.should.equal(2); + return cb(); + }); + } + ], done); + }); + + it('Sorting when all fields are undefined', function (done) { + async.waterfall([ + function (cb) { + d.remove({}, { multi: true }, function (err) { + if (err) { return cb(err); } + + d.insert({ name: 'jako'}, function () { + d.insert({ name: 'jakeb' }, function () { + d.insert({ name: 'sue' }, function () { + return cb(); + }); + }); + }); + }); + } + , function (cb) { + var cursor = new Cursor(d, {}); + cursor.sort({ other: 1 }).exec(function (err, docs) { + docs.length.should.equal(3); + return cb(); + }); + } + , function (cb) { + var cursor = new Cursor(d, { name: { $in: [ 'sue', 'jakeb', 'jakob' ] } }); + cursor.sort({ other: -1 }).exec(function (err, docs) { + docs.length.should.equal(2); + return cb(); + }); + } + ], done); + }); + + it('Multiple consecutive sorts', function(done) { + async.waterfall([ + function (cb) { + d.remove({}, { multi: true }, function (err) { + if (err) { return cb(err); } + + d.insert({ name: 'jako', age: 43, nid: 1 }, function () { + d.insert({ name: 'jakeb', age: 43, nid: 2 }, function () { + d.insert({ name: 'sue', age: 12, nid: 3 }, function () { + d.insert({ name: 'zoe', age: 23, nid: 4 }, function () { + d.insert({ name: 'jako', age: 35, nid: 5 }, function () { + return cb(); + }); + }); + }); + }); + }); + }); + } + , function (cb) { + var cursor = new Cursor(d, {}); + cursor.sort({ name: 1, age: -1 }).exec(function (err, docs) { + docs.length.should.equal(5); + + docs[0].nid.should.equal(2); + docs[1].nid.should.equal(1); + docs[2].nid.should.equal(5); + docs[3].nid.should.equal(3); + docs[4].nid.should.equal(4); + return cb(); + }); + } + , function (cb) { + var cursor = new Cursor(d, {}); + cursor.sort({ name: 1, age: 1 }).exec(function (err, docs) { + docs.length.should.equal(5); + + docs[0].nid.should.equal(2); + docs[1].nid.should.equal(5); + docs[2].nid.should.equal(1); + docs[3].nid.should.equal(3); + docs[4].nid.should.equal(4); + return cb(); + }); + } + , function (cb) { + var cursor = new Cursor(d, {}); + cursor.sort({ age: 1, name: 1 }).exec(function (err, docs) { + docs.length.should.equal(5); + + docs[0].nid.should.equal(3); + docs[1].nid.should.equal(4); + docs[2].nid.should.equal(5); + docs[3].nid.should.equal(2); + docs[4].nid.should.equal(1); + return cb(); + }); + } + , function (cb) { + var cursor = new Cursor(d, {}); + cursor.sort({ age: 1, name: -1 }).exec(function (err, docs) { + docs.length.should.equal(5); + + docs[0].nid.should.equal(3); + docs[1].nid.should.equal(4); + docs[2].nid.should.equal(5); + docs[3].nid.should.equal(1); + docs[4].nid.should.equal(2); + return cb(); + }); + } + ], done); }); + + it('Similar data, multiple consecutive sorts', function(done) { + var i, j, id + , companies = [ 'acme', 'milkman', 'zoinks' ] + , entities = [] + ; + + async.waterfall([ + function (cb) { + d.remove({}, { multi: true }, function (err) { + if (err) { return cb(err); } + + id = 1; + for (i = 0; i < companies.length; i++) { + for (j = 5; j <= 100; j += 5) { + entities.push({ + company: companies[i], + cost: j, + nid: id + }); + id++; + } + } + + async.each(entities, function(entity, callback) { + d.insert(entity, function() { + callback(); + }); + }, function(err) { + return cb(); + }); + }); + } + , function (cb) { + var cursor = new Cursor(d, {}); + cursor.sort({ company: 1, cost: 1 }).exec(function (err, docs) { + docs.length.should.equal(60); + + for (var i = 0; i < docs.length; i++) { + docs[i].nid.should.equal(i+1); + }; + return cb(); + }); + } + ], done); }); + + }); // ===== End of 'Sorting' ===== + + + describe('Projections', function () { + var doc1, doc2, doc3, doc4, doc0; + + + beforeEach(function (done) { + // We don't know the order in which docs wil be inserted but we ensure correctness by testing both sort orders + d.insert({ age: 5, name: 'Jo', planet: 'B' }, function (err, _doc0) { + doc0 = _doc0; + d.insert({ age: 57, name: 'Louis', planet: 'R' }, function (err, _doc1) { + doc1 = _doc1; + d.insert({ age: 52, name: 'Grafitti', planet: 'C' }, function (err, _doc2) { + doc2 = _doc2; + d.insert({ age: 23, name: 'LM', planet: 'S' }, function (err, _doc3) { + doc3 = _doc3; + d.insert({ age: 89, planet: 'Earth' }, function (err, _doc4) { + doc4 = _doc4; + return done(); + }); + }); + }); + }); + }); + }); + + it('Takes all results if no projection or empty object given', function (done) { + var cursor = new Cursor(d, {}); + cursor.sort({ age: 1 }); // For easier finding + cursor.exec(function (err, docs) { + assert.isNull(err); + docs.length.should.equal(5); + assert.deepEqual(docs[0], doc0); + assert.deepEqual(docs[1], doc3); + assert.deepEqual(docs[2], doc2); + assert.deepEqual(docs[3], doc1); + assert.deepEqual(docs[4], doc4); + + cursor.projection({}); + cursor.exec(function (err, docs) { + assert.isNull(err); + docs.length.should.equal(5); + assert.deepEqual(docs[0], doc0); + assert.deepEqual(docs[1], doc3); + assert.deepEqual(docs[2], doc2); + assert.deepEqual(docs[3], doc1); + assert.deepEqual(docs[4], doc4); + + done(); + }); + }); + }); + + it('Can take only the expected fields', function (done) { + var cursor = new Cursor(d, {}); + cursor.sort({ age: 1 }); // For easier finding + cursor.projection({ age: 1, name: 1 }); + cursor.exec(function (err, docs) { + assert.isNull(err); + docs.length.should.equal(5); + // Takes the _id by default + assert.deepEqual(docs[0], { age: 5, name: 'Jo', _id: doc0._id }); + assert.deepEqual(docs[1], { age: 23, name: 'LM', _id: doc3._id }); + assert.deepEqual(docs[2], { age: 52, name: 'Grafitti', _id: doc2._id }); + assert.deepEqual(docs[3], { age: 57, name: 'Louis', _id: doc1._id }); + assert.deepEqual(docs[4], { age: 89, _id: doc4._id }); // No problems if one field to take doesn't exist + + cursor.projection({ age: 1, name: 1, _id: 0 }); + cursor.exec(function (err, docs) { + assert.isNull(err); + docs.length.should.equal(5); + assert.deepEqual(docs[0], { age: 5, name: 'Jo' }); + assert.deepEqual(docs[1], { age: 23, name: 'LM' }); + assert.deepEqual(docs[2], { age: 52, name: 'Grafitti' }); + assert.deepEqual(docs[3], { age: 57, name: 'Louis' }); + assert.deepEqual(docs[4], { age: 89 }); // No problems if one field to take doesn't exist + + done(); + }); + }); + }); + + it('Can omit only the expected fields', function (done) { + var cursor = new Cursor(d, {}); + cursor.sort({ age: 1 }); // For easier finding + cursor.projection({ age: 0, name: 0 }); + cursor.exec(function (err, docs) { + assert.isNull(err); + docs.length.should.equal(5); + // Takes the _id by default + assert.deepEqual(docs[0], { planet: 'B', _id: doc0._id }); + assert.deepEqual(docs[1], { planet: 'S', _id: doc3._id }); + assert.deepEqual(docs[2], { planet: 'C', _id: doc2._id }); + assert.deepEqual(docs[3], { planet: 'R', _id: doc1._id }); + assert.deepEqual(docs[4], { planet: 'Earth', _id: doc4._id }); + + cursor.projection({ age: 0, name: 0, _id: 0 }); + cursor.exec(function (err, docs) { + assert.isNull(err); + docs.length.should.equal(5); + assert.deepEqual(docs[0], { planet: 'B' }); + assert.deepEqual(docs[1], { planet: 'S' }); + assert.deepEqual(docs[2], { planet: 'C' }); + assert.deepEqual(docs[3], { planet: 'R' }); + assert.deepEqual(docs[4], { planet: 'Earth' }); + + done(); + }); + }); + }); + + it('Cannot use both modes except for _id', function (done) { + var cursor = new Cursor(d, {}); + cursor.sort({ age: 1 }); // For easier finding + cursor.projection({ age: 1, name: 0 }); + cursor.exec(function (err, docs) { + assert.isNotNull(err); + assert.isUndefined(docs); + done(); + }); + }); + + }); // ==== End of 'Projections' ==== + +}); + + + + + + + + diff --git a/src/node_modules/nedb/test/customUtil.test.js b/src/node_modules/nedb/test/customUtil.test.js new file mode 100644 index 0000000..3b1ad69 --- /dev/null +++ b/src/node_modules/nedb/test/customUtil.test.js @@ -0,0 +1,26 @@ +var should = require('chai').should() + , assert = require('chai').assert + , customUtils = require('../lib/customUtils') + , fs = require('fs') + ; + + +describe('customUtils', function () { + + describe('uid', function () { + + it('Generates a string of the expected length', function () { + customUtils.uid(3).length.should.equal(3); + customUtils.uid(16).length.should.equal(16); + customUtils.uid(42).length.should.equal(42); + customUtils.uid(1000).length.should.equal(1000); + }); + + // Very small probability of conflict + it('Generated uids should not be the same', function () { + customUtils.uid(56).should.not.equal(customUtils.uid(56)); + }); + + }); + +}); diff --git a/src/node_modules/nedb/test/db.test.js b/src/node_modules/nedb/test/db.test.js new file mode 100644 index 0000000..64490bc --- /dev/null +++ b/src/node_modules/nedb/test/db.test.js @@ -0,0 +1,2419 @@ +var should = require('chai').should() + , assert = require('chai').assert + , testDb = 'workspace/test.db' + , fs = require('fs') + , path = require('path') + , _ = require('underscore') + , async = require('async') + , model = require('../lib/model') + , Datastore = require('../lib/datastore') + , Persistence = require('../lib/persistence') + ; + + +describe('Database', function () { + var d; + + beforeEach(function (done) { + d = new Datastore({ filename: testDb }); + d.filename.should.equal(testDb); + d.inMemoryOnly.should.equal(false); + + async.waterfall([ + function (cb) { + Persistence.ensureDirectoryExists(path.dirname(testDb), function () { + fs.exists(testDb, function (exists) { + if (exists) { + fs.unlink(testDb, cb); + } else { return cb(); } + }); + }); + } + , function (cb) { + d.loadDatabase(function (err) { + assert.isNull(err); + d.getAllData().length.should.equal(0); + return cb(); + }); + } + ], done); + }); + + it('Constructor compatibility with v0.6-', function () { + var dbef = new Datastore('somefile'); + dbef.filename.should.equal('somefile'); + dbef.inMemoryOnly.should.equal(false); + + var dbef = new Datastore(''); + assert.isNull(dbef.filename); + dbef.inMemoryOnly.should.equal(true); + + var dbef = new Datastore(); + assert.isNull(dbef.filename); + dbef.inMemoryOnly.should.equal(true); + }); + + describe('Autoloading', function () { + + it('Can autoload a database and query it right away', function (done) { + var fileStr = model.serialize({ _id: '1', a: 5, planet: 'Earth' }) + '\n' + model.serialize({ _id: '2', a: 5, planet: 'Mars' }) + '\n' + , autoDb = 'workspace/auto.db' + , db + ; + + fs.writeFileSync(autoDb, fileStr, 'utf8'); + db = new Datastore({ filename: autoDb, autoload: true }) + + db.find({}, function (err, docs) { + assert.isNull(err); + docs.length.should.equal(2); + done(); + }); + }); + + it('Throws if autoload fails', function (done) { + var fileStr = model.serialize({ _id: '1', a: 5, planet: 'Earth' }) + '\n' + model.serialize({ _id: '2', a: 5, planet: 'Mars' }) + '\n' + '{"$$indexCreated":{"fieldName":"a","unique":true}}' + , autoDb = 'workspace/auto.db' + , db + ; + + fs.writeFileSync(autoDb, fileStr, 'utf8'); + + // Check the loadDatabase generated an error + function onload (err) { + err.errorType.should.equal('uniqueViolated'); + done(); + } + + db = new Datastore({ filename: autoDb, autoload: true, onload: onload }) + + db.find({}, function (err, docs) { + done("Find should not be executed since autoload failed"); + }); + }); + + }); + + describe('Insert', function () { + + it('Able to insert a document in the database, setting an _id if none provided, and retrieve it even after a reload', function (done) { + d.find({}, function (err, docs) { + docs.length.should.equal(0); + + d.insert({ somedata: 'ok' }, function (err) { + // The data was correctly updated + d.find({}, function (err, docs) { + assert.isNull(err); + docs.length.should.equal(1); + Object.keys(docs[0]).length.should.equal(2); + docs[0].somedata.should.equal('ok'); + assert.isDefined(docs[0]._id); + + // After a reload the data has been correctly persisted + d.loadDatabase(function (err) { + d.find({}, function (err, docs) { + assert.isNull(err); + docs.length.should.equal(1); + Object.keys(docs[0]).length.should.equal(2); + docs[0].somedata.should.equal('ok'); + assert.isDefined(docs[0]._id); + + done(); + }); + }); + }); + }); + }); + }); + + it('Can insert multiple documents in the database', function (done) { + d.find({}, function (err, docs) { + docs.length.should.equal(0); + + d.insert({ somedata: 'ok' }, function (err) { + d.insert({ somedata: 'another' }, function (err) { + d.insert({ somedata: 'again' }, function (err) { + d.find({}, function (err, docs) { + docs.length.should.equal(3); + _.pluck(docs, 'somedata').should.contain('ok'); + _.pluck(docs, 'somedata').should.contain('another'); + _.pluck(docs, 'somedata').should.contain('again'); + done(); + }); + }); + }); + }); + }); + }); + + it('Can insert and get back from DB complex objects with all primitive and secondary types', function (done) { + var da = new Date() + , obj = { a: ['ee', 'ff', 42], date: da, subobj: { a: 'b', b: 'c' } } + ; + + d.insert(obj, function (err) { + d.findOne({}, function (err, res) { + assert.isNull(err); + res.a.length.should.equal(3); + res.a[0].should.equal('ee'); + res.a[1].should.equal('ff'); + res.a[2].should.equal(42); + res.date.getTime().should.equal(da.getTime()); + res.subobj.a.should.equal('b'); + res.subobj.b.should.equal('c'); + + done(); + }); + }); + }); + + it('If an object returned from the DB is modified and refetched, the original value should be found', function (done) { + d.insert({ a: 'something' }, function () { + d.findOne({}, function (err, doc) { + doc.a.should.equal('something'); + doc.a = 'another thing'; + doc.a.should.equal('another thing'); + + // Re-fetching with findOne should yield the persisted value + d.findOne({}, function (err, doc) { + doc.a.should.equal('something'); + doc.a = 'another thing'; + doc.a.should.equal('another thing'); + + // Re-fetching with find should yield the persisted value + d.find({}, function (err, docs) { + docs[0].a.should.equal('something'); + + done(); + }); + }); + }); + }); + }); + + it('Cannot insert a doc that has a field beginning with a $ sign', function (done) { + d.insert({ $something: 'atest' }, function (err) { + assert.isDefined(err); + done(); + }); + }); + + it('If an _id is already given when we insert a document, use that instead of generating a random one', function (done) { + d.insert({ _id: 'test', stuff: true }, function (err, newDoc) { + if (err) { return done(err); } + + newDoc.stuff.should.equal(true); + newDoc._id.should.equal('test'); + + d.insert({ _id: 'test', otherstuff: 42 }, function (err) { + err.errorType.should.equal('uniqueViolated'); + + done(); + }); + }); + }); + + it('Modifying the insertedDoc after an insert doesnt change the copy saved in the database', function (done) { + d.insert({ a: 2, hello: 'world' }, function (err, newDoc) { + newDoc.hello = 'changed'; + + d.findOne({ a: 2 }, function (err, doc) { + doc.hello.should.equal('world'); + done(); + }); + }); + }); + + it('Can insert an array of documents at once', function (done) { + var docs = [{ a: 5, b: 'hello' }, { a: 42, b: 'world' }]; + + d.insert(docs, function (err) { + d.find({}, function (err, docs) { + var data; + + docs.length.should.equal(2); + _.find(docs, function (doc) { return doc.a === 5; }).b.should.equal('hello'); + _.find(docs, function (doc) { return doc.a === 42; }).b.should.equal('world'); + + // The data has been persisted correctly + data = _.filter(fs.readFileSync(testDb, 'utf8').split('\n'), function (line) { return line.length > 0; }); + data.length.should.equal(2); + model.deserialize(data[0]).a.should.equal(5); + model.deserialize(data[0]).b.should.equal('hello'); + model.deserialize(data[1]).a.should.equal(42); + model.deserialize(data[1]).b.should.equal('world'); + + done(); + }); + }); + }); + + it('If a bulk insert violates a constraint, all changes are rolled back', function (done) { + var docs = [{ a: 5, b: 'hello' }, { a: 42, b: 'world' }, { a: 5, b: 'bloup' }, { a: 7 }]; + + d.ensureIndex({ fieldName: 'a', unique: true }); + + d.insert(docs, function (err) { + err.errorType.should.equal('uniqueViolated'); + + d.find({}, function (err, docs) { + // Datafile only contains index definition + var datafileContents = model.deserialize(fs.readFileSync(testDb, 'utf8')); + assert.deepEqual(datafileContents, { $$indexCreated: { fieldName: 'a', unique: true } }); + + docs.length.should.equal(0); + + done(); + }); + }); + }); + + /** + * Complicated behavior here. Basically we need to test that when a user function throws an exception, it is not caught + * in NeDB and the callback called again, transforming a user error into a NeDB error. + * + * So we need a way to check that the callback is called only once and the exception thrown is indeed the client exception + * Mocha's exception handling mechanism interferes with this since it already registers a listener on uncaughtException + * which we need to use since findOne is not called in the same turn of the event loop (so no try/catch) + * So we remove all current listeners, put our own which when called will register the former listeners (incl. Mocha's) again. + * + * Note: maybe using an in-memory only NeDB would give us an easier solution + */ + it('If the callback throws an uncaught execption, dont catch it inside findOne, this is userspace concern', function (done) { + var tryCount = 0 + , currentUncaughtExceptionHandlers = process.listeners('uncaughtException') + , i + ; + + process.removeAllListeners('uncaughtException'); + + process.on('uncaughtException', function MINE (ex) { + for (i = 0; i < currentUncaughtExceptionHandlers.length; i += 1) { + process.on('uncaughtException', currentUncaughtExceptionHandlers[i]); + } + + ex.should.equal('SOME EXCEPTION'); + done(); + }); + + d.insert({ a: 5 }, function () { + d.findOne({ a : 5}, function (err, doc) { + if (tryCount === 0) { + tryCount += 1; + throw 'SOME EXCEPTION'; + } else { + done('Callback was called twice'); + } + }); + }); + }); + + }); // ==== End of 'Insert' ==== // + + + describe('#getCandidates', function () { + + it('Can use an index to get docs with a basic match', function (done) { + d.ensureIndex({ fieldName: 'tf' }, function (err) { + d.insert({ tf: 4 }, function (err, _doc1) { + d.insert({ tf: 6 }, function () { + d.insert({ tf: 4, an: 'other' }, function (err, _doc2) { + d.insert({ tf: 9 }, function () { + var data = d.getCandidates({ r: 6, tf: 4 }) + , doc1 = _.find(data, function (d) { return d._id === _doc1._id; }) + , doc2 = _.find(data, function (d) { return d._id === _doc2._id; }) + ; + + data.length.should.equal(2); + assert.deepEqual(doc1, { _id: doc1._id, tf: 4 }); + assert.deepEqual(doc2, { _id: doc2._id, tf: 4, an: 'other' }); + + done(); + }); + }); + }); + }); + }); + }); + + it('Can use an index to get docs with a $in match', function (done) { + d.ensureIndex({ fieldName: 'tf' }, function (err) { + d.insert({ tf: 4 }, function (err) { + d.insert({ tf: 6 }, function (err, _doc1) { + d.insert({ tf: 4, an: 'other' }, function (err) { + d.insert({ tf: 9 }, function (err, _doc2) { + var data = d.getCandidates({ r: 6, tf: { $in: [6, 9, 5] } }) + , doc1 = _.find(data, function (d) { return d._id === _doc1._id; }) + , doc2 = _.find(data, function (d) { return d._id === _doc2._id; }) + ; + + data.length.should.equal(2); + assert.deepEqual(doc1, { _id: doc1._id, tf: 6 }); + assert.deepEqual(doc2, { _id: doc2._id, tf: 9 }); + + done(); + }); + }); + }); + }); + }); + }); + + it('If no index can be used, return the whole database', function (done) { + d.ensureIndex({ fieldName: 'tf' }, function (err) { + d.insert({ tf: 4 }, function (err, _doc1) { + d.insert({ tf: 6 }, function (err, _doc2) { + d.insert({ tf: 4, an: 'other' }, function (err, _doc3) { + d.insert({ tf: 9 }, function (err, _doc4) { + var data = d.getCandidates({ r: 6, notf: { $in: [6, 9, 5] } }) + , doc1 = _.find(data, function (d) { return d._id === _doc1._id; }) + , doc2 = _.find(data, function (d) { return d._id === _doc2._id; }) + , doc3 = _.find(data, function (d) { return d._id === _doc3._id; }) + , doc4 = _.find(data, function (d) { return d._id === _doc4._id; }) + ; + + data.length.should.equal(4); + assert.deepEqual(doc1, { _id: doc1._id, tf: 4 }); + assert.deepEqual(doc2, { _id: doc2._id, tf: 6 }); + assert.deepEqual(doc3, { _id: doc3._id, tf: 4, an: 'other' }); + assert.deepEqual(doc4, { _id: doc4._id, tf: 9 }); + + done(); + }); + }); + }); + }); + }); + }); + + it('Can use indexes for comparison matches', function (done) { + d.ensureIndex({ fieldName: 'tf' }, function (err) { + d.insert({ tf: 4 }, function (err, _doc1) { + d.insert({ tf: 6 }, function (err, _doc2) { + d.insert({ tf: 4, an: 'other' }, function (err, _doc3) { + d.insert({ tf: 9 }, function (err, _doc4) { + var data = d.getCandidates({ r: 6, tf: { $lte: 9, $gte: 6 } }) + , doc2 = _.find(data, function (d) { return d._id === _doc2._id; }) + , doc4 = _.find(data, function (d) { return d._id === _doc4._id; }) + ; + + data.length.should.equal(2); + assert.deepEqual(doc2, { _id: doc2._id, tf: 6 }); + assert.deepEqual(doc4, { _id: doc4._id, tf: 9 }); + + done(); + }); + }); + }); + }); + }); + }); + + }); // ==== End of '#getCandidates' ==== // + + + describe('Find', function () { + + it('Can find all documents if an empty query is used', function (done) { + async.waterfall([ + function (cb) { + d.insert({ somedata: 'ok' }, function (err) { + d.insert({ somedata: 'another', plus: 'additional data' }, function (err) { + d.insert({ somedata: 'again' }, function (err) { return cb(err); }); + }); + }); + } + , function (cb) { // Test with empty object + d.find({}, function (err, docs) { + assert.isNull(err); + docs.length.should.equal(3); + _.pluck(docs, 'somedata').should.contain('ok'); + _.pluck(docs, 'somedata').should.contain('another'); + _.find(docs, function (d) { return d.somedata === 'another' }).plus.should.equal('additional data'); + _.pluck(docs, 'somedata').should.contain('again'); + return cb(); + }); + } + ], done); + }); + + it('Can find all documents matching a basic query', function (done) { + async.waterfall([ + function (cb) { + d.insert({ somedata: 'ok' }, function (err) { + d.insert({ somedata: 'again', plus: 'additional data' }, function (err) { + d.insert({ somedata: 'again' }, function (err) { return cb(err); }); + }); + }); + } + , function (cb) { // Test with query that will return docs + d.find({ somedata: 'again' }, function (err, docs) { + assert.isNull(err); + docs.length.should.equal(2); + _.pluck(docs, 'somedata').should.not.contain('ok'); + return cb(); + }); + } + , function (cb) { // Test with query that doesn't match anything + d.find({ somedata: 'nope' }, function (err, docs) { + assert.isNull(err); + docs.length.should.equal(0); + return cb(); + }); + } + ], done); + }); + + it('Can find one document matching a basic query and return null if none is found', function (done) { + async.waterfall([ + function (cb) { + d.insert({ somedata: 'ok' }, function (err) { + d.insert({ somedata: 'again', plus: 'additional data' }, function (err) { + d.insert({ somedata: 'again' }, function (err) { return cb(err); }); + }); + }); + } + , function (cb) { // Test with query that will return docs + d.findOne({ somedata: 'ok' }, function (err, doc) { + assert.isNull(err); + Object.keys(doc).length.should.equal(2); + doc.somedata.should.equal('ok'); + assert.isDefined(doc._id); + return cb(); + }); + } + , function (cb) { // Test with query that doesn't match anything + d.findOne({ somedata: 'nope' }, function (err, doc) { + assert.isNull(err); + assert.isNull(doc); + return cb(); + }); + } + ], done); + }); + + it('Can find dates and objects (non JS-native types)', function (done) { + var date1 = new Date(1234543) + , date2 = new Date(9999) + ; + + d.insert({ now: date1, sth: { name: 'nedb' } }, function () { + d.findOne({ now: date1 }, function (err, doc) { + assert.isNull(err); + doc.sth.name.should.equal('nedb'); + + d.findOne({ now: date2 }, function (err, doc) { + assert.isNull(err); + assert.isNull(doc); + + d.findOne({ sth: { name: 'nedb' } }, function (err, doc) { + assert.isNull(err); + doc.sth.name.should.equal('nedb'); + + d.findOne({ sth: { name: 'other' } }, function (err, doc) { + assert.isNull(err); + assert.isNull(doc); + + done(); + }); + }); + }); + }); + }); + }); + + it('Can use dot-notation to query subfields', function (done) { + d.insert({ greeting: { english: 'hello' } }, function () { + d.findOne({ "greeting.english": 'hello' }, function (err, doc) { + assert.isNull(err); + doc.greeting.english.should.equal('hello'); + + d.findOne({ "greeting.english": 'hellooo' }, function (err, doc) { + assert.isNull(err); + assert.isNull(doc); + + d.findOne({ "greeting.englis": 'hello' }, function (err, doc) { + assert.isNull(err); + assert.isNull(doc); + + done(); + }); + }); + }); + }); + }); + + it('Array fields match if any element matches', function (done) { + d.insert({ fruits: ['pear', 'apple', 'banana'] }, function (err, doc1) { + d.insert({ fruits: ['coconut', 'orange', 'pear'] }, function (err, doc2) { + d.insert({ fruits: ['banana'] }, function (err, doc3) { + d.find({ fruits: 'pear' }, function (err, docs) { + assert.isNull(err); + docs.length.should.equal(2); + _.pluck(docs, '_id').should.contain(doc1._id); + _.pluck(docs, '_id').should.contain(doc2._id); + + d.find({ fruits: 'banana' }, function (err, docs) { + assert.isNull(err); + docs.length.should.equal(2); + _.pluck(docs, '_id').should.contain(doc1._id); + _.pluck(docs, '_id').should.contain(doc3._id); + + d.find({ fruits: 'doesntexist' }, function (err, docs) { + assert.isNull(err); + docs.length.should.equal(0); + + done(); + }); + }); + }); + }); + }); + }); + }); + + it('Returns an error if the query is not well formed', function (done) { + d.insert({ hello: 'world' }, function () { + d.find({ $or: { hello: 'world' } }, function (err, docs) { + assert.isDefined(err); + assert.isUndefined(docs); + + d.findOne({ $or: { hello: 'world' } }, function (err, doc) { + assert.isDefined(err); + assert.isUndefined(doc); + + done(); + }); + }); + }); + }); + + it('Changing the documents returned by find or findOne do not change the database state', function (done) { + d.insert({ a: 2, hello: 'world' }, function () { + d.findOne({ a: 2 }, function (err, doc) { + doc.hello = 'changed'; + + d.findOne({ a: 2 }, function (err, doc) { + doc.hello.should.equal('world'); + + d.find({ a: 2 }, function (err, docs) { + docs[0].hello = 'changed'; + + d.findOne({ a: 2 }, function (err, doc) { + doc.hello.should.equal('world'); + + done(); + }); + }); + }); + }); + }); + }); + + it('Can use sort, skip and limit if the callback is not passed to find but to exec', function (done) { + d.insert({ a: 2, hello: 'world' }, function () { + d.insert({ a: 24, hello: 'earth' }, function () { + d.insert({ a: 13, hello: 'blueplanet' }, function () { + d.insert({ a: 15, hello: 'home' }, function () { + d.find({}).sort({ a: 1 }).limit(2).exec(function (err, docs) { + assert.isNull(err); + docs.length.should.equal(2); + docs[0].hello.should.equal('world'); + docs[1].hello.should.equal('blueplanet'); + done(); + }); + }); + }); + }); + }); + }); + + it('Can use sort and skip if the callback is not passed to findOne but to exec', function (done) { + d.insert({ a: 2, hello: 'world' }, function () { + d.insert({ a: 24, hello: 'earth' }, function () { + d.insert({ a: 13, hello: 'blueplanet' }, function () { + d.insert({ a: 15, hello: 'home' }, function () { + // No skip no query + d.findOne({}).sort({ a: 1 }).exec(function (err, doc) { + assert.isNull(err); + doc.hello.should.equal('world'); + + // A query + d.findOne({ a: { $gt: 14 } }).sort({ a: 1 }).exec(function (err, doc) { + assert.isNull(err); + doc.hello.should.equal('home'); + + // And a skip + d.findOne({ a: { $gt: 14 } }).sort({ a: 1 }).skip(1).exec(function (err, doc) { + assert.isNull(err); + doc.hello.should.equal('earth'); + + // No result + d.findOne({ a: { $gt: 14 } }).sort({ a: 1 }).skip(2).exec(function (err, doc) { + assert.isNull(err); + assert.isNull(doc); + + done(); + }); + }); + }); + }); + }); + }); + }); + }); + }); + + it('Can use projections in find, normal or cursor way', function (done) { + d.insert({ a: 2, hello: 'world' }, function (err, doc0) { + d.insert({ a: 24, hello: 'earth' }, function (err, doc1) { + d.find({ a: 2 }, { a: 0, _id: 0 }, function (err, docs) { + assert.isNull(err); + docs.length.should.equal(1); + assert.deepEqual(docs[0], { hello: 'world' }); + + d.find({ a: 2 }, { a: 0, _id: 0 }).exec(function (err, docs) { + assert.isNull(err); + docs.length.should.equal(1); + assert.deepEqual(docs[0], { hello: 'world' }); + + // Can't use both modes at once if not _id + d.find({ a: 2 }, { a: 0, hello: 1 }, function (err, docs) { + assert.isNotNull(err); + assert.isUndefined(docs); + + d.find({ a: 2 }, { a: 0, hello: 1 }).exec(function (err, docs) { + assert.isNotNull(err); + assert.isUndefined(docs); + + done(); + }); + }); + }); + }); + }); + }); + }); + + it('Can use projections in findOne, normal or cursor way', function (done) { + d.insert({ a: 2, hello: 'world' }, function (err, doc0) { + d.insert({ a: 24, hello: 'earth' }, function (err, doc1) { + d.findOne({ a: 2 }, { a: 0, _id: 0 }, function (err, doc) { + assert.isNull(err); + assert.deepEqual(doc, { hello: 'world' }); + + d.findOne({ a: 2 }, { a: 0, _id: 0 }).exec(function (err, doc) { + assert.isNull(err); + assert.deepEqual(doc, { hello: 'world' }); + + // Can't use both modes at once if not _id + d.findOne({ a: 2 }, { a: 0, hello: 1 }, function (err, doc) { + assert.isNotNull(err); + assert.isUndefined(doc); + + d.findOne({ a: 2 }, { a: 0, hello: 1 }).exec(function (err, doc) { + assert.isNotNull(err); + assert.isUndefined(doc); + + done(); + }); + }); + }); + }); + }); + }); + }); + + }); // ==== End of 'Find' ==== // + + describe('Count', function() { + + it('Count all documents if an empty query is used', function (done) { + async.waterfall([ + function (cb) { + d.insert({ somedata: 'ok' }, function (err) { + d.insert({ somedata: 'another', plus: 'additional data' }, function (err) { + d.insert({ somedata: 'again' }, function (err) { return cb(err); }); + }); + }); + } + , function (cb) { // Test with empty object + d.count({}, function (err, docs) { + assert.isNull(err); + docs.should.equal(3); + return cb(); + }); + } + ], done); + }); + + it('Count all documents matching a basic query', function (done) { + async.waterfall([ + function (cb) { + d.insert({ somedata: 'ok' }, function (err) { + d.insert({ somedata: 'again', plus: 'additional data' }, function (err) { + d.insert({ somedata: 'again' }, function (err) { return cb(err); }); + }); + }); + } + , function (cb) { // Test with query that will return docs + d.count({ somedata: 'again' }, function (err, docs) { + assert.isNull(err); + docs.should.equal(2); + return cb(); + }); + } + , function (cb) { // Test with query that doesn't match anything + d.count({ somedata: 'nope' }, function (err, docs) { + assert.isNull(err); + docs.should.equal(0); + return cb(); + }); + } + ], done); + }); + + it('Array fields match if any element matches', function (done) { + d.insert({ fruits: ['pear', 'apple', 'banana'] }, function (err, doc1) { + d.insert({ fruits: ['coconut', 'orange', 'pear'] }, function (err, doc2) { + d.insert({ fruits: ['banana'] }, function (err, doc3) { + d.count({ fruits: 'pear' }, function (err, docs) { + assert.isNull(err); + docs.should.equal(2); + + d.count({ fruits: 'banana' }, function (err, docs) { + assert.isNull(err); + docs.should.equal(2); + + d.count({ fruits: 'doesntexist' }, function (err, docs) { + assert.isNull(err); + docs.should.equal(0); + + done(); + }); + }); + }); + }); + }); + }); + }); + + it('Returns an error if the query is not well formed', function (done) { + d.insert({ hello: 'world' }, function () { + d.count({ $or: { hello: 'world' } }, function (err, docs) { + assert.isDefined(err); + assert.isUndefined(docs); + + done(); + }); + }); + }); + + }); + + describe('Update', function () { + + it("If the query doesn't match anything, database is not modified", function (done) { + async.waterfall([ + function (cb) { + d.insert({ somedata: 'ok' }, function (err) { + d.insert({ somedata: 'again', plus: 'additional data' }, function (err) { + d.insert({ somedata: 'another' }, function (err) { return cb(err); }); + }); + }); + } + , function (cb) { // Test with query that doesn't match anything + d.update({ somedata: 'nope' }, { newDoc: 'yes' }, { multi: true }, function (err, n) { + assert.isNull(err); + n.should.equal(0); + + d.find({}, function (err, docs) { + var doc1 = _.find(docs, function (d) { return d.somedata === 'ok'; }) + , doc2 = _.find(docs, function (d) { return d.somedata === 'again'; }) + , doc3 = _.find(docs, function (d) { return d.somedata === 'another'; }) + ; + + docs.length.should.equal(3); + assert.isUndefined(_.find(docs, function (d) { return d.newDoc === 'yes'; })); + + assert.deepEqual(doc1, { _id: doc1._id, somedata: 'ok' }); + assert.deepEqual(doc2, { _id: doc2._id, somedata: 'again', plus: 'additional data' }); + assert.deepEqual(doc3, { _id: doc3._id, somedata: 'another' }); + + return cb(); + }); + }); + } + ], done); + }); + + it("Can update multiple documents matching the query", function (done) { + var id1, id2, id3; + + // Test DB state after update and reload + function testPostUpdateState (cb) { + d.find({}, function (err, docs) { + var doc1 = _.find(docs, function (d) { return d._id === id1; }) + , doc2 = _.find(docs, function (d) { return d._id === id2; }) + , doc3 = _.find(docs, function (d) { return d._id === id3; }) + ; + + docs.length.should.equal(3); + + Object.keys(doc1).length.should.equal(2); + doc1.somedata.should.equal('ok'); + doc1._id.should.equal(id1); + + Object.keys(doc2).length.should.equal(2); + doc2.newDoc.should.equal('yes'); + doc2._id.should.equal(id2); + + Object.keys(doc3).length.should.equal(2); + doc3.newDoc.should.equal('yes'); + doc3._id.should.equal(id3); + + return cb(); + }); + } + + // Actually launch the tests + async.waterfall([ + function (cb) { + d.insert({ somedata: 'ok' }, function (err, doc1) { + id1 = doc1._id; + d.insert({ somedata: 'again', plus: 'additional data' }, function (err, doc2) { + id2 = doc2._id; + d.insert({ somedata: 'again' }, function (err, doc3) { + id3 = doc3._id; + return cb(err); + }); + }); + }); + } + , function (cb) { + d.update({ somedata: 'again' }, { newDoc: 'yes' }, { multi: true }, function (err, n) { + assert.isNull(err); + n.should.equal(2); + return cb(); + }); + } + , async.apply(testPostUpdateState) + , function (cb) { + d.loadDatabase(function (err) { cb(err); }); + } + , async.apply(testPostUpdateState) + ], done); + }); + + it("Can update only one document matching the query", function (done) { + var id1, id2, id3; + + // Test DB state after update and reload + function testPostUpdateState (cb) { + d.find({}, function (err, docs) { + var doc1 = _.find(docs, function (d) { return d._id === id1; }) + , doc2 = _.find(docs, function (d) { return d._id === id2; }) + , doc3 = _.find(docs, function (d) { return d._id === id3; }) + ; + + docs.length.should.equal(3); + + assert.deepEqual(doc1, { somedata: 'ok', _id: doc1._id }); + + // doc2 or doc3 was modified. Since we sort on _id and it is random + // it can be either of two situations + try { + assert.deepEqual(doc2, { newDoc: 'yes', _id: doc2._id }); + assert.deepEqual(doc3, { somedata: 'again', _id: doc3._id }); + } catch (e) { + assert.deepEqual(doc2, { somedata: 'again', plus: 'additional data', _id: doc2._id }); + assert.deepEqual(doc3, { newDoc: 'yes', _id: doc3._id }); + } + + return cb(); + }); + } + + // Actually launch the test + async.waterfall([ + function (cb) { + d.insert({ somedata: 'ok' }, function (err, doc1) { + id1 = doc1._id; + d.insert({ somedata: 'again', plus: 'additional data' }, function (err, doc2) { + id2 = doc2._id; + d.insert({ somedata: 'again' }, function (err, doc3) { + id3 = doc3._id; + return cb(err); + }); + }); + }); + } + , function (cb) { // Test with query that doesn't match anything + d.update({ somedata: 'again' }, { newDoc: 'yes' }, { multi: false }, function (err, n) { + assert.isNull(err); + n.should.equal(1); + return cb(); + }); + } + , async.apply(testPostUpdateState) + , function (cb) { + d.loadDatabase(function (err) { return cb(err); }); + } + , async.apply(testPostUpdateState) // The persisted state has been updated + ], done); + }); + + it('Can perform upserts if needed', function (done) { + d.update({ impossible: 'db is empty anyway' }, { newDoc: true }, {}, function (err, nr, upsert) { + assert.isNull(err); + nr.should.equal(0); + assert.isUndefined(upsert); + + d.find({}, function (err, docs) { + docs.length.should.equal(0); // Default option for upsert is false + + d.update({ impossible: 'db is empty anyway' }, { something: "created ok" }, { upsert: true }, function (err, nr, newDoc) { + assert.isNull(err); + nr.should.equal(1); + newDoc.something.should.equal("created ok"); + assert.isDefined(newDoc._id); + + d.find({}, function (err, docs) { + docs.length.should.equal(1); // Default option for upsert is false + docs[0].something.should.equal("created ok"); + + // Modifying the returned upserted document doesn't modify the database + newDoc.newField = true; + d.find({}, function (err, docs) { + docs[0].something.should.equal("created ok"); + assert.isUndefined(docs[0].newField); + + done(); + }); + }); + }); + }); + }); + }); + + it('Cannot perform update if the update query is not either registered-modifiers-only or copy-only, or contain badly formatted fields', function (done) { + d.insert({ something: 'yup' }, function () { + d.update({}, { boom: { $badfield: 5 } }, { multi: false }, function (err) { + assert.isDefined(err); + + d.update({}, { boom: { "bad.field": 5 } }, { multi: false }, function (err) { + assert.isDefined(err); + + d.update({}, { $inc: { test: 5 }, mixed: 'rrr' }, { multi: false }, function (err) { + assert.isDefined(err); + + d.update({}, { $inexistent: { test: 5 } }, { multi: false }, function (err) { + assert.isDefined(err); + + done(); + }); + }); + }); + }); + }); + }); + + it('Can update documents using multiple modifiers', function (done) { + var id; + + d.insert({ something: 'yup', other: 40 }, function (err, newDoc) { + id = newDoc._id; + + d.update({}, { $set: { something: 'changed' }, $inc: { other: 10 } }, { multi: false }, function (err, nr) { + assert.isNull(err); + nr.should.equal(1); + + d.findOne({ _id: id }, function (err, doc) { + Object.keys(doc).length.should.equal(3); + doc._id.should.equal(id); + doc.something.should.equal('changed'); + doc.other.should.equal(50); + + done(); + }); + }); + }); + }); + + it('Can upsert a document even with modifiers', function (done) { + d.update({ bloup: 'blap' }, { $set: { hello: 'world' } }, { upsert: true }, function (err, nr, newDoc) { + assert.isNull(err); + nr.should.equal(1); + newDoc.bloup.should.equal('blap'); + newDoc.hello.should.equal('world'); + assert.isDefined(newDoc._id); + + d.find({}, function (err, docs) { + docs.length.should.equal(1); + Object.keys(docs[0]).length.should.equal(3); + docs[0].hello.should.equal('world'); + docs[0].bloup.should.equal('blap'); + assert.isDefined(docs[0]._id); + + done(); + }); + }); + }); + + it('When using modifiers, the only way to update subdocs is with the dot-notation', function (done) { + d.insert({ bloup: { blip: "blap", other: true } }, function () { + // Correct methos + d.update({}, { $set: { "bloup.blip": "hello" } }, {}, function () { + d.findOne({}, function (err, doc) { + doc.bloup.blip.should.equal("hello"); + doc.bloup.other.should.equal(true); + + // Wrong + d.update({}, { $set: { bloup: { blip: "ola" } } }, {}, function () { + d.findOne({}, function (err, doc) { + doc.bloup.blip.should.equal("ola"); + assert.isUndefined(doc.bloup.other); // This information was lost + + done(); + }); + }); + }); + }); + }); + }); + + it('Returns an error if the query is not well formed', function (done) { + d.insert({ hello: 'world' }, function () { + d.update({ $or: { hello: 'world' } }, { a: 1 }, {}, function (err, nr, upsert) { + assert.isDefined(err); + assert.isUndefined(nr); + assert.isUndefined(upsert); + + done(); + }); + }); + }); + + it('If an error is thrown by a modifier, the database state is not changed', function (done) { + d.insert({ hello: 'world' }, function (err, newDoc) { + d.update({}, { $inc: { hello: 4 } }, {}, function (err, nr) { + assert.isDefined(err); + assert.isUndefined(nr); + + d.find({}, function (err, docs) { + assert.deepEqual(docs, [ { _id: newDoc._id, hello: 'world' } ]); + + done(); + }); + }); + }); + }); + + it('Cant change the _id of a document', function (done) { + d.insert({ a: 2 }, function (err, newDoc) { + d.update({ a: 2 }, { a: 2, _id: 'nope' }, {}, function (err) { + assert.isDefined(err); + + d.find({}, function (err, docs) { + docs.length.should.equal(1); + Object.keys(docs[0]).length.should.equal(2); + docs[0].a.should.equal(2); + docs[0]._id.should.equal(newDoc._id); + + d.update({ a: 2 }, { $set: { _id: 'nope' } }, {}, function (err) { + assert.isDefined(err); + + d.find({}, function (err, docs) { + docs.length.should.equal(1); + Object.keys(docs[0]).length.should.equal(2); + docs[0].a.should.equal(2); + docs[0]._id.should.equal(newDoc._id); + + done(); + }); + }); + }); + }); + }); + }); + + it('Non-multi updates are persistent', function (done) { + d.insert({ a:1, hello: 'world' }, function (err, doc1) { + d.insert({ a:2, hello: 'earth' }, function (err, doc2) { + d.update({ a: 2 }, { $set: { hello: 'changed' } }, {}, function (err) { + assert.isNull(err); + + d.find({}, function (err, docs) { + docs.sort(function (a, b) { return a.a - b.a; }); + docs.length.should.equal(2); + _.isEqual(docs[0], { _id: doc1._id, a:1, hello: 'world' }).should.equal(true); + _.isEqual(docs[1], { _id: doc2._id, a:2, hello: 'changed' }).should.equal(true); + + // Even after a reload the database state hasn't changed + d.loadDatabase(function (err) { + assert.isNull(err); + + d.find({}, function (err, docs) { + docs.sort(function (a, b) { return a.a - b.a; }); + docs.length.should.equal(2); + _.isEqual(docs[0], { _id: doc1._id, a:1, hello: 'world' }).should.equal(true); + _.isEqual(docs[1], { _id: doc2._id, a:2, hello: 'changed' }).should.equal(true); + + done(); + }); + }); + }); + }); + }); + }); + }); + + it('Multi updates are persistent', function (done) { + d.insert({ a:1, hello: 'world' }, function (err, doc1) { + d.insert({ a:2, hello: 'earth' }, function (err, doc2) { + d.insert({ a:5, hello: 'pluton' }, function (err, doc3) { + d.update({ a: { $in: [1, 2] } }, { $set: { hello: 'changed' } }, { multi: true }, function (err) { + assert.isNull(err); + + d.find({}, function (err, docs) { + docs.sort(function (a, b) { return a.a - b.a; }); + docs.length.should.equal(3); + _.isEqual(docs[0], { _id: doc1._id, a:1, hello: 'changed' }).should.equal(true); + _.isEqual(docs[1], { _id: doc2._id, a:2, hello: 'changed' }).should.equal(true); + _.isEqual(docs[2], { _id: doc3._id, a:5, hello: 'pluton' }).should.equal(true); + + // Even after a reload the database state hasn't changed + d.loadDatabase(function (err) { + assert.isNull(err); + + d.find({}, function (err, docs) { + docs.sort(function (a, b) { return a.a - b.a; }); + docs.length.should.equal(3); + _.isEqual(docs[0], { _id: doc1._id, a:1, hello: 'changed' }).should.equal(true); + _.isEqual(docs[1], { _id: doc2._id, a:2, hello: 'changed' }).should.equal(true); + _.isEqual(docs[2], { _id: doc3._id, a:5, hello: 'pluton' }).should.equal(true); + + done(); + }); + }); + }); + }); + }); + }); + }); + }); + + it('Can update without the options arg (will use defaults then)', function (done) { + d.insert({ a:1, hello: 'world' }, function (err, doc1) { + d.insert({ a:2, hello: 'earth' }, function (err, doc2) { + d.insert({ a:5, hello: 'pluton' }, function (err, doc3) { + d.update({ a: 2 }, { $inc: { a: 10 } }, function (err, nr) { + assert.isNull(err); + nr.should.equal(1); + d.find({}, function (err, docs) { + var d1 = _.find(docs, function (doc) { return doc._id === doc1._id }) + , d2 = _.find(docs, function (doc) { return doc._id === doc2._id }) + , d3 = _.find(docs, function (doc) { return doc._id === doc3._id }) + ; + + d1.a.should.equal(1); + d2.a.should.equal(12); + d3.a.should.equal(5); + + done(); + }); + }); + }); + }); + }); + }); + + it('If a multi update fails on one document, previous updates should be rolled back', function (done) { + d.ensureIndex({ fieldName: 'a' }); + d.insert({ a: 4 }, function (err, doc1) { + d.insert({ a: 5 }, function (err, doc2) { + d.insert({ a: 'abc' }, function (err, doc3) { + // With this query, candidates are always returned in the order 4, 5, 'abc' so it's always the last one which fails + d.update({ a: { $in: [4, 5, 'abc'] } }, { $inc: { a: 10 } }, { multi: true }, function (err) { + assert.isDefined(err); + + // No index modified + _.each(d.indexes, function (index) { + var docs = index.getAll() + , d1 = _.find(docs, function (doc) { return doc._id === doc1._id }) + , d2 = _.find(docs, function (doc) { return doc._id === doc2._id }) + , d3 = _.find(docs, function (doc) { return doc._id === doc3._id }) + ; + + // All changes rolled back, including those that didn't trigger an error + d1.a.should.equal(4); + d2.a.should.equal(5); + d3.a.should.equal('abc'); + }); + + done(); + }); + }); + }); + }); + }); + + it('If an index constraint is violated by an update, all changes should be rolled back', function (done) { + d.ensureIndex({ fieldName: 'a', unique: true }); + d.insert({ a: 4 }, function (err, doc1) { + d.insert({ a: 5 }, function (err, doc2) { + // With this query, candidates are always returned in the order 4, 5, 'abc' so it's always the last one which fails + d.update({ a: { $in: [4, 5, 'abc'] } }, { $set: { a: 10 } }, { multi: true }, function (err) { + assert.isDefined(err); + + // Check that no index was modified + _.each(d.indexes, function (index) { + var docs = index.getAll() + , d1 = _.find(docs, function (doc) { return doc._id === doc1._id }) + , d2 = _.find(docs, function (doc) { return doc._id === doc2._id }) + ; + + d1.a.should.equal(4); + d2.a.should.equal(5); + }); + + done(); + }); + }); + }); + }); + + }); // ==== End of 'Update' ==== // + + + describe('Remove', function () { + + it('Can remove multiple documents', function (done) { + var id1, id2, id3; + + // Test DB status + function testPostUpdateState (cb) { + d.find({}, function (err, docs) { + docs.length.should.equal(1); + + Object.keys(docs[0]).length.should.equal(2); + docs[0]._id.should.equal(id1); + docs[0].somedata.should.equal('ok'); + + return cb(); + }); + } + + // Actually launch the test + async.waterfall([ + function (cb) { + d.insert({ somedata: 'ok' }, function (err, doc1) { + id1 = doc1._id; + d.insert({ somedata: 'again', plus: 'additional data' }, function (err, doc2) { + id2 = doc2._id; + d.insert({ somedata: 'again' }, function (err, doc3) { + id3 = doc3._id; + return cb(err); + }); + }); + }); + } + , function (cb) { // Test with query that doesn't match anything + d.remove({ somedata: 'again' }, { multi: true }, function (err, n) { + assert.isNull(err); + n.should.equal(2); + return cb(); + }); + } + , async.apply(testPostUpdateState) + , function (cb) { + d.loadDatabase(function (err) { return cb(err); }); + } + , async.apply(testPostUpdateState) + ], done); + }); + + // This tests concurrency issues + it('Remove can be called multiple times in parallel and everything that needs to be removed will be', function (done) { + d.insert({ planet: 'Earth' }, function () { + d.insert({ planet: 'Mars' }, function () { + d.insert({ planet: 'Saturn' }, function () { + d.find({}, function (err, docs) { + docs.length.should.equal(3); + + // Remove two docs simultaneously + var toRemove = ['Mars', 'Saturn']; + async.each(toRemove, function(planet, cb) { + d.remove({ planet: planet }, function (err) { return cb(err); }); + }, function (err) { + d.find({}, function (err, docs) { + docs.length.should.equal(1); + + done(); + }); + }); + }); + }); + }); + }); + }); + + it('Returns an error if the query is not well formed', function (done) { + d.insert({ hello: 'world' }, function () { + d.remove({ $or: { hello: 'world' } }, {}, function (err, nr, upsert) { + assert.isDefined(err); + assert.isUndefined(nr); + assert.isUndefined(upsert); + + done(); + }); + }); + }); + + it('Non-multi removes are persistent', function (done) { + d.insert({ a:1, hello: 'world' }, function (err, doc1) { + d.insert({ a:2, hello: 'earth' }, function (err, doc2) { + d.insert({ a:3, hello: 'moto' }, function (err, doc3) { + d.remove({ a: 2 }, {}, function (err) { + assert.isNull(err); + + d.find({}, function (err, docs) { + docs.sort(function (a, b) { return a.a - b.a; }); + docs.length.should.equal(2); + _.isEqual(docs[0], { _id: doc1._id, a:1, hello: 'world' }).should.equal(true); + _.isEqual(docs[1], { _id: doc3._id, a:3, hello: 'moto' }).should.equal(true); + + // Even after a reload the database state hasn't changed + d.loadDatabase(function (err) { + assert.isNull(err); + + d.find({}, function (err, docs) { + docs.sort(function (a, b) { return a.a - b.a; }); + docs.length.should.equal(2); + _.isEqual(docs[0], { _id: doc1._id, a:1, hello: 'world' }).should.equal(true); + _.isEqual(docs[1], { _id: doc3._id, a:3, hello: 'moto' }).should.equal(true); + + done(); + }); + }); + }); + }); + }); + }); + }); + }); + + it('Multi removes are persistent', function (done) { + d.insert({ a:1, hello: 'world' }, function (err, doc1) { + d.insert({ a:2, hello: 'earth' }, function (err, doc2) { + d.insert({ a:3, hello: 'moto' }, function (err, doc3) { + d.remove({ a: { $in: [1, 3] } }, { multi: true }, function (err) { + assert.isNull(err); + + d.find({}, function (err, docs) { + docs.length.should.equal(1); + _.isEqual(docs[0], { _id: doc2._id, a:2, hello: 'earth' }).should.equal(true); + + // Even after a reload the database state hasn't changed + d.loadDatabase(function (err) { + assert.isNull(err); + + d.find({}, function (err, docs) { + docs.length.should.equal(1); + _.isEqual(docs[0], { _id: doc2._id, a:2, hello: 'earth' }).should.equal(true); + + done(); + }); + }); + }); + }); + }); + }); + }); + }); + + it('Can remove without the options arg (will use defaults then)', function (done) { + d.insert({ a:1, hello: 'world' }, function (err, doc1) { + d.insert({ a:2, hello: 'earth' }, function (err, doc2) { + d.insert({ a:5, hello: 'pluton' }, function (err, doc3) { + d.remove({ a: 2 }, function (err, nr) { + assert.isNull(err); + nr.should.equal(1); + d.find({}, function (err, docs) { + var d1 = _.find(docs, function (doc) { return doc._id === doc1._id }) + , d2 = _.find(docs, function (doc) { return doc._id === doc2._id }) + , d3 = _.find(docs, function (doc) { return doc._id === doc3._id }) + ; + + d1.a.should.equal(1); + assert.isUndefined(d2); + d3.a.should.equal(5); + + done(); + }); + }); + }); + }); + }); + }); + + }); // ==== End of 'Remove' ==== // + + + describe('Using indexes', function () { + + describe('ensureIndex and index initialization in database loading', function () { + + it('ensureIndex can be called right after a loadDatabase and be initialized and filled correctly', function (done) { + var now = new Date() + , rawData = model.serialize({ _id: "aaa", z: "1", a: 2, ages: [1, 5, 12] }) + '\n' + + model.serialize({ _id: "bbb", z: "2", hello: 'world' }) + '\n' + + model.serialize({ _id: "ccc", z: "3", nested: { today: now } }) + ; + + d.getAllData().length.should.equal(0); + + fs.writeFile(testDb, rawData, 'utf8', function () { + d.loadDatabase(function () { + d.getAllData().length.should.equal(3); + + assert.deepEqual(Object.keys(d.indexes), ['_id']); + + d.ensureIndex({ fieldName: 'z' }); + d.indexes.z.fieldName.should.equal('z'); + d.indexes.z.unique.should.equal(false); + d.indexes.z.sparse.should.equal(false); + d.indexes.z.tree.getNumberOfKeys().should.equal(3); + d.indexes.z.tree.search('1')[0].should.equal(d.getAllData()[0]); + d.indexes.z.tree.search('2')[0].should.equal(d.getAllData()[1]); + d.indexes.z.tree.search('3')[0].should.equal(d.getAllData()[2]); + + done(); + }); + }); + }); + + it('ensureIndex can be called twice on the same field, the second call will ahve no effect', function (done) { + Object.keys(d.indexes).length.should.equal(1); + Object.keys(d.indexes)[0].should.equal("_id"); + + d.insert({ planet: "Earth" }, function () { + d.insert({ planet: "Mars" }, function () { + d.find({}, function (err, docs) { + docs.length.should.equal(2); + + d.ensureIndex({ fieldName: "planet" }, function (err) { + assert.isNull(err); + Object.keys(d.indexes).length.should.equal(2); + Object.keys(d.indexes)[0].should.equal("_id"); + Object.keys(d.indexes)[1].should.equal("planet"); + + d.indexes.planet.getAll().length.should.equal(2); + + // This second call has no effect, documents don't get inserted twice in the index + d.ensureIndex({ fieldName: "planet" }, function (err) { + assert.isNull(err); + Object.keys(d.indexes).length.should.equal(2); + Object.keys(d.indexes)[0].should.equal("_id"); + Object.keys(d.indexes)[1].should.equal("planet"); + + d.indexes.planet.getAll().length.should.equal(2); + + done(); + }); + }); + }); + }); + }); + }); + + it('ensureIndex can be called after the data set was modified and the index still be correct', function (done) { + var rawData = model.serialize({ _id: "aaa", z: "1", a: 2, ages: [1, 5, 12] }) + '\n' + + model.serialize({ _id: "bbb", z: "2", hello: 'world' }) + ; + + d.getAllData().length.should.equal(0); + + fs.writeFile(testDb, rawData, 'utf8', function () { + d.loadDatabase(function () { + d.getAllData().length.should.equal(2); + + assert.deepEqual(Object.keys(d.indexes), ['_id']); + + d.insert({ z: "12", yes: 'yes' }, function (err, newDoc1) { + d.insert({ z: "14", nope: 'nope' }, function (err, newDoc2) { + d.remove({ z: "2" }, {}, function () { + d.update({ z: "1" }, { $set: { 'yes': 'yep' } }, {}, function () { + assert.deepEqual(Object.keys(d.indexes), ['_id']); + + d.ensureIndex({ fieldName: 'z' }); + d.indexes.z.fieldName.should.equal('z'); + d.indexes.z.unique.should.equal(false); + d.indexes.z.sparse.should.equal(false); + d.indexes.z.tree.getNumberOfKeys().should.equal(3); + + // The pointers in the _id and z indexes are the same + d.indexes.z.tree.search('1')[0].should.equal(d.indexes._id.getMatching('aaa')[0]); + d.indexes.z.tree.search('12')[0].should.equal(d.indexes._id.getMatching(newDoc1._id)[0]); + d.indexes.z.tree.search('14')[0].should.equal(d.indexes._id.getMatching(newDoc2._id)[0]); + + // The data in the z index is correct + d.find({}, function (err, docs) { + var doc0 = _.find(docs, function (doc) { return doc._id === 'aaa'; }) + , doc1 = _.find(docs, function (doc) { return doc._id === newDoc1._id; }) + , doc2 = _.find(docs, function (doc) { return doc._id === newDoc2._id; }) + ; + + docs.length.should.equal(3); + + assert.deepEqual(doc0, { _id: "aaa", z: "1", a: 2, ages: [1, 5, 12], yes: 'yep' }); + assert.deepEqual(doc1, { _id: newDoc1._id, z: "12", yes: 'yes' }); + assert.deepEqual(doc2, { _id: newDoc2._id, z: "14", nope: 'nope' }); + + done(); + }); + }); + }); + }); + }); + }); + }); + }); + + it('ensureIndex can be called before a loadDatabase and still be initialized and filled correctly', function (done) { + var now = new Date() + , rawData = model.serialize({ _id: "aaa", z: "1", a: 2, ages: [1, 5, 12] }) + '\n' + + model.serialize({ _id: "bbb", z: "2", hello: 'world' }) + '\n' + + model.serialize({ _id: "ccc", z: "3", nested: { today: now } }) + ; + + d.getAllData().length.should.equal(0); + + d.ensureIndex({ fieldName: 'z' }); + d.indexes.z.fieldName.should.equal('z'); + d.indexes.z.unique.should.equal(false); + d.indexes.z.sparse.should.equal(false); + d.indexes.z.tree.getNumberOfKeys().should.equal(0); + + fs.writeFile(testDb, rawData, 'utf8', function () { + d.loadDatabase(function () { + var doc1 = _.find(d.getAllData(), function (doc) { return doc.z === "1"; }) + , doc2 = _.find(d.getAllData(), function (doc) { return doc.z === "2"; }) + , doc3 = _.find(d.getAllData(), function (doc) { return doc.z === "3"; }) + ; + + d.getAllData().length.should.equal(3); + + d.indexes.z.tree.getNumberOfKeys().should.equal(3); + d.indexes.z.tree.search('1')[0].should.equal(doc1); + d.indexes.z.tree.search('2')[0].should.equal(doc2); + d.indexes.z.tree.search('3')[0].should.equal(doc3); + + done(); + }); + }); + }); + + it('Can initialize multiple indexes on a database load', function (done) { + var now = new Date() + , rawData = model.serialize({ _id: "aaa", z: "1", a: 2, ages: [1, 5, 12] }) + '\n' + + model.serialize({ _id: "bbb", z: "2", a: 'world' }) + '\n' + + model.serialize({ _id: "ccc", z: "3", a: { today: now } }) + ; + + d.getAllData().length.should.equal(0); + + d.ensureIndex({ fieldName: 'z' }); + d.ensureIndex({ fieldName: 'a' }); + d.indexes.a.tree.getNumberOfKeys().should.equal(0); + d.indexes.z.tree.getNumberOfKeys().should.equal(0); + + fs.writeFile(testDb, rawData, 'utf8', function () { + d.loadDatabase(function () { + var doc1 = _.find(d.getAllData(), function (doc) { return doc.z === "1"; }) + , doc2 = _.find(d.getAllData(), function (doc) { return doc.z === "2"; }) + , doc3 = _.find(d.getAllData(), function (doc) { return doc.z === "3"; }) + ; + + d.getAllData().length.should.equal(3); + + d.indexes.z.tree.getNumberOfKeys().should.equal(3); + d.indexes.z.tree.search('1')[0].should.equal(doc1); + d.indexes.z.tree.search('2')[0].should.equal(doc2); + d.indexes.z.tree.search('3')[0].should.equal(doc3); + + d.indexes.a.tree.getNumberOfKeys().should.equal(3); + d.indexes.a.tree.search(2)[0].should.equal(doc1); + d.indexes.a.tree.search('world')[0].should.equal(doc2); + d.indexes.a.tree.search({ today: now })[0].should.equal(doc3); + + done(); + }); + }); + }); + + it('If a unique constraint is not respected, database loading will not work and no data will be inserted', function (done) { + var now = new Date() + , rawData = model.serialize({ _id: "aaa", z: "1", a: 2, ages: [1, 5, 12] }) + '\n' + + model.serialize({ _id: "bbb", z: "2", a: 'world' }) + '\n' + + model.serialize({ _id: "ccc", z: "1", a: { today: now } }) + ; + + d.getAllData().length.should.equal(0); + + d.ensureIndex({ fieldName: 'z', unique: true }); + d.indexes.z.tree.getNumberOfKeys().should.equal(0); + + fs.writeFile(testDb, rawData, 'utf8', function () { + d.loadDatabase(function (err) { + err.errorType.should.equal('uniqueViolated'); + err.key.should.equal("1"); + d.getAllData().length.should.equal(0); + d.indexes.z.tree.getNumberOfKeys().should.equal(0); + + done(); + }); + }); + }); + + it('If a unique constraint is not respected, ensureIndex will return an error and not create an index', function (done) { + d.insert({ a: 1, b: 4 }, function () { + d.insert({ a: 2, b: 45 }, function () { + d.insert({ a: 1, b: 3 }, function () { + d.ensureIndex({ fieldName: 'b' }, function (err) { + assert.isNull(err); + + d.ensureIndex({ fieldName: 'a', unique: true }, function (err) { + err.errorType.should.equal('uniqueViolated'); + assert.deepEqual(Object.keys(d.indexes), ['_id', 'b']); + + done(); + }); + }); + }); + }); + }); + }); + + it('Can remove an index', function (done) { + d.ensureIndex({ fieldName: 'e' }, function (err) { + assert.isNull(err); + + Object.keys(d.indexes).length.should.equal(2); + assert.isNotNull(d.indexes.e); + + d.removeIndex("e", function (err) { + assert.isNull(err); + Object.keys(d.indexes).length.should.equal(1); + assert.isUndefined(d.indexes.e); + + done(); + }); + }); + }); + + }); // ==== End of 'ensureIndex and index initialization in database loading' ==== // + + + describe('Indexing newly inserted documents', function () { + + it('Newly inserted documents are indexed', function (done) { + d.ensureIndex({ fieldName: 'z' }); + d.indexes.z.tree.getNumberOfKeys().should.equal(0); + + d.insert({ a: 2, z: 'yes' }, function (err, newDoc) { + d.indexes.z.tree.getNumberOfKeys().should.equal(1); + assert.deepEqual(d.indexes.z.getMatching('yes'), [newDoc]); + + d.insert({ a: 5, z: 'nope' }, function (err, newDoc) { + d.indexes.z.tree.getNumberOfKeys().should.equal(2); + assert.deepEqual(d.indexes.z.getMatching('nope'), [newDoc]); + + done(); + }); + }); + }); + + it('If multiple indexes are defined, the document is inserted in all of them', function (done) { + d.ensureIndex({ fieldName: 'z' }); + d.ensureIndex({ fieldName: 'ya' }); + d.indexes.z.tree.getNumberOfKeys().should.equal(0); + + d.insert({ a: 2, z: 'yes', ya: 'indeed' }, function (err, newDoc) { + d.indexes.z.tree.getNumberOfKeys().should.equal(1); + d.indexes.ya.tree.getNumberOfKeys().should.equal(1); + assert.deepEqual(d.indexes.z.getMatching('yes'), [newDoc]); + assert.deepEqual(d.indexes.ya.getMatching('indeed'), [newDoc]); + + d.insert({ a: 5, z: 'nope', ya: 'sure' }, function (err, newDoc2) { + d.indexes.z.tree.getNumberOfKeys().should.equal(2); + d.indexes.ya.tree.getNumberOfKeys().should.equal(2); + assert.deepEqual(d.indexes.z.getMatching('nope'), [newDoc2]); + assert.deepEqual(d.indexes.ya.getMatching('sure'), [newDoc2]); + + done(); + }); + }); + }); + + it('Can insert two docs at the same key for a non unique index', function (done) { + d.ensureIndex({ fieldName: 'z' }); + d.indexes.z.tree.getNumberOfKeys().should.equal(0); + + d.insert({ a: 2, z: 'yes' }, function (err, newDoc) { + d.indexes.z.tree.getNumberOfKeys().should.equal(1); + assert.deepEqual(d.indexes.z.getMatching('yes'), [newDoc]); + + d.insert({ a: 5, z: 'yes' }, function (err, newDoc2) { + d.indexes.z.tree.getNumberOfKeys().should.equal(1); + assert.deepEqual(d.indexes.z.getMatching('yes'), [newDoc, newDoc2]); + + done(); + }); + }); + }); + + it('If the index has a unique constraint, an error is thrown if it is violated and the data is not modified', function (done) { + d.ensureIndex({ fieldName: 'z', unique: true }); + d.indexes.z.tree.getNumberOfKeys().should.equal(0); + + d.insert({ a: 2, z: 'yes' }, function (err, newDoc) { + d.indexes.z.tree.getNumberOfKeys().should.equal(1); + assert.deepEqual(d.indexes.z.getMatching('yes'), [newDoc]); + + d.insert({ a: 5, z: 'yes' }, function (err) { + err.errorType.should.equal('uniqueViolated'); + err.key.should.equal('yes'); + + // Index didn't change + d.indexes.z.tree.getNumberOfKeys().should.equal(1); + assert.deepEqual(d.indexes.z.getMatching('yes'), [newDoc]); + + // Data didn't change + assert.deepEqual(d.getAllData(), [newDoc]); + d.loadDatabase(function () { + d.getAllData().length.should.equal(1); + assert.deepEqual(d.getAllData()[0], newDoc); + + done(); + }); + }); + }); + }); + + it('If an index has a unique constraint, other indexes cannot be modified when it raises an error', function (done) { + d.ensureIndex({ fieldName: 'nonu1' }); + d.ensureIndex({ fieldName: 'uni', unique: true }); + d.ensureIndex({ fieldName: 'nonu2' }); + + d.insert({ nonu1: 'yes', nonu2: 'yes2', uni: 'willfail' }, function (err, newDoc) { + assert.isNull(err); + d.indexes.nonu1.tree.getNumberOfKeys().should.equal(1); + d.indexes.uni.tree.getNumberOfKeys().should.equal(1); + d.indexes.nonu2.tree.getNumberOfKeys().should.equal(1); + + d.insert({ nonu1: 'no', nonu2: 'no2', uni: 'willfail' }, function (err) { + err.errorType.should.equal('uniqueViolated'); + + // No index was modified + d.indexes.nonu1.tree.getNumberOfKeys().should.equal(1); + d.indexes.uni.tree.getNumberOfKeys().should.equal(1); + d.indexes.nonu2.tree.getNumberOfKeys().should.equal(1); + + assert.deepEqual(d.indexes.nonu1.getMatching('yes'), [newDoc]); + assert.deepEqual(d.indexes.uni.getMatching('willfail'), [newDoc]); + assert.deepEqual(d.indexes.nonu2.getMatching('yes2'), [newDoc]); + + done(); + }); + }); + }); + + it('Unique indexes prevent you from inserting two docs where the field is undefined except if theyre sparse', function (done) { + d.ensureIndex({ fieldName: 'zzz', unique: true }); + d.indexes.zzz.tree.getNumberOfKeys().should.equal(0); + + d.insert({ a: 2, z: 'yes' }, function (err, newDoc) { + d.indexes.zzz.tree.getNumberOfKeys().should.equal(1); + assert.deepEqual(d.indexes.zzz.getMatching(undefined), [newDoc]); + + d.insert({ a: 5, z: 'other' }, function (err) { + err.errorType.should.equal('uniqueViolated'); + assert.isUndefined(err.key); + + d.ensureIndex({ fieldName: 'yyy', unique: true, sparse: true }); + + d.insert({ a: 5, z: 'other', zzz: 'set' }, function (err) { + assert.isNull(err); + d.indexes.yyy.getAll().length.should.equal(0); // Nothing indexed + d.indexes.zzz.getAll().length.should.equal(2); + + done(); + }); + }); + }); + }); + + it('Insertion still works as before with indexing', function (done) { + d.ensureIndex({ fieldName: 'a' }); + d.ensureIndex({ fieldName: 'b' }); + + d.insert({ a: 1, b: 'hello' }, function (err, doc1) { + d.insert({ a: 2, b: 'si' }, function (err, doc2) { + d.find({}, function (err, docs) { + assert.deepEqual(doc1, _.find(docs, function (d) { return d._id === doc1._id; })); + assert.deepEqual(doc2, _.find(docs, function (d) { return d._id === doc2._id; })); + + done(); + }); + }); + }); + }); + + it('All indexes point to the same data as the main index on _id', function (done) { + d.ensureIndex({ fieldName: 'a' }); + + d.insert({ a: 1, b: 'hello' }, function (err, doc1) { + d.insert({ a: 2, b: 'si' }, function (err, doc2) { + d.find({}, function (err, docs) { + docs.length.should.equal(2); + d.getAllData().length.should.equal(2); + + d.indexes._id.getMatching(doc1._id).length.should.equal(1); + d.indexes.a.getMatching(1).length.should.equal(1); + d.indexes._id.getMatching(doc1._id)[0].should.equal(d.indexes.a.getMatching(1)[0]); + + d.indexes._id.getMatching(doc2._id).length.should.equal(1); + d.indexes.a.getMatching(2).length.should.equal(1); + d.indexes._id.getMatching(doc2._id)[0].should.equal(d.indexes.a.getMatching(2)[0]); + + done(); + }); + }); + }); + }); + + it('If a unique constraint is violated, no index is changed, including the main one', function (done) { + d.ensureIndex({ fieldName: 'a', unique: true }); + + d.insert({ a: 1, b: 'hello' }, function (err, doc1) { + d.insert({ a: 1, b: 'si' }, function (err) { + assert.isDefined(err); + + d.find({}, function (err, docs) { + docs.length.should.equal(1); + d.getAllData().length.should.equal(1); + + d.indexes._id.getMatching(doc1._id).length.should.equal(1); + d.indexes.a.getMatching(1).length.should.equal(1); + d.indexes._id.getMatching(doc1._id)[0].should.equal(d.indexes.a.getMatching(1)[0]); + + d.indexes.a.getMatching(2).length.should.equal(0); + + done(); + }); + }); + }); + }); + + }); // ==== End of 'Indexing newly inserted documents' ==== // + + describe('Updating indexes upon document update', function () { + + it('Updating docs still works as before with indexing', function (done) { + d.ensureIndex({ fieldName: 'a' }); + + d.insert({ a: 1, b: 'hello' }, function (err, _doc1) { + d.insert({ a: 2, b: 'si' }, function (err, _doc2) { + d.update({ a: 1 }, { $set: { a: 456, b: 'no' } }, {}, function (err, nr) { + var data = d.getAllData() + , doc1 = _.find(data, function (doc) { return doc._id === _doc1._id; }) + , doc2 = _.find(data, function (doc) { return doc._id === _doc2._id; }) + ; + + assert.isNull(err); + nr.should.equal(1); + + data.length.should.equal(2); + assert.deepEqual(doc1, { a: 456, b: 'no', _id: _doc1._id }); + assert.deepEqual(doc2, { a: 2, b: 'si', _id: _doc2._id }); + + d.update({}, { $inc: { a: 10 }, $set: { b: 'same' } }, { multi: true }, function (err, nr) { + var data = d.getAllData() + , doc1 = _.find(data, function (doc) { return doc._id === _doc1._id; }) + , doc2 = _.find(data, function (doc) { return doc._id === _doc2._id; }) + ; + + assert.isNull(err); + nr.should.equal(2); + + data.length.should.equal(2); + assert.deepEqual(doc1, { a: 466, b: 'same', _id: _doc1._id }); + assert.deepEqual(doc2, { a: 12, b: 'same', _id: _doc2._id }); + + done(); + }); + }); + }); + }); + }); + + it('Indexes get updated when a document (or multiple documents) is updated', function (done) { + d.ensureIndex({ fieldName: 'a' }); + d.ensureIndex({ fieldName: 'b' }); + + d.insert({ a: 1, b: 'hello' }, function (err, doc1) { + d.insert({ a: 2, b: 'si' }, function (err, doc2) { + // Simple update + d.update({ a: 1 }, { $set: { a: 456, b: 'no' } }, {}, function (err, nr) { + assert.isNull(err); + nr.should.equal(1); + + d.indexes.a.tree.getNumberOfKeys().should.equal(2); + d.indexes.a.getMatching(456)[0]._id.should.equal(doc1._id); + d.indexes.a.getMatching(2)[0]._id.should.equal(doc2._id); + + d.indexes.b.tree.getNumberOfKeys().should.equal(2); + d.indexes.b.getMatching('no')[0]._id.should.equal(doc1._id); + d.indexes.b.getMatching('si')[0]._id.should.equal(doc2._id); + + // The same pointers are shared between all indexes + d.indexes.a.tree.getNumberOfKeys().should.equal(2); + d.indexes.b.tree.getNumberOfKeys().should.equal(2); + d.indexes._id.tree.getNumberOfKeys().should.equal(2); + d.indexes.a.getMatching(456)[0].should.equal(d.indexes._id.getMatching(doc1._id)[0]); + d.indexes.b.getMatching('no')[0].should.equal(d.indexes._id.getMatching(doc1._id)[0]); + d.indexes.a.getMatching(2)[0].should.equal(d.indexes._id.getMatching(doc2._id)[0]); + d.indexes.b.getMatching('si')[0].should.equal(d.indexes._id.getMatching(doc2._id)[0]); + + // Multi update + d.update({}, { $inc: { a: 10 }, $set: { b: 'same' } }, { multi: true }, function (err, nr) { + assert.isNull(err); + nr.should.equal(2); + + d.indexes.a.tree.getNumberOfKeys().should.equal(2); + d.indexes.a.getMatching(466)[0]._id.should.equal(doc1._id); + d.indexes.a.getMatching(12)[0]._id.should.equal(doc2._id); + + d.indexes.b.tree.getNumberOfKeys().should.equal(1); + d.indexes.b.getMatching('same').length.should.equal(2); + _.pluck(d.indexes.b.getMatching('same'), '_id').should.contain(doc1._id); + _.pluck(d.indexes.b.getMatching('same'), '_id').should.contain(doc2._id); + + // The same pointers are shared between all indexes + d.indexes.a.tree.getNumberOfKeys().should.equal(2); + d.indexes.b.tree.getNumberOfKeys().should.equal(1); + d.indexes.b.getAll().length.should.equal(2); + d.indexes._id.tree.getNumberOfKeys().should.equal(2); + d.indexes.a.getMatching(466)[0].should.equal(d.indexes._id.getMatching(doc1._id)[0]); + d.indexes.a.getMatching(12)[0].should.equal(d.indexes._id.getMatching(doc2._id)[0]); + // Can't test the pointers in b as their order is randomized, but it is the same as with a + + done(); + }); + }); + }); + }); + }); + + it('If a simple update violates a contraint, all changes are rolled back and an error is thrown', function (done) { + d.ensureIndex({ fieldName: 'a', unique: true }); + d.ensureIndex({ fieldName: 'b', unique: true }); + d.ensureIndex({ fieldName: 'c', unique: true }); + + d.insert({ a: 1, b: 10, c: 100 }, function (err, _doc1) { + d.insert({ a: 2, b: 20, c: 200 }, function (err, _doc2) { + d.insert({ a: 3, b: 30, c: 300 }, function (err, _doc3) { + // Will conflict with doc3 + d.update({ a: 2 }, { $inc: { a: 10, c: 1000 }, $set: { b: 30 } }, {}, function (err) { + var data = d.getAllData() + , doc1 = _.find(data, function (doc) { return doc._id === _doc1._id; }) + , doc2 = _.find(data, function (doc) { return doc._id === _doc2._id; }) + , doc3 = _.find(data, function (doc) { return doc._id === _doc3._id; }) + ; + + err.errorType.should.equal('uniqueViolated'); + + // Data left unchanged + data.length.should.equal(3); + assert.deepEqual(doc1, { a: 1, b: 10, c: 100, _id: _doc1._id }); + assert.deepEqual(doc2, { a: 2, b: 20, c: 200, _id: _doc2._id }); + assert.deepEqual(doc3, { a: 3, b: 30, c: 300, _id: _doc3._id }); + + // All indexes left unchanged and pointing to the same docs + d.indexes.a.tree.getNumberOfKeys().should.equal(3); + d.indexes.a.getMatching(1)[0].should.equal(doc1); + d.indexes.a.getMatching(2)[0].should.equal(doc2); + d.indexes.a.getMatching(3)[0].should.equal(doc3); + + d.indexes.b.tree.getNumberOfKeys().should.equal(3); + d.indexes.b.getMatching(10)[0].should.equal(doc1); + d.indexes.b.getMatching(20)[0].should.equal(doc2); + d.indexes.b.getMatching(30)[0].should.equal(doc3); + + d.indexes.c.tree.getNumberOfKeys().should.equal(3); + d.indexes.c.getMatching(100)[0].should.equal(doc1); + d.indexes.c.getMatching(200)[0].should.equal(doc2); + d.indexes.c.getMatching(300)[0].should.equal(doc3); + + done(); + }); + }); + }); + }); + }); + + it('If a multi update violates a contraint, all changes are rolled back and an error is thrown', function (done) { + d.ensureIndex({ fieldName: 'a', unique: true }); + d.ensureIndex({ fieldName: 'b', unique: true }); + d.ensureIndex({ fieldName: 'c', unique: true }); + + d.insert({ a: 1, b: 10, c: 100 }, function (err, _doc1) { + d.insert({ a: 2, b: 20, c: 200 }, function (err, _doc2) { + d.insert({ a: 3, b: 30, c: 300 }, function (err, _doc3) { + // Will conflict with doc3 + d.update({ a: { $in: [1, 2] } }, { $inc: { a: 10, c: 1000 }, $set: { b: 30 } }, { multi: true }, function (err) { + var data = d.getAllData() + , doc1 = _.find(data, function (doc) { return doc._id === _doc1._id; }) + , doc2 = _.find(data, function (doc) { return doc._id === _doc2._id; }) + , doc3 = _.find(data, function (doc) { return doc._id === _doc3._id; }) + ; + + err.errorType.should.equal('uniqueViolated'); + + // Data left unchanged + data.length.should.equal(3); + assert.deepEqual(doc1, { a: 1, b: 10, c: 100, _id: _doc1._id }); + assert.deepEqual(doc2, { a: 2, b: 20, c: 200, _id: _doc2._id }); + assert.deepEqual(doc3, { a: 3, b: 30, c: 300, _id: _doc3._id }); + + // All indexes left unchanged and pointing to the same docs + d.indexes.a.tree.getNumberOfKeys().should.equal(3); + d.indexes.a.getMatching(1)[0].should.equal(doc1); + d.indexes.a.getMatching(2)[0].should.equal(doc2); + d.indexes.a.getMatching(3)[0].should.equal(doc3); + + d.indexes.b.tree.getNumberOfKeys().should.equal(3); + d.indexes.b.getMatching(10)[0].should.equal(doc1); + d.indexes.b.getMatching(20)[0].should.equal(doc2); + d.indexes.b.getMatching(30)[0].should.equal(doc3); + + d.indexes.c.tree.getNumberOfKeys().should.equal(3); + d.indexes.c.getMatching(100)[0].should.equal(doc1); + d.indexes.c.getMatching(200)[0].should.equal(doc2); + d.indexes.c.getMatching(300)[0].should.equal(doc3); + + done(); + }); + }); + }); + }); + }); + + }); // ==== End of 'Updating indexes upon document update' ==== // + + describe('Updating indexes upon document remove', function () { + + it('Removing docs still works as before with indexing', function (done) { + d.ensureIndex({ fieldName: 'a' }); + + d.insert({ a: 1, b: 'hello' }, function (err, _doc1) { + d.insert({ a: 2, b: 'si' }, function (err, _doc2) { + d.insert({ a: 3, b: 'coin' }, function (err, _doc3) { + d.remove({ a: 1 }, {}, function (err, nr) { + var data = d.getAllData() + , doc2 = _.find(data, function (doc) { return doc._id === _doc2._id; }) + , doc3 = _.find(data, function (doc) { return doc._id === _doc3._id; }) + ; + + assert.isNull(err); + nr.should.equal(1); + + data.length.should.equal(2); + assert.deepEqual(doc2, { a: 2, b: 'si', _id: _doc2._id }); + assert.deepEqual(doc3, { a: 3, b: 'coin', _id: _doc3._id }); + + d.remove({ a: { $in: [2, 3] } }, { multi: true }, function (err, nr) { + var data = d.getAllData() + ; + + assert.isNull(err); + nr.should.equal(2); + data.length.should.equal(0); + + done(); + }); + }); + }); + }); + }); + }); + + it('Indexes get updated when a document (or multiple documents) is removed', function (done) { + d.ensureIndex({ fieldName: 'a' }); + d.ensureIndex({ fieldName: 'b' }); + + d.insert({ a: 1, b: 'hello' }, function (err, doc1) { + d.insert({ a: 2, b: 'si' }, function (err, doc2) { + d.insert({ a: 3, b: 'coin' }, function (err, doc3) { + // Simple remove + d.remove({ a: 1 }, {}, function (err, nr) { + assert.isNull(err); + nr.should.equal(1); + + d.indexes.a.tree.getNumberOfKeys().should.equal(2); + d.indexes.a.getMatching(2)[0]._id.should.equal(doc2._id); + d.indexes.a.getMatching(3)[0]._id.should.equal(doc3._id); + + d.indexes.b.tree.getNumberOfKeys().should.equal(2); + d.indexes.b.getMatching('si')[0]._id.should.equal(doc2._id); + d.indexes.b.getMatching('coin')[0]._id.should.equal(doc3._id); + + // The same pointers are shared between all indexes + d.indexes.a.tree.getNumberOfKeys().should.equal(2); + d.indexes.b.tree.getNumberOfKeys().should.equal(2); + d.indexes._id.tree.getNumberOfKeys().should.equal(2); + d.indexes.a.getMatching(2)[0].should.equal(d.indexes._id.getMatching(doc2._id)[0]); + d.indexes.b.getMatching('si')[0].should.equal(d.indexes._id.getMatching(doc2._id)[0]); + d.indexes.a.getMatching(3)[0].should.equal(d.indexes._id.getMatching(doc3._id)[0]); + d.indexes.b.getMatching('coin')[0].should.equal(d.indexes._id.getMatching(doc3._id)[0]); + + // Multi remove + d.remove({}, { multi: true }, function (err, nr) { + assert.isNull(err); + nr.should.equal(2); + + d.indexes.a.tree.getNumberOfKeys().should.equal(0); + d.indexes.b.tree.getNumberOfKeys().should.equal(0); + d.indexes._id.tree.getNumberOfKeys().should.equal(0); + + done(); + }); + }); + }); + }); + }); + }); + + }); // ==== End of 'Updating indexes upon document remove' ==== // + + + describe('Persisting indexes', function () { + + it('Indexes are persisted to a separate file and recreated upon reload', function (done) { + var persDb = "workspace/persistIndexes.db" + , db + ; + + if (fs.existsSync(persDb)) { fs.writeFileSync(persDb, '', 'utf8'); } + db = new Datastore({ filename: persDb, autoload: true }); + + Object.keys(db.indexes).length.should.equal(1); + Object.keys(db.indexes)[0].should.equal("_id"); + + db.insert({ planet: "Earth" }, function (err) { + assert.isNull(err); + db.insert({ planet: "Mars" }, function (err) { + assert.isNull(err); + + db.ensureIndex({ fieldName: "planet" }, function (err) { + Object.keys(db.indexes).length.should.equal(2); + Object.keys(db.indexes)[0].should.equal("_id"); + Object.keys(db.indexes)[1].should.equal("planet"); + db.indexes._id.getAll().length.should.equal(2); + db.indexes.planet.getAll().length.should.equal(2); + db.indexes.planet.fieldName.should.equal("planet"); + + // After a reload the indexes are recreated + db = new Datastore({ filename: persDb }); + db.loadDatabase(function (err) { + assert.isNull(err); + Object.keys(db.indexes).length.should.equal(2); + Object.keys(db.indexes)[0].should.equal("_id"); + Object.keys(db.indexes)[1].should.equal("planet"); + db.indexes._id.getAll().length.should.equal(2); + db.indexes.planet.getAll().length.should.equal(2); + db.indexes.planet.fieldName.should.equal("planet"); + + // After another reload the indexes are still there (i.e. they are preserved during autocompaction) + db = new Datastore({ filename: persDb }); + db.loadDatabase(function (err) { + assert.isNull(err); + Object.keys(db.indexes).length.should.equal(2); + Object.keys(db.indexes)[0].should.equal("_id"); + Object.keys(db.indexes)[1].should.equal("planet"); + db.indexes._id.getAll().length.should.equal(2); + db.indexes.planet.getAll().length.should.equal(2); + db.indexes.planet.fieldName.should.equal("planet"); + + done(); + }); + }); + }); + }); + }); + }); + + it('Indexes are persisted with their options and recreated even if some db operation happen between loads', function (done) { + var persDb = "workspace/persistIndexes.db" + , db + ; + + if (fs.existsSync(persDb)) { fs.writeFileSync(persDb, '', 'utf8'); } + db = new Datastore({ filename: persDb, autoload: true }); + + Object.keys(db.indexes).length.should.equal(1); + Object.keys(db.indexes)[0].should.equal("_id"); + + db.insert({ planet: "Earth" }, function (err) { + assert.isNull(err); + db.insert({ planet: "Mars" }, function (err) { + assert.isNull(err); + + db.ensureIndex({ fieldName: "planet", unique: true, sparse: false }, function (err) { + Object.keys(db.indexes).length.should.equal(2); + Object.keys(db.indexes)[0].should.equal("_id"); + Object.keys(db.indexes)[1].should.equal("planet"); + db.indexes._id.getAll().length.should.equal(2); + db.indexes.planet.getAll().length.should.equal(2); + db.indexes.planet.unique.should.equal(true); + db.indexes.planet.sparse.should.equal(false); + + db.insert({ planet: "Jupiter" }, function (err) { + assert.isNull(err); + + // After a reload the indexes are recreated + db = new Datastore({ filename: persDb }); + db.loadDatabase(function (err) { + assert.isNull(err); + Object.keys(db.indexes).length.should.equal(2); + Object.keys(db.indexes)[0].should.equal("_id"); + Object.keys(db.indexes)[1].should.equal("planet"); + db.indexes._id.getAll().length.should.equal(3); + db.indexes.planet.getAll().length.should.equal(3); + db.indexes.planet.unique.should.equal(true); + db.indexes.planet.sparse.should.equal(false); + + db.ensureIndex({ fieldName: 'bloup', unique: false, sparse: true }, function (err) { + assert.isNull(err); + Object.keys(db.indexes).length.should.equal(3); + Object.keys(db.indexes)[0].should.equal("_id"); + Object.keys(db.indexes)[1].should.equal("planet"); + Object.keys(db.indexes)[2].should.equal("bloup"); + db.indexes._id.getAll().length.should.equal(3); + db.indexes.planet.getAll().length.should.equal(3); + db.indexes.bloup.getAll().length.should.equal(0); + db.indexes.planet.unique.should.equal(true); + db.indexes.planet.sparse.should.equal(false); + db.indexes.bloup.unique.should.equal(false); + db.indexes.bloup.sparse.should.equal(true); + + // After another reload the indexes are still there (i.e. they are preserved during autocompaction) + db = new Datastore({ filename: persDb }); + db.loadDatabase(function (err) { + assert.isNull(err); + Object.keys(db.indexes).length.should.equal(3); + Object.keys(db.indexes)[0].should.equal("_id"); + Object.keys(db.indexes)[1].should.equal("planet"); + Object.keys(db.indexes)[2].should.equal("bloup"); + db.indexes._id.getAll().length.should.equal(3); + db.indexes.planet.getAll().length.should.equal(3); + db.indexes.bloup.getAll().length.should.equal(0); + db.indexes.planet.unique.should.equal(true); + db.indexes.planet.sparse.should.equal(false); + db.indexes.bloup.unique.should.equal(false); + db.indexes.bloup.sparse.should.equal(true); + + done(); + }); + }); + }); + }); + }); + }); + }); + }); + + it('Indexes can also be removed and the remove persisted', function (done) { + var persDb = "workspace/persistIndexes.db" + , db + ; + + if (fs.existsSync(persDb)) { fs.writeFileSync(persDb, '', 'utf8'); } + db = new Datastore({ filename: persDb, autoload: true }); + + Object.keys(db.indexes).length.should.equal(1); + Object.keys(db.indexes)[0].should.equal("_id"); + + db.insert({ planet: "Earth" }, function (err) { + assert.isNull(err); + db.insert({ planet: "Mars" }, function (err) { + assert.isNull(err); + + db.ensureIndex({ fieldName: "planet" }, function (err) { + assert.isNull(err); + db.ensureIndex({ fieldName: "another" }, function (err) { + assert.isNull(err); + Object.keys(db.indexes).length.should.equal(3); + Object.keys(db.indexes)[0].should.equal("_id"); + Object.keys(db.indexes)[1].should.equal("planet"); + Object.keys(db.indexes)[2].should.equal("another"); + db.indexes._id.getAll().length.should.equal(2); + db.indexes.planet.getAll().length.should.equal(2); + db.indexes.planet.fieldName.should.equal("planet"); + + // After a reload the indexes are recreated + db = new Datastore({ filename: persDb }); + db.loadDatabase(function (err) { + assert.isNull(err); + Object.keys(db.indexes).length.should.equal(3); + Object.keys(db.indexes)[0].should.equal("_id"); + Object.keys(db.indexes)[1].should.equal("planet"); + Object.keys(db.indexes)[2].should.equal("another"); + db.indexes._id.getAll().length.should.equal(2); + db.indexes.planet.getAll().length.should.equal(2); + db.indexes.planet.fieldName.should.equal("planet"); + + // Index is removed + db.removeIndex("planet", function (err) { + assert.isNull(err); + Object.keys(db.indexes).length.should.equal(2); + Object.keys(db.indexes)[0].should.equal("_id"); + Object.keys(db.indexes)[1].should.equal("another"); + db.indexes._id.getAll().length.should.equal(2); + + // After a reload indexes are preserved + db = new Datastore({ filename: persDb }); + db.loadDatabase(function (err) { + assert.isNull(err); + Object.keys(db.indexes).length.should.equal(2); + Object.keys(db.indexes)[0].should.equal("_id"); + Object.keys(db.indexes)[1].should.equal("another"); + db.indexes._id.getAll().length.should.equal(2); + + // After another reload the indexes are still there (i.e. they are preserved during autocompaction) + db = new Datastore({ filename: persDb }); + db.loadDatabase(function (err) { + assert.isNull(err); + Object.keys(db.indexes).length.should.equal(2); + Object.keys(db.indexes)[0].should.equal("_id"); + Object.keys(db.indexes)[1].should.equal("another"); + db.indexes._id.getAll().length.should.equal(2); + + done(); + }); + }); + }); + }); + }); + }); + }); + }); + }); + + }); // ==== End of 'Persisting indexes' ==== + + }); // ==== End of 'Using indexes' ==== // + + +}); diff --git a/src/node_modules/nedb/test/executor.test.js b/src/node_modules/nedb/test/executor.test.js new file mode 100644 index 0000000..65f15e5 --- /dev/null +++ b/src/node_modules/nedb/test/executor.test.js @@ -0,0 +1,163 @@ +var should = require('chai').should() + , assert = require('chai').assert + , testDb = 'workspace/test.db' + , fs = require('fs') + , path = require('path') + , _ = require('underscore') + , async = require('async') + , model = require('../lib/model') + , Datastore = require('../lib/datastore') + , Persistence = require('../lib/persistence') + ; + + +// Test that even if a callback throws an exception, the next DB operations will still be executed +// We prevent Mocha from catching the exception we throw on purpose by remembering all current handlers, remove them and register them back after test ends +function testThrowInCallback (d, done) { + var currentUncaughtExceptionHandlers = process.listeners('uncaughtException'); + + process.removeAllListeners('uncaughtException'); + + process.on('uncaughtException', function (err) { + // Do nothing with the error which is only there to test we stay on track + }); + + d.find({}, function (err) { + process.nextTick(function () { + d.insert({ bar: 1 }, function (err) { + for (var i = 0; i < currentUncaughtExceptionHandlers.length; i += 1) { + process.on('uncaughtException', currentUncaughtExceptionHandlers[i]); + } + + done(); + }); + }); + + throw 'Some error'; + }); +} + + +// Test that operations are executed in the right order +// We prevent Mocha from catching the exception we throw on purpose by remembering all current handlers, remove them and register them back after test ends +function testRightOrder (d, done) { + var currentUncaughtExceptionHandlers = process.listeners('uncaughtException'); + + process.removeAllListeners('uncaughtException'); + + process.on('uncaughtException', function (err) { + // Do nothing with the error which is only there to test we stay on track + }); + + d.find({}, function (err, docs) { + docs.length.should.equal(0); + + d.insert({ a: 1 }, function () { + d.update({ a: 1 }, { a: 2 }, {}, function () { + d.find({}, function (err, docs) { + docs[0].a.should.equal(2); + + process.nextTick(function () { + d.update({ a: 2 }, { a: 3 }, {}, function () { + d.find({}, function (err, docs) { + docs[0].a.should.equal(3); + done(); + + }); + }); + }); + + throw 'Some error'; + }); + }); + }); + }); +} + + + +// Note: The following test does not have any assertion because it +// is meant to address the deprecation warning: +// (node) warning: Recursive process.nextTick detected. This will break in the next version of node. Please use setImmediate for recursive deferral. +// see +var testEventLoopStarvation = function(d, done){ + var times = 1001; + var i = 0; + while ( i 0) { filledCount += 1; } }); + filledCount.should.equal(3); + + d.loadDatabase(function (err) { + assert.isNull(err); + + // Now, the file has been compacted and is only 1 line long + var data = fs.readFileSync(d.filename, 'utf8').split('\n') + , filledCount = 0; + + data.forEach(function (item) { if (item.length > 0) { filledCount += 1; } }); + filledCount.should.equal(1); + + done(); + }); + }) + }); + }); + }); + + it('Calling loadDatabase after the data was modified doesnt change its contents', function (done) { + d.loadDatabase(function () { + d.insert({ a: 1 }, function (err) { + assert.isNull(err); + d.insert({ a: 2 }, function (err) { + var data = d.getAllData() + , doc1 = _.find(data, function (doc) { return doc.a === 1; }) + , doc2 = _.find(data, function (doc) { return doc.a === 2; }) + ; + assert.isNull(err); + data.length.should.equal(2); + doc1.a.should.equal(1); + doc2.a.should.equal(2); + + d.loadDatabase(function (err) { + var data = d.getAllData() + , doc1 = _.find(data, function (doc) { return doc.a === 1; }) + , doc2 = _.find(data, function (doc) { return doc.a === 2; }) + ; + assert.isNull(err); + data.length.should.equal(2); + doc1.a.should.equal(1); + doc2.a.should.equal(2); + + done(); + }); + }); + }); + }); + }); + + it('Calling loadDatabase after the datafile was removed will reset the database', function (done) { + d.loadDatabase(function () { + d.insert({ a: 1 }, function (err) { + assert.isNull(err); + d.insert({ a: 2 }, function (err) { + var data = d.getAllData() + , doc1 = _.find(data, function (doc) { return doc.a === 1; }) + , doc2 = _.find(data, function (doc) { return doc.a === 2; }) + ; + assert.isNull(err); + data.length.should.equal(2); + doc1.a.should.equal(1); + doc2.a.should.equal(2); + + fs.unlink(testDb, function (err) { + assert.isNull(err); + d.loadDatabase(function (err) { + assert.isNull(err); + d.getAllData().length.should.equal(0); + + done(); + }); + }); + }); + }); + }); + }); + + it('Calling loadDatabase after the datafile was modified loads the new data', function (done) { + d.loadDatabase(function () { + d.insert({ a: 1 }, function (err) { + assert.isNull(err); + d.insert({ a: 2 }, function (err) { + var data = d.getAllData() + , doc1 = _.find(data, function (doc) { return doc.a === 1; }) + , doc2 = _.find(data, function (doc) { return doc.a === 2; }) + ; + assert.isNull(err); + data.length.should.equal(2); + doc1.a.should.equal(1); + doc2.a.should.equal(2); + + fs.writeFile(testDb, '{"a":3,"_id":"aaa"}', 'utf8', function (err) { + assert.isNull(err); + d.loadDatabase(function (err) { + var data = d.getAllData() + , doc1 = _.find(data, function (doc) { return doc.a === 1; }) + , doc2 = _.find(data, function (doc) { return doc.a === 2; }) + , doc3 = _.find(data, function (doc) { return doc.a === 3; }) + ; + assert.isNull(err); + data.length.should.equal(1); + doc3.a.should.equal(3); + assert.isUndefined(doc1); + assert.isUndefined(doc2); + + done(); + }); + }); + }); + }); + }); + }); + + + describe('Prevent dataloss when persisting data', function () { + + it('Creating a datastore with in memory as true and a bad filename wont cause an error', function () { + new Datastore({ filename: 'workspace/bad.db~', inMemoryOnly: true }); + }) + + it('Creating a persistent datastore with a bad filename will cause an error', function () { + (function () { new Datastore({ filename: 'workspace/bad.db~' }); }).should.throw(); + }) + + it('If no file exists, ensureDatafileIntegrity creates an empty datafile', function (done) { + var p = new Persistence({ db: { inMemoryOnly: false, filename: 'workspace/it.db' } }); + + if (fs.existsSync('workspace/it.db')) { fs.unlinkSync('workspace/it.db'); } + if (fs.existsSync('workspace/it.db~~')) { fs.unlinkSync('workspace/it.db~~'); } + + fs.existsSync('workspace/it.db').should.equal(false); + fs.existsSync('workspace/it.db~~').should.equal(false); + + p.ensureDatafileIntegrity(function (err) { + assert.isNull(err); + + fs.existsSync('workspace/it.db').should.equal(true); + fs.existsSync('workspace/it.db~~').should.equal(false); + + fs.readFileSync('workspace/it.db', 'utf8').should.equal(''); + + done(); + }); + }); + + it('If only datafile exists, ensureDatafileIntegrity will use it', function (done) { + var p = new Persistence({ db: { inMemoryOnly: false, filename: 'workspace/it.db' } }); + + if (fs.existsSync('workspace/it.db')) { fs.unlinkSync('workspace/it.db'); } + if (fs.existsSync('workspace/it.db~~')) { fs.unlinkSync('workspace/it.db~~'); } + + fs.writeFileSync('workspace/it.db', 'something', 'utf8'); + + fs.existsSync('workspace/it.db').should.equal(true); + fs.existsSync('workspace/it.db~~').should.equal(false); + + p.ensureDatafileIntegrity(function (err) { + assert.isNull(err); + + fs.existsSync('workspace/it.db').should.equal(true); + fs.existsSync('workspace/it.db~~').should.equal(false); + + fs.readFileSync('workspace/it.db', 'utf8').should.equal('something'); + + done(); + }); + }); + + it('If old datafile exists and datafile doesnt, ensureDatafileIntegrity will use it', function (done) { + var p = new Persistence({ db: { inMemoryOnly: false, filename: 'workspace/it.db' } }); + + if (fs.existsSync('workspace/it.db')) { fs.unlinkSync('workspace/it.db'); } + if (fs.existsSync('workspace/it.db~~')) { fs.unlinkSync('workspace/it.db~~'); } + + fs.writeFileSync('workspace/it.db~~', 'something', 'utf8'); + + fs.existsSync('workspace/it.db').should.equal(false); + fs.existsSync('workspace/it.db~~').should.equal(true); + + p.ensureDatafileIntegrity(function (err) { + assert.isNull(err); + + fs.existsSync('workspace/it.db').should.equal(true); + fs.existsSync('workspace/it.db~~').should.equal(false); + + fs.readFileSync('workspace/it.db', 'utf8').should.equal('something'); + + done(); + }); + }); + + it('If both old and current datafiles exist, ensureDatafileIntegrity will use the datafile, it means step 4 of persistence failed', function (done) { + var theDb = new Datastore({ filename: 'workspace/it.db' }); + + if (fs.existsSync('workspace/it.db')) { fs.unlinkSync('workspace/it.db'); } + if (fs.existsSync('workspace/it.db~~')) { fs.unlinkSync('workspace/it.db~~'); } + + fs.writeFileSync('workspace/it.db', '{"_id":"0","hello":"world"}', 'utf8'); + fs.writeFileSync('workspace/it.db~~', '{"_id":"0","hello":"other"}', 'utf8'); + + fs.existsSync('workspace/it.db').should.equal(true); + fs.existsSync('workspace/it.db~~').should.equal(true); + + theDb.persistence.ensureDatafileIntegrity(function (err) { + assert.isNull(err); + + fs.existsSync('workspace/it.db').should.equal(true); + fs.existsSync('workspace/it.db~~').should.equal(true); + + fs.readFileSync('workspace/it.db', 'utf8').should.equal('{"_id":"0","hello":"world"}'); + + theDb.loadDatabase(function (err) { + assert.isNull(err); + theDb.find({}, function (err, docs) { + assert.isNull(err); + docs.length.should.equal(1); + docs[0].hello.should.equal("world"); + done(); + }); + }); + }); + }); + + it('persistCachedDatabase should update the contents of the datafile and leave a clean state', function (done) { + d.insert({ hello: 'world' }, function () { + d.find({}, function (err, docs) { + docs.length.should.equal(1); + + if (fs.existsSync(testDb)) { fs.unlinkSync(testDb); } + if (fs.existsSync(testDb + '~')) { fs.unlinkSync(testDb + '~'); } + if (fs.existsSync(testDb + '~~')) { fs.unlinkSync(testDb + '~~'); } + fs.existsSync(testDb).should.equal(false); + + fs.writeFileSync(testDb + '~', 'something', 'utf8'); + fs.writeFileSync(testDb + '~~', 'something else', 'utf8'); + fs.existsSync(testDb + '~').should.equal(true); + fs.existsSync(testDb + '~~').should.equal(true); + + d.persistence.persistCachedDatabase(function (err) { + var contents = fs.readFileSync(testDb, 'utf8'); + assert.isNull(err); + fs.existsSync(testDb).should.equal(true); + fs.existsSync(testDb + '~').should.equal(false); + fs.existsSync(testDb + '~~').should.equal(false); + if (!contents.match(/^{"hello":"world","_id":"[0-9a-zA-Z]{16}"}\n$/)) { + throw "Datafile contents not as expected"; + } + done(); + }); + }); + }); + }); + + it('After a persistCachedDatabase, there should be no temp or old filename', function (done) { + d.insert({ hello: 'world' }, function () { + d.find({}, function (err, docs) { + docs.length.should.equal(1); + + if (fs.existsSync(testDb)) { fs.unlinkSync(testDb); } + if (fs.existsSync(testDb + '~')) { fs.unlinkSync(testDb + '~'); } + if (fs.existsSync(testDb + '~~')) { fs.unlinkSync(testDb + '~~'); } + fs.existsSync(testDb).should.equal(false); + + fs.writeFileSync(testDb + '~', 'bloup', 'utf8'); + fs.writeFileSync(testDb + '~~', 'blap', 'utf8'); + fs.existsSync(testDb + '~').should.equal(true); + fs.existsSync(testDb + '~~').should.equal(true); + + d.persistence.persistCachedDatabase(function (err) { + var contents = fs.readFileSync(testDb, 'utf8'); + assert.isNull(err); + fs.existsSync(testDb).should.equal(true); + fs.existsSync(testDb + '~').should.equal(false); + fs.existsSync(testDb + '~~').should.equal(false); + if (!contents.match(/^{"hello":"world","_id":"[0-9a-zA-Z]{16}"}\n$/)) { + throw "Datafile contents not as expected"; + } + done(); + }); + }); + }); + }); + + it('persistCachedDatabase should update the contents of the datafile and leave a clean state even if there is a temp or old datafile', function (done) { + d.insert({ hello: 'world' }, function () { + d.find({}, function (err, docs) { + docs.length.should.equal(1); + + if (fs.existsSync(testDb)) { fs.unlinkSync(testDb); } + fs.writeFileSync(testDb + '~', 'blabla', 'utf8'); + fs.writeFileSync(testDb + '~~', 'bloblo', 'utf8'); + fs.existsSync(testDb).should.equal(false); + fs.existsSync(testDb + '~').should.equal(true); + fs.existsSync(testDb + '~~').should.equal(true); + + d.persistence.persistCachedDatabase(function (err) { + var contents = fs.readFileSync(testDb, 'utf8'); + assert.isNull(err); + fs.existsSync(testDb).should.equal(true); + fs.existsSync(testDb + '~').should.equal(false); + fs.existsSync(testDb + '~~').should.equal(false); + if (!contents.match(/^{"hello":"world","_id":"[0-9a-zA-Z]{16}"}\n$/)) { + throw "Datafile contents not as expected"; + } + done(); + }); + }); + }); + }); + + it('persistCachedDatabase should update the contents of the datafile and leave a clean state even if there is a temp or old datafile', function (done) { + var dbFile = 'workspace/test2.db', theDb; + + if (fs.existsSync(dbFile)) { fs.unlinkSync(dbFile); } + if (fs.existsSync(dbFile + '~')) { fs.unlinkSync(dbFile + '~'); } + if (fs.existsSync(dbFile + '~~')) { fs.unlinkSync(dbFile + '~~'); } + + theDb = new Datastore({ filename: dbFile }); + + theDb.loadDatabase(function (err) { + var contents = fs.readFileSync(dbFile, 'utf8'); + assert.isNull(err); + fs.existsSync(dbFile).should.equal(true); + fs.existsSync(dbFile + '~').should.equal(false); + fs.existsSync(dbFile + '~~').should.equal(false); + if (contents != "") { + throw "Datafile contents not as expected"; + } + done(); + }); + }); + + it('Persistence works as expected when everything goes fine', function (done) { + var dbFile = 'workspace/test2.db', theDb, theDb2, doc1, doc2; + + async.waterfall([ + async.apply(Persistence.ensureFileDoesntExist, dbFile) + , async.apply(Persistence.ensureFileDoesntExist, dbFile + '~') + , async.apply(Persistence.ensureFileDoesntExist, dbFile + '~~') + , function (cb) { + theDb = new Datastore({ filename: dbFile }); + theDb.loadDatabase(cb); + } + , function (cb) { + theDb.find({}, function (err, docs) { + assert.isNull(err); + docs.length.should.equal(0); + return cb(); + }); + } + , function (cb) { + theDb.insert({ a: 'hello' }, function (err, _doc1) { + assert.isNull(err); + doc1 = _doc1; + theDb.insert({ a: 'world' }, function (err, _doc2) { + assert.isNull(err); + doc2 = _doc2; + return cb(); + }); + }); + } + , function (cb) { + theDb.find({}, function (err, docs) { + assert.isNull(err); + docs.length.should.equal(2); + _.find(docs, function (item) { return item._id === doc1._id }).a.should.equal('hello'); + _.find(docs, function (item) { return item._id === doc2._id }).a.should.equal('world'); + return cb(); + }); + } + , function (cb) { + theDb.loadDatabase(cb); + } + , function (cb) { // No change + theDb.find({}, function (err, docs) { + assert.isNull(err); + docs.length.should.equal(2); + _.find(docs, function (item) { return item._id === doc1._id }).a.should.equal('hello'); + _.find(docs, function (item) { return item._id === doc2._id }).a.should.equal('world'); + return cb(); + }); + } + , function (cb) { + fs.existsSync(dbFile).should.equal(true); + fs.existsSync(dbFile + '~').should.equal(false); + fs.existsSync(dbFile + '~~').should.equal(false); + return cb(); + } + , function (cb) { + theDb2 = new Datastore({ filename: dbFile }); + theDb2.loadDatabase(cb); + } + , function (cb) { // No change in second db + theDb2.find({}, function (err, docs) { + assert.isNull(err); + docs.length.should.equal(2); + _.find(docs, function (item) { return item._id === doc1._id }).a.should.equal('hello'); + _.find(docs, function (item) { return item._id === doc2._id }).a.should.equal('world'); + return cb(); + }); + } + , function (cb) { + fs.existsSync(dbFile).should.equal(true); + fs.existsSync(dbFile + '~').should.equal(false); + fs.existsSync(dbFile + '~~').should.equal(false); + return cb(); + } + ], done); + }); + + + // This test is a bit complicated since it depends on the time I/O actions take to execute + // That depends on the machine and the load on the machine when the tests are run + // It is timed for my machine with nothing else running but may not work as expected on others (it will not fail but may not be a proof) + // Every new version of NeDB passes it on my machine before rtelease + it('If system crashes during a loadDatabase, the former version is not lost', function (done) { + var cp, N = 150000, toWrite = "", i; + + // Ensuring the state is clean + if (fs.existsSync('workspace/lac.db')) { fs.unlinkSync('workspace/lac.db'); } + if (fs.existsSync('workspace/lac.db~')) { fs.unlinkSync('workspace/lac.db~'); } + + // Creating a db file with 150k records (a bit long to load) + for (i = 0; i < N; i += 1) { + toWrite += model.serialize({ _id: customUtils.uid(16), hello: 'world' }) + '\n'; + } + fs.writeFileSync('workspace/lac.db', toWrite, 'utf8'); + + // Loading it in a separate process that we will crash before finishing the loadDatabase + cp = child_process.fork('test_lac/loadAndCrash.test') + + // Kill the child process when we're at step 3 of persistCachedDatabase (during write to datafile) + setTimeout(function() { + cp.kill('SIGINT'); + + // If the timing is correct, only the temp datafile contains data + // The datafile was in the middle of being written and is empty + + // Let the process crash be finished then load database without a crash, and test we didn't lose data + setTimeout(function () { + var db = new Datastore({ filename: 'workspace/lac.db' }); + db.loadDatabase(function (err) { + assert.isNull(err); + + db.count({}, function (err, n) { + // Data has not been lost + assert.isNull(err); + n.should.equal(150000); + + // State is clean, the temp datafile has been erased and the datafile contains all the data + fs.existsSync('workspace/lac.db').should.equal(true); + fs.existsSync('workspace/lac.db~').should.equal(false); + + done(); + }); + }); + }, 100); + }, 2000); + }); + + }); // ==== End of 'Prevent dataloss when persisting data' ==== + + + describe('ensureFileDoesntExist', function () { + + it('Doesnt do anything if file already doesnt exist', function (done) { + Persistence.ensureFileDoesntExist('workspace/nonexisting', function (err) { + assert.isNull(err); + fs.existsSync('workspace/nonexisting').should.equal(false); + done(); + }); + }); + + it('Deletes file if it exists', function (done) { + fs.writeFileSync('workspace/existing', 'hello world', 'utf8'); + fs.existsSync('workspace/existing').should.equal(true); + + Persistence.ensureFileDoesntExist('workspace/existing', function (err) { + assert.isNull(err); + fs.existsSync('workspace/existing').should.equal(false); + done(); + }); + }); + + }); // ==== End of 'ensureFileDoesntExist' ==== + + +}); diff --git a/src/node_modules/nedb/test_lac/loadAndCrash.test.js b/src/node_modules/nedb/test_lac/loadAndCrash.test.js new file mode 100644 index 0000000..8ffd5dc --- /dev/null +++ b/src/node_modules/nedb/test_lac/loadAndCrash.test.js @@ -0,0 +1,5 @@ +var Nedb = require('../lib/datastore.js') + , db = new Nedb({ filename: 'workspace/lac.db' }) + ; + +db.loadDatabase(); \ No newline at end of file diff --git a/src/package.json b/src/package.json index 1f8ab41..e20fdf5 100644 --- a/src/package.json +++ b/src/package.json @@ -3,7 +3,7 @@ "version": "0.1-pre", "main": "index.html", "window": { - "toolbar": false, + "toolbar": true, "min_width": 400, "min_height": 400, "width": 900,