├── .gitignore ├── .travis.yml ├── CONTRIBUTING.md ├── Gruntfile.js ├── LICENSE ├── README.md ├── chromeappbackground.js ├── docs ├── _layouts │ └── default.html ├── api.md ├── gql.md ├── index.md ├── static │ ├── js │ │ ├── highlight.pack.js │ │ └── pouchdb.website.js │ └── style │ │ ├── noun_project_5618.png │ │ ├── noun_project_5618.svg │ │ ├── pouchdb.css │ │ └── solarized_dark.min.css ├── todo-list-tutorial.md └── tutorials │ ├── index.html │ ├── todo-list-tutorial.js │ └── todo-list │ ├── base.css │ ├── index.html │ └── js │ ├── app.js │ └── pouch.alpha.js ├── manifest.json ├── package.json ├── src ├── adapters │ ├── pouch.http.js │ ├── pouch.idb.js │ ├── pouch.leveldb.js │ └── pouch.websql.js ├── deps │ ├── ajax.js │ ├── extend.js │ ├── polyfill.js │ └── uuid.js ├── plugins │ ├── pouchdb.gql.js │ ├── pouchdb.mapreduce.js │ ├── pouchdb.spatial.js │ └── pouchdb.visualizeRevTree.js ├── pouch.adapter.js ├── pouch.collate.js ├── pouch.js ├── pouch.merge.js ├── pouch.replicate.js └── pouch.utils.js └── tests ├── buildstatus ├── _ddoc │ └── views │ │ ├── by_fail │ │ └── map.js │ │ └── by_started │ │ └── map.js ├── css │ └── buildstatus.css ├── index.html └── js │ ├── pouch.alpha.min.js │ └── pouchdb.buildstatus.js ├── flashcards ├── benchmark.js ├── flashcards.js └── index.html ├── perf.attachments.js ├── postTest.js ├── qunit ├── junitlogger.js ├── qunit.css └── qunit.js ├── test.all_dbs.js ├── test.all_docs.js ├── test.attachments.js ├── test.auth_replication.js ├── test.basics.js ├── test.bulk_docs.js ├── test.changes.js ├── test.compaction.js ├── test.conflicts.js ├── test.design_docs.js ├── test.get.js ├── test.gql.js ├── test.html ├── test.http.js ├── test.issue221.js ├── test.merge_rev_tree.js ├── test.replication.js ├── test.revs_diff.js ├── test.spatial.js ├── test.taskqueue.js ├── test.utils.js ├── test.views.js └── webrunner.js /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | .* 3 | !.gitignore 4 | sauce_connect.log* 5 | node_modules 6 | test_suite_db 7 | testdb_* 8 | _allDbs/ 9 | docs/_site 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 0.8 4 | services: 5 | - couchdb 6 | 7 | branches: 8 | only: 9 | - master 10 | 11 | before_script: 12 | - npm install -g grunt-cli -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | [PouchDB](http://pouchdb.com/) - The Javascript Database that Syncs 2 | ================================================== 3 | 4 | Welcome, so you are thinking about contributing to PouchDB? awesome, this is a great place to start. 5 | 6 | Guide to Contributions 7 | -------------------------------------- 8 | 9 | * Almost all Pull Requests for features or bug fixes will need tests 10 | * Looking for something to work on? look for bugs marked [goodfirstbug](https://github.com/daleharvey/pouchdb/issues?labels=goodfirstbug&page=1&state=open) 11 | * We follow [Felix's Node.js Style Guide](http://nodeguide.com/style.html) 12 | * Almost all Pull Requests for features or bug fixes will need tests (seriously, its really important) 13 | * Before opening a pull request run `$ grunt test` to lint test the changes and run node tests. Preferably run the browser tests as well. 14 | * Commit messages should follow the following style: 15 | 16 | ``` 17 | (#99) - A brief one line description < 50 chars 18 | 19 | Followed by further explanation if needed, this should be wrapped at 20 | around 72 characters. Most commits should reference an existing 21 | issue 22 | ``` 23 | 24 | Dependencies 25 | -------------------------------------- 26 | 27 | PouchDB needs the following to be able to build and test your build, if you havent installed them then best to do do so now, we will wait. 28 | 29 | * [Node.js](http://nodejs.org/) 30 | * [CouchDB](http://couchdb.apache.org/) 31 | 32 | Building PouchDB 33 | -------------------------------------- 34 | 35 | All dependancies installed? great, now building PouchDB itself is a breeze: 36 | 37 | $ cd pouchdb 38 | $ npm install -g grunt-cli 39 | $ npm install 40 | $ grunt 41 | 42 | You will now have various distributions of PouchDB in your `dist` folder, congratulations. 43 | 44 | Running PouchDB Tests 45 | -------------------------------------- 46 | 47 | The PouchDB test suite expects an instance of CouchDB running on http://127.0.0.1:5984 and it will need to be in Admin Party. 48 | 49 | ### Node Tests 50 | 51 | Run all tests with: 52 | 53 | $ grunt node-qunit 54 | 55 | Run single test file `test.basics.js` with: 56 | 57 | $ grunt node-qunit --test=basics 58 | 59 | ### Browser Tests 60 | 61 | $ grunt browser 62 | # Now visit http://127.0.0.1:8000/tests/test.html in your browser 63 | # add ?testFiles=test.basics.js to run single test file 64 | 65 | Git Essentials 66 | -------------------------------------- 67 | 68 | Workflows can vary, but here is a very simple workflow for contributing a bug fix: 69 | 70 | $ git clone git@github.com:myfork/pouchdb.git 71 | $ git remote add pouchdb https://github.com/daleharvey/pouchdb.git 72 | 73 | $ git checkout -b 121-issue-keyword master 74 | # Write tests + code 75 | $ git add src/afile.js 76 | $ git commit -m "(#121) - A brief description of what I changed" 77 | $ git push origin 121-issue-keyword 78 | 79 | Questions? 80 | ---------- 81 | 82 | If you have any questions, please feel free to ask on the 83 | [PouchDB Mailing List](https://groups.google.com/forum/#!forum/pouchdb) or in #pouchdb on irc.freenode.net. 84 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | /*jshint node: true */ 2 | 3 | var url = require('url'); 4 | var fs = require('fs'); 5 | var cp = require('child_process'); 6 | 7 | var nano = require('nano'); 8 | var cors_proxy = require("corsproxy"); 9 | var http_proxy = require("http-proxy"); 10 | 11 | var srcFiles = [ 12 | "src/pouch.js", "src/pouch.collate.js", "src/pouch.merge.js", 13 | "src/pouch.replicate.js", "src/pouch.utils.js", "src/pouch.adapter.js", 14 | "src/adapters/pouch.http.js", "src/adapters/pouch.idb.js", 15 | "src/adapters/pouch.websql.js", "src/plugins/pouchdb.mapreduce.js" 16 | ]; 17 | 18 | var testFiles = fs.readdirSync("./tests").filter(function(name){ 19 | return (/^test\.([a-z0-9_])*\.js$/).test(name) && 20 | name !== 'test.spatial.js' && name !== 'test.auth_replication.js' && 21 | name !== 'test.gql.js'; 22 | }); 23 | 24 | var browserConfig = [{ 25 | browserName: 'chrome', 26 | platform: 'Windows 2003', 27 | name: 'win2003/chrome', 28 | 'chrome.switches' : ['disable-file-system'] 29 | }, { 30 | browserName: 'firefox', 31 | version: '19', 32 | platform: 'Linux', 33 | name: 'linux/firefox' 34 | // }, { 35 | // browserName: 'opera', 36 | // version: '12', 37 | // platform: 'Windows 2008', 38 | // name: 'win2008/opera' 39 | }]; 40 | 41 | module.exports = function(grunt) { 42 | 43 | var testStartTime = new Date(); 44 | var testResults = {}; 45 | 46 | grunt.initConfig({ 47 | 'pkg': '', 48 | 49 | 'clean': { 50 | build : ["./dist"], 51 | "node-qunit": ["./testdb_*"] 52 | }, 53 | 54 | 'concat': { 55 | options: { 56 | banner: "/*PouchDB*/\n(function() {\n ", 57 | footer: "\n })(this);" 58 | }, 59 | amd: { 60 | options: { 61 | banner : "define('pouchdb',[ 'simple-uuid', 'md5'], function(uuid, md5) { " + 62 | "Math.uuid = uuid.uuid; Crypto = {MD5 : md5.hex}; $ = jquery;", 63 | footer : " return Pouch });" 64 | }, 65 | src: grunt.util._.flatten([ 66 | "", srcFiles,"" 67 | ]), 68 | dest: 'dist/pouchdb.amd-nightly.js' 69 | }, 70 | all: { 71 | src: grunt.util._.flatten([ 72 | "src/deps/uuid.js", 73 | "src/deps/polyfill.js", "src/deps/extend.js","src/deps/ajax.js", srcFiles 74 | ]), 75 | dest: 'dist/pouchdb-nightly.js' 76 | }, 77 | spatial: { 78 | src: grunt.util._.flatten([ 79 | "src/deps/uuid.js", 80 | "src/deps/polyfill.js", "src/deps/extend.js","src/deps/ajax.js", srcFiles, 81 | "src/plugins/pouchdb.spatial.js" 82 | ]), 83 | dest: 'dist/pouchdb.spatial-nightly.js' 84 | }, 85 | gql: { 86 | src: grunt.util._.flatten([ 87 | srcFiles, "src/plugins/pouchdb.gql.js" 88 | ]), 89 | dest: 'dist/pouchdb.gql-nightly.js' 90 | } 91 | }, 92 | 93 | 'uglify': { 94 | dist: { 95 | src: "./dist/pouchdb-nightly.js", 96 | dest: 'dist/pouchdb-nightly.min.js' 97 | }, 98 | spatial: { 99 | src: 'dist/pouchdb.spatial-nightly.js', 100 | dest: 'dist/pouchdb.spatial-nightly.min.js' 101 | }, 102 | gql: { 103 | src: 'dist/pouchdb.gql-nightly.js', 104 | dest: 'dist/pouchdb.gql-nightly.min.js' 105 | } 106 | }, 107 | 108 | // Servers 109 | 'connect' : { 110 | server: { 111 | options: { 112 | base: '.', 113 | port: 8000 114 | } 115 | } 116 | }, 117 | 118 | 'cors-server': { 119 | base: 'http://127.0.0.1:5984', 120 | port: 2020 121 | }, 122 | 123 | jshint: { 124 | files: ["src/adapters/*.js", "tests/*.js", "src/*.js", "src/plugins/pouchdb.gql.js"], 125 | options: { 126 | curly: true, 127 | eqeqeq: true, 128 | immed: true, 129 | latedef: true, 130 | newcap: true, 131 | noarg: true, 132 | sub: true, 133 | undef: true, 134 | eqnull: true, 135 | browser: true, 136 | strict: true, 137 | globalstrict: true, 138 | globals: { 139 | // Tests. 140 | _: true, 141 | QUnit: true, 142 | asyncTest: true, 143 | test: true, 144 | DB: true, 145 | deepEqual: true, 146 | equal: true, 147 | expect: true, 148 | fail: true, 149 | module: true, 150 | nextTest: true, 151 | notEqual: true, 152 | ok: true, 153 | sample: true, 154 | start: true, 155 | stop: true, 156 | unescape: true, 157 | process: true, 158 | global: true, 159 | require: true, 160 | console: true, 161 | Pouch: true 162 | } 163 | } 164 | }, 165 | 166 | 'node-qunit': { 167 | all: { 168 | deps: ['./src/deps/extend.js','./src/deps/ajax.js','./src/pouch.js'], 169 | code: './src/adapters/pouch.leveldb.js', 170 | tests: (function() { 171 | var testFilesToRun = testFiles; 172 | 173 | // takes in an optional --test= flag 174 | // to allow running specific test files 175 | var testFileRegex = grunt.option('test'); 176 | if (testFileRegex) { 177 | testFilesToRun = testFilesToRun.filter(function (n) { 178 | return new RegExp(testFileRegex, "i").test(n); 179 | }); 180 | } 181 | return testFilesToRun.map(function (n) { 182 | return "./tests/" + n; 183 | }); 184 | })(), 185 | done: function(err, res) { 186 | !err && (testResults['node'] = res); 187 | return true; 188 | } 189 | } 190 | }, 191 | 192 | 'saucelabs-qunit': { 193 | all: { 194 | username: 'pouchdb', 195 | key: '97de9ee0-2712-49f0-9b17-4b9751d79073', 196 | testname: 'PouchDB Tests', 197 | tags: [process.env.TRAVIS_BRANCH || "unknown"], 198 | testTimeout: 1000 * 60 * 15, // 15 minutes 199 | testInterval: 1000 * 30, // 30 seconds 200 | tunnelTimeout: 1000 * 60 * 15, // 15 minutes 201 | urls: ["http://127.0.0.1:8000/tests/test.html?test=release-min&id=" + 202 | testStartTime.getTime() + "&testFiles=" + testFiles.join(',')], 203 | browsers: browserConfig, 204 | onTestComplete: function(status, page, config, browser) { 205 | var done = this.async(); 206 | var browserDB = nano('http://127.0.0.1:5984').use('test_results'); 207 | var retries = 0; 208 | (function getResults() { 209 | browser.eval("window.testReport", function(err, val) { 210 | testResults[config.name] = err ? "No results" : val; 211 | done(true); 212 | }); 213 | }()); 214 | } 215 | } 216 | }, 217 | 'publish-results': { 218 | server: 'http://couchdb.pouchdb.com', 219 | db: 'test_results' 220 | } 221 | }); 222 | 223 | // Custom tasks 224 | grunt.registerTask("forever", 'Runs forever', function(){ 225 | console.log("Visit http://127.0.0.1:8000/tests/test.html in your browser to run tests."); 226 | this.async(); 227 | }); 228 | 229 | grunt.registerTask("cors-server", "Runs a CORS proxy", function(){ 230 | var corsPort = arguments[0] || grunt.config("cors-server.port"); 231 | var couchUrl = grunt.util._.toArray(arguments).slice(1).join(":") || 232 | grunt.config("cors-server.base"); 233 | grunt.log.writeln("Starting CORS server " + corsPort + " => " + couchUrl); 234 | 235 | cors_proxy.options = {target: couchUrl}; 236 | http_proxy.createServer(cors_proxy).listen(corsPort); 237 | }); 238 | 239 | grunt.registerTask("publish-results", 240 | "Publishes the results of the test to a server", function(){ 241 | var done = this.async(); 242 | cp.exec('git rev-list HEAD --max-count=1', function(err, stdout, stderr) { 243 | var results = { 244 | started: testStartTime, 245 | completed: new Date(), 246 | git_hash: stdout.replace(/[\n\r]/g, ''), 247 | passed: true, 248 | runs: {}, 249 | runner: 'grunt' 250 | }; 251 | for (var key in testResults) { 252 | results.runs[key] = { 253 | started: testResults[key].started || "", 254 | completed: testResults[key].completed || "", 255 | passed: !!(testResults[key].passed), 256 | report: testResults[key] 257 | }; 258 | console.log("Test Result for %s is %s".yellow , key , results.runs[key].passed); 259 | results.passed = results.passed && results.runs[key].passed; 260 | } 261 | nano(grunt.config("publish-results.server")) 262 | .use(grunt.config("publish-results.db")) 263 | .insert(results, testStartTime.getTime() + "", function(err, body){ 264 | console.log(testStartTime.getTime(), err ? err.message : body); 265 | done(results.passed && err === null); 266 | }); 267 | }); 268 | }); 269 | 270 | grunt.loadNpmTasks('grunt-saucelabs'); 271 | grunt.loadNpmTasks('grunt-node-qunit'); 272 | grunt.loadNpmTasks('grunt-contrib-connect'); 273 | grunt.loadNpmTasks('grunt-contrib-concat'); 274 | grunt.loadNpmTasks('grunt-contrib-uglify'); 275 | grunt.loadNpmTasks('grunt-contrib-jshint'); 276 | grunt.loadNpmTasks('grunt-contrib-connect'); 277 | grunt.loadNpmTasks('grunt-contrib-clean'); 278 | grunt.loadNpmTasks('grunt-contrib-watch'); 279 | 280 | grunt.registerTask("build", ["concat:amd", "concat:all" , "uglify:dist"]); 281 | grunt.registerTask("browser", ["connect", "cors-server", "forever"]); 282 | grunt.registerTask("full", ["concat", "uglify"]); 283 | 284 | grunt.registerTask("spatial", ["concat:spatial", "uglify:spatial"]); 285 | grunt.registerTask("gql", ["concat:gql", "uglify:gql"]); 286 | 287 | grunt.registerTask("test", ["jshint", "node-qunit"]); 288 | grunt.registerTask("test-travis", ["jshint", "build", "connect", "cors-server", 289 | "node-qunit", "saucelabs-qunit", 290 | "publish-results"]); 291 | 292 | grunt.registerTask('default', 'build'); 293 | }; 294 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [PouchDB](http://pouchdb.com/) - The Javascript Database that Syncs 2 | ================================================== 3 | 4 | PouchDB is a JavaScript library that allows you to store and query data for web applications that need to work offline, and sync with an online database when you are online. 5 | 6 | [![Build Status](https://secure.travis-ci.org/daleharvey/pouchdb.png?branch=master)](http://travis-ci.org/daleharvey/pouchdb) 7 | 8 | * [Getting Started Guides + Documentation](http://pouchdb.com) 9 | * [Guide to Contributing](https://github.com/daleharvey/pouchdb/blob/master/CONTRIBUTING.md) 10 | 11 | Questions? 12 | ---------- 13 | 14 | If you have any questions, please feel free to ask on the 15 | [PouchDB Mailing List](https://groups.google.com/forum/#!forum/pouchdb) or in #pouchdb on irc.freenode.net. -------------------------------------------------------------------------------- /chromeappbackground.js: -------------------------------------------------------------------------------- 1 | chrome.app.runtime.onLaunched.addListener(function(){ 2 | chrome.app.window.create("./tests/test.html", { 3 | "width": 1000, 4 | "height": 800 5 | }); 6 | }); 7 | -------------------------------------------------------------------------------- /docs/_layouts/default.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {{ page.title }} 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |

PouchDB

15 |
The JavaScript Database that Syncs!
16 | 17 |
18 | 20 |
21 | 22 | 23 |
24 | 25 | 26 |
27 | 28 | 38 |
39 | 40 |
{{ content }}
41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: PouchDB, the JavaScript Database that Syncs! 4 | --- 5 | 6 | # PouchDB 7 | 8 | PouchDB is a JavaScript library that allows you to store and query data for web applications that need to work offline, and sync with an online database when you are online. 9 | 10 | ### The Browser Database that Syncs 11 | 12 | Based on the work of Apache CouchDB, PouchDB provides a simple API in which to store and retrieve JSON objects, due to the similiar API, and CouchDB's HTTP API it is possible to sync data that is stored in your local PouchDB to an online CouchDB as well as syncing data from CouchDB down to PouchDB (you can even sync between 2 PouchDB databases). 13 | 14 | ### Status & Browser Support 15 | 16 | PouchDB is currently in alpha preview. Currently tested in: 17 | 18 | * Firefox 12+ 19 | * Chrome 19+ 20 | * Opera 12+ 21 | * Safari 5+ 22 | * node.js 23 | -------------------------------------------------------------------------------- /docs/static/js/highlight.pack.js: -------------------------------------------------------------------------------- 1 | var hljs=new function(){function l(o){return o.replace(/&/gm,"&").replace(//gm,">")}function b(p){for(var o=p.firstChild;o;o=o.nextSibling){if(o.nodeName=="CODE"){return o}if(!(o.nodeType==3&&o.nodeValue.match(/\s+/))){break}}}function h(p,o){return Array.prototype.map.call(p.childNodes,function(q){if(q.nodeType==3){return o?q.nodeValue.replace(/\n/g,""):q.nodeValue}if(q.nodeName=="BR"){return"\n"}return h(q,o)}).join("")}function a(q){var p=(q.className+" "+q.parentNode.className).split(/\s+/);p=p.map(function(r){return r.replace(/^language-/,"")});for(var o=0;o"}while(x.length||v.length){var u=t().splice(0,1)[0];y+=l(w.substr(p,u.offset-p));p=u.offset;if(u.event=="start"){y+=s(u.node);r.push(u.node)}else{if(u.event=="stop"){var o,q=r.length;do{q--;o=r[q];y+=("")}while(o!=u.node);r.splice(q,1);while(q'+L[0]+""}else{r+=L[0]}N=A.lR.lastIndex;L=A.lR.exec(K)}return r+K.substr(N)}function z(){if(A.sL&&!e[A.sL]){return l(w)}var r=A.sL?d(A.sL,w):g(w);if(A.r>0){v+=r.keyword_count;B+=r.r}return''+r.value+""}function J(){return A.sL!==undefined?z():G()}function I(L,r){var K=L.cN?'':"";if(L.rB){x+=K;w=""}else{if(L.eB){x+=l(r)+K;w=""}else{x+=K;w=r}}A=Object.create(L,{parent:{value:A}});B+=L.r}function C(K,r){w+=K;if(r===undefined){x+=J();return 0}var L=o(r,A);if(L){x+=J();I(L,r);return L.rB?0:r.length}var M=s(A,r);if(M){if(!(M.rE||M.eE)){w+=r}x+=J();do{if(A.cN){x+=""}A=A.parent}while(A!=M.parent);if(M.eE){x+=l(r)}w="";if(M.starts){I(M.starts,"")}return M.rE?0:r.length}if(t(r,A)){throw"Illegal"}w+=r;return r.length||1}var F=e[D];f(F);var A=F;var w="";var B=0;var v=0;var x="";try{var u,q,p=0;while(true){A.t.lastIndex=p;u=A.t.exec(E);if(!u){break}q=C(E.substr(p,u.index-p),u[0]);p=u.index+q}C(E.substr(p));return{r:B,keyword_count:v,value:x,language:D}}catch(H){if(H=="Illegal"){return{r:0,keyword_count:0,value:l(E)}}else{throw H}}}function g(s){var o={keyword_count:0,r:0,value:l(s)};var q=o;for(var p in e){if(!e.hasOwnProperty(p)){continue}var r=d(p,s);r.language=p;if(r.keyword_count+r.r>q.keyword_count+q.r){q=r}if(r.keyword_count+r.r>o.keyword_count+o.r){q=o;o=r}}if(q.language){o.second_best=q}return o}function i(q,p,o){if(p){q=q.replace(/^((<[^>]+>|\t)+)/gm,function(r,v,u,t){return v.replace(/\t/g,p)})}if(o){q=q.replace(/\n/g,"
")}return q}function m(r,u,p){var v=h(r,p);var t=a(r);if(t=="no-highlight"){return}var w=t?d(t,v):g(v);t=w.language;var o=c(r);if(o.length){var q=document.createElement("pre");q.innerHTML=w.value;w.value=j(o,c(q),v)}w.value=i(w.value,u,p);var s=r.className;if(!s.match("(\\s|^)(language-)?"+t+"(\\s|$)")){s=s?(s+" "+t):t}r.innerHTML=w.value;r.className=s;r.result={language:t,kw:w.keyword_count,re:w.r};if(w.second_best){r.second_best={language:w.second_best.language,kw:w.second_best.keyword_count,re:w.second_best.r}}}function n(){if(n.called){return}n.called=true;Array.prototype.map.call(document.getElementsByTagName("pre"),b).filter(Boolean).forEach(function(o){m(o,hljs.tabReplace)})}function k(){window.addEventListener("DOMContentLoaded",n,false);window.addEventListener("load",n,false)}var e={};this.LANGUAGES=e;this.highlight=d;this.highlightAuto=g;this.fixMarkup=i;this.highlightBlock=m;this.initHighlighting=n;this.initHighlightingOnLoad=k;this.IR="[a-zA-Z][a-zA-Z0-9_]*";this.UIR="[a-zA-Z_][a-zA-Z0-9_]*";this.NR="\\b\\d+(\\.\\d+)?";this.CNR="(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)";this.BNR="\\b(0b[01]+)";this.RSR="!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|\\.|-|-=|/|/=|:|;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~";this.BE={b:"\\\\[\\s\\S]",r:0};this.ASM={cN:"string",b:"'",e:"'",i:"\\n",c:[this.BE],r:0};this.QSM={cN:"string",b:'"',e:'"',i:"\\n",c:[this.BE],r:0};this.CLCM={cN:"comment",b:"//",e:"$"};this.CBLCLM={cN:"comment",b:"/\\*",e:"\\*/"};this.HCM={cN:"comment",b:"#",e:"$"};this.NM={cN:"number",b:this.NR,r:0};this.CNM={cN:"number",b:this.CNR,r:0};this.BNM={cN:"number",b:this.BNR,r:0};this.inherit=function(q,r){var o={};for(var p in q){o[p]=q[p]}if(r){for(var p in r){o[p]=r[p]}}return o}}();hljs.LANGUAGES.javascript=function(a){return{k:{keyword:"in if for while finally var new function do return void else break catch instanceof with throw case default try this switch continue typeof delete let yield const",literal:"true false null undefined NaN Infinity"},c:[a.ASM,a.QSM,a.CLCM,a.CBLCLM,a.CNM,{b:"("+a.RSR+"|\\b(case|return|throw)\\b)\\s*",k:"return throw case",c:[a.CLCM,a.CBLCLM,{cN:"regexp",b:"/",e:"/[gim]*",i:"\\n",c:[{b:"\\\\/"}]},{b:"<",e:">;",sL:"xml"}],r:0},{cN:"function",bWK:true,e:"{",k:"function",c:[{cN:"title",b:"[A-Za-z$_][0-9A-Za-z$_]*"},{cN:"params",b:"\\(",e:"\\)",c:[a.CLCM,a.CBLCLM],i:"[\"'\\(]"}],i:"\\[|%"}]}}(hljs);hljs.LANGUAGES.css=function(a){var b={cN:"function",b:a.IR+"\\(",e:"\\)",c:[a.NM,a.ASM,a.QSM]};return{cI:true,i:"[=/|']",c:[a.CBLCLM,{cN:"id",b:"\\#[A-Za-z0-9_-]+"},{cN:"class",b:"\\.[A-Za-z0-9_-]+",r:0},{cN:"attr_selector",b:"\\[",e:"\\]",i:"$"},{cN:"pseudo",b:":(:)?[a-zA-Z0-9\\_\\-\\+\\(\\)\\\"\\']+"},{cN:"at_rule",b:"@(font-face|page)",l:"[a-z-]+",k:"font-face page"},{cN:"at_rule",b:"@",e:"[{;]",eE:true,k:"import page media charset",c:[b,a.ASM,a.QSM,a.NM]},{cN:"tag",b:a.IR,r:0},{cN:"rules",b:"{",e:"}",i:"[^\\s]",r:0,c:[a.CBLCLM,{cN:"rule",b:"[^\\s]",rB:true,e:";",eW:true,c:[{cN:"attribute",b:"[A-Z\\_\\.\\-]+",e:":",eE:true,i:"[^\\s]",starts:{cN:"value",eW:true,eE:true,c:[b,a.NM,a.QSM,a.ASM,a.CBLCLM,{cN:"hexcolor",b:"\\#[0-9A-F]+"},{cN:"important",b:"!important"}]}}]}]}]}}(hljs);hljs.LANGUAGES.xml=function(a){var c="[A-Za-z0-9\\._:-]+";var b={eW:true,c:[{cN:"attribute",b:c,r:0},{b:'="',rB:true,e:'"',c:[{cN:"value",b:'"',eW:true}]},{b:"='",rB:true,e:"'",c:[{cN:"value",b:"'",eW:true}]},{b:"=",c:[{cN:"value",b:"[^\\s/>]+"}]}]};return{cI:true,c:[{cN:"pi",b:"<\\?",e:"\\?>",r:10},{cN:"doctype",b:"",r:10,c:[{b:"\\[",e:"\\]"}]},{cN:"comment",b:"",r:10},{cN:"cdata",b:"<\\!\\[CDATA\\[",e:"\\]\\]>",r:10},{cN:"tag",b:"|$)",e:">",k:{title:"style"},c:[b],starts:{e:"",rE:true,sL:"css"}},{cN:"tag",b:"|$)",e:">",k:{title:"script"},c:[b],starts:{e:"<\/script>",rE:true,sL:"javascript"}},{b:"<%",e:"%>",sL:"vbscript"},{cN:"tag",b:"",c:[{cN:"title",b:"[^ />]+"},b]}]}}(hljs);hljs.LANGUAGES.http=function(a){return{i:"\\S",c:[{cN:"status",b:"^HTTP/[0-9\\.]+",e:"$",c:[{cN:"number",b:"\\b\\d{3}\\b"}]},{cN:"request",b:"^[A-Z]+ (.*?) HTTP/[0-9\\.]+$",rB:true,e:"$",c:[{cN:"string",b:" ",e:" ",eB:true,eE:true}]},{cN:"attribute",b:"^\\w",e:": ",eE:true,i:"\\n|\\s|=",starts:{cN:"string",e:"$"}},{b:"\\n\\n",starts:{sL:"",eW:true}}]}}(hljs);hljs.LANGUAGES.json=function(a){var e={literal:"true false null"};var d=[a.QSM,a.CNM];var c={cN:"value",e:",",eW:true,eE:true,c:d,k:e};var b={b:"{",e:"}",c:[{cN:"attribute",b:'\\s*"',e:'"\\s*:\\s*',eB:true,eE:true,c:[a.BE],i:"\\n",starts:c}],i:"\\S"};var f={b:"\\[",e:"\\]",c:[a.inherit(c,{cN:null})],i:"\\S"};d.splice(d.length,0,b,f);return{c:d,k:e,i:"\\S"}}(hljs); -------------------------------------------------------------------------------- /docs/static/js/pouchdb.website.js: -------------------------------------------------------------------------------- 1 | 2 | "use strict"; 3 | 4 | var downloadRoot = 'http://download.pouchdb.com/'; 5 | var qs = function(selector) { 6 | return document.querySelector(selector); 7 | }; 8 | 9 | qs('#download-button').addEventListener('click', function() { 10 | var selected = document.querySelector('[name=download]:checked').value; 11 | var prefix = (selected === 'min') ? 'min.' : ''; 12 | var url = downloadRoot + 'pouchdb-nightly.' + prefix + 'js'; 13 | document.location.href = url; 14 | }); -------------------------------------------------------------------------------- /docs/static/style/noun_project_5618.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikeal/pouchdb/35e547919c5cd74d6219f918dec844538aac96c4/docs/static/style/noun_project_5618.png -------------------------------------------------------------------------------- /docs/static/style/noun_project_5618.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /docs/static/style/pouchdb.css: -------------------------------------------------------------------------------- 1 | @import "https://fonts.googleapis.com/css?family=Lato:300italic,700italic,300,700"; 2 | 3 | html { 4 | overflow-y: scroll; 5 | } 6 | 7 | body { 8 | color: #777777; 9 | font: 300 14px/1.5 Lato,"Helvetica Neue",Helvetica,Arial,sans-serif; 10 | margin: 40px auto; 11 | width: 860px; 12 | } 13 | 14 | a { 15 | color: #3399CC; 16 | font-weight: 400; 17 | text-decoration: none; 18 | } 19 | 20 | header { 21 | float: left; 22 | position: fixed; 23 | width: 270px; 24 | } 25 | 26 | h1 { 27 | font-size: 28px; 28 | color: black; 29 | } 30 | 31 | h1, h2, h3 { 32 | line-height: 1.1; 33 | } 34 | 35 | h1, h2, h3, h4, h5, h6 { 36 | color: #222; 37 | margin: 0 0 20px; 38 | } 39 | 40 | h3, h4, h5, h6 { 41 | color: #494949; 42 | } 43 | 44 | header h5 { 45 | margin-top: 0; 46 | font-weight: normal; 47 | } 48 | 49 | header h1 { 50 | margin-bottom: 0; 51 | } 52 | 53 | header h1 a { 54 | color: black; 55 | font-weight: bold; 56 | } 57 | 58 | header h1 a:hover { 59 | text-decoration: underline; 60 | } 61 | 62 | nav ul { 63 | list-style-type: none; 64 | padding: 0; 65 | } 66 | 67 | nav li { 68 | margin: 0 0 15px; 69 | } 70 | 71 | nav a { 72 | font-size: 21px; 73 | line-height: 1.1; 74 | } 75 | 76 | #content { 77 | float: right; 78 | padding-bottom: 50px; 79 | width: 550px; 80 | } 81 | 82 | pre { 83 | background: none repeat scroll 0 0 #002B36; 84 | width: 100%; 85 | overflow: auto; 86 | font-size: 12px; 87 | } 88 | 89 | pre, code { 90 | color: #333333; 91 | font-family: Monaco,Bitstream Vera Sans Mono,Lucida Console,Terminal; 92 | font-size: 12px; 93 | } 94 | 95 | #download-button { 96 | border: 0; 97 | font-size: 14px; 98 | padding: 10px 10px 10px 40px; 99 | background: #DDD url(./noun_project_5618.svg) no-repeat 10px center; 100 | background-size: 24px 24px; 101 | border-radius: 3px; 102 | margin-top: 10px; 103 | cursor: pointer; 104 | } 105 | #download-button:hover { 106 | background-color: #CCC; 107 | } 108 | 109 | header section { 110 | float: left; 111 | background: #EEE; 112 | padding-top: 5px; 113 | } 114 | 115 | header section input { 116 | margin-left: 10px; 117 | } 118 | 119 | nav { 120 | padding-top: 10px; 121 | clear: both; 122 | } -------------------------------------------------------------------------------- /docs/static/style/solarized_dark.min.css: -------------------------------------------------------------------------------- 1 | pre code{display:block;padding:.5em;background:#002b36;color:#839496}pre .comment,pre .template_comment,pre .diff .header,pre .doctype,pre .pi,pre .lisp .string,pre .javadoc{color:#586e75;font-style:italic}pre .keyword,pre .winutils,pre .method,pre .addition,pre .css .tag,pre .request,pre .status,pre .nginx .title{color:#859900}pre .number,pre .command,pre .string,pre .tag .value,pre .phpdoc,pre .tex .formula,pre .regexp,pre .hexcolor{color:#2aa198}pre .title,pre .localvars,pre .chunk,pre .decorator,pre .built_in,pre .identifier,pre .vhdl .literal,pre .id{color:#268bd2}pre .attribute,pre .variable,pre .lisp .body,pre .smalltalk .number,pre .constant,pre .class .title,pre .parent,pre .haskell .type{color:#b58900}pre .preprocessor,pre .preprocessor .keyword,pre .shebang,pre .symbol,pre .symbol .string,pre .diff .change,pre .special,pre .attr_selector,pre .important,pre .subst,pre .cdata,pre .clojure .title{color:#cb4b16}pre .deletion{color:#dc322f}pre .tex .formula{background:#073642} -------------------------------------------------------------------------------- /docs/todo-list-tutorial.md: -------------------------------------------------------------------------------- 1 | ---- 2 | layout: default 3 | title: Simple todo list with sync 4 | ---- 5 | 6 | # Simple todo list with sync 7 | 8 | This short tutorial shows the very simple application using pouchdb. It replaces [todomvc todos’](http://todomvc.com/vanilla-examples/vanillajs/) localStorage with pouchdb. 9 | 10 | First you need to start with cloning pouchdb: 11 | 12 | https://github.com/daleharvey/pouchdb 13 | 14 | After that we go straight away to our example todo-list located in tutorials. 15 | 16 | To run it - due to [CORS](http://en.wikipedia.org/wiki/Cross-origin_resource_sharing) - you need to put it onto your webserver or start your own. If you have python2 just try: (http.server in python 3) 17 | 18 | python2 -m SimpleHTTPServer 8888 19 | 20 | Then simply run our example: [http://localhost:8888](http://localhost:8888). Unfortunately, sync doesn’t work yet. 21 | 22 | ## Sync 23 | 24 | More difficult part concerns couchdb server we want to sync our data to. 25 | 26 | If you already have one - you just simply edit the remove variable in app.js file. (you need to create database called todos) 27 | 28 | Otherwise, follow [couchdb’s tests](https://github.com/daleharvey/pouchdb/wiki/Running-and-Writing-Tests) tutorial to get couchdb and cors-server up and running. Create todos database and you are ready to go. 29 | 30 | -------------------------------------------------------------------------------- /docs/tutorials/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | VanillaJS • TodoMVC 7 | 8 | 27 | 30 | 31 | 32 |
33 | 37 |
38 | 39 | 40 |
    41 |
    42 |
    43 | 44 | 45 |
    46 |
    47 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /docs/tutorials/todo-list/js/app.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | var todos = [], 5 | stat = {}, 6 | ENTER_KEY = 13, 7 | db = null, 8 | todos = {}, 9 | dbname = 'idb://todos', 10 | remote = 'http://127.0.0.1:2020/todos'; 11 | 12 | window.addEventListener( 'load', loadPouch, false ); 13 | 14 | function Todo( title, completed ) { 15 | this._id = getUuid(); 16 | this.title = title; 17 | this.completed = completed; 18 | this.created = Date.now(); 19 | } 20 | 21 | function Stat() { 22 | this.todoLeft = 0; 23 | this.todoCompleted = 0; 24 | this.totalTodo = 0; 25 | } 26 | 27 | function loadPouch() { 28 | //Pouch.destroy(dbname); 29 | Pouch(dbname, function(err, pouchdb){ 30 | if(err){ 31 | alert('Can\'t open pouchdb database'); 32 | }else{ 33 | document.getElementById('push').addEventListener('click', function(){ 34 | var orig = this.innerHTML; 35 | var that = this; 36 | this.innerHTML = "working"; 37 | Pouch.replicate(dbname, remote, function(err, changes){ 38 | if(!err){ 39 | that.innerHTML = "synced"; 40 | }else{ 41 | that.innerHTML = "failed"; 42 | } 43 | setTimeout(function(){ 44 | that.innerHTML = orig; 45 | }, 1000); 46 | }); 47 | }, false); 48 | 49 | document.getElementById('get').addEventListener('click', function(){ 50 | var orig = this.innerHTML; 51 | var that = this; 52 | this.innerHTML = "working"; 53 | Pouch.replicate(remote, dbname, function(err, changes){ 54 | if(!err){ 55 | loadData(); 56 | }else{ 57 | console.log('Replication get error'); 58 | } 59 | that.innerHTML = orig; 60 | }); 61 | }, false); 62 | 63 | db = pouchdb; 64 | windowLoadHandler(); 65 | } 66 | }); 67 | } 68 | 69 | function loadData(){ 70 | db.allDocs({include_docs: true}, function(err, res){ 71 | var i, todo; 72 | if(!err){ 73 | todos = {}; 74 | for(i = 0; i < res.rows.length; i++){ 75 | todo = res.rows[i].doc; 76 | todos[todo._id] = todo; 77 | } 78 | refreshData(); 79 | }else{ 80 | console.log('Error getting all docs'); 81 | } 82 | }); 83 | } 84 | 85 | function windowLoadHandler() { 86 | loadData(); 87 | addEventListeners(); 88 | } 89 | 90 | function addEventListeners() { 91 | document.getElementById('new-todo').addEventListener( 'keypress', newTodoKeyPressHandler, false ); 92 | document.getElementById('toggle-all').addEventListener( 'change', toggleAllChangeHandler, false ); 93 | } 94 | 95 | function inputEditTodoKeyPressHandler( event ) { 96 | var inputEditTodo = event.target, 97 | trimmedText = inputEditTodo.value.trim(), 98 | todoId = event.target.id.slice( 6 ); 99 | 100 | if ( trimmedText ) { 101 | if ( event.keyCode === ENTER_KEY ) { 102 | editTodo( todoId, {'title': trimmedText} ); 103 | } 104 | } else { 105 | removeTodoById( todoId ); 106 | } 107 | } 108 | 109 | function inputEditTodoBlurHandler( event ) { 110 | var inputEditTodo = event.target, 111 | todoId = event.target.id.slice( 6 ); 112 | 113 | editTodo( todoId, {'title': inputEditTodo.value} ); 114 | } 115 | 116 | function newTodoKeyPressHandler( event ) { 117 | if ( event.keyCode === ENTER_KEY ) { 118 | addTodo( document.getElementById('new-todo').value ); 119 | } 120 | } 121 | 122 | function toggleAllChangeHandler( event ) { 123 | var i, todo; 124 | for(i in todos){ 125 | if(todos.hasOwnProperty(i)){ 126 | var todo = todos[i]; 127 | editTodo(todo._id, {'completed': event.target.checked}); 128 | } 129 | } 130 | } 131 | 132 | function spanDeleteClickHandler( event ) { 133 | removeTodoById( event.target.getAttribute('data-todo-id') ); 134 | } 135 | 136 | function hrefClearClickHandler() { 137 | removeTodosCompleted(); 138 | refreshData(); 139 | } 140 | 141 | function todoContentHandler( event ) { 142 | var todoId = event.target.getAttribute('data-todo-id'), 143 | div = document.getElementById( 'li_' + todoId ), 144 | inputEditTodo = document.getElementById( 'input_' + todoId ); 145 | 146 | div.className = 'editing'; 147 | inputEditTodo.focus(); 148 | } 149 | 150 | function checkboxChangeHandler( event ) { 151 | var checkbox = event.target, 152 | todoId = checkbox.getAttribute('data-todo-id'); 153 | editTodo( todoId, {'completed': checkbox.checked} ); 154 | } 155 | 156 | function addTodo( text ) { 157 | var trimmedText = text.trim(); 158 | 159 | if ( trimmedText ) { 160 | var todo = new Todo( trimmedText, false ); 161 | } 162 | 163 | db.post(todo, function(err, res){ 164 | if(!err){ 165 | console.log('Todo added', todo._id); 166 | todos[todo._id] = todo; 167 | console.log(todo) 168 | refreshData(); 169 | }else{ 170 | console.log('Add failed', err); 171 | } 172 | }); 173 | } 174 | 175 | /* opt: text, completed */ 176 | function editTodo( todoId, opt ) { 177 | 178 | db.get(todoId, function(err, todo){ 179 | console.log(todo); 180 | if('title' in opt){ 181 | todo.title = opt.title; 182 | } 183 | if('completed' in opt){ 184 | todo.completed = opt.completed; 185 | } 186 | db.put(todo, function(err, res){ 187 | if(!err){ 188 | console.log('Todo edited', opt); 189 | todos[todoId] = todo; 190 | refreshData(); 191 | }else{ 192 | console.log('Edit failed'); 193 | } 194 | }); 195 | }); 196 | } 197 | 198 | function removeTodoById( todoId ) { 199 | db.get(todoId, function(err, res){ 200 | db.remove(res, function(err, res){ 201 | if(!err){ 202 | console.log('Todo removed'); 203 | delete todos[todoId]; 204 | refreshData(); 205 | }else{ 206 | console.log('Remove failed'); 207 | } 208 | }); 209 | }); 210 | } 211 | 212 | function removeTodosCompleted() { 213 | var i, todo; 214 | for(i in todos){ 215 | if(todos.hasOwnProperty(i)){ 216 | var todo = todos[i]; 217 | if(todo.completed){ 218 | removeTodoById(todo._id); 219 | } 220 | } 221 | } 222 | } 223 | 224 | function refreshData() { 225 | var todosArr = []; 226 | var i, todo; 227 | for(i in todos){ 228 | if(todos.hasOwnProperty(i)){ 229 | todo = todos[i]; 230 | todosArr.push(todo); 231 | } 232 | } 233 | todosArr.sort(function(a, b){ 234 | return a.created - b.created; 235 | }); 236 | computeStats(todosArr); 237 | redrawTodosUI(todosArr); 238 | redrawStatsUI(todosArr); 239 | changeToggleAllCheckboxState(todosArr); 240 | } 241 | 242 | function computeStats(todos) { 243 | var i, l; 244 | 245 | stat = new Stat(); 246 | stat.totalTodo = todos.length; 247 | 248 | for ( i = 0, l = todos.length; i < l; i++ ) { 249 | if ( todos[ i ].completed ) { 250 | stat.todoCompleted++; 251 | } 252 | } 253 | 254 | stat.todoLeft = stat.totalTodo - stat.todoCompleted; 255 | } 256 | 257 | 258 | function redrawTodosUI(todos) { 259 | var todo, checkbox, label, deleteLink, divDisplay, inputEditTodo, li, i, l, 260 | ul = document.getElementById('todo-list'); 261 | 262 | document.getElementById('main').style.display = todos.length ? 'block' : 'none'; 263 | 264 | ul.innerHTML = ''; 265 | document.getElementById('new-todo').value = ''; 266 | 267 | for ( i = 0, l = todos.length; i < l; i++ ) { 268 | todo = todos[ i ]; 269 | 270 | // create checkbox 271 | checkbox = document.createElement('input'); 272 | checkbox.className = 'toggle'; 273 | checkbox.setAttribute( 'data-todo-id', todo._id ); 274 | checkbox.type = 'checkbox'; 275 | checkbox.addEventListener( 'change', checkboxChangeHandler ); 276 | 277 | // create div text 278 | label = document.createElement('label'); 279 | label.setAttribute( 'data-todo-id', todo._id ); 280 | label.appendChild( document.createTextNode( todo.title ) ); 281 | label.addEventListener( 'dblclick', todoContentHandler ); 282 | 283 | 284 | // create delete button 285 | deleteLink = document.createElement('button'); 286 | deleteLink.className = 'destroy'; 287 | deleteLink.setAttribute( 'data-todo-id', todo._id ); 288 | deleteLink.addEventListener( 'click', spanDeleteClickHandler ); 289 | 290 | // create divDisplay 291 | divDisplay = document.createElement('div'); 292 | divDisplay.className = 'view'; 293 | divDisplay.setAttribute( 'data-todo-id', todo._id ); 294 | divDisplay.appendChild( checkbox ); 295 | divDisplay.appendChild( label ); 296 | divDisplay.appendChild( deleteLink ); 297 | 298 | // create todo input 299 | inputEditTodo = document.createElement('input'); 300 | inputEditTodo.id = 'input_' + todo._id; 301 | inputEditTodo.className = 'edit'; 302 | inputEditTodo.value = todo.title; 303 | inputEditTodo.addEventListener( 'keypress', inputEditTodoKeyPressHandler ); 304 | inputEditTodo.addEventListener( 'blur', inputEditTodoBlurHandler ); 305 | 306 | 307 | // create li 308 | li = document.createElement('li'); 309 | li.id = 'li_' + todo._id; 310 | li.appendChild( divDisplay ); 311 | li.appendChild( inputEditTodo ); 312 | 313 | 314 | if ( todo.completed ) { 315 | li.className += 'complete'; 316 | checkbox.checked = true; 317 | } 318 | 319 | ul.appendChild( li ); 320 | } 321 | } 322 | 323 | function changeToggleAllCheckboxState(todos) { 324 | var toggleAll = document.getElementById('toggle-all'); 325 | 326 | toggleAll.checked = stat.todoCompleted === todos.length; 327 | } 328 | 329 | function redrawStatsUI(todos) { 330 | removeChildren( document.getElementsByTagName('footer')[0] ); 331 | document.getElementById('footer').style.display = todos.length ? 'block' : 'none'; 332 | 333 | if ( stat.todoCompleted ) { 334 | drawTodoClear(); 335 | } 336 | 337 | if ( stat.totalTodo ) { 338 | drawTodoCount(); 339 | } 340 | } 341 | 342 | function drawTodoCount() { 343 | var number = document.createElement('strong'), 344 | remaining = document.createElement('span'), 345 | text = ' ' + ( stat.todoLeft === 1 ? 'item' : 'items' ) + ' left'; 346 | 347 | // create remaining count 348 | number.innerHTML = stat.todoLeft; 349 | 350 | remaining.id = 'todo-count'; 351 | remaining.appendChild( number ); 352 | remaining.appendChild( document.createTextNode( text ) ); 353 | 354 | document.getElementsByTagName('footer')[0].appendChild( remaining ); 355 | } 356 | 357 | function drawTodoClear() { 358 | var buttonClear = document.createElement('button'); 359 | 360 | buttonClear.id = 'clear-completed'; 361 | buttonClear.addEventListener( 'click', hrefClearClickHandler ); 362 | buttonClear.innerHTML = 'Clear completed (' + stat.todoCompleted + ')'; 363 | 364 | document.getElementsByTagName('footer')[0].appendChild( buttonClear ); 365 | } 366 | 367 | function removeChildren( node ) { 368 | node.innerHTML = ''; 369 | } 370 | 371 | function getUuid() { 372 | var i, random, 373 | uuid = ''; 374 | 375 | for ( i = 0; i < 32; i++ ) { 376 | random = Math.random() * 16 | 0; 377 | if ( i === 8 || i === 12 || i === 16 || i === 20 ) { 378 | uuid += '-'; 379 | } 380 | uuid += ( i === 12 ? 4 : (i === 16 ? (random & 3 | 8) : random) ).toString( 16 ); 381 | } 382 | return uuid; 383 | } 384 | })(); 385 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Pouchdb Tests", 3 | "description": "Pouchdb chrome app tests", 4 | "version": "0.1", 5 | "manifest_version": 1, 6 | "app": { 7 | "background": { 8 | "scripts": ["chromeappbackground.js"] 9 | } 10 | }, 11 | "content_security_policy": "default-src 'none' ; script-src 'self' 'http://localhost' 'unsafe-eval'; object-src 'self' 'http://localhost'", 12 | "permissions": [ 13 | "storage" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pouchdb", 3 | "release": "nightly", 4 | "description": "PouchDB is a pocket-sized database.", 5 | "author": "Dale Harvey", 6 | "version": "0.0.0", 7 | "main": "./src/pouch.js", 8 | "homepage": "https://github.com/daleharvey/pouchdb", 9 | "keywords": [ 10 | "db", 11 | "couchdb", 12 | "pouchdb" 13 | ], 14 | "tags": [ 15 | "db", 16 | "couchdb", 17 | "pouchdb" 18 | ], 19 | "dependencies": { 20 | "levelup": "*", 21 | "request": "*", 22 | "underscore": "*" 23 | }, 24 | "jam": { 25 | "main": "dist/pouchdb.amd-nightly.js", 26 | "include": [ 27 | "dist/pouchdb.amd-nightly.js", 28 | "README.md", 29 | "LICENSE" 30 | ], 31 | "dependencies": { 32 | "simple-uuid": null, 33 | "md5": null 34 | } 35 | }, 36 | "devDependencies": { 37 | "wd": "*", 38 | "commander": "*", 39 | "colors": "*", 40 | "assert": "*", 41 | "nano": "*", 42 | "send": "*", 43 | "grunt": "~0.4", 44 | "grunt-saucelabs": "~3.0", 45 | "grunt-node-qunit": "~2.0", 46 | "corsproxy": "*", 47 | "http-proxy": "*", 48 | "grunt-contrib-connect": "~0.1.2", 49 | "grunt-contrib-concat": "~0.1.3", 50 | "grunt-contrib-uglify": "~0.1.1", 51 | "grunt-contrib-jshint": "~0.1.1", 52 | "grunt-contrib-clean": "~0.4.0", 53 | "grunt-contrib-watch": "~0.2.0" 54 | }, 55 | "maintainers": [ 56 | { 57 | "name": "Dale Harvey", 58 | "web": "https://github.com/daleharvey" 59 | }, 60 | { 61 | "name": "Ryan Ramage", 62 | "web": "https://github.com/ryanramage" 63 | } 64 | ], 65 | "repositories": [ 66 | { 67 | "type": "git", 68 | "url": "https://github.com/daleharvey/pouchdb" 69 | } 70 | ], 71 | "scripts": { 72 | "test": "grunt test-travis" 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/deps/ajax.js: -------------------------------------------------------------------------------- 1 | var ajax = function ajax(options, callback) { 2 | if (typeof options === "function") { 3 | callback = options; 4 | options = {}; 5 | } 6 | var call = function(fun) { 7 | var args = Array.prototype.slice.call(arguments, 1); 8 | if (typeof fun === typeof Function) { 9 | fun.apply(this, args); 10 | } 11 | }; 12 | var defaultOptions = { 13 | method : "GET", 14 | headers: {}, 15 | json: true, 16 | processData: true, 17 | timeout: 10000 18 | }; 19 | options = extend(true, defaultOptions, options); 20 | if (options.auth) { 21 | var token = btoa(options.auth.username + ':' + options.auth.password); 22 | options.headers.Authorization = 'Basic ' + token; 23 | } 24 | var onSuccess = function(obj, resp, cb){ 25 | if (!options.binary && !options.json && options.processData && typeof obj !== 'string') { 26 | obj = JSON.stringify(obj); 27 | } else if (!options.binary && options.json && typeof obj === 'string') { 28 | try { 29 | obj = JSON.parse(obj); 30 | } catch (e) { 31 | // Probably a malformed JSON from server 32 | call(cb, e); 33 | return; 34 | } 35 | } 36 | call(cb, null, obj, resp); 37 | }; 38 | var onError = function(err, cb){ 39 | var errParsed; 40 | var errObj = {status: err.status}; 41 | try{ 42 | errParsed = JSON.parse(err.responseText); //would prefer not to have a try/catch clause 43 | errObj = extend(true, {}, errObj, errParsed); 44 | } catch(e){} 45 | call(cb, errObj); 46 | }; 47 | if (typeof window !== 'undefined' && window.XMLHttpRequest) { 48 | var timer,timedout = false; 49 | var xhr = new XMLHttpRequest(); 50 | xhr.open(options.method, options.url); 51 | if (options.json) { 52 | options.headers.Accept = 'application/json'; 53 | options.headers['Content-Type'] = options.headers['Content-Type'] || 'application/json'; 54 | if (options.body && options.processData && typeof options.body !== "string") { 55 | options.body = JSON.stringify(options.body); 56 | } 57 | } 58 | if (options.binary) { 59 | xhr.responseType = 'arraybuffer'; 60 | } 61 | for (var key in options.headers){ 62 | xhr.setRequestHeader(key, options.headers[key]); 63 | } 64 | if (!("body" in options)) { 65 | options.body = null; 66 | } 67 | 68 | var abortReq = function() { 69 | timedout=true; 70 | xhr.abort(); 71 | call(onError, xhr, callback); 72 | }; 73 | xhr.onreadystatechange = function() { 74 | if (xhr.readyState !== 4 || timedout) { 75 | return; 76 | } 77 | clearTimeout(timer); 78 | if (xhr.status >= 200 && xhr.status < 300) { 79 | var data; 80 | if (options.binary) { 81 | data = new Blob([xhr.response || ''], {type: xhr.getResponseHeader('Content-Type')}); 82 | } else { 83 | data = xhr.responseText; 84 | } 85 | call(onSuccess, data, xhr, callback); 86 | } else { 87 | call(onError, xhr, callback); 88 | } 89 | }; 90 | if (options.timeout > 0) { 91 | timer = setTimeout(abortReq, options.timeout); 92 | } 93 | xhr.send(options.body); 94 | return {abort:abortReq}; 95 | } else { 96 | if (options.json) { 97 | if (!options.binary) { 98 | options.headers.Accept = 'application/json'; 99 | } 100 | options.headers['Content-Type'] = options.headers['Content-Type'] || 'application/json'; 101 | } 102 | if (options.binary) { 103 | options.encoding = null; 104 | options.json = false; 105 | } 106 | if (!options.processData) { 107 | options.json = false; 108 | } 109 | return request(options, function(err, response, body) { 110 | if (err) { 111 | err.status = response ? response.statusCode : 400; 112 | return call(onError, err, callback); 113 | } 114 | 115 | var content_type = response.headers['content-type']; 116 | var data = (body || ''); 117 | 118 | // CouchDB doesn't always return the right content-type for JSON data, so 119 | // we check for ^{ and }$ (ignoring leading/trailing whitespace) 120 | if (!options.binary && (options.json || !options.processData) && typeof data !== 'object' && 121 | (/json/.test(content_type) || 122 | (/^[\s]*\{/.test(data) && /\}[\s]*$/.test(data)))) { 123 | data = JSON.parse(data); 124 | } 125 | 126 | if (response.statusCode >= 200 && response.statusCode < 300) { 127 | call(onSuccess, data, response, callback); 128 | } 129 | else { 130 | if (options.binary) { 131 | var data = JSON.parse(data.toString()); 132 | } 133 | data.status = response.statusCode; 134 | call(callback, data); 135 | } 136 | }); 137 | } 138 | }; 139 | 140 | if (typeof module !== 'undefined' && module.exports) { 141 | module.exports = ajax; 142 | } 143 | -------------------------------------------------------------------------------- /src/deps/extend.js: -------------------------------------------------------------------------------- 1 | // Extends method 2 | // (taken from http://code.jquery.com/jquery-1.9.0.js) 3 | // Populate the class2type map 4 | var class2type = {}; 5 | 6 | var types = ["Boolean", "Number", "String", "Function", "Array", "Date", "RegExp", "Object", "Error"]; 7 | for (var i = 0; i < types.length; i++) { 8 | var typename = types[i]; 9 | class2type[ "[object " + typename + "]" ] = typename.toLowerCase(); 10 | } 11 | 12 | var core_toString = class2type.toString; 13 | var core_hasOwn = class2type.hasOwnProperty; 14 | 15 | var type = function(obj) { 16 | if (obj === null) { 17 | return String( obj ); 18 | } 19 | return typeof obj === "object" || typeof obj === "function" ? 20 | class2type[core_toString.call(obj)] || "object" : 21 | typeof obj; 22 | }; 23 | 24 | var isWindow = function(obj) { 25 | return obj !== null && obj === obj.window; 26 | }; 27 | 28 | var isPlainObject = function( obj ) { 29 | // Must be an Object. 30 | // Because of IE, we also have to check the presence of the constructor property. 31 | // Make sure that DOM nodes and window objects don't pass through, as well 32 | if ( !obj || type(obj) !== "object" || obj.nodeType || isWindow( obj ) ) { 33 | return false; 34 | } 35 | 36 | try { 37 | // Not own constructor property must be Object 38 | if ( obj.constructor && 39 | !core_hasOwn.call(obj, "constructor") && 40 | !core_hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) { 41 | return false; 42 | } 43 | } catch ( e ) { 44 | // IE8,9 Will throw exceptions on certain host objects #9897 45 | return false; 46 | } 47 | 48 | // Own properties are enumerated firstly, so to speed up, 49 | // if last one is own, then all properties are own. 50 | 51 | var key; 52 | for ( key in obj ) {} 53 | 54 | return key === undefined || core_hasOwn.call( obj, key ); 55 | }; 56 | 57 | var isFunction = function(obj) { 58 | return type(obj) === "function"; 59 | }; 60 | 61 | var isArray = Array.isArray || function(obj) { 62 | return type(obj) === "array"; 63 | }; 64 | 65 | var extend = function() { 66 | var options, name, src, copy, copyIsArray, clone, 67 | target = arguments[0] || {}, 68 | i = 1, 69 | length = arguments.length, 70 | deep = false; 71 | 72 | // Handle a deep copy situation 73 | if ( typeof target === "boolean" ) { 74 | deep = target; 75 | target = arguments[1] || {}; 76 | // skip the boolean and the target 77 | i = 2; 78 | } 79 | 80 | // Handle case when target is a string or something (possible in deep copy) 81 | if ( typeof target !== "object" && !isFunction(target) ) { 82 | target = {}; 83 | } 84 | 85 | // extend jQuery itself if only one argument is passed 86 | if ( length === i ) { 87 | target = this; 88 | --i; 89 | } 90 | 91 | for ( ; i < length; i++ ) { 92 | // Only deal with non-null/undefined values 93 | if ((options = arguments[ i ]) != null) { 94 | // Extend the base object 95 | for ( name in options ) { 96 | src = target[ name ]; 97 | copy = options[ name ]; 98 | 99 | // Prevent never-ending loop 100 | if ( target === copy ) { 101 | continue; 102 | } 103 | 104 | // Recurse if we're merging plain objects or arrays 105 | if ( deep && copy && ( isPlainObject(copy) || (copyIsArray = isArray(copy)) ) ) { 106 | if ( copyIsArray ) { 107 | copyIsArray = false; 108 | clone = src && isArray(src) ? src : []; 109 | 110 | } else { 111 | clone = src && isPlainObject(src) ? src : {}; 112 | } 113 | 114 | // Never move original objects, clone them 115 | target[ name ] = extend( deep, clone, copy ); 116 | 117 | // Don't bring in undefined values 118 | } else if ( copy !== undefined ) { 119 | target[ name ] = copy; 120 | } 121 | } 122 | } 123 | } 124 | 125 | // Return the modified object 126 | return target; 127 | }; 128 | 129 | if (typeof module !== 'undefined' && module.exports) { 130 | module.exports = extend; 131 | } 132 | -------------------------------------------------------------------------------- /src/deps/polyfill.js: -------------------------------------------------------------------------------- 1 | //---------------------------------------------------------------------- 2 | // 3 | // ECMAScript 5 Polyfills 4 | // from www.calocomrmen./polyfill/ 5 | // 6 | //---------------------------------------------------------------------- 7 | 8 | //---------------------------------------------------------------------- 9 | // ES5 15.2 Object Objects 10 | //---------------------------------------------------------------------- 11 | 12 | 13 | 14 | // ES 15.2.3.6 Object.defineProperty ( O, P, Attributes ) 15 | // Partial support for most common case - getters, setters, and values 16 | (function() { 17 | if (!Object.defineProperty || 18 | !(function () { try { Object.defineProperty({}, 'x', {}); return true; } catch (e) { return false; } } ())) { 19 | var orig = Object.defineProperty; 20 | Object.defineProperty = function (o, prop, desc) { 21 | "use strict"; 22 | 23 | // In IE8 try built-in implementation for defining properties on DOM prototypes. 24 | if (orig) { try { return orig(o, prop, desc); } catch (e) {} } 25 | 26 | if (o !== Object(o)) { throw new TypeError("Object.defineProperty called on non-object"); } 27 | if (Object.prototype.__defineGetter__ && ('get' in desc)) { 28 | Object.prototype.__defineGetter__.call(o, prop, desc.get); 29 | } 30 | if (Object.prototype.__defineSetter__ && ('set' in desc)) { 31 | Object.prototype.__defineSetter__.call(o, prop, desc.set); 32 | } 33 | if ('value' in desc) { 34 | o[prop] = desc.value; 35 | } 36 | return o; 37 | }; 38 | } 39 | }()); 40 | 41 | 42 | 43 | // ES5 15.2.3.14 Object.keys ( O ) 44 | // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/keys 45 | if (!Object.keys) { 46 | Object.keys = function (o) { 47 | if (o !== Object(o)) { throw new TypeError('Object.keys called on non-object'); } 48 | var ret = [], p; 49 | for (p in o) { 50 | if (Object.prototype.hasOwnProperty.call(o, p)) { 51 | ret.push(p); 52 | } 53 | } 54 | return ret; 55 | }; 56 | } 57 | 58 | //---------------------------------------------------------------------- 59 | // ES5 15.4 Array Objects 60 | //---------------------------------------------------------------------- 61 | 62 | 63 | 64 | // ES5 15.4.4.18 Array.prototype.forEach ( callbackfn [ , thisArg ] ) 65 | // From https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/forEach 66 | if (!Array.prototype.forEach) { 67 | Array.prototype.forEach = function (fun /*, thisp */) { 68 | "use strict"; 69 | 70 | if (this === void 0 || this === null) { throw new TypeError(); } 71 | 72 | var t = Object(this); 73 | var len = t.length >>> 0; 74 | if (typeof fun !== "function") { throw new TypeError(); } 75 | 76 | var thisp = arguments[1], i; 77 | for (i = 0; i < len; i++) { 78 | if (i in t) { 79 | fun.call(thisp, t[i], i, t); 80 | } 81 | } 82 | }; 83 | } 84 | 85 | 86 | // ES5 15.4.4.19 Array.prototype.map ( callbackfn [ , thisArg ] ) 87 | // From https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/Map 88 | if (!Array.prototype.map) { 89 | Array.prototype.map = function (fun /*, thisp */) { 90 | "use strict"; 91 | 92 | if (this === void 0 || this === null) { throw new TypeError(); } 93 | 94 | var t = Object(this); 95 | var len = t.length >>> 0; 96 | if (typeof fun !== "function") { throw new TypeError(); } 97 | 98 | var res = []; res.length = len; 99 | var thisp = arguments[1], i; 100 | for (i = 0; i < len; i++) { 101 | if (i in t) { 102 | res[i] = fun.call(thisp, t[i], i, t); 103 | } 104 | } 105 | 106 | return res; 107 | }; 108 | } 109 | 110 | -------------------------------------------------------------------------------- /src/deps/uuid.js: -------------------------------------------------------------------------------- 1 | // BEGIN Math.uuid.js 2 | 3 | /*! 4 | Math.uuid.js (v1.4) 5 | http://www.broofa.com 6 | mailto:robert@broofa.com 7 | 8 | Copyright (c) 2010 Robert Kieffer 9 | Dual licensed under the MIT and GPL licenses. 10 | */ 11 | 12 | /* 13 | * Generate a random uuid. 14 | * 15 | * USAGE: Math.uuid(length, radix) 16 | * length - the desired number of characters 17 | * radix - the number of allowable values for each character. 18 | * 19 | * EXAMPLES: 20 | * // No arguments - returns RFC4122, version 4 ID 21 | * >>> Math.uuid() 22 | * "92329D39-6F5C-4520-ABFC-AAB64544E172" 23 | * 24 | * // One argument - returns ID of the specified length 25 | * >>> Math.uuid(15) // 15 character ID (default base=62) 26 | * "VcydxgltxrVZSTV" 27 | * 28 | * // Two arguments - returns ID of the specified length, and radix. (Radix must be <= 62) 29 | * >>> Math.uuid(8, 2) // 8 character ID (base=2) 30 | * "01001010" 31 | * >>> Math.uuid(8, 10) // 8 character ID (base=10) 32 | * "47473046" 33 | * >>> Math.uuid(8, 16) // 8 character ID (base=16) 34 | * "098F4D35" 35 | */ 36 | (function() { 37 | // Private array of chars to use 38 | var CHARS = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split(''); 39 | 40 | Math.uuid = function (len, radix) { 41 | var chars = CHARS, uuid = []; 42 | radix = radix || chars.length; 43 | 44 | if (len) { 45 | // Compact form 46 | for (var i = 0; i < len; i++) uuid[i] = chars[0 | Math.random()*radix]; 47 | } else { 48 | // rfc4122, version 4 form 49 | var r; 50 | 51 | // rfc4122 requires these characters 52 | uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-'; 53 | uuid[14] = '4'; 54 | 55 | // Fill in random data. At i==19 set the high bits of clock sequence as 56 | // per rfc4122, sec. 4.1.5 57 | for (var i = 0; i < 36; i++) { 58 | if (!uuid[i]) { 59 | r = 0 | Math.random()*16; 60 | uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r]; 61 | } 62 | } 63 | } 64 | 65 | return uuid.join(''); 66 | }; 67 | 68 | // A more performant, but slightly bulkier, RFC4122v4 solution. We boost performance 69 | // by minimizing calls to random() 70 | Math.uuidFast = function() { 71 | var chars = CHARS, uuid = new Array(36), rnd=0, r; 72 | for (var i = 0; i < 36; i++) { 73 | if (i==8 || i==13 || i==18 || i==23) { 74 | uuid[i] = '-'; 75 | } else if (i==14) { 76 | uuid[i] = '4'; 77 | } else { 78 | if (rnd <= 0x02) rnd = 0x2000000 + (Math.random()*0x1000000)|0; 79 | r = rnd & 0xf; 80 | rnd = rnd >> 4; 81 | uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r]; 82 | } 83 | } 84 | return uuid.join(''); 85 | }; 86 | 87 | // A more compact, but less performant, RFC4122v4 solution: 88 | Math.uuidCompact = function() { 89 | return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { 90 | var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8); 91 | return v.toString(16); 92 | }).toUpperCase(); 93 | }; 94 | })(); 95 | 96 | // END Math.uuid.js 97 | 98 | /** 99 | * 100 | * MD5 (Message-Digest Algorithm) 101 | * 102 | * For original source see http://www.webtoolkit.info/ 103 | * Download: 15.02.2009 from http://www.webtoolkit.info/javascript-md5.html 104 | * 105 | * Licensed under CC-BY 2.0 License 106 | * (http://creativecommons.org/licenses/by/2.0/uk/) 107 | * 108 | **/ 109 | 110 | var Crypto = {}; 111 | (function() { 112 | Crypto.MD5 = function(string) { 113 | 114 | function RotateLeft(lValue, iShiftBits) { 115 | return (lValue<>>(32-iShiftBits)); 116 | } 117 | 118 | function AddUnsigned(lX,lY) { 119 | var lX4,lY4,lX8,lY8,lResult; 120 | lX8 = (lX & 0x80000000); 121 | lY8 = (lY & 0x80000000); 122 | lX4 = (lX & 0x40000000); 123 | lY4 = (lY & 0x40000000); 124 | lResult = (lX & 0x3FFFFFFF)+(lY & 0x3FFFFFFF); 125 | if (lX4 & lY4) { 126 | return (lResult ^ 0x80000000 ^ lX8 ^ lY8); 127 | } 128 | if (lX4 | lY4) { 129 | if (lResult & 0x40000000) { 130 | return (lResult ^ 0xC0000000 ^ lX8 ^ lY8); 131 | } else { 132 | return (lResult ^ 0x40000000 ^ lX8 ^ lY8); 133 | } 134 | } else { 135 | return (lResult ^ lX8 ^ lY8); 136 | } 137 | } 138 | 139 | function F(x,y,z) { return (x & y) | ((~x) & z); } 140 | function G(x,y,z) { return (x & z) | (y & (~z)); } 141 | function H(x,y,z) { return (x ^ y ^ z); } 142 | function I(x,y,z) { return (y ^ (x | (~z))); } 143 | 144 | function FF(a,b,c,d,x,s,ac) { 145 | a = AddUnsigned(a, AddUnsigned(AddUnsigned(F(b, c, d), x), ac)); 146 | return AddUnsigned(RotateLeft(a, s), b); 147 | }; 148 | 149 | function GG(a,b,c,d,x,s,ac) { 150 | a = AddUnsigned(a, AddUnsigned(AddUnsigned(G(b, c, d), x), ac)); 151 | return AddUnsigned(RotateLeft(a, s), b); 152 | }; 153 | 154 | function HH(a,b,c,d,x,s,ac) { 155 | a = AddUnsigned(a, AddUnsigned(AddUnsigned(H(b, c, d), x), ac)); 156 | return AddUnsigned(RotateLeft(a, s), b); 157 | }; 158 | 159 | function II(a,b,c,d,x,s,ac) { 160 | a = AddUnsigned(a, AddUnsigned(AddUnsigned(I(b, c, d), x), ac)); 161 | return AddUnsigned(RotateLeft(a, s), b); 162 | }; 163 | 164 | function ConvertToWordArray(string) { 165 | var lWordCount; 166 | var lMessageLength = string.length; 167 | var lNumberOfWords_temp1=lMessageLength + 8; 168 | var lNumberOfWords_temp2=(lNumberOfWords_temp1-(lNumberOfWords_temp1 % 64))/64; 169 | var lNumberOfWords = (lNumberOfWords_temp2+1)*16; 170 | var lWordArray=Array(lNumberOfWords-1); 171 | var lBytePosition = 0; 172 | var lByteCount = 0; 173 | while ( lByteCount < lMessageLength ) { 174 | lWordCount = (lByteCount-(lByteCount % 4))/4; 175 | lBytePosition = (lByteCount % 4)*8; 176 | lWordArray[lWordCount] = (lWordArray[lWordCount] | (string.charCodeAt(lByteCount)<>>29; 184 | return lWordArray; 185 | }; 186 | 187 | function WordToHex(lValue) { 188 | var WordToHexValue="",WordToHexValue_temp="",lByte,lCount; 189 | for (lCount = 0;lCount<=3;lCount++) { 190 | lByte = (lValue>>>(lCount*8)) & 255; 191 | WordToHexValue_temp = "0" + lByte.toString(16); 192 | WordToHexValue = WordToHexValue + WordToHexValue_temp.substr(WordToHexValue_temp.length-2,2); 193 | } 194 | return WordToHexValue; 195 | }; 196 | 197 | //** function Utf8Encode(string) removed. Aready defined in pidcrypt_utils.js 198 | 199 | var x=Array(); 200 | var k,AA,BB,CC,DD,a,b,c,d; 201 | var S11=7, S12=12, S13=17, S14=22; 202 | var S21=5, S22=9 , S23=14, S24=20; 203 | var S31=4, S32=11, S33=16, S34=23; 204 | var S41=6, S42=10, S43=15, S44=21; 205 | 206 | // string = Utf8Encode(string); #function call removed 207 | 208 | x = ConvertToWordArray(string); 209 | 210 | a = 0x67452301; b = 0xEFCDAB89; c = 0x98BADCFE; d = 0x10325476; 211 | 212 | for (k=0;k 0) return; 46 | if (options.key && Pouch.collate(key, options.key) !== 0) return; 47 | 48 | num_started++; 49 | if (options.include_docs) { 50 | //in this special case, join on _id (issue #106) 51 | if (val && typeof val === 'object' && val._id){ 52 | db.get(val._id, 53 | function(_, joined_doc){ 54 | if (joined_doc) { 55 | viewRow.doc = joined_doc; 56 | } 57 | results.push(viewRow); 58 | checkComplete(); 59 | }); 60 | return; 61 | } else { 62 | viewRow.doc = current.doc; 63 | } 64 | } 65 | results.push(viewRow); 66 | }; 67 | 68 | // ugly way to make sure references to 'emit' in map/reduce bind to the 69 | // above emit 70 | eval('fun.map = ' + fun.map.toString() + ';'); 71 | if (fun.reduce) { 72 | eval('fun.reduce = ' + fun.reduce.toString() + ';'); 73 | } 74 | 75 | //only proceed once all documents are mapped and joined 76 | var checkComplete= function(){ 77 | if (completed && results.length == num_started){ 78 | results.sort(function(a, b) { 79 | return Pouch.collate(a.key, b.key); 80 | }); 81 | if (options.descending) { 82 | results.reverse(); 83 | } 84 | if (options.reduce === false) { 85 | return options.complete(null, { 86 | rows: ('limit' in options) 87 | ? results.slice(0, options.limit) 88 | : results, 89 | total_rows: results.length 90 | }); 91 | } 92 | 93 | var groups = []; 94 | results.forEach(function(e) { 95 | var last = groups[groups.length-1] || null; 96 | if (last && Pouch.collate(last.key[0][0], e.key) === 0) { 97 | last.key.push([e.key, e.id]); 98 | last.value.push(e.value); 99 | return; 100 | } 101 | groups.push({key: [[e.key, e.id]], value: [e.value]}); 102 | }); 103 | groups.forEach(function(e) { 104 | e.value = fun.reduce(e.key, e.value) || null; 105 | e.key = e.key[0][0]; 106 | }); 107 | options.complete(null, { 108 | rows: ('limit' in options) 109 | ? groups.slice(0, options.limit) 110 | : groups, 111 | total_rows: groups.length 112 | }); 113 | } 114 | } 115 | 116 | db.changes({ 117 | conflicts: true, 118 | include_docs: true, 119 | onChange: function(doc) { 120 | if (!('deleted' in doc)) { 121 | current = {doc: doc.doc}; 122 | fun.map.call(this, doc.doc); 123 | } 124 | }, 125 | complete: function() { 126 | completed= true; 127 | checkComplete(); 128 | } 129 | }); 130 | } 131 | 132 | function httpQuery(fun, opts, callback) { 133 | 134 | // List of parameters to add to the PUT request 135 | var params = []; 136 | var body = undefined; 137 | var method = 'GET'; 138 | 139 | // If opts.reduce exists and is defined, then add it to the list 140 | // of parameters. 141 | // If reduce=false then the results are that of only the map function 142 | // not the final result of map and reduce. 143 | if (typeof opts.reduce !== 'undefined') { 144 | params.push('reduce=' + opts.reduce); 145 | } 146 | if (typeof opts.include_docs !== 'undefined') { 147 | params.push('include_docs=' + opts.include_docs); 148 | } 149 | if (typeof opts.limit !== 'undefined') { 150 | params.push('limit=' + opts.limit); 151 | } 152 | if (typeof opts.descending !== 'undefined') { 153 | params.push('descending=' + opts.descending); 154 | } 155 | if (typeof opts.startkey !== 'undefined') { 156 | params.push('startkey=' + encodeURIComponent(JSON.stringify(opts.startkey))); 157 | } 158 | if (typeof opts.endkey !== 'undefined') { 159 | params.push('endkey=' + encodeURIComponent(JSON.stringify(opts.endkey))); 160 | } 161 | if (typeof opts.key !== 'undefined') { 162 | params.push('key=' + encodeURIComponent(JSON.stringify(opts.key))); 163 | } 164 | 165 | // If keys are supplied, issue a POST request to circumvent GET query string limits 166 | // see http://wiki.apache.org/couchdb/HTTP_view_API#Querying_Options 167 | if (typeof opts.keys !== 'undefined') { 168 | method = 'POST'; 169 | body = JSON.stringify({keys:opts.keys}); 170 | } 171 | 172 | // Format the list of parameters into a valid URI query string 173 | params = params.join('&'); 174 | params = params === '' ? '' : '?' + params; 175 | 176 | // We are referencing a query defined in the design doc 177 | if (typeof fun === 'string') { 178 | var parts = fun.split('/'); 179 | db.request({ 180 | method: method, 181 | url: '_design/' + parts[0] + '/_view/' + parts[1] + params, 182 | body: body 183 | }, callback); 184 | return; 185 | } 186 | 187 | // We are using a temporary view, terrible for performance but good for testing 188 | var queryObject = JSON.parse(JSON.stringify(fun, function(key, val) { 189 | if (typeof val === 'function') { 190 | return val + ''; // implicitly `toString` it 191 | } 192 | return val; 193 | })); 194 | 195 | db.request({ 196 | method:'POST', 197 | url: '_temp_view' + params, 198 | body: queryObject 199 | }, callback); 200 | } 201 | 202 | function query(fun, opts, callback) { 203 | if (typeof opts === 'function') { 204 | callback = opts; 205 | opts = {}; 206 | } 207 | 208 | if (callback) { 209 | opts.complete = callback; 210 | } 211 | 212 | if (db.type() === 'http') { 213 | if (typeof fun === 'function'){ 214 | return httpQuery({map: fun}, opts, callback); 215 | } 216 | return httpQuery(fun, opts, callback); 217 | } 218 | 219 | if (typeof fun === 'object') { 220 | return viewQuery(fun, opts); 221 | } 222 | 223 | if (typeof fun === 'function') { 224 | return viewQuery({map: fun}, opts); 225 | } 226 | 227 | var parts = fun.split('/'); 228 | db.get('_design/' + parts[0], function(err, doc) { 229 | if (err) { 230 | if (callback) callback(err); 231 | return; 232 | } 233 | viewQuery({ 234 | map: doc.views[parts[1]].map, 235 | reduce: doc.views[parts[1]].reduce 236 | }, opts); 237 | }); 238 | } 239 | 240 | return {'query': query}; 241 | }; 242 | 243 | // Deletion is a noop since we dont store the results of the view 244 | MapReduce._delete = function() { }; 245 | 246 | Pouch.plugin('mapreduce', MapReduce); 247 | -------------------------------------------------------------------------------- /src/plugins/pouchdb.spatial.js: -------------------------------------------------------------------------------- 1 | /*global Pouch: true */ 2 | 3 | "use strict"; 4 | 5 | // If we wanted to store incremental views we can do it here by listening 6 | // to the changes feed (keeping track of our last update_seq between page loads) 7 | // and storing the result of the map function (possibly using the upcoming 8 | // extracted adapter functions) 9 | 10 | var Spatial = function(db) { 11 | 12 | var isArray = Array.isArray || function(obj) { 13 | return type(obj) === "array"; 14 | }; 15 | 16 | function viewQuery(fun, options) { 17 | if (!options.complete) { 18 | return; 19 | } 20 | 21 | var results = []; 22 | var current = null; 23 | var num_started= 0; 24 | var completed= false; 25 | 26 | // Make the key a proper one. If a value is a single point, transform it 27 | // to a range. If the first element (or the whole key) is a geometry, 28 | // calculate its bounding box. 29 | // The geometry is also returned (`null` if there is none). 30 | var normalizeKey = function(key) { 31 | var newKey = []; 32 | var geometry = null; 33 | 34 | // Whole key is one geometry 35 | if (!isArray(key) && typeof key === "object") { 36 | return { 37 | key: Spatial.calculateBbox(key), 38 | geometry: key 39 | }; 40 | } 41 | 42 | if (!isArray(key[0]) && typeof key[0] === "object") { 43 | newKey = Spatial.calculateBbox(key[0]); 44 | geometry = key[0]; 45 | key = key.slice(1); 46 | } 47 | 48 | for(var i=0; i= start_range[i] || start_range[i] === null)) 77 | // End is set 78 | || (end >= start_range[i] || start_range[i] === null))) { 79 | continue; 80 | } else { 81 | return false; 82 | } 83 | } 84 | return true; 85 | }; 86 | 87 | var emit = function(key, val) { 88 | var keyGeom = normalizeKey(key); 89 | var viewRow = { 90 | id: current.doc._id, 91 | key: keyGeom.key, 92 | value: val, 93 | geometry: keyGeom.geometry 94 | }; 95 | 96 | // If no range is given, return everything 97 | if (options.start_range !== undefined && 98 | options.end_range !== undefined) { 99 | if (!within(keyGeom.key, options.start_range, options.end_range)) { 100 | return; 101 | } 102 | } 103 | 104 | num_started++; 105 | if (options.include_docs) { 106 | //in this special case, join on _id (issue #106) 107 | if (val && typeof val === 'object' && val._id){ 108 | db.get(val._id, 109 | function(_, joined_doc){ 110 | if (joined_doc) { 111 | viewRow.doc = joined_doc; 112 | } 113 | results.push(viewRow); 114 | checkComplete(); 115 | }); 116 | return; 117 | } else { 118 | viewRow.doc = current.doc; 119 | } 120 | } 121 | results.push(viewRow); 122 | }; 123 | 124 | // ugly way to make sure references to 'emit' in map/reduce bind to the 125 | // above emit 126 | eval('fun = ' + fun.toString() + ';'); 127 | 128 | // exclude _conflicts key by default 129 | // or to use options.conflicts if it's set when called by db.query 130 | var conflicts = ('conflicts' in options ? options.conflicts : false); 131 | 132 | // only proceed once all documents are mapped and joined 133 | var checkComplete= function() { 134 | if (completed && results.length == num_started){ 135 | return options.complete(null, {rows: results}); 136 | } 137 | } 138 | 139 | db.changes({ 140 | conflicts: conflicts, 141 | include_docs: true, 142 | onChange: function(doc) { 143 | // Don't index deleted or design documents 144 | if (!('deleted' in doc) && doc.id.indexOf('_design/') !== 0) { 145 | current = {doc: doc.doc}; 146 | fun.call(this, doc.doc); 147 | } 148 | }, 149 | complete: function() { 150 | completed= true; 151 | checkComplete(); 152 | } 153 | }); 154 | } 155 | 156 | function httpQuery(location, opts, callback) { 157 | 158 | // List of parameters to add to the PUT request 159 | var params = []; 160 | 161 | // TODO vmx 2013-01-27: Support skip and limit 162 | 163 | if (typeof opts.start_range !== 'undefined') { 164 | params.push('start_range=' + encodeURIComponent(JSON.stringify( 165 | opts.start_range))); 166 | } 167 | if (typeof opts.end_range !== 'undefined') { 168 | params.push('end_range=' + encodeURIComponent(JSON.stringify( 169 | opts.end_range))); 170 | } 171 | if (typeof opts.key !== 'undefined') { 172 | params.push('key=' + encodeURIComponent(JSON.stringify(opts.key))); 173 | } 174 | 175 | // Format the list of parameters into a valid URI query string 176 | params = params.join('&'); 177 | params = params === '' ? '' : '?' + params; 178 | 179 | // We are referencing a query defined in the design doc 180 | var parts = location.split('/'); 181 | db.request({ 182 | method: 'GET', 183 | url: '_design/' + parts[0] + '/_spatial/' + parts[1] + params 184 | }, callback); 185 | } 186 | 187 | function query(fun, opts, callback) { 188 | if (typeof opts === 'function') { 189 | callback = opts; 190 | opts = {}; 191 | } 192 | 193 | if (callback) { 194 | opts.complete = callback; 195 | } 196 | 197 | if (typeof fun !== 'string') { 198 | var error = Pouch.error( Pouch.Errors.INVALID_REQUEST, 'Querying with a function is not supported for Spatial Views'); 199 | return callback ? callback(error) : undefined; 200 | } 201 | 202 | if (db.type() === 'http') { 203 | return httpQuery(fun, opts, callback); 204 | } 205 | 206 | var parts = fun.split('/'); 207 | db.get('_design/' + parts[0], function(err, doc) { 208 | if (err) { 209 | if (callback) callback(err); 210 | return; 211 | } 212 | viewQuery(doc.spatial[parts[1]], opts); 213 | }); 214 | } 215 | 216 | return {spatial: query}; 217 | }; 218 | 219 | // Store it in the Spatial object, so we can test it 220 | Spatial.calculateBbox = function (geom) { 221 | var coords = geom.coordinates; 222 | if (geom.type === 'Point') { 223 | return [[coords[0], coords[0]], [coords[1], coords[1]]]; 224 | } 225 | if (geom.type === 'GeometryCollection') { 226 | coords = geom.geometries.map(function(g) { 227 | return Spatial.calculateBbox(g); 228 | }); 229 | 230 | // Merge all bounding boxes into one big one that encloses all 231 | return coords.reduce(function (acc, bbox) { 232 | var minX = Math.min(acc[0][0], bbox[0][0]); 233 | var minY = Math.min(acc[1][0], bbox[1][0]); 234 | var maxX = Math.max(acc[0][1], bbox[0][1]); 235 | var maxY = Math.max(acc[1][1], bbox[1][1]); 236 | return [[minX, maxX], [minY, maxY]]; 237 | }); 238 | } 239 | 240 | // Flatten coords as much as possible 241 | while (Array.isArray(coords[0][0])) { 242 | coords = coords.reduce(function(a, b) { 243 | return a.concat(b); 244 | }); 245 | }; 246 | 247 | // Calculate the enclosing bounding box of all coordinates 248 | return coords.reduce(function (acc, coord) { 249 | if (acc === null) { 250 | return [[coord[0], coord[0]], [coord[1], coord[1]]]; 251 | } 252 | var minX = Math.min(acc[0][0], coord[0]); 253 | var minY = Math.min(acc[1][0], coord[1]); 254 | var maxX = Math.max(acc[0][1], coord[0]); 255 | var maxY = Math.max(acc[1][1], coord[1]); 256 | return [[minX, maxX], [minY, maxY]]; 257 | }, null); 258 | }; 259 | 260 | // Deletion is a noop since we dont store the results of the view 261 | Spatial._delete = function() { }; 262 | 263 | Pouch.plugin('spatial', Spatial); 264 | -------------------------------------------------------------------------------- /src/pouch.collate.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // a few hacks to get things in the right place for node.js 4 | if (typeof module !== 'undefined' && module.exports) { 5 | module.exports = Pouch; 6 | } 7 | 8 | var stringCollate = function(a, b) { 9 | // See: https://github.com/daleharvey/pouchdb/issues/40 10 | // This is incompatible with the CouchDB implementation, but its the 11 | // best we can do for now 12 | return (a === b) ? 0 : ((a > b) ? 1 : -1); 13 | }; 14 | 15 | var objectCollate = function(a, b) { 16 | var ak = Object.keys(a), bk = Object.keys(b); 17 | var len = Math.min(ak.length, bk.length); 18 | for (var i = 0; i < len; i++) { 19 | // First sort the keys 20 | var sort = Pouch.collate(ak[i], bk[i]); 21 | if (sort !== 0) { 22 | return sort; 23 | } 24 | // if the keys are equal sort the values 25 | sort = Pouch.collate(a[ak[i]], b[bk[i]]); 26 | if (sort !== 0) { 27 | return sort; 28 | } 29 | 30 | } 31 | return (ak.length === bk.length) ? 0 : 32 | (ak.length > bk.length) ? 1 : -1; 33 | }; 34 | 35 | var arrayCollate = function(a, b) { 36 | var len = Math.min(a.length, b.length); 37 | for (var i = 0; i < len; i++) { 38 | var sort = Pouch.collate(a[i], b[i]); 39 | if (sort !== 0) { 40 | return sort; 41 | } 42 | } 43 | return (a.length === b.length) ? 0 : 44 | (a.length > b.length) ? 1 : -1; 45 | }; 46 | 47 | // The collation is defined by erlangs ordered terms 48 | // the atoms null, true, false come first, then numbers, strings, 49 | // arrays, then objects 50 | var collationIndex = function(x) { 51 | var id = ['boolean', 'number', 'string', 'object']; 52 | if (id.indexOf(typeof x) !== -1) { 53 | if (x === null) { 54 | return 1; 55 | } 56 | return id.indexOf(typeof x) + 2; 57 | } 58 | if (Array.isArray(x)) { 59 | return 4.5; 60 | } 61 | }; 62 | 63 | Pouch.collate = function(a, b) { 64 | var ai = collationIndex(a); 65 | var bi = collationIndex(b); 66 | if ((ai - bi) !== 0) { 67 | return ai - bi; 68 | } 69 | if (a === null) { 70 | return 0; 71 | } 72 | if (typeof a === 'number') { 73 | return a - b; 74 | } 75 | if (typeof a === 'boolean') { 76 | return a < b ? -1 : 1; 77 | } 78 | if (typeof a === 'string') { 79 | return stringCollate(a, b); 80 | } 81 | if (Array.isArray(a)) { 82 | return arrayCollate(a, b); 83 | } 84 | if (typeof a === 'object') { 85 | return objectCollate(a, b); 86 | } 87 | }; -------------------------------------------------------------------------------- /src/pouch.merge.js: -------------------------------------------------------------------------------- 1 | /*globals rootToLeaf: false, extend: false */ 2 | 3 | 'use strict'; 4 | 5 | // a few hacks to get things in the right place for node.js 6 | if (typeof module !== 'undefined' && module.exports) { 7 | module.exports = Pouch; 8 | var utils = require('./pouch.utils.js'); 9 | for (var k in utils) { 10 | global[k] = utils[k]; 11 | } 12 | } 13 | 14 | // for a better overview of what this is doing, read: 15 | // https://github.com/apache/couchdb/blob/master/src/couchdb/couch_key_tree.erl 16 | // 17 | // But for a quick intro, CouchDB uses a revision tree to store a documents 18 | // history, A -> B -> C, when a document has conflicts, that is a branch in the 19 | // tree, A -> (B1 | B2 -> C), We store these as a nested array in the format 20 | // 21 | // KeyTree = [Path ... ] 22 | // Path = {pos: position_from_root, ids: Tree} 23 | // Tree = [Key, Opts, [Tree, ...]], in particular single node: [Key, []] 24 | 25 | // Turn a path as a flat array into a tree with a single branch 26 | function pathToTree(path) { 27 | var doc = path.shift(); 28 | var root = [doc.id, doc.opts, []]; 29 | var leaf = root; 30 | var nleaf; 31 | 32 | while (path.length) { 33 | doc = path.shift(); 34 | nleaf = [doc.id, doc.opts, []]; 35 | leaf[2].push(nleaf); 36 | leaf = nleaf; 37 | } 38 | return root; 39 | } 40 | 41 | // Merge two trees together 42 | // The roots of tree1 and tree2 must be the same revision 43 | function mergeTree(in_tree1, in_tree2) { 44 | var queue = [{tree1: in_tree1, tree2: in_tree2}]; 45 | var conflicts = false; 46 | while (queue.length > 0) { 47 | var item = queue.pop(); 48 | var tree1 = item.tree1; 49 | var tree2 = item.tree2; 50 | 51 | if (tree1[1].status || tree2[1].status) { 52 | tree1[1].status = (tree1[1].status === 'available' || 53 | tree2[1].status === 'available') ? 'available' : 'missing'; 54 | } 55 | 56 | for (var i = 0; i < tree2[2].length; i++) { 57 | if (!tree1[2][0]) { 58 | conflicts = 'new_leaf'; 59 | tree1[2][0] = tree2[2][i]; 60 | continue; 61 | } 62 | 63 | var merged = false; 64 | for (var j = 0; j < tree1[2].length; j++) { 65 | if (tree1[2][j][0] === tree2[2][i][0]) { 66 | queue.push({tree1: tree1[2][j], tree2: tree2[2][i]}); 67 | merged = true; 68 | } 69 | } 70 | if (!merged) { 71 | conflicts = 'new_branch'; 72 | tree1[2].push(tree2[2][i]); 73 | tree1[2].sort(); 74 | } 75 | } 76 | } 77 | return {conflicts: conflicts, tree: in_tree1}; 78 | } 79 | 80 | function doMerge(tree, path, dontExpand) { 81 | var restree = []; 82 | var conflicts = false; 83 | var merged = false; 84 | var res, branch; 85 | 86 | if (!tree.length) { 87 | return {tree: [path], conflicts: 'new_leaf'}; 88 | } 89 | 90 | tree.forEach(function(branch) { 91 | if (branch.pos === path.pos && branch.ids[0] === path.ids[0]) { 92 | // Paths start at the same position and have the same root, so they need 93 | // merged 94 | res = mergeTree(branch.ids, path.ids); 95 | restree.push({pos: branch.pos, ids: res.tree}); 96 | conflicts = conflicts || res.conflicts; 97 | merged = true; 98 | } else if (dontExpand !== true) { 99 | // The paths start at a different position, take the earliest path and 100 | // traverse up until it as at the same point from root as the path we want to 101 | // merge. If the keys match we return the longer path with the other merged 102 | // After stemming we dont want to expand the trees 103 | 104 | var t1 = branch.pos < path.pos ? branch : path; 105 | var t2 = branch.pos < path.pos ? path : branch; 106 | var diff = t2.pos - t1.pos; 107 | 108 | var candidateParents = []; 109 | 110 | var trees = []; 111 | trees.push({ids: t1.ids, diff: diff, parent: null, parentIdx: null}); 112 | while (trees.length > 0) { 113 | var item = trees.pop(); 114 | if (item.diff === 0) { 115 | if (item.ids[0] === t2.ids[0]) { 116 | candidateParents.push(item); 117 | } 118 | continue; 119 | } 120 | if (!item.ids) { 121 | continue; 122 | } 123 | /*jshint loopfunc:true */ 124 | item.ids[2].forEach(function(el, idx) { 125 | trees.push({ids: el, diff: item.diff-1, parent: item.ids, parentIdx: idx}); 126 | }); 127 | } 128 | 129 | var el = candidateParents[0]; 130 | 131 | if (!el) { 132 | restree.push(branch); 133 | } else { 134 | res = mergeTree(el.ids, t2.ids); 135 | el.parent[2][el.parentIdx] = res.tree; 136 | restree.push({pos: t1.pos, ids: t1.ids}); 137 | conflicts = conflicts || res.conflicts; 138 | merged = true; 139 | } 140 | } else { 141 | restree.push(branch); 142 | } 143 | }); 144 | 145 | // We didnt find 146 | if (!merged) { 147 | restree.push(path); 148 | } 149 | 150 | restree.sort(function(a, b) { 151 | return a.pos - b.pos; 152 | }); 153 | 154 | return { 155 | tree: restree, 156 | conflicts: conflicts || 'internal_node' 157 | }; 158 | } 159 | 160 | // To ensure we dont grow the revision tree infinitely, we stem old revisions 161 | function stem(tree, depth) { 162 | // First we break out the tree into a complete list of root to leaf paths, 163 | // we cut off the start of the path and generate a new set of flat trees 164 | var stemmedPaths = rootToLeaf(tree).map(function(path) { 165 | var stemmed = path.ids.slice(-depth); 166 | return { 167 | pos: path.pos + (path.ids.length - stemmed.length), 168 | ids: pathToTree(stemmed) 169 | }; 170 | }); 171 | // Then we remerge all those flat trees together, ensuring that we dont 172 | // connect trees that would go beyond the depth limit 173 | return stemmedPaths.reduce(function(prev, current, i, arr) { 174 | return doMerge(prev, current, true).tree; 175 | }, [stemmedPaths.shift()]); 176 | } 177 | 178 | Pouch.merge = function(tree, path, depth) { 179 | // Ugh, nicer way to not modify arguments in place? 180 | tree = extend(true, [], tree); 181 | path = extend(true, {}, path); 182 | var newTree = doMerge(tree, path); 183 | return { 184 | tree: stem(newTree.tree, depth), 185 | conflicts: newTree.conflicts 186 | }; 187 | }; 188 | 189 | // We fetch all leafs of the revision tree, and sort them based on tree length 190 | // and whether they were deleted, undeleted documents with the longest revision 191 | // tree (most edits) win 192 | // The final sort algorithm is slightly documented in a sidebar here: 193 | // http://guide.couchdb.org/draft/conflicts.html 194 | Pouch.merge.winningRev = function(metadata) { 195 | var leafs = []; 196 | Pouch.merge.traverseRevTree(metadata.rev_tree, 197 | function(isLeaf, pos, id, something, opts) { 198 | if (isLeaf) { 199 | leafs.push({pos: pos, id: id, deleted: !!opts.deleted}); 200 | } 201 | }); 202 | leafs.sort(function(a, b) { 203 | if (a.deleted !== b.deleted) { 204 | return a.deleted > b.deleted ? 1 : -1; 205 | } 206 | if (a.pos !== b.pos) { 207 | return b.pos - a.pos; 208 | } 209 | return a.id < b.id ? 1 : -1; 210 | }); 211 | 212 | return leafs[0].pos + '-' + leafs[0].id; 213 | }; 214 | 215 | // Pretty much all below can be combined into a higher order function to 216 | // traverse revisions 217 | // Callback has signature function(isLeaf, pos, id, [context]) 218 | // The return value from the callback will be passed as context to all 219 | // children of that node 220 | Pouch.merge.traverseRevTree = function(revs, callback) { 221 | var toVisit = []; 222 | 223 | revs.forEach(function(tree) { 224 | toVisit.push({pos: tree.pos, ids: tree.ids}); 225 | }); 226 | while (toVisit.length > 0) { 227 | var node = toVisit.pop(); 228 | var pos = node.pos; 229 | var tree = node.ids; 230 | var newCtx = callback(tree[2].length === 0, pos, tree[0], node.ctx, tree[1]); 231 | /*jshint loopfunc: true */ 232 | tree[2].forEach(function(branch) { 233 | toVisit.push({pos: pos+1, ids: branch, ctx: newCtx}); 234 | }); 235 | } 236 | }; 237 | 238 | Pouch.merge.collectLeaves = function(revs) { 239 | var leaves = []; 240 | Pouch.merge.traverseRevTree(revs, function(isLeaf, pos, id, acc, opts) { 241 | if (isLeaf) { 242 | leaves.unshift({rev: pos + "-" + id, pos: pos, opts: opts}); 243 | } 244 | }); 245 | leaves.sort(function(a, b) { 246 | return b.pos - a.pos; 247 | }); 248 | leaves.map(function(leaf) { delete leaf.pos; }); 249 | return leaves; 250 | }; 251 | 252 | // returns all conflicts that is leaves such that 253 | // 1. are not deleted and 254 | // 2. are different than winning revision 255 | Pouch.merge.collectConflicts = function(metadata) { 256 | var win = Pouch.merge.winningRev(metadata); 257 | var leaves = Pouch.merge.collectLeaves(metadata.rev_tree); 258 | var conflicts = []; 259 | leaves.forEach(function(leaf) { 260 | if (leaf.rev !== win && !leaf.opts.deleted) { 261 | conflicts.push(leaf.rev); 262 | } 263 | }); 264 | return conflicts; 265 | }; 266 | 267 | -------------------------------------------------------------------------------- /src/pouch.replicate.js: -------------------------------------------------------------------------------- 1 | /*globals call: false, Crypto: false*/ 2 | 3 | 'use strict'; 4 | 5 | if (typeof module !== 'undefined' && module.exports) { 6 | module.exports = Pouch; 7 | } 8 | 9 | // We create a basic promise so the caller can cancel the replication possibly 10 | // before we have actually started listening to changes etc 11 | var Promise = function() { 12 | this.cancelled = false; 13 | this.cancel = function() { 14 | this.cancelled = true; 15 | }; 16 | }; 17 | 18 | // The RequestManager ensures that only one database request is active at 19 | // at time, it ensures we dont max out simultaneous HTTP requests and makes 20 | // the replication process easier to reason about 21 | var RequestManager = function() { 22 | 23 | var queue = []; 24 | var api = {}; 25 | var processing = false; 26 | 27 | // Add a new request to the queue, if we arent currently processing anything 28 | // then process it immediately 29 | api.enqueue = function(fun, args) { 30 | queue.push({fun: fun, args: args}); 31 | if (!processing) { 32 | api.process(); 33 | } 34 | }; 35 | 36 | // Process the next request 37 | api.process = function() { 38 | if (processing || !queue.length) { 39 | return; 40 | } 41 | processing = true; 42 | var task = queue.shift(); 43 | task.fun.apply(null, task.args); 44 | }; 45 | 46 | // We need to be notified whenever a request is complete to process 47 | // the next request 48 | api.notifyRequestComplete = function() { 49 | processing = false; 50 | api.process(); 51 | }; 52 | 53 | return api; 54 | }; 55 | 56 | // TODO: check CouchDB's replication id generation, generate a unique id particular 57 | // to this replication 58 | var genReplicationId = function(src, target, opts) { 59 | var filterFun = opts.filter ? opts.filter.toString() : ''; 60 | return '_local/' + Crypto.MD5(src.id() + target.id() + filterFun); 61 | }; 62 | 63 | // A checkpoint lets us restart replications from when they were last cancelled 64 | var fetchCheckpoint = function(target, id, callback) { 65 | target.get(id, function(err, doc) { 66 | if (err && err.status === 404) { 67 | callback(null, 0); 68 | } else { 69 | callback(null, doc.last_seq); 70 | } 71 | }); 72 | }; 73 | 74 | var writeCheckpoint = function(target, id, checkpoint, callback) { 75 | var check = { 76 | _id: id, 77 | last_seq: checkpoint 78 | }; 79 | target.get(check._id, function(err, doc) { 80 | if (doc && doc._rev) { 81 | check._rev = doc._rev; 82 | } 83 | target.put(check, function(err, doc) { 84 | callback(); 85 | }); 86 | }); 87 | }; 88 | 89 | function replicate(src, target, opts, promise) { 90 | 91 | var requests = new RequestManager(); 92 | var writeQueue = []; 93 | var repId = genReplicationId(src, target, opts); 94 | var results = []; 95 | var completed = false; 96 | var pending = 0; 97 | var last_seq = 0; 98 | var continuous = opts.continuous || false; 99 | var doc_ids = opts.doc_ids; 100 | var result = { 101 | ok: true, 102 | start_time: new Date(), 103 | docs_read: 0, 104 | docs_written: 0 105 | }; 106 | 107 | function docsWritten(err, res, len) { 108 | requests.notifyRequestComplete(); 109 | if (opts.onChange) { 110 | for (var i = 0; i < len; i++) { 111 | /*jshint validthis:true */ 112 | opts.onChange.apply(this, [result]); 113 | } 114 | } 115 | pending -= len; 116 | result.docs_written += len; 117 | isCompleted(); 118 | } 119 | 120 | function writeDocs() { 121 | if (!writeQueue.length) { 122 | return requests.notifyRequestComplete(); 123 | } 124 | var len = writeQueue.length; 125 | target.bulkDocs({docs: writeQueue}, {new_edits: false}, function(err, res) { 126 | docsWritten(err, res, len); 127 | }); 128 | writeQueue = []; 129 | } 130 | 131 | function eachRev(id, rev) { 132 | src.get(id, {revs: true, rev: rev, attachments: true}, function(err, doc) { 133 | requests.notifyRequestComplete(); 134 | writeQueue.push(doc); 135 | requests.enqueue(writeDocs); 136 | }); 137 | } 138 | 139 | function onRevsDiff(err, diffs) { 140 | requests.notifyRequestComplete(); 141 | if (err) { 142 | if (continuous) { 143 | promise.cancel(); 144 | } 145 | call(opts.complete, err, null); 146 | return; 147 | } 148 | 149 | // We already have the full document stored 150 | if (Object.keys(diffs).length === 0) { 151 | pending--; 152 | isCompleted(); 153 | return; 154 | } 155 | 156 | var _enqueuer = function (rev) { 157 | requests.enqueue(eachRev, [id, rev]); 158 | }; 159 | 160 | for (var id in diffs) { 161 | diffs[id].missing.forEach(_enqueuer); 162 | } 163 | } 164 | 165 | function fetchRevsDiff(diff) { 166 | target.revsDiff(diff, onRevsDiff); 167 | } 168 | 169 | function onChange(change) { 170 | last_seq = change.seq; 171 | results.push(change); 172 | result.docs_read++; 173 | pending++; 174 | var diff = {}; 175 | diff[change.id] = change.changes.map(function(x) { return x.rev; }); 176 | requests.enqueue(fetchRevsDiff, [diff]); 177 | } 178 | 179 | function complete() { 180 | completed = true; 181 | isCompleted(); 182 | } 183 | 184 | function isCompleted() { 185 | if (completed && pending === 0) { 186 | result.end_time = Date.now(); 187 | writeCheckpoint(target, repId, last_seq, function(err, res) { 188 | call(opts.complete, err, result); 189 | }); 190 | } 191 | } 192 | 193 | fetchCheckpoint(target, repId, function(err, checkpoint) { 194 | 195 | if (err) { 196 | return call(opts.complete, err); 197 | } 198 | 199 | last_seq = checkpoint; 200 | 201 | // Was the replication cancelled by the caller before it had a chance 202 | // to start. Shouldnt we be calling complete? 203 | if (promise.cancelled) { 204 | return; 205 | } 206 | 207 | var repOpts = { 208 | limit: 25, 209 | continuous: continuous, 210 | since: last_seq, 211 | style: 'all_docs', 212 | onChange: onChange, 213 | complete: complete, 214 | doc_ids: doc_ids 215 | }; 216 | 217 | if (opts.filter) { 218 | repOpts.filter = opts.filter; 219 | } 220 | 221 | if (opts.query_params) { 222 | repOpts.query_params = opts.query_params; 223 | } 224 | 225 | var changes = src.changes(repOpts); 226 | 227 | if (opts.continuous) { 228 | promise.cancel = changes.cancel; 229 | } 230 | }); 231 | 232 | } 233 | 234 | function toPouch(db, callback) { 235 | if (typeof db === 'string') { 236 | return new Pouch(db, callback); 237 | } 238 | callback(null, db); 239 | } 240 | 241 | Pouch.replicate = function(src, target, opts, callback) { 242 | if (opts instanceof Function) { 243 | callback = opts; 244 | opts = {}; 245 | } 246 | if (opts === undefined) { 247 | opts = {}; 248 | } 249 | opts.complete = callback; 250 | var replicateRet = new Promise(); 251 | toPouch(src, function(err, src) { 252 | if (err) { 253 | return call(callback, err); 254 | } 255 | toPouch(target, function(err, target) { 256 | if (err) { 257 | return call(callback, err); 258 | } 259 | replicate(src, target, opts, replicateRet); 260 | }); 261 | }); 262 | return replicateRet; 263 | }; 264 | -------------------------------------------------------------------------------- /tests/buildstatus/_ddoc/views/by_fail/map.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | function by_fail(doc) { 4 | 5 | if (!('passed' in doc) || doc.passed === true) { 6 | return; 7 | } 8 | 9 | emit(doc.started, {}); 10 | } -------------------------------------------------------------------------------- /tests/buildstatus/_ddoc/views/by_started/map.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | function by_started(doc) { 4 | 5 | function isDate(d) { 6 | return Object.prototype.toString.call(d) === '[object Date]' && isFinite(d); 7 | } 8 | 9 | if (!('started' in doc)) return; 10 | var started = new Date(doc.started); 11 | if (!isDate(started)) return; 12 | 13 | emit(doc.started, { 14 | started: doc.started, 15 | completed: doc.completed, 16 | passed: doc.passed, 17 | git_hash: doc.git_hash, 18 | travis_job: doc.travis_job 19 | }); 20 | } -------------------------------------------------------------------------------- /tests/buildstatus/css/buildstatus.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 3 | font-size: 14px; 4 | } 5 | 6 | #results-table th { 7 | font-size: 12px; 8 | } 9 | 10 | #results-table { 11 | border: 1px solid #DDD; 12 | border-bottom: 0; 13 | } 14 | 15 | .status { 16 | text-align: center; 17 | } 18 | .status:before { 19 | content:"●"; 20 | } 21 | 22 | #results-table td, #results-table th { 23 | padding: 5px; 24 | border-bottom: 1px solid #DDD; 25 | } 26 | tr.fail, tr.stalled { 27 | font-weight: bold; 28 | } 29 | tr.fail .status, tr.stalled .status { 30 | color: red; 31 | } 32 | tr.pass .status { 33 | color: green; 34 | } 35 | tr.inprogress .status { 36 | color: yellow; 37 | } 38 | -------------------------------------------------------------------------------- /tests/buildstatus/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PouchDB Build Status 6 | 7 | 8 | 9 | 10 |

    Recent Test Runs

    11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |   23 |
    StatusStartedDurationGit HashResults DocumentTravis Run
    24 | 25 |

    Last Fail

    26 |
    27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /tests/buildstatus/js/pouchdb.buildstatus.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var results_db_name = 'test_results'; 4 | var results_db; 5 | var ui; 6 | 7 | function formatDate(date) { 8 | var diff = (((new Date()).getTime() - date.getTime()) / 1000); 9 | var day_diff = Math.floor(diff / 86400); 10 | 11 | if ( isNaN(day_diff) || day_diff < 0 || day_diff >= 31 ) 12 | return; 13 | 14 | return day_diff === 0 && ( 15 | diff < 60 && "just now" || 16 | diff < 120 && "1 minute ago" || 17 | diff < 3600 && Math.floor( diff / 60 ) + " minutes ago" || 18 | diff < 7200 && "1 hour ago" || 19 | diff < 86400 && Math.floor( diff / 3600 ) + " hours ago") || 20 | day_diff == 1 && "Yesterday" || 21 | day_diff < 7 && day_diff + " days ago" || 22 | day_diff < 31 && Math.ceil( day_diff / 7 ) + " weeks ago"; 23 | } 24 | 25 | function padLeft(num, length) { 26 | var r = String(num); 27 | while (r.length < length) { 28 | r = '0' + r; 29 | } 30 | return r; 31 | } 32 | 33 | function formatDuration(duration) { 34 | duration /= 1000; 35 | var minutes = Math.floor(duration / 60); 36 | var seconds = Math.round(duration % 60); 37 | if (minutes < 60) { 38 | return padLeft(minutes, 2) + 'min ' + padLeft(seconds, 2) + 'sec'; 39 | } 40 | return ''; 41 | } 42 | 43 | var BuildStatus = function(db) { 44 | 45 | var api = {}; 46 | var TESTS_TIMEOUT = 1000 * 60 * 60; 47 | 48 | function createResultRow(doc) { 49 | var started = new Date(doc.value.started); 50 | var completed = new Date(doc.value.completed); 51 | var resultsDocLink = '' + 53 | doc.id + ''; 54 | var travisLink = '' + doc.value.travis_job + ''; 56 | var row = document.createElement('tr'); 57 | 58 | var status; 59 | if ('passed' in doc.value) { 60 | status = doc.value.passed ? 'pass' : 'fail'; 61 | } else if ((new Date().getTime() - started.getTime()) < TESTS_TIMEOUT) { 62 | status = 'inprogress'; 63 | } else { 64 | status = 'stalled'; 65 | } 66 | 67 | var duration = status === 'inprogress' ? 'In Progress' 68 | : status === 'stalled' ? 'stalled' 69 | : formatDuration(completed - started); 70 | 71 | row.setAttribute('data-doc-id', doc.id); 72 | row.classList.add(status); 73 | row.insertAdjacentHTML('beforeend', ''); 74 | row.insertAdjacentHTML('beforeend', '' + formatDate(started) + ''); 75 | row.insertAdjacentHTML('beforeend', '' + duration + ''); 76 | row.insertAdjacentHTML('beforeend', '' + doc.value.git_hash.substr(0, 7) + ''); 77 | row.insertAdjacentHTML('beforeend', '' + resultsDocLink + ''); 78 | row.insertAdjacentHTML('beforeend', '' + travisLink + ''); 79 | 80 | return row; 81 | } 82 | 83 | api.showAllRuns = function() { 84 | db.query('buildstatus/by_started', {descending: true}, function(err, result) { 85 | var tbody = document.createElement('tbody'); 86 | tbody.setAttribute('id', 'results-table-body'); 87 | result.rows.forEach(function(doc) { 88 | tbody.appendChild(createResultRow(doc)); 89 | }); 90 | document.getElementById('results-table') 91 | .replaceChild(tbody, document.getElementById('results-table-body')); 92 | }); 93 | }; 94 | 95 | api.showLastFail = function() { 96 | var opts = {descending: true, limit: 1, include_docs: true}; 97 | db.query('buildstatus/by_fail', opts, function(err, result) { 98 | var doc = result.rows[0].doc; 99 | var failed = []; 100 | var dom = document.createElement('div'); 101 | 102 | var resultsDocLink = '' + 104 | formatDate(new Date(doc.started)) + ''; 105 | 106 | dom.insertAdjacentHTML('beforeend', resultsDocLink); 107 | 108 | for (var key in doc.runs) { 109 | if (doc.runs[key].passed === false) { 110 | doc.runs[key].key = key; 111 | failed.push(doc.runs[key]); 112 | } 113 | } 114 | failed.forEach(function(fail) { 115 | dom.insertAdjacentHTML('beforeend', '

    ' + fail.key + '

    '); 116 | if (!fail.report) { 117 | return; 118 | } 119 | fail.report.suites.forEach(function(suite) { 120 | if (suite.failures === 0) { 121 | return; 122 | } 123 | dom.insertAdjacentHTML('beforeend', '
    ' + suite.stdout + '
    '); 124 | }); 125 | }); 126 | 127 | document.getElementById('last-fail').appendChild(dom); 128 | }); 129 | }; 130 | 131 | return api; 132 | }; 133 | 134 | new Pouch(results_db_name, function(err, db) { 135 | results_db = db; 136 | ui = new BuildStatus(results_db); 137 | ui.showAllRuns(); 138 | ui.showLastFail(); 139 | }); -------------------------------------------------------------------------------- /tests/flashcards/flashcards.js: -------------------------------------------------------------------------------- 1 | 2 | var CORS_PROXY_URL = "http://localhost:9292/"; 3 | 4 | var POUCH_DB_NAME = "idb://flashcards_tests"; 5 | // my local dev instance: 6 | //var COUCH_DB_PATH = "127.0.0.1:5984/test_replic"; 7 | // Pouch CI test instance: 8 | //var COUCH_DB_PATH = "127.0.0.1:2020/perf-results" 9 | 10 | // db with documents to load into Pouch before running tests 11 | var COUCH_DATASET = "pouchdb.iriscouch.com:80/perf-flashcards_tests"; 12 | 13 | // local and remote results db: results are persisted locally and then replicated to remote Couch 14 | var POUCH_RESULTS_DB_NAME = "idb://flashcards_tests_results"; 15 | var COUCH_RESULTS_DB_PATH = "127.0.0.1:5984/flashcards_test_results"; 16 | 17 | var replic = function() { 18 | Pouch.destroy("idb://replic_test", function() { 19 | Pouch("idb://replic_test", function(err, db) { 20 | if (err) { 21 | console.error(err); 22 | } 23 | db.replicate.from("http://127.0.0.1:2020/perf-flashcards_tests/", function(err, result) { 24 | if (err) { 25 | console.error(err); 26 | } 27 | db.info(function(err, info) { 28 | if (err) { 29 | console.error(err); 30 | } 31 | }); 32 | }); 33 | }); 34 | }); 35 | } 36 | 37 | var replot = function() { 38 | // replot 39 | var docId = $(this).attr("class").split(" ")[1].substr(2, 50); // FIXME: !!that 50 40 | Pouch(POUCH_RESULTS_DB_NAME, function(err, db) { 41 | db.get(docId, function(err, doc) { 42 | var sample = doc.sample; 43 | var graphData = $.map(sample, function(val, idx) { 44 | return {x: idx, y: val}; 45 | }); 46 | 47 | var graph = new Rickshaw.Graph({ 48 | element: document.querySelector("#chart"), 49 | renderer: "bar", 50 | series: [{ 51 | data: graphData, 52 | color: "steelblue" 53 | }] 54 | }); 55 | graph.render(); 56 | var hoverDetail = new Rickshaw.Graph.HoverDetail( { 57 | graph: graph 58 | } ); 59 | 60 | // var xAxis = new Rickshaw.Graph.Axis.Time( { 61 | // graph: graph, 62 | // ticksTreatment: "glow" 63 | // } ); 64 | 65 | var yAxis = new Rickshaw.Graph.Axis.Y({ 66 | graph: graph, 67 | timeUnit: (new Rickshaw.Fixtures.Time()).unit("second"), 68 | ticksTreatment: "glow" 69 | }); 70 | yAxis.render(); 71 | }); 72 | }); 73 | }; 74 | 75 | var logMessage = function(message) { 76 | $("#messages>ul").append("
  • " + message + "
  • ") 77 | } 78 | 79 | var test = function() { 80 | // Wipe out existing db and create a fresh one from Couch first. 81 | 82 | Pouch.destroy(POUCH_DB_NAME, function() { 83 | logMessage("local db reset"); 84 | Pouch(POUCH_DB_NAME, function(err, db) { 85 | db.replicate.from(CORS_PROXY_URL + COUCH_DATASET, function(err, result) { 86 | 87 | if (err) { 88 | console.error(err); 89 | } 90 | 91 | logMessage("replicated dataset from Couch"); 92 | logMessage("running the test suite... this can take a while"); 93 | db.info(function(err, info) { 94 | console.info(info); 95 | }); 96 | 97 | var doc = {"side1": "oproerpolitie", "side2": "riot police"}; 98 | var suite = new Benchmark.Suite("pouchTestSuite", { 99 | async: false, 100 | minSamples: 5, 101 | maxTime: 60 102 | }); 103 | suite.add("Pouchdb#post", function() { 104 | db.post(doc); 105 | }).on("complete", function() { 106 | var bench = this; 107 | var stats = bench[0].stats; 108 | 109 | // save results 110 | Pouch(POUCH_RESULTS_DB_NAME, function(err, db) { 111 | // TODO: Record browser info here. 112 | var testId = new Date().getTime(); 113 | var doc = { 114 | "_id": String(testId), 115 | "sample": stats.sample, 116 | "mean": stats.mean, 117 | "deviation": stats.deviation 118 | }; 119 | db.put(doc, function(err, resp) { 120 | db.replicate.to(CORS_PROXY_URL+COUCH_RESULTS_DB_PATH, function(err, results) { 121 | // Update body tag for CI 122 | document.body.attributes["data-results-id"] = testId; 123 | document.body.attributes["class"] = "complete"; 124 | 125 | logMessage("replicated test result data"); 126 | $("#pastRunsDiv").show(); 127 | db.replicate.from(CORS_PROXY_URL+COUCH_RESULTS_DB_PATH, function(err, results) { 128 | db.allDocs(function(err, resp) { 129 | $.each(resp.rows, function(idx, val) { 130 | var $li = $("
    "); 131 | $("#pastRuns").append($li); 132 | $li.click(replot); 133 | }); 134 | }); 135 | }); 136 | }); 137 | }); 138 | }); 139 | }).run(); 140 | }); 141 | }) 142 | }); 143 | 144 | } 145 | -------------------------------------------------------------------------------- /tests/flashcards/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | pouchdb benchmark 6 | 7 | 8 | 9 | 14 | 15 | 16 |

    Pouchdb flashcards benchmark

    17 | 18 |
    19 |
      20 |
    21 |
    22 | 23 |
    24 |
    25 | 26 | 31 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /tests/perf.attachments.js: -------------------------------------------------------------------------------- 1 | ["idb-1"].map(function(adapter) { 2 | 3 | module('attachment performance: ' + adapter, { 4 | setup : function () { 5 | this.name = generateAdapterUrl(adapter) 6 | } 7 | }); 8 | 9 | asyncTest("Test large attachments", function() { 10 | 11 | var fiveMB=""; 12 | for (i=0;i<5000000;i++){ //not sure if this is exactly 5MB but it's big enough 13 | fiveMB+="a"; 14 | } 15 | 16 | var fiveMB2=""; 17 | for (i=0;i<5000000;i++){ 18 | fiveMB2+="b"; 19 | } 20 | 21 | initTestDB(this.name, function(err, db) { 22 | 23 | var queryStartTime1=new Date().getTime(); 24 | function map(doc){ 25 | {emit(null, doc);} 26 | } 27 | 28 | db.query({map: map},{reduce: false}, function(err, response) { 29 | var duration1 = new Date().getTime() - queryStartTime1; 30 | console.log("query 1 took " + duration1 + " ms"); 31 | db.put({ _id: 'mydoc' }, function(err, resp) { 32 | db.putAttachment('mydoc/mytext', resp.rev, fiveMB, 'text/plain', function(err, res) { 33 | db.put({ _id: 'mydoc2' }, function(err, resp) { 34 | db.putAttachment('mydoc/mytext2', resp.rev, fiveMB2, 'text/plain', function(err, res) { 35 | var queryStartTime2 = new Date().getTime(); 36 | function map(doc) { emit(null, doc); } 37 | db.query({map: map},{reduce: false}, function(err, response) { 38 | var duration2 = new Date().getTime()-queryStartTime2; 39 | console.log("query 2 took "+duration2+" ms"); 40 | ok(duration2<=duration1*10, 'Query finished within order of magnitude'); 41 | start(); 42 | }); 43 | }); 44 | }); 45 | }); 46 | }); 47 | }); 48 | }); 49 | }); 50 | 51 | }); 52 | -------------------------------------------------------------------------------- /tests/postTest.js: -------------------------------------------------------------------------------- 1 | /*globals openTestDB: false */ 2 | 3 | "use strict"; 4 | 5 | module("misc", { 6 | setup : function () { 7 | var dbname = location.search.match(/[?&]dbname=([^&]+)/); 8 | this.name = dbname && decodeURIComponent(dbname[1]); 9 | } 10 | }); 11 | 12 | asyncTest("Add a doc", 2, function() { 13 | openTestDB(this.name, function(err, db) { 14 | ok(!err, 'opened the pouch'); 15 | db.post({test:"somestuff"}, function (err, info) { 16 | ok(!err, 'saved a doc with post'); 17 | start(); 18 | }); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /tests/qunit/junitlogger.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var count = 0, suiteCount = 0, currentSuite, currentTest, suites = [], assertCount, start, results = {failed:0, passed:0, total:0, time:0}; 3 | 4 | QUnit.jUnitReport = function(data) { 5 | // Gets called when a report is generated 6 | }; 7 | 8 | QUnit.moduleStart(function(data) { 9 | currentSuite = { 10 | name: data.name, 11 | tests: [], 12 | failures: 0, 13 | time: 0, 14 | stdout : '', 15 | stderr : '' 16 | }; 17 | 18 | suites.push(currentSuite); 19 | }); 20 | 21 | QUnit.moduleDone(function(data) { 22 | }); 23 | 24 | QUnit.testStart(function(data) { 25 | if(!start){ start = new Date(); } 26 | 27 | assertCount = 0; 28 | 29 | currentTest = { 30 | name: data.name, 31 | failures: [], 32 | start: new Date() 33 | }; 34 | 35 | // Setup default suite if no module was specified 36 | if (!currentSuite) { 37 | currentSuite = { 38 | name: "default", 39 | tests: [], 40 | failures: 0, 41 | time: 0, 42 | stdout : '', 43 | stderr : '' 44 | }; 45 | 46 | suites.push(currentSuite); 47 | } 48 | 49 | currentSuite.tests.push(currentTest); 50 | }); 51 | 52 | QUnit.testDone(function(data) { 53 | currentTest.failed = data.failed; 54 | currentTest.total = data.total; 55 | currentSuite.failures += data.failed; 56 | 57 | results.failed += data.failed; 58 | results.passed += data.passed; 59 | results.total += data.total; 60 | }); 61 | 62 | QUnit.log(function(data) { 63 | assertCount++; 64 | 65 | if (!data.result) { 66 | currentTest.failures.push(data.message); 67 | 68 | // Add log message of failure to make it easier to find in jenkins UI 69 | currentSuite.stdout += '[' + currentSuite.name + ', ' + currentTest.name + ', ' + assertCount + '] ' + data.message + '\n'; 70 | } 71 | }); 72 | 73 | QUnit.done(function(data) { 74 | function ISODateString(d) { 75 | function pad(n) { 76 | return n < 10 ? '0' + n : n; 77 | } 78 | 79 | return d.getUTCFullYear() + '-' + 80 | pad(d.getUTCMonth() + 1)+'-' + 81 | pad(d.getUTCDate()) + 'T' + 82 | pad(d.getUTCHours()) + ':' + 83 | pad(d.getUTCMinutes()) + ':' + 84 | pad(d.getUTCSeconds()) + 'Z'; 85 | } 86 | 87 | // Generate XML report 88 | var i, ti, fi, test, suite, 89 | xmlWriter = new XmlWriter({ 90 | linebreak_at : "testsuites,testsuite,testcase,failure,system-out,system-err" 91 | }), 92 | now = new Date(); 93 | 94 | xmlWriter.start('testsuites'); 95 | 96 | for (i = 0; i < suites.length; i++) { 97 | suite = suites[i]; 98 | 99 | // Calculate time 100 | for (ti = 0; ti < suite.tests.length; ti++) { 101 | test = suite.tests[ti]; 102 | 103 | test.time = (now.getTime() - test.start.getTime()) / 1000; 104 | suite.time += test.time; 105 | } 106 | 107 | xmlWriter.start('testsuite', { 108 | id: "" + i, 109 | name: suite.name, 110 | errors: "0", 111 | failures: suite.failures, 112 | hostname: "localhost", 113 | tests: suite.tests.length, 114 | time: Math.round(suite.time * 1000) / 1000, 115 | timestamp: ISODateString(now) 116 | }); 117 | 118 | for (ti = 0; ti < suite.tests.length; ti++) { 119 | test = suite.tests[ti]; 120 | 121 | xmlWriter.start('testcase', { 122 | name: test.name, 123 | total: test.total, 124 | failed: test.failed, 125 | time: Math.round(test.time * 1000) / 1000 126 | }); 127 | 128 | for (fi = 0; fi < test.failures.length; fi++) { 129 | xmlWriter.start('failure', {type: "AssertionFailedError", message: test.failures[fi]}, true); 130 | } 131 | 132 | xmlWriter.end('testcase'); 133 | } 134 | 135 | if (suite.stdout) { 136 | xmlWriter.start('system-out'); 137 | xmlWriter.cdata('\n' + suite.stdout); 138 | xmlWriter.end('system-out'); 139 | } 140 | 141 | if (suite.stderr) { 142 | xmlWriter.start('system-err'); 143 | xmlWriter.cdata('\n' + suite.stderr); 144 | xmlWriter.end('system-err'); 145 | } 146 | 147 | xmlWriter.end('testsuite'); 148 | } 149 | 150 | xmlWriter.end('testsuites'); 151 | 152 | results.time = new Date() - start; 153 | 154 | QUnit.jUnitReport({ 155 | suites : suites, 156 | results:results, 157 | xml: xmlWriter.getString() 158 | }); 159 | }); 160 | 161 | function XmlWriter(settings) { 162 | function addLineBreak(name) { 163 | if (lineBreakAt[name] && data[data.length - 1] !== '\n') { 164 | data.push('\n'); 165 | } 166 | } 167 | 168 | function makeMap(items, delim, map) { 169 | var i; 170 | 171 | items = items || []; 172 | 173 | if (typeof(items) === "string") { 174 | items = items.split(','); 175 | } 176 | 177 | map = map || {}; 178 | 179 | i = items.length; 180 | while (i--) { 181 | map[items[i]] = {}; 182 | } 183 | 184 | return map; 185 | } 186 | 187 | function encode(text) { 188 | var baseEntities = { 189 | '"' : '"', 190 | "'" : ''', 191 | '<' : '<', 192 | '>' : '>', 193 | '&' : '&' 194 | }; 195 | 196 | return ('' + text).replace(/[<>&\"\']/g, function(chr) { 197 | return baseEntities[chr] || chr; 198 | }); 199 | } 200 | 201 | var data = [], stack = [], lineBreakAt; 202 | 203 | settings = settings || {}; 204 | lineBreakAt = makeMap(settings.linebreak_at || 'mytag'); 205 | 206 | this.start = function(name, attrs, empty) { 207 | if (!empty) { 208 | stack.push(name); 209 | } 210 | 211 | data.push('<', name); 212 | 213 | for (var aname in attrs) { 214 | data.push(" " + encode(aname), '="', encode(attrs[aname]), '"'); 215 | } 216 | 217 | data.push(empty ? ' />' : '>'); 218 | addLineBreak(name); 219 | }; 220 | 221 | this.end = function(name) { 222 | stack.pop(); 223 | addLineBreak(name); 224 | data.push(''); 225 | addLineBreak(name); 226 | }; 227 | 228 | this.text = function(text) { 229 | data.push(encode(text)); 230 | }; 231 | 232 | this.cdata = function(text) { 233 | data.push(''); 234 | }; 235 | 236 | this.comment = function(text) { 237 | data.push(''); 238 | }; 239 | 240 | this.pi = function(name, text) { 241 | if (text) { 242 | data.push('\n'); 243 | } else { 244 | data.push('\n'); 245 | } 246 | }; 247 | 248 | this.doctype = function(text) { 249 | data.push('\n'); 250 | }; 251 | 252 | this.getString = function() { 253 | for (var i = stack.length - 1; i >= 0; i--) { 254 | this.end(stack[i]); 255 | } 256 | 257 | stack = []; 258 | 259 | return data.join('').replace(/\n$/, ''); 260 | }; 261 | 262 | this.reset = function() { 263 | data = []; 264 | stack = []; 265 | }; 266 | 267 | this.pi(settings.xmldecl || 'xml version="1.0" encoding="UTF-8"'); 268 | } 269 | })(); -------------------------------------------------------------------------------- /tests/qunit/qunit.css: -------------------------------------------------------------------------------- 1 | /** 2 | * QUnit v1.9.0pre - A JavaScript Unit Testing Framework 3 | * 4 | * http://docs.jquery.com/QUnit 5 | * 6 | * Copyright (c) 2012 John Resig, Jörn Zaefferer 7 | * Dual licensed under the MIT (MIT-LICENSE.txt) 8 | * or GPL (GPL-LICENSE.txt) licenses. 9 | */ 10 | 11 | /** Font Family and Sizes */ 12 | 13 | #qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult { 14 | font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif; 15 | } 16 | 17 | #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; } 18 | #qunit-tests { font-size: smaller; } 19 | 20 | 21 | /** Resets */ 22 | 23 | #qunit-tests, #qunit-tests ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult { 24 | margin: 0; 25 | padding: 0; 26 | } 27 | 28 | 29 | /** Header */ 30 | 31 | #qunit-header { 32 | padding: 0.5em 0 0.5em 1em; 33 | 34 | color: #8699a4; 35 | background-color: #0d3349; 36 | 37 | font-size: 1.5em; 38 | line-height: 1em; 39 | font-weight: normal; 40 | 41 | border-radius: 15px 15px 0 0; 42 | -moz-border-radius: 15px 15px 0 0; 43 | -webkit-border-top-right-radius: 15px; 44 | -webkit-border-top-left-radius: 15px; 45 | } 46 | 47 | #qunit-header a { 48 | text-decoration: none; 49 | color: #c2ccd1; 50 | } 51 | 52 | #qunit-header a:hover, 53 | #qunit-header a:focus { 54 | color: #fff; 55 | } 56 | 57 | #qunit-header label { 58 | display: inline-block; 59 | padding-left: 0.5em; 60 | } 61 | 62 | #qunit-banner { 63 | height: 5px; 64 | } 65 | 66 | #qunit-testrunner-toolbar { 67 | padding: 0.5em 0 0.5em 2em; 68 | color: #5E740B; 69 | background-color: #eee; 70 | } 71 | 72 | #qunit-userAgent { 73 | padding: 0.5em 0 0.5em 2.5em; 74 | background-color: #2b81af; 75 | color: #fff; 76 | text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; 77 | } 78 | 79 | 80 | /** Tests: Pass/Fail */ 81 | 82 | #qunit-tests { 83 | list-style-position: inside; 84 | } 85 | 86 | #qunit-tests li { 87 | padding: 0.4em 0.5em 0.4em 2.5em; 88 | border-bottom: 1px solid #fff; 89 | list-style-position: inside; 90 | } 91 | 92 | #qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running { 93 | display: none; 94 | } 95 | 96 | #qunit-tests li strong { 97 | cursor: pointer; 98 | } 99 | 100 | #qunit-tests li a { 101 | padding: 0.5em; 102 | color: #c2ccd1; 103 | text-decoration: none; 104 | } 105 | #qunit-tests li a:hover, 106 | #qunit-tests li a:focus { 107 | color: #000; 108 | } 109 | 110 | #qunit-tests ol { 111 | margin-top: 0.5em; 112 | padding: 0.5em; 113 | 114 | background-color: #fff; 115 | 116 | border-radius: 15px; 117 | -moz-border-radius: 15px; 118 | -webkit-border-radius: 15px; 119 | 120 | box-shadow: inset 0px 2px 13px #999; 121 | -moz-box-shadow: inset 0px 2px 13px #999; 122 | -webkit-box-shadow: inset 0px 2px 13px #999; 123 | } 124 | 125 | #qunit-tests table { 126 | border-collapse: collapse; 127 | margin-top: .2em; 128 | } 129 | 130 | #qunit-tests th { 131 | text-align: right; 132 | vertical-align: top; 133 | padding: 0 .5em 0 0; 134 | } 135 | 136 | #qunit-tests td { 137 | vertical-align: top; 138 | } 139 | 140 | #qunit-tests pre { 141 | margin: 0; 142 | white-space: pre-wrap; 143 | word-wrap: break-word; 144 | } 145 | 146 | #qunit-tests del { 147 | background-color: #e0f2be; 148 | color: #374e0c; 149 | text-decoration: none; 150 | } 151 | 152 | #qunit-tests ins { 153 | background-color: #ffcaca; 154 | color: #500; 155 | text-decoration: none; 156 | } 157 | 158 | /*** Test Counts */ 159 | 160 | #qunit-tests b.counts { color: black; } 161 | #qunit-tests b.passed { color: #5E740B; } 162 | #qunit-tests b.failed { color: #710909; } 163 | 164 | #qunit-tests li li { 165 | margin: 0.5em; 166 | padding: 0.4em 0.5em 0.4em 0.5em; 167 | background-color: #fff; 168 | border-bottom: none; 169 | list-style-position: inside; 170 | } 171 | 172 | /*** Passing Styles */ 173 | 174 | #qunit-tests li li.pass { 175 | color: #5E740B; 176 | background-color: #fff; 177 | border-left: 26px solid #C6E746; 178 | } 179 | 180 | #qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; } 181 | #qunit-tests .pass .test-name { color: #366097; } 182 | 183 | #qunit-tests .pass .test-actual, 184 | #qunit-tests .pass .test-expected { color: #999999; } 185 | 186 | #qunit-banner.qunit-pass { background-color: #C6E746; } 187 | 188 | /*** Failing Styles */ 189 | 190 | #qunit-tests li li.fail { 191 | color: #710909; 192 | background-color: #fff; 193 | border-left: 26px solid #EE5757; 194 | white-space: pre; 195 | } 196 | 197 | #qunit-tests > li:last-child { 198 | border-radius: 0 0 15px 15px; 199 | -moz-border-radius: 0 0 15px 15px; 200 | -webkit-border-bottom-right-radius: 15px; 201 | -webkit-border-bottom-left-radius: 15px; 202 | } 203 | 204 | #qunit-tests .fail { color: #000000; background-color: #EE5757; } 205 | #qunit-tests .fail .test-name, 206 | #qunit-tests .fail .module-name { color: #000000; } 207 | 208 | #qunit-tests .fail .test-actual { color: #EE5757; } 209 | #qunit-tests .fail .test-expected { color: green; } 210 | 211 | #qunit-banner.qunit-fail { background-color: #EE5757; } 212 | 213 | 214 | /** Result */ 215 | 216 | #qunit-testresult { 217 | padding: 0.5em 0.5em 0.5em 2.5em; 218 | 219 | color: #2b81af; 220 | background-color: #D2E0E6; 221 | 222 | border-bottom: 1px solid white; 223 | } 224 | #qunit-testresult .module-name { 225 | font-weight: bold; 226 | } 227 | 228 | /** Fixture */ 229 | 230 | #qunit-fixture { 231 | position: absolute; 232 | top: -10000px; 233 | left: -10000px; 234 | width: 1000px; 235 | height: 1000px; 236 | } 237 | -------------------------------------------------------------------------------- /tests/test.auth_replication.js: -------------------------------------------------------------------------------- 1 | /*globals initTestDB: false, emit: true, generateAdapterUrl: false */ 2 | /*globals PERSIST_DATABASES: false, initDBPair: false, utils: true */ 3 | /*globals ajax: true, LevelPouch: true, makeDocs: false */ 4 | 5 | "use strict"; 6 | 7 | var remote = { 8 | host: 'localhost:2020' 9 | }; 10 | var local = 'test_suite_db'; 11 | var qunit = module; 12 | 13 | if (typeof module !== undefined && module.exports) { 14 | Pouch = require('../src/pouch.js'); 15 | LevelPouch = require('../src/adapters/pouch.leveldb.js'); 16 | utils = require('./test.utils.js'); 17 | ajax = Pouch.utils.ajax; 18 | 19 | for (var k in utils) { 20 | global[k] = global[k] || utils[k]; 21 | } 22 | qunit = QUnit.module; 23 | } 24 | 25 | qunit('auth_replication', { 26 | setup: function () { 27 | this.name = local; 28 | this.remote = 'http://' + remote.host + '/test_suite_db/'; 29 | }, 30 | teardown: function() { 31 | if (!PERSIST_DATABASES) { 32 | Pouch.destroy(this.name); 33 | Pouch.destroy(this.remote); 34 | } 35 | } 36 | }); 37 | 38 | function login(username, password, callback) { 39 | ajax({ 40 | type: 'POST', 41 | url: 'http://' + remote.host + '/_session', 42 | data: {name: username, password: password}, 43 | beforeSend: function(xhr) { 44 | xhr.setRequestHeader('Accept', 'application/json'); 45 | }, 46 | success: function () { 47 | callback(); 48 | }, 49 | error: function (err) { 50 | callback(err); 51 | } 52 | }); 53 | } 54 | 55 | function logout(callback) { 56 | ajax({ 57 | type: 'DELETE', 58 | url: 'http://' + remote.host + '/_session', 59 | success: function () { 60 | callback(); 61 | }, 62 | error: function (err) { 63 | callback(err); 64 | } 65 | }); 66 | } 67 | 68 | function createAdminUser(callback) { 69 | // create admin user 70 | var adminuser = { 71 | _id: 'org.couchdb.user:adminuser', 72 | name: 'adminuser', 73 | type: 'user', 74 | password: 'password', 75 | roles: [] 76 | }; 77 | 78 | ajax({ 79 | url: 'http://' + remote.host + '/_config/admins/adminuser', 80 | type: 'PUT', 81 | data: JSON.stringify(adminuser.password), 82 | contentType: 'application/json', 83 | success: function () { 84 | setTimeout(function() { 85 | login('adminuser', 'password', function (err) { 86 | if (err) { 87 | return callback(err); 88 | } 89 | ajax({ 90 | url: 'http://' + remote.host + '/_users/' + 91 | 'org.couchdb.user%3Aadminuser', 92 | type: 'PUT', 93 | data: JSON.stringify(adminuser), 94 | contentType: 'application/json', 95 | dataType: 'json', 96 | success: function (data) { 97 | logout(function (err) { 98 | if (err) { 99 | return callback(err); 100 | } 101 | callback(null, adminuser); 102 | }); 103 | }, 104 | error: function (err) { 105 | callback(null, adminuser); 106 | } 107 | }); 108 | }); 109 | }, 500); 110 | }, 111 | error: function (err) { 112 | callback(err); 113 | } 114 | }); 115 | } 116 | 117 | function deleteAdminUser(adminuser, callback) { 118 | ajax({ 119 | type: 'DELETE', 120 | beforeSend: function (xhr) { 121 | var token = btoa('adminuser:password'); 122 | xhr.setRequestHeader("Authorization", "Basic " + token); 123 | }, 124 | url: 'http://' + remote.host + '/_config/admins/adminuser', 125 | contentType: 'application/json', 126 | success: function () { 127 | var adminUrl = 'http://' + remote.host + '/_users/' + 128 | 'org.couchdb.user%3Aadminuser'; 129 | ajax({ 130 | type: 'GET', 131 | url: adminUrl, 132 | dataType: 'json', 133 | success: function(doc) { 134 | ajax({ 135 | type: 'DELETE', 136 | url: 'http://' + remote.host + '/_users/' + 137 | 'org.couchdb.user%3Aadminuser?rev=' + doc._rev, 138 | contentType: 'application/json', 139 | success: function () { 140 | callback(); 141 | }, 142 | error: function (err) { 143 | callback(); 144 | } 145 | }); 146 | }, 147 | error: function() { 148 | callback(); 149 | } 150 | }); 151 | }, 152 | error: function (err) { 153 | callback(err); 154 | } 155 | }); 156 | } 157 | 158 | asyncTest("Replicate from DB as non-admin user", function() { 159 | // SEE: https://github.com/apache/couchdb/blob/master/share/www/script/couch_test_runner.js 160 | // - create new DB 161 | // - push docs to new DB 162 | // - add new admin user 163 | // - login as new admin user 164 | // - add new user (non admin) 165 | // - login as new user 166 | // - replicate from new DB 167 | // - login as admin user 168 | // - delete users and return to admin party 169 | // - delete original DB 170 | 171 | var self = this; 172 | 173 | var docs = [ 174 | {_id: 'one', count: 1}, 175 | {_id: 'two', count: 2} 176 | ]; 177 | 178 | function cleanup() { 179 | deleteAdminUser(self.adminuser, function (err) { 180 | if (err) { 181 | console.error(err); 182 | } 183 | logout(function (err) { 184 | if (err) { 185 | console.error(err); 186 | } 187 | start(); 188 | }); 189 | }); 190 | } 191 | 192 | initDBPair(self.name, self.remote, function(db, remote) { 193 | 194 | // add user 195 | createAdminUser(function (err, adminuser) { 196 | if (err) { 197 | ok(false, 'unable to create admin user'); 198 | console.error(err); 199 | return cleanup(); 200 | } 201 | 202 | self.adminuser = adminuser; 203 | 204 | login('adminuser', 'password', function (err) { 205 | if (err) { 206 | console.error(err); 207 | } 208 | remote.bulkDocs({docs: docs}, {}, function(err, results) { 209 | Pouch.replicate(self.remote, self.name, {}, function(err, result) { 210 | db.allDocs(function(err, result) { 211 | ok(result.rows.length === docs.length, 'correct # docs exist'); 212 | cleanup(); 213 | }); 214 | }); 215 | }); 216 | }); 217 | }); 218 | }); 219 | 220 | }); 221 | -------------------------------------------------------------------------------- /tests/test.basics.js: -------------------------------------------------------------------------------- 1 | /*globals initTestDB: false, emit: true, generateAdapterUrl: false */ 2 | /*globals PERSIST_DATABASES: false, initDBPair: false, utils: true */ 3 | /*globals ajax: true, LevelPouch: true */ 4 | /*globals cleanupTestDatabases: false */ 5 | 6 | "use strict"; 7 | 8 | var adapters = ['http-1', 'local-1']; 9 | var qunit = module; 10 | var LevelPouch; 11 | 12 | if (typeof module !== undefined && module.exports) { 13 | Pouch = require('../src/pouch.js'); 14 | LevelPouch = require('../src/adapters/pouch.leveldb.js'); 15 | utils = require('./test.utils.js'); 16 | 17 | for (var k in utils) { 18 | global[k] = global[k] || utils[k]; 19 | } 20 | qunit = QUnit.module; 21 | } 22 | 23 | adapters.map(function(adapter) { 24 | 25 | qunit("basics: " + adapter, { 26 | setup: function() { 27 | this.name = generateAdapterUrl(adapter); 28 | Pouch.enableAllDbs = true; 29 | }, 30 | teardown: cleanupTestDatabases 31 | }); 32 | 33 | asyncTest("Create a pouch", 1, function() { 34 | initTestDB(this.name, function(err, db) { 35 | ok(!err, 'created a pouch'); 36 | start(); 37 | }); 38 | }); 39 | 40 | asyncTest("Remove a pouch", 1, function() { 41 | var name = this.name; 42 | initTestDB(name, function(err, db) { 43 | Pouch.destroy(name, function(err, db) { 44 | ok(!err); 45 | start(); 46 | }); 47 | }); 48 | }); 49 | 50 | asyncTest("Add a doc", 2, function() { 51 | initTestDB(this.name, function(err, db) { 52 | ok(!err, 'opened the pouch'); 53 | db.post({test:"somestuff"}, function (err, info) { 54 | ok(!err, 'saved a doc with post'); 55 | start(); 56 | }); 57 | }); 58 | }); 59 | 60 | asyncTest("Modify a doc", 3, function() { 61 | initTestDB(this.name, function(err, db) { 62 | ok(!err, 'opened the pouch'); 63 | db.post({test: "somestuff"}, function (err, info) { 64 | ok(!err, 'saved a doc with post'); 65 | db.put({_id: info.id, _rev: info.rev, another: 'test'}, function(err, info2) { 66 | ok(!err && info2.rev !== info._rev, 'updated a doc with put'); 67 | start(); 68 | }); 69 | }); 70 | }); 71 | }); 72 | 73 | asyncTest("Modify a doc with incorrect rev", 3, function() { 74 | initTestDB(this.name, function(err, db) { 75 | ok(!err, 'opened the pouch'); 76 | db.post({test: "somestuff"}, function (err, info) { 77 | ok(!err, 'saved a doc with post'); 78 | var nDoc = {_id: info.id, _rev: info.rev + 'broken', another: 'test'}; 79 | db.put(nDoc, function(err, info2) { 80 | ok(err, 'put was denied'); 81 | start(); 82 | }); 83 | }); 84 | }); 85 | }); 86 | 87 | asyncTest("Add a doc with leading underscore in id", function() { 88 | initTestDB(this.name, function(err, db) { 89 | db.post({_id: '_testing', value: 42}, function(err, info) { 90 | ok(err); 91 | start(); 92 | }); 93 | }); 94 | }); 95 | 96 | asyncTest("Remove doc", 1, function() { 97 | initTestDB(this.name, function(err, db) { 98 | db.post({test:"somestuff"}, function(err, info) { 99 | db.remove({test:"somestuff", _id:info.id, _rev:info.rev}, function(doc) { 100 | db.get(info.id, function(err) { 101 | ok(err.error); 102 | start(); 103 | }); 104 | }); 105 | }); 106 | }); 107 | }); 108 | 109 | asyncTest("Doc removal leaves only stub", 1, function() { 110 | initTestDB(this.name, function(err, db) { 111 | db.put({_id: "foo", value: "test"}, function(err, res) { 112 | db.get("foo", function(err, doc) { 113 | db.remove(doc, function(err, res) { 114 | db.get("foo", {rev: res.rev}, function(err, doc) { 115 | deepEqual(doc, {_id: res.id, _rev: res.rev, _deleted: true}, "removal left only stub"); 116 | start(); 117 | }); 118 | }); 119 | }); 120 | }); 121 | }); 122 | }); 123 | 124 | asyncTest("Remove doc twice with specified id", 4, function() { 125 | initTestDB(this.name, function(err, db) { 126 | db.put({_id:"specifiedId", test:"somestuff"}, function(err, info) { 127 | db.get("specifiedId", function(err, doc) { 128 | ok(doc.test, "Put and got doc"); 129 | db.remove(doc, function(err, response) { 130 | ok(!err, "Removed doc"); 131 | db.put({_id:"specifiedId", test:"somestuff2"}, function(err, info) { 132 | db.get("specifiedId", function(err, doc){ 133 | ok(doc, "Put and got doc again"); 134 | db.remove(doc, function(err, response) { 135 | ok(!err, "Removed doc again"); 136 | start(); 137 | }); 138 | }); 139 | }); 140 | }); 141 | }); 142 | }); 143 | }); 144 | }); 145 | 146 | asyncTest("Remove doc, no callback", 2, function() { 147 | initTestDB(this.name, function(err, db) { 148 | var changes = db.changes({ 149 | continuous: true, 150 | include_docs: true, 151 | onChange: function(change){ 152 | if (change.seq === 2){ 153 | ok(change.doc._deleted, 'Doc deleted properly'); 154 | changes.cancel(); 155 | start(); 156 | } 157 | } 158 | }); 159 | db.post({_id:"somestuff"}, function (err, res) { 160 | ok(!err, 'save a doc with post'); 161 | db.remove({_id: res.id, _rev: res.rev}); 162 | }); 163 | }); 164 | }); 165 | 166 | asyncTest("Delete document without id", 1, function () { 167 | initTestDB(this.name, function(err, db) { 168 | db.remove({test:'ing'}, function(err) { 169 | ok(err, 'failed to delete'); 170 | start(); 171 | }); 172 | }); 173 | }); 174 | 175 | asyncTest("Bulk docs", 3, function() { 176 | initTestDB(this.name, function(err, db) { 177 | ok(!err, 'opened the pouch'); 178 | db.bulkDocs({docs: [{test:"somestuff"}, {test:"another"}]}, function(err, infos) { 179 | ok(!infos[0].error); 180 | ok(!infos[1].error); 181 | start(); 182 | }); 183 | }); 184 | }); 185 | 186 | /* 187 | asyncTest("Sync a doc", 6, function() { 188 | var couch = generateAdapterUrl('http-2'); 189 | initTestDB(this.name, function(err, db) { 190 | ok(!err, 'opened the pouch'); 191 | initTestDB(couch, function(err, db2) { 192 | ok(!err, 'opened the couch'); 193 | db.put({_id:"adoc", test:"somestuff"}, function (err, info) { 194 | ok(!err, 'saved a doc with post'); 195 | db.replicate.to(couch, function(err, info) { 196 | ok(!err, 'replicated pouch to couch'); 197 | db.replicate.from(couch, function(err, info) { 198 | ok(!err, 'replicated couch back to pouch'); 199 | db.get("adoc", {conflicts:true}, function(err, doc) { 200 | ok(!doc._conflicts, 'doc has no conflicts'); 201 | start(); 202 | }); 203 | }); 204 | }); 205 | }); 206 | }) 207 | }); 208 | }); 209 | */ 210 | 211 | // From here we are copying over tests from CouchDB 212 | // https://github.com/apache/couchdb/blob/master/share/www/script/test/basics.js 213 | /* 214 | asyncTest("Check database with slashes", 1, function() { 215 | initTestDB('idb://test_suite_db%2Fwith_slashes', function(err, db) { 216 | ok(!err, 'opened'); 217 | start(); 218 | }); 219 | }); 220 | */ 221 | 222 | asyncTest("Basic checks", 8, function() { 223 | initTestDB(this.name, function(err, db) { 224 | db.info(function(err, info) { 225 | var updateSeq = info.update_seq; 226 | var doc = {_id: '0', a: 1, b:1}; 227 | ok(info.doc_count === 0); 228 | db.put(doc, function(err, res) { 229 | ok(res.ok === true); 230 | ok(res.id); 231 | ok(res.rev); 232 | db.info(function(err, info) { 233 | ok(info.doc_count === 1); 234 | equal(info.update_seq, updateSeq + 1, 'update seq incremented'); 235 | db.get(doc._id, function(err, doc) { 236 | ok(doc._id === res.id && doc._rev === res.rev); 237 | db.get(doc._id, {revs_info: true}, function(err, doc) { 238 | ok(doc._revs_info[0].status === 'available'); 239 | start(); 240 | }); 241 | }); 242 | }); 243 | }); 244 | }); 245 | }); 246 | }); 247 | 248 | asyncTest("Testing issue #48", 1, function() { 249 | 250 | var docs = [{"id":"0"}, {"id":"1"}, {"id":"2"}, {"id":"3"}, {"id":"4"}, {"id":"5"}]; 251 | var x = 0; 252 | var timer; 253 | 254 | initTestDB(this.name, function(err, db) { 255 | var save = function() { 256 | db.bulkDocs({docs: docs}, function(err, res) { 257 | if (++x === 10) { 258 | ok(true, 'all updated succedded'); 259 | clearInterval(timer); 260 | start(); 261 | } 262 | }); 263 | }; 264 | timer = setInterval(save, 50); 265 | }); 266 | }); 267 | 268 | asyncTest("Testing valid id", 1, function() { 269 | initTestDB(this.name, function(err, db) { 270 | db.post({'_id': 123, test: "somestuff"}, function (err, info) { 271 | ok(err, 'id must be a string'); 272 | start(); 273 | }); 274 | }); 275 | }); 276 | 277 | asyncTest("Put doc without _id should fail", 1, function() { 278 | initTestDB(this.name, function(err, db) { 279 | db.put({test:"somestuff"}, function(err, info) { 280 | ok(err, '_id is required'); 281 | start(); 282 | }); 283 | }); 284 | }); 285 | 286 | asyncTest('update_seq persists', 2, function() { 287 | var name = this.name; 288 | initTestDB(name, function(err, db) { 289 | db.post({test:"somestuff"}, function (err, info) { 290 | new Pouch(name, function(err, db) { 291 | db.info(function(err, info) { 292 | equal(info.update_seq, 1, 'Update seq persisted'); 293 | equal(info.doc_count, 1, 'Doc Count persists'); 294 | start(); 295 | }); 296 | }); 297 | }); 298 | }); 299 | }); 300 | 301 | asyncTest('deletions persists', 1, function() { 302 | var doc = {_id: 'staticId', contents: 'stuff'}; 303 | function writeAndDelete(db, cb) { 304 | db.put(doc, function(err, info) { 305 | db.remove({_id:info.id, _rev:info.rev}, function(doc) { 306 | cb(); 307 | }); 308 | }); 309 | } 310 | initTestDB(this.name, function(err, db) { 311 | writeAndDelete(db, function() { 312 | writeAndDelete(db, function() { 313 | db.put(doc, function() { 314 | db.get(doc._id, {conflicts: true}, function(err, details) { 315 | equal(false, '_conflicts' in details, 'Should not have conflicts'); 316 | start(); 317 | }); 318 | }); 319 | }); 320 | }); 321 | }); 322 | }); 323 | test('Error works', 1, function() { 324 | deepEqual(Pouch.error(Pouch.Errors.BAD_REQUEST, "love needs no reason"), 325 | {status: 400, error: "bad_request", reason: "love needs no reason"}, 326 | "should be the same"); 327 | }); 328 | }); 329 | -------------------------------------------------------------------------------- /tests/test.bulk_docs.js: -------------------------------------------------------------------------------- 1 | /*globals initTestDB: false, emit: true, generateAdapterUrl: false */ 2 | /*globals PERSIST_DATABASES: false, initDBPair: false, utils: true */ 3 | /*globals ajax: true, LevelPouch: true, makeDocs: false */ 4 | /*globals cleanupTestDatabases: false */ 5 | 6 | "use strict"; 7 | 8 | // Porting tests from Apache CouchDB bulk docs tests 9 | // https://github.com/apache/couchdb/blob/master/share/www/script/test/bulk_docs.js 10 | 11 | var adapters = ['local-1', 'http-1']; 12 | var qunit = module; 13 | var LevelPouch; 14 | 15 | if (typeof module !== undefined && module.exports) { 16 | Pouch = require('../src/pouch.js'); 17 | LevelPouch = require('../src/adapters/pouch.leveldb.js'); 18 | utils = require('./test.utils.js'); 19 | 20 | for (var k in utils) { 21 | global[k] = global[k] || utils[k]; 22 | } 23 | qunit = QUnit.module; 24 | } 25 | 26 | adapters.map(function(adapter) { 27 | 28 | qunit('bulk_docs: ' + adapter, { 29 | setup : function () { 30 | this.name = generateAdapterUrl(adapter); 31 | Pouch.enableAllDbs = true; 32 | }, 33 | teardown: cleanupTestDatabases 34 | }); 35 | 36 | var authors = [ 37 | {name: 'Dale Harvey', commits: 253}, 38 | {name: 'Mikeal Rogers', commits: 42}, 39 | {name: 'Johannes J. Schmidt', commits: 13}, 40 | {name: 'Randall Leeds', commits: 9} 41 | ]; 42 | 43 | asyncTest('Testing bulk docs', function() { 44 | initTestDB(this.name, function(err, db) { 45 | var docs = makeDocs(5); 46 | db.bulkDocs({docs: docs}, function(err, results) { 47 | ok(results.length === 5, 'results length matches'); 48 | for (var i = 0; i < 5; i++) { 49 | ok(results[i].id === docs[i]._id, 'id matches'); 50 | ok(results[i].rev, 'rev is set'); 51 | // Update the doc 52 | docs[i]._rev = results[i].rev; 53 | docs[i].string = docs[i].string + ".00"; 54 | } 55 | db.bulkDocs({docs: docs}, function(err, results) { 56 | ok(results.length === 5, 'results length matches'); 57 | for (i = 0; i < 5; i++) { 58 | ok(results[i].id === i.toString(), 'id matches again'); 59 | // set the delete flag to delete the docs in the next step 60 | docs[i]._rev = results[i].rev; 61 | docs[i]._deleted = true; 62 | } 63 | db.put(docs[0], function(err, doc) { 64 | db.bulkDocs({docs: docs}, function(err, results) { 65 | ok(results[0].error === 'conflict', 'First doc should be in conflict'); 66 | ok(typeof results[0].rev === "undefined", 'no rev in conflict'); 67 | for (i = 1; i < 5; i++) { 68 | ok(results[i].id === i.toString()); 69 | ok(results[i].rev); 70 | } 71 | start(); 72 | }); 73 | }); 74 | }); 75 | }); 76 | }); 77 | }); 78 | 79 | asyncTest('No id in bulk docs', function() { 80 | initTestDB(this.name, function(err, db) { 81 | var newdoc = {"_id": "foobar", "body": "baz"}; 82 | db.put(newdoc, function(err, doc) { 83 | ok(doc.ok); 84 | var docs = [ 85 | {"_id": newdoc._id, "_rev": newdoc._rev, "body": "blam"}, 86 | {"_id": newdoc._id, "_rev": newdoc._rev, "_deleted": true} 87 | ]; 88 | db.bulkDocs({docs: docs}, function(err, results) { 89 | ok(results[0].error === 'conflict' || results[1].error === 'conflict'); 90 | start(); 91 | }); 92 | }); 93 | }); 94 | }); 95 | 96 | asyncTest("Test errors on invalid doc id", function() { 97 | var docs = [ 98 | {'_id': '_invalid', foo: 'bar'} 99 | ]; 100 | initTestDB(this.name, function(err, db) { 101 | db.bulkDocs({docs: docs}, function(err, info) { 102 | equal(err.error, 'bad_request', 'correct error returned'); 103 | ok(!info, 'info is empty'); 104 | start(); 105 | }); 106 | }); 107 | }); 108 | 109 | asyncTest("Test two errors on invalid doc id", function() { 110 | var docs = [ 111 | {'_id': '_invalid', foo: 'bar'}, 112 | {'_id': 123, foo: 'bar'} 113 | ]; 114 | initTestDB(this.name, function(err, db) { 115 | db.bulkDocs({docs: docs}, function(err, info) { 116 | equal(err.error, 'bad_request', 'correct error returned'); 117 | equal(err.reason, Pouch.Errors.RESERVED_ID.reason, 'correct error message returned'); 118 | ok(!info, 'info is empty'); 119 | start(); 120 | }); 121 | }); 122 | }); 123 | 124 | asyncTest('No docs', function() { 125 | initTestDB(this.name, function(err, db) { 126 | db.bulkDocs({"doc": [{"foo":"bar"}]}, function(err, result) { 127 | ok(err.status === 400); 128 | ok(err.error === 'bad_request'); 129 | ok(err.reason === "Missing JSON list of 'docs'"); 130 | start(); 131 | }); 132 | }); 133 | }); 134 | 135 | asyncTest('Jira 911', function() { 136 | initTestDB(this.name, function(err, db) { 137 | var docs = [ 138 | {"_id":"0", "a" : 0}, 139 | {"_id":"1", "a" : 1}, 140 | {"_id":"1", "a" : 1}, 141 | {"_id":"3", "a" : 3} 142 | ]; 143 | db.bulkDocs({docs: docs}, function(err, results) { 144 | ok(results[1].id === "1", 'check ordering'); 145 | ok(results[1].error === undefined, 'first id succeded'); 146 | ok(results[2].error === "conflict", 'second conflicted'); 147 | ok(results.length === 4, 'got right amount of results'); 148 | start(); 149 | }); 150 | }); 151 | }); 152 | 153 | asyncTest('Test multiple bulkdocs', function() { 154 | initTestDB(this.name, function(err, db) { 155 | db.bulkDocs({docs: authors}, function (err, res) { 156 | db.bulkDocs({docs: authors}, function (err, res) { 157 | db.allDocs(function(err, result) { 158 | ok(result.total_rows === 8, 'correct number of results'); 159 | start(); 160 | }); 161 | }); 162 | }); 163 | }); 164 | }); 165 | 166 | asyncTest('Bulk with new_edits=false', function() { 167 | initTestDB(this.name, function(err, db) { 168 | var docs = [ 169 | {"_id":"foo","_rev":"2-x","_revisions": 170 | {"start":2,"ids":["x","a"]} 171 | }, 172 | {"_id":"foo","_rev":"2-y","_revisions": 173 | {"start":2,"ids":["y","a"]} 174 | } 175 | ]; 176 | db.bulkDocs({docs: docs}, {new_edits: false}, function(err, res){ 177 | //ok(res.length === 0, "empty array returned"); 178 | db.get("foo", {open_revs: "all"}, function(err, res){ 179 | ok(res[0].ok._rev === "2-x", "doc1 ok"); 180 | ok(res[1].ok._rev === "2-y", "doc2 ok"); 181 | start(); 182 | }); 183 | }); 184 | }); 185 | }); 186 | 187 | asyncTest('656 regression in handling deleted docs', function() { 188 | initTestDB(this.name, function(err, db) { 189 | db.bulkDocs({docs: [{_id: "foo", _rev: "1-a", _deleted: true}]}, {new_edits: false}, function(err, res){ 190 | db.get("foo", function(err, res){ 191 | ok(err, "deleted"); 192 | start(); 193 | }); 194 | }); 195 | }); 196 | }); 197 | }); 198 | -------------------------------------------------------------------------------- /tests/test.compaction.js: -------------------------------------------------------------------------------- 1 | /*globals initTestDB: false, emit: true, generateAdapterUrl: false */ 2 | /*globals PERSIST_DATABASES: false, initDBPair: false, utils: true, putTree: false */ 3 | /*globals ajax: true, LevelPouch: true, makeDocs: false, strictEqual: false */ 4 | /*globals cleanupTestDatabases: false */ 5 | 6 | "use strict"; 7 | 8 | var adapters = ['local-1', 'http-1']; 9 | var autoCompactionAdapters = ['local-1']; 10 | 11 | var qunit = module; 12 | var LevelPouch; 13 | 14 | // if we are running under node.js, set things up 15 | // a little differently, and only test the leveldb adapter 16 | if (typeof module !== undefined && module.exports) { 17 | Pouch = require('../src/pouch.js'); 18 | LevelPouch = require('../src/adapters/pouch.leveldb.js'); 19 | utils = require('./test.utils.js'); 20 | 21 | for (var k in utils) { 22 | global[k] = global[k] || utils[k]; 23 | } 24 | qunit = QUnit.module; 25 | } 26 | 27 | adapters.map(function(adapter) { 28 | qunit('compaction: ' + adapter, { 29 | setup : function () { 30 | this.name = generateAdapterUrl(adapter); 31 | Pouch.enableAllDbs = true; 32 | }, 33 | teardown: cleanupTestDatabases 34 | }); 35 | 36 | asyncTest('Compation document with no revisions to remove', function() { 37 | initTestDB(this.name, function(err, db) { 38 | var doc = {_id: "foo", value: "bar"}; 39 | db.put(doc, function(err, res) { 40 | db.compact(function(){ 41 | ok(true, "compaction finished"); 42 | db.get("foo", function(err, doc) { 43 | ok(!err, "document not deleted"); 44 | start(); 45 | }); 46 | }); 47 | }); 48 | }); 49 | }); 50 | 51 | asyncTest('Compation on empty db', function() { 52 | initTestDB(this.name, function(err, db) { 53 | db.compact(function(){ 54 | ok(true, "compaction finished"); 55 | start(); 56 | }); 57 | }); 58 | }); 59 | 60 | asyncTest('Simple compation test', function() { 61 | initTestDB(this.name, function(err, db) { 62 | var doc = {_id: "foo", value: "bar"}; 63 | 64 | db.post(doc, function(err, res) { 65 | var rev1 = res.rev; 66 | doc._rev = rev1; 67 | doc.value = "baz"; 68 | db.post(doc, function(err, res) { 69 | var rev2 = res.rev; 70 | db.compact(function(){ 71 | ok(true, "compaction finished"); 72 | db.get("foo", {rev: rev1}, function(err, doc){ 73 | ok(err.status === 404 && err.error === "not_found", "compacted document is missing"); 74 | db.get("foo", {rev: rev2}, function(err, doc){ 75 | ok(!err, "newest revision does not get compacted"); 76 | start(); 77 | }); 78 | }); 79 | }); 80 | }); 81 | }); 82 | }); 83 | }); 84 | 85 | var checkBranch = function(db, docs, callback) { 86 | function check(i) { 87 | var doc = docs[i]; 88 | db.get(doc._id, {rev: doc._rev}, function(err, doc) { 89 | if (i < docs.length - 1) { 90 | ok(err && err.status === 404, "compacted!"); 91 | check(i+1); 92 | } else { 93 | ok(!err, "not compacted!"); 94 | callback(); 95 | } 96 | }); 97 | } 98 | check(0); 99 | }; 100 | 101 | var checkTree = function(db, tree, callback) { 102 | function check(i) { 103 | checkBranch(db, tree[i], function() { 104 | if (i < tree.length - 1) { 105 | check(i + 1); 106 | } else { 107 | callback(); 108 | } 109 | }); 110 | } 111 | check(0); 112 | }; 113 | 114 | var exampleTree = [ 115 | [ 116 | {_id: "foo", _rev: "1-a", value: "foo a"}, 117 | {_id: "foo", _rev: "2-b", value: "foo b"}, 118 | {_id: "foo", _rev: "3-c", value: "foo c"} 119 | ], 120 | [ 121 | {_id: "foo", _rev: "1-a", value: "foo a"}, 122 | {_id: "foo", _rev: "2-d", value: "foo d"}, 123 | {_id: "foo", _rev: "3-e", value: "foo e"}, 124 | {_id: "foo", _rev: "4-f", value: "foo f"} 125 | ], 126 | [ 127 | {_id: "foo", _rev: "1-a", value: "foo a"}, 128 | {_id: "foo", _rev: "2-g", value: "foo g"}, 129 | {_id: "foo", _rev: "3-h", value: "foo h"}, 130 | {_id: "foo", _rev: "4-i", value: "foo i"}, 131 | {_id: "foo", _rev: "5-j", _deleted: true, value: "foo j"} 132 | ] 133 | ]; 134 | 135 | var exampleTree2 = [ 136 | [ 137 | {_id: "bar", _rev: "1-m", value: "bar m"}, 138 | {_id: "bar", _rev: "2-n", value: "bar n"}, 139 | {_id: "bar", _rev: "3-o", _deleted: true, value: "foo o"} 140 | ], 141 | [ 142 | {_id: "bar", _rev: "2-n", value: "bar n"}, 143 | {_id: "bar", _rev: "3-p", value: "bar p"}, 144 | {_id: "bar", _rev: "4-r", value: "bar r"}, 145 | {_id: "bar", _rev: "5-s", value: "bar s"} 146 | ], 147 | [ 148 | {_id: "bar", _rev: "3-p", value: "bar p"}, 149 | {_id: "bar", _rev: "4-t", value: "bar t"}, 150 | {_id: "bar", _rev: "5-u", value: "bar u"} 151 | ] 152 | ]; 153 | 154 | asyncTest('Compact more complicated tree', function() { 155 | initTestDB(this.name, function(err, db) { 156 | putTree(db, exampleTree, function() { 157 | db.compact(function() { 158 | checkTree(db, exampleTree, function() { 159 | ok(1, "checks finished"); 160 | start(); 161 | }); 162 | }); 163 | }); 164 | }); 165 | }); 166 | 167 | asyncTest('Compact two times more complicated tree', function() { 168 | initTestDB(this.name, function(err, db) { 169 | putTree(db, exampleTree, function() { 170 | db.compact(function() { 171 | db.compact(function() { 172 | checkTree(db, exampleTree, function() { 173 | ok(1, "checks finished"); 174 | start(); 175 | }); 176 | }); 177 | }); 178 | }); 179 | }); 180 | }); 181 | 182 | asyncTest('Compact database with at least two documents', function() { 183 | initTestDB(this.name, function(err, db) { 184 | putTree(db, exampleTree, function() { 185 | putTree(db, exampleTree2, function() { 186 | db.compact(function() { 187 | checkTree(db, exampleTree, function() { 188 | checkTree(db, exampleTree2, function() { 189 | ok(1, "checks finished"); 190 | start(); 191 | }); 192 | }); 193 | }); 194 | }); 195 | }); 196 | }); 197 | }); 198 | 199 | asyncTest('Compact deleted document', function() { 200 | initTestDB(this.name, function(err, db) { 201 | db.put({_id: "foo"}, function(err, res) { 202 | var firstRev = res.rev; 203 | db.remove({_id: "foo", _rev: firstRev}, function(err, res) { 204 | db.compact(function() { 205 | db.get("foo", {rev: firstRev}, function(err, res) { 206 | ok(err, "got error"); 207 | strictEqual(err.reason, "missing", "correct reason"); 208 | start(); 209 | }); 210 | }); 211 | }); 212 | }); 213 | }); 214 | }); 215 | 216 | if (autoCompactionAdapters.indexOf(adapter) > -1) { 217 | asyncTest('Auto-compaction test', function() { 218 | initTestDB(this.name, {auto_compaction: true}, function(err, db) { 219 | var doc = {_id: "doc", val: "1"}; 220 | db.post(doc, function(err, res) { 221 | var rev1 = res.rev; 222 | doc._rev = rev1; 223 | doc.val = "2"; 224 | db.post(doc, function(err, res) { 225 | var rev2 = res.rev; 226 | doc._rev = rev2; 227 | doc.val = "3"; 228 | db.post(doc, function(err, res) { 229 | var rev3 = res.rev; 230 | db.get("doc", {rev: rev1}, function(err, doc) { 231 | strictEqual(err.status, 404, "compacted document is missing"); 232 | strictEqual(err.error, "not_found", "compacted document is missing"); 233 | db.get("doc", {rev: rev2}, function(err, doc) { 234 | ok(!err, "leaf's parent does not get compacted"); 235 | db.get("doc", {rev: rev3}, function(err, doc) { 236 | ok(!err, "leaf revision does not get compacted"); 237 | start(); 238 | }); 239 | }); 240 | }); 241 | }); 242 | }); 243 | }); 244 | }); 245 | }); 246 | } 247 | }); 248 | -------------------------------------------------------------------------------- /tests/test.conflicts.js: -------------------------------------------------------------------------------- 1 | /*globals initTestDB: false, emit: true, generateAdapterUrl: false */ 2 | /*globals PERSIST_DATABASES: false, initDBPair: false, utils: true */ 3 | /*globals ajax: true, LevelPouch: true */ 4 | /*globals cleanupTestDatabases: false */ 5 | 6 | "use strict"; 7 | 8 | var adapters = ['http-1', 'local-1']; 9 | var qunit = module; 10 | var LevelPouch; 11 | 12 | // if we are running under node.js, set things up 13 | // a little differently, and only test the leveldb adapter 14 | if (typeof module !== undefined && module.exports) { 15 | Pouch = require('../src/pouch.js'); 16 | LevelPouch = require('../src/adapters/pouch.leveldb.js'); 17 | utils = require('./test.utils.js'); 18 | 19 | for (var k in utils) { 20 | global[k] = global[k] || utils[k]; 21 | } 22 | qunit = QUnit.module; 23 | } 24 | 25 | adapters.map(function(adapter) { 26 | 27 | qunit('conflicts: ' + adapter, { 28 | setup : function () { 29 | this.name = generateAdapterUrl(adapter); 30 | Pouch.enableAllDbs = true; 31 | }, 32 | teardown: cleanupTestDatabases 33 | }); 34 | 35 | asyncTest('Testing conflicts', function() { 36 | initTestDB(this.name, function(err, db) { 37 | var doc = {_id: 'foo', a:1, b: 1}; 38 | db.put(doc, function(err, res) { 39 | doc._rev = res.rev; 40 | ok(res.ok, 'Put first document'); 41 | db.get('foo', function(err, doc2) { 42 | ok(doc._id === doc2._id && doc._rev && doc2._rev, 'Docs had correct id + rev'); 43 | doc.a = 2; 44 | doc2.a = 3; 45 | db.put(doc, function(err, res) { 46 | ok(res.ok, 'Put second doc'); 47 | db.put(doc2, function(err) { 48 | ok(err.error === 'conflict', 'Put got a conflicts'); 49 | db.changes({ 50 | complete: function(err, results) { 51 | ok(results.results.length === 1, 'We have one entry in changes'); 52 | doc2._rev = undefined; 53 | db.put(doc2, function(err) { 54 | ok(err.error === 'conflict', 'Another conflict'); 55 | start(); 56 | }); 57 | } 58 | }); 59 | }); 60 | }); 61 | }); 62 | }); 63 | }); 64 | }); 65 | 66 | asyncTest('Testing conflicts', function() { 67 | var doc = {_id: 'fubar', a:1, b: 1}; 68 | initTestDB(this.name, function(err, db) { 69 | db.put(doc, function(err, ndoc) { 70 | doc._rev = ndoc.rev; 71 | db.remove(doc, function() { 72 | delete doc._rev; 73 | db.put(doc, function(err, ndoc) { 74 | if (err) { 75 | ok(false); 76 | start(); 77 | return; 78 | } 79 | ok(ndoc.ok, 'written previously deleted doc without rev'); 80 | start(); 81 | }); 82 | }); 83 | }); 84 | }); 85 | }); 86 | 87 | }); 88 | -------------------------------------------------------------------------------- /tests/test.design_docs.js: -------------------------------------------------------------------------------- 1 | /*globals initTestDB: false, emit: true, generateAdapterUrl: false */ 2 | /*globals PERSIST_DATABASES: false, initDBPair: false, utils: true */ 3 | /*globals ajax: true, LevelPouch: true */ 4 | /*globals cleanupTestDatabases: false */ 5 | 6 | "use strict"; 7 | 8 | var adapters = ['local-1', 'http-1']; 9 | var qunit = module; 10 | var LevelPouch; 11 | 12 | // if we are running under node.js, set things up 13 | // a little differently, and only test the leveldb adapter 14 | if (typeof module !== undefined && module.exports) { 15 | Pouch = require('../src/pouch.js'); 16 | LevelPouch = require('../src/adapters/pouch.leveldb.js'); 17 | utils = require('./test.utils.js'); 18 | 19 | for (var k in utils) { 20 | global[k] = global[k] || utils[k]; 21 | } 22 | qunit = QUnit.module; 23 | } 24 | 25 | adapters.map(function(adapter) { 26 | 27 | qunit("design_docs: " + adapter, { 28 | setup : function () { 29 | this.name = generateAdapterUrl(adapter); 30 | Pouch.enableAllDbs = true; 31 | }, 32 | teardown: cleanupTestDatabases 33 | }); 34 | 35 | var doc = { 36 | _id: '_design/foo', 37 | views: { 38 | scores: { 39 | map: 'function(doc) { if (doc.score) { emit(null, doc.score); } }', 40 | reduce: 'function(keys, values, rereduce) { return sum(values); }' 41 | } 42 | }, 43 | filters: { 44 | even: 'function(doc) { return doc.integer % 2 === 0; }' 45 | } 46 | }; 47 | 48 | asyncTest("Test writing design doc", function () { 49 | initTestDB(this.name, function(err, db) { 50 | db.post(doc, function (err, info) { 51 | ok(!err, 'Wrote design doc'); 52 | db.get('_design/foo', function (err, info) { 53 | ok(!err, 'Read design doc'); 54 | start(); 55 | }); 56 | }); 57 | }); 58 | }); 59 | 60 | asyncTest("Changes filter", function() { 61 | 62 | var docs1 = [ 63 | doc, 64 | {_id: "0", integer: 0}, 65 | {_id: "1", integer: 1}, 66 | {_id: "2", integer: 2}, 67 | {_id: "3", integer: 3} 68 | ]; 69 | 70 | var docs2 = [ 71 | {_id: "4", integer: 4}, 72 | {_id: "5", integer: 5}, 73 | {_id: "6", integer: 6}, 74 | {_id: "7", integer: 7} 75 | ]; 76 | 77 | initTestDB(this.name, function(err, db) { 78 | var count = 0; 79 | db.bulkDocs({docs: docs1}, function(err, info) { 80 | var changes = db.changes({ 81 | filter: 'foo/even', 82 | onChange: function(change) { 83 | count += 1; 84 | if (count === 4) { 85 | ok(true, 'We got all the changes'); 86 | changes.cancel(); 87 | start(); 88 | } 89 | }, 90 | continuous: true 91 | }); 92 | db.bulkDocs({docs: docs2}, {}); 93 | }); 94 | }); 95 | }); 96 | 97 | asyncTest("Basic views", function () { 98 | 99 | var docs1 = [ 100 | doc, 101 | {_id: "dale", score: 3}, 102 | {_id: "mikeal", score: 5}, 103 | {_id: "max", score: 4}, 104 | {_id: "nuno", score: 3} 105 | ]; 106 | 107 | initTestDB(this.name, function(err, db) { 108 | db.bulkDocs({docs: docs1}, function(err, info) { 109 | db.query('foo/scores', {reduce: false}, function(err, result) { 110 | equal(result.rows.length, 4, 'Correct # of results'); 111 | db.query('foo/scores', function(err, result) { 112 | equal(result.rows[0].value, 15, 'Reduce gave correct result'); 113 | start(); 114 | }); 115 | }); 116 | }); 117 | }); 118 | }); 119 | 120 | asyncTest("Concurrent queries", function() { 121 | initTestDB(this.name, function(err, db) { 122 | db.bulkDocs({docs: [doc, {_id: "dale", score: 3}]}, function(err, info) { 123 | var cnt = 0; 124 | db.query('foo/scores', {reduce: false}, function(err, result) { 125 | equal(result.rows.length, 1, 'Correct # of results'); 126 | if (cnt++ === 1) { 127 | start(); 128 | } 129 | }); 130 | db.query('foo/scores', {reduce: false}, function(err, result) { 131 | equal(result.rows.length, 1, 'Correct # of results'); 132 | if (cnt++ === 1) { 133 | start(); 134 | } 135 | }); 136 | }); 137 | }); 138 | }); 139 | 140 | }); 141 | -------------------------------------------------------------------------------- /tests/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | QUnit Test Suite 5 | 6 | 7 | 8 | 9 | 10 | 11 |

    QUnit Test Suite

    12 |

    13 |
    14 |

    15 |
      16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /tests/test.http.js: -------------------------------------------------------------------------------- 1 | /*globals initTestDB: false, emit: true, generateAdapterUrl: false */ 2 | /*globals PERSIST_DATABASES: false, initDBPair: false, utils: true */ 3 | /*globals ajax: true, LevelPouch: true */ 4 | 5 | "use strict"; 6 | 7 | var adapter = 'http-1'; 8 | var qunit = module; 9 | var LevelPouch; 10 | 11 | if (typeof module !== undefined && module.exports) { 12 | Pouch = require('../src/pouch.js'); 13 | LevelPouch = require('../src/adapters/pouch.leveldb.js'); 14 | utils = require('./test.utils.js'); 15 | 16 | for (var k in utils) { 17 | global[k] = global[k] || utils[k]; 18 | } 19 | qunit = QUnit.module; 20 | } 21 | 22 | qunit("http-adapter", { 23 | setup: function() { 24 | this.name = generateAdapterUrl(adapter); 25 | }, 26 | teardown: function() { 27 | if (!PERSIST_DATABASES) { 28 | Pouch.destroy(this.name); 29 | } 30 | } 31 | }); 32 | 33 | 34 | 35 | asyncTest("Create a pouch without DB setup", function() { 36 | var instantDB; 37 | var name = this.name; 38 | Pouch.destroy(name, function() { 39 | instantDB = new Pouch(name, {skipSetup: true}); 40 | instantDB.post({test:"abc"}, function(err, info) { 41 | ok(err && err.error === 'not_found', 'Skipped setup of database'); 42 | start(); 43 | }); 44 | }); 45 | }); 46 | 47 | 48 | -------------------------------------------------------------------------------- /tests/test.issue221.js: -------------------------------------------------------------------------------- 1 | /*globals initTestDB: false, emit: true, generateAdapterUrl: false, strictEqual: false */ 2 | /*globals PERSIST_DATABASES: false, initDBPair: false, utils: true */ 3 | /*globals ajax: true, LevelPouch: true */ 4 | /*globals cleanupTestDatabases: false */ 5 | 6 | "use strict"; 7 | 8 | var adapters = [ 9 | ['local-1', 'http-1'], 10 | ['http-1', 'http-2'], 11 | ['http-1', 'local-1'], 12 | ['local-1', 'local-2'] 13 | ]; 14 | var qunit = module; 15 | var LevelPouch; 16 | 17 | if (typeof module !== undefined && module.exports) { 18 | Pouch = require('../src/pouch.js'); 19 | LevelPouch = require('../src/adapters/pouch.leveldb.js'); 20 | utils = require('./test.utils.js'); 21 | 22 | for (var k in utils) { 23 | global[k] = global[k] || utils[k]; 24 | } 25 | qunit = QUnit.module; 26 | } 27 | 28 | adapters.map(function(adapters) { 29 | 30 | qunit('replication + compaction: ' + adapters[0] + ':' + adapters[1], { 31 | setup: function() { 32 | this.local = generateAdapterUrl(adapters[0]); 33 | this.remote = generateAdapterUrl(adapters[1]); 34 | Pouch.enableAllDbs = true; 35 | }, 36 | teardown: cleanupTestDatabases 37 | }); 38 | 39 | var doc = { _id: '0', integer: 0 }; 40 | 41 | asyncTest('Testing issue #221', function() { 42 | var self = this; 43 | // Create databases. 44 | initDBPair(self.local, self.remote, function(local, remote) { 45 | // Write a doc in CouchDB. 46 | remote.put(doc, function(err, results) { 47 | // Update the doc. 48 | doc._rev = results.rev; 49 | doc.integer = 1; 50 | remote.put(doc, function(err, results) { 51 | // Compact the db. 52 | remote.compact(function() { 53 | remote.get(doc._id, {revs_info:true},function(err, data) { 54 | var correctRev = data._revs_info[0]; 55 | local.replicate.from(remote, function(err, results) { 56 | // Check the PouchDB doc. 57 | local.get(doc._id, function(err, results) { 58 | strictEqual(results._rev, correctRev.rev, 59 | 'correct rev stored after replication'); 60 | strictEqual(results.integer, 1, 61 | 'correct content stored after replication'); 62 | start(); 63 | }); 64 | }); 65 | }); 66 | }); 67 | }); 68 | }); 69 | }); 70 | }); 71 | 72 | asyncTest('Testing issue #221 again', function() { 73 | var self = this; 74 | // Create databases. 75 | initDBPair(self.local, self.remote, function(local, remote) { 76 | // Write a doc in CouchDB. 77 | remote.put(doc, function(err, results) { 78 | doc._rev = results.rev; 79 | // Second doc so we get 2 revisions from replicate. 80 | remote.put(doc, function(err, results) { 81 | doc._rev = results.rev; 82 | local.replicate.from(remote, function(err, results) { 83 | doc.integer = 1; 84 | // One more change 85 | remote.put(doc, function(err, results) { 86 | // Testing if second replications fails now 87 | local.replicate.from(remote, function(err, results) { 88 | local.get(doc._id, function(err, results) { 89 | strictEqual(results.integer, 1, 'correct content stored after replication'); 90 | start(); 91 | }); 92 | }); 93 | }); 94 | }); 95 | }); 96 | }); 97 | }); 98 | }); 99 | }); 100 | -------------------------------------------------------------------------------- /tests/test.revs_diff.js: -------------------------------------------------------------------------------- 1 | /*globals initTestDB: false, emit: true, generateAdapterUrl: false */ 2 | /*globals PERSIST_DATABASES: false */ 3 | /*globals cleanupTestDatabases: false */ 4 | 5 | "use strict"; 6 | 7 | var adapters = ['http-1', 'local-1']; 8 | var qunit = module; 9 | var LevelPouch; 10 | 11 | // if we are running under node.js, set things up 12 | // a little differently, and only test the leveldb adapter 13 | if (typeof module !== undefined && module.exports) { 14 | var Pouch = require('../src/pouch.js'); 15 | var LevelPouch = require('../src/adapters/pouch.leveldb.js'); 16 | var utils = require('./test.utils.js'); 17 | 18 | for (var k in utils) { 19 | global[k] = global[k] || utils[k]; 20 | } 21 | qunit = QUnit.module; 22 | } 23 | 24 | adapters.map(function(adapter) { 25 | 26 | qunit("revs diff:" + adapter, { 27 | setup : function () { 28 | this.name = generateAdapterUrl(adapter); 29 | Pouch.enableAllDbs = true; 30 | }, 31 | teardown: cleanupTestDatabases 32 | }); 33 | 34 | asyncTest("Test revs diff", function() { 35 | var revs = []; 36 | initTestDB(this.name, function(err, db) { 37 | db.post({test: "somestuff", _id: 'somestuff'}, function (err, info) { 38 | revs.push(info.rev); 39 | db.put({_id: info.id, _rev: info.rev, another: 'test'}, function(err, info2) { 40 | revs.push(info2.rev); 41 | db.revsDiff({'somestuff': revs}, function(err, results) { 42 | ok(!('somestuff' in results), 'werent missing any revs'); 43 | revs.push('2-randomid'); 44 | db.revsDiff({'somestuff': revs}, function(err, results) { 45 | ok('somestuff' in results, 'listed missing revs'); 46 | ok(results.somestuff.missing.length === 1, 'listed currect number of'); 47 | start(); 48 | }); 49 | }); 50 | }); 51 | }); 52 | }); 53 | }); 54 | 55 | }); 56 | -------------------------------------------------------------------------------- /tests/test.taskqueue.js: -------------------------------------------------------------------------------- 1 | /*globals openTestAsyncDB: false, emit: true, generateAdapterUrl: false */ 2 | /*globals PERSIST_DATABASES: false, initDBPair: false, utils: true */ 3 | /*globals ajax: true, LevelPouch: true */ 4 | /*globals Pouch: true, QUnit, uuid, asyncTest, ok, start*/ 5 | /*globals cleanupTestDatabases: false */ 6 | 7 | "use strict"; 8 | 9 | var adapters = ['http-1', 'local-1']; 10 | var qunit = module; 11 | 12 | if (typeof module !== undefined && module.exports) { 13 | var Pouch = require('../src/pouch.js'); 14 | var LevelPouch = require('../src/adapters/pouch.leveldb.js'); 15 | var utils = require('./test.utils.js'); 16 | 17 | for (var k in utils) { 18 | global[k] = global[k] || utils[k]; 19 | } 20 | qunit = QUnit.module; 21 | } 22 | 23 | adapters.map(function(adapter) { 24 | 25 | qunit("taskqueue: " + adapter, { 26 | setup: function() { 27 | this.name = generateAdapterUrl(adapter); 28 | Pouch.enableAllDbs = true; 29 | }, 30 | teardown: cleanupTestDatabases 31 | }); 32 | 33 | asyncTest("Add a doc", 1, function() { 34 | var name = this.name; 35 | Pouch.destroy(name, function() { 36 | var db = openTestAsyncDB(name); 37 | db.post({test:"somestuff"}, function (err, info) { 38 | ok(!err, 'saved a doc with post'); 39 | start(); 40 | }); 41 | }); 42 | }); 43 | 44 | asyncTest("Query", 1, function() { 45 | var name = this.name; 46 | Pouch.destroy(name, function() { 47 | var db = openTestAsyncDB(name); 48 | var queryFun = { 49 | map: function(doc) { } 50 | }; 51 | db.query(queryFun, { reduce: false }, function (_, res) { 52 | equal(res.rows.length, 0); 53 | start(); 54 | }); 55 | }); 56 | }); 57 | 58 | asyncTest("Bulk docs", 2, function() { 59 | var name = this.name; 60 | Pouch.destroy(name, function() { 61 | var db = openTestAsyncDB(name); 62 | 63 | db.bulkDocs({docs: [{test:"somestuff"}, {test:"another"}]}, function(err, infos) { 64 | ok(!infos[0].error); 65 | ok(!infos[1].error); 66 | start(); 67 | }); 68 | }); 69 | }); 70 | 71 | asyncTest("Get", 1, function() { 72 | var name = this.name; 73 | Pouch.destroy(name, function() { 74 | var db = openTestAsyncDB(name); 75 | 76 | db.get('0', function(err, res) { 77 | ok(err); 78 | start(); 79 | }); 80 | }); 81 | }); 82 | 83 | asyncTest("Info", 2, function() { 84 | var name = this.name; 85 | Pouch.destroy(name, function() { 86 | var db = openTestAsyncDB(name); 87 | 88 | db.info(function(err, info) { 89 | ok(info.doc_count === 0); 90 | ok(info.update_seq === 0); 91 | start(); 92 | }); 93 | }); 94 | }); 95 | }); 96 | -------------------------------------------------------------------------------- /tests/test.utils.js: -------------------------------------------------------------------------------- 1 | /*globals extend: false, Buffer: false, Pouch: true */ 2 | "use strict"; 3 | 4 | var PERSIST_DATABASES = false; 5 | 6 | function cleanupAllDbs() { 7 | 8 | var deleted = 0; 9 | var adapters = Object.keys(Pouch.adapters).filter(function(adapter) { 10 | return adapter !== 'http' && adapter !== 'https'; 11 | }); 12 | 13 | function finished() { 14 | // Restart text execution 15 | start(); 16 | } 17 | 18 | function dbDeleted() { 19 | deleted++; 20 | if (deleted === adapters.length) { 21 | finished(); 22 | } 23 | } 24 | 25 | if (!adapters.length) { 26 | finished(); 27 | } 28 | 29 | // Remove old allDbs to prevent DOM exception 30 | adapters.forEach(function(adapter) { 31 | if (adapter === "http" || adapter === "https") { 32 | return; 33 | } 34 | Pouch.destroy(Pouch.allDBName(adapter), dbDeleted); 35 | }); 36 | } 37 | 38 | function cleanupTestDatabases() { 39 | 40 | if (PERSIST_DATABASES) { 41 | return; 42 | } 43 | 44 | // Stop the tests from executing 45 | stop(); 46 | 47 | var dbCount; 48 | var deleted = 0; 49 | 50 | function finished() { 51 | cleanupAllDbs(); 52 | } 53 | 54 | function dbDeleted() { 55 | if (++deleted === dbCount) { 56 | finished(); 57 | } 58 | } 59 | 60 | Pouch.allDbs(function(err, dbs) { 61 | if (!dbs.length) { 62 | finished(); 63 | } 64 | dbCount = dbs.length; 65 | dbs.forEach(function(db) { 66 | Pouch.destroy(db, dbDeleted); 67 | }); 68 | }); 69 | } 70 | 71 | function uuid() { 72 | var S4 = function() { 73 | return Math.floor(Math.random() * 0x10000).toString(16); 74 | }; 75 | 76 | return ( 77 | S4() + S4() + "-" + 78 | S4() + "-" + 79 | S4() + "-" + 80 | S4() + "-" + 81 | S4() + S4() + S4() 82 | ); 83 | } 84 | 85 | function makeDocs(start, end, templateDoc) { 86 | var templateDocSrc = templateDoc ? JSON.stringify(templateDoc) : "{}"; 87 | if (end === undefined) { 88 | end = start; 89 | start = 0; 90 | } 91 | var docs = []; 92 | for (var i = start; i < end; i++) { 93 | /*jshint evil:true */ 94 | var newDoc = eval("(" + templateDocSrc + ")"); 95 | newDoc._id = (i).toString(); 96 | newDoc.integer = i; 97 | newDoc.string = (i).toString(); 98 | docs.push(newDoc); 99 | } 100 | return docs; 101 | } 102 | 103 | function makeBlob(data, type) { 104 | if (typeof module !== 'undefined' && module.exports) { 105 | return new Buffer(data); 106 | } else { 107 | return new Blob([data], {type: type}); 108 | } 109 | } 110 | 111 | function readBlob(blob, callback) { 112 | if (typeof module !== 'undefined' && module.exports) { 113 | callback(blob.toString()); 114 | } else { 115 | var reader = new FileReader(); 116 | reader.onloadend = function(e) { 117 | callback(this.result); 118 | }; 119 | reader.readAsBinaryString(blob); 120 | } 121 | } 122 | 123 | function openTestAsyncDB(name) { 124 | return new Pouch(name, function(err,db) { 125 | if (err) { 126 | console.error(err); 127 | ok(false, 'failed to open database'); 128 | return start(); 129 | } 130 | }); 131 | } 132 | 133 | function openTestDB(name, opts, callback) { 134 | if (typeof opts === 'function') { 135 | callback = opts; 136 | opts = {}; 137 | } 138 | new Pouch(name, opts, function(err, db) { 139 | if (err) { 140 | console.error(err); 141 | ok(false, 'failed to open database'); 142 | return start(); 143 | } 144 | callback.apply(this, arguments); 145 | }); 146 | } 147 | 148 | function initTestDB(name, opts, callback) { 149 | // ignore errors, the database might not exist 150 | Pouch.destroy(name, function(err) { 151 | if (err && err.status !== 404 && err.statusText !== 'timeout') { 152 | console.error(err); 153 | ok(false, 'failed to open database'); 154 | return start(); 155 | } 156 | openTestDB(name, opts, callback); 157 | }); 158 | } 159 | 160 | function initDBPair(local, remote, callback) { 161 | initTestDB(local, function(err, localDb) { 162 | initTestDB(remote, function(err, remoteDb) { 163 | callback(localDb, remoteDb); 164 | }); 165 | }); 166 | } 167 | 168 | var testId = uuid(); 169 | 170 | function generateAdapterUrl(id) { 171 | var opt = id.split('-'); 172 | if (opt[0] === 'local') { 173 | return 'testdb_' + testId + '_' + opt[1]; 174 | } 175 | if (opt[0] === 'http') { 176 | return (typeof module !== 'undefined' && module.exports) ? 177 | 'http://localhost:5984/testdb_' + testId + '_' + opt[1] : 178 | 'http://localhost:2020/testdb_' + testId + '_' + opt[1]; 179 | } 180 | } 181 | 182 | // Put doc after prevRev (so that doc is a child of prevDoc 183 | // in rev_tree). Doc must have _rev. If prevRev is not specified 184 | // just insert doc with correct _rev (new_edits=false!) 185 | function putAfter(db, doc, prevRev, callback){ 186 | var newDoc = extend({}, doc); 187 | if (!prevRev) { 188 | db.put(newDoc, {new_edits: false}, callback); 189 | return; 190 | } 191 | newDoc._revisions = { 192 | start: +newDoc._rev.split('-')[0], 193 | ids: [ 194 | newDoc._rev.split('-')[1], 195 | prevRev.split('-')[1] 196 | ] 197 | }; 198 | db.put(newDoc, {new_edits: false}, callback); 199 | } 200 | 201 | // docs will be inserted one after another 202 | // starting from root 203 | var putBranch = function(db, docs, callback) { 204 | function insert(i) { 205 | var doc = docs[i]; 206 | var prev = i > 0 ? docs[i-1]._rev : null; 207 | function next() { 208 | if (i < docs.length - 1) { 209 | insert(i+1); 210 | } else { 211 | callback(); 212 | } 213 | } 214 | db.get(doc._id, {rev: doc._rev}, function(err, ok){ 215 | if(err){ 216 | putAfter(db, docs[i], prev, function(err, doc) { 217 | next(); 218 | }); 219 | }else{ 220 | next(); 221 | } 222 | }); 223 | } 224 | insert(0); 225 | }; 226 | 227 | 228 | var putTree = function(db, tree, callback) { 229 | function insert(i) { 230 | var branch = tree[i]; 231 | putBranch(db, branch, function() { 232 | if (i < tree.length - 1) { 233 | insert(i+1); 234 | } else { 235 | callback(); 236 | } 237 | }); 238 | } 239 | insert(0); 240 | }; 241 | 242 | if (typeof module !== 'undefined' && module.exports) { 243 | Pouch = require('../src/pouch.js'); 244 | module.exports = { 245 | uuid: uuid, 246 | makeDocs: makeDocs, 247 | makeBlob: makeBlob, 248 | readBlob: readBlob, 249 | initTestDB: initTestDB, 250 | initDBPair: initDBPair, 251 | openTestDB: openTestDB, 252 | openTestAsyncDB: openTestAsyncDB, 253 | generateAdapterUrl: generateAdapterUrl, 254 | putAfter: putAfter, 255 | putBranch: putBranch, 256 | putTree: putTree, 257 | cleanupTestDatabases: cleanupTestDatabases, 258 | PERSIST_DATABASES: PERSIST_DATABASES 259 | }; 260 | } 261 | -------------------------------------------------------------------------------- /tests/test.views.js: -------------------------------------------------------------------------------- 1 | /*globals initTestDB: false, emit: true, generateAdapterUrl: false */ 2 | /*globals PERSIST_DATABASES: false, initDBPair: false, utils: true */ 3 | /*globals cleanupTestDatabases: false */ 4 | 5 | "use strict"; 6 | 7 | var adapters = ['local-1', 'http-1']; 8 | var qunit = module; 9 | var LevelPouch; 10 | 11 | // if we are running under node.js, set things up 12 | // a little differently, and only test the leveldb adapter 13 | if (typeof module !== undefined && module.exports) { 14 | var Pouch = require('../src/pouch.js'); 15 | var LevelPouch = require('../src/adapters/pouch.leveldb.js'); 16 | var utils = require('./test.utils.js'); 17 | 18 | for (var k in utils) { 19 | global[k] = global[k] || utils[k]; 20 | } 21 | qunit = QUnit.module; 22 | } 23 | 24 | adapters.map(function(adapter) { 25 | 26 | qunit('views: ' + adapter, { 27 | setup : function () { 28 | this.name = generateAdapterUrl(adapter); 29 | this.remote = generateAdapterUrl('local-2'); 30 | Pouch.enableAllDbs = true; 31 | }, 32 | teardown: cleanupTestDatabases 33 | }); 34 | 35 | asyncTest("Test basic view", function() { 36 | initTestDB(this.name, function(err, db) { 37 | db.bulkDocs({docs: [{foo: 'bar'}, { _id: 'volatile', foo: 'baz' }]}, {}, function() { 38 | var queryFun = { 39 | map: function(doc) { emit(doc.foo, doc); } 40 | }; 41 | db.get('volatile', function(_, doc) { 42 | db.remove(doc, function(_, resp) { 43 | db.query(queryFun, {include_docs: true, reduce: false}, function(_, res) { 44 | equal(res.rows.length, 1, 'Dont include deleted documents'); 45 | equal(res.total_rows, 1, 'Include total_rows property.'); 46 | res.rows.forEach(function(x, i) { 47 | ok(x.id, 'emitted row has id'); 48 | ok(x.key, 'emitted row has key'); 49 | ok(x.value, 'emitted row has value'); 50 | ok(x.value._rev, 'emitted doc has rev'); 51 | ok(x.doc, 'doc included'); 52 | ok(x.doc && x.doc._rev, 'included doc has rev'); 53 | }); 54 | start(); 55 | }); 56 | }); 57 | }); 58 | }); 59 | }); 60 | }); 61 | 62 | asyncTest("Test passing just a function", function() { 63 | initTestDB(this.name, function(err, db) { 64 | db.bulkDocs({docs: [{foo: 'bar'}, { _id: 'volatile', foo: 'baz' }]}, {}, function() { 65 | var queryFun = function(doc) { emit(doc.foo, doc); }; 66 | db.get('volatile', function(_, doc) { 67 | db.remove(doc, function(_, resp) { 68 | db.query(queryFun, {include_docs: true, reduce: false}, function(_, res) { 69 | equal(res.rows.length, 1, 'Dont include deleted documents'); 70 | res.rows.forEach(function(x, i) { 71 | ok(x.id, 'emitted row has id'); 72 | ok(x.key, 'emitted row has key'); 73 | ok(x.value, 'emitted row has value'); 74 | ok(x.value._rev, 'emitted doc has rev'); 75 | ok(x.doc, 'doc included'); 76 | ok(x.doc && x.doc._rev, 'included doc has rev'); 77 | }); 78 | start(); 79 | }); 80 | }); 81 | }); 82 | }); 83 | }); 84 | }); 85 | 86 | asyncTest("Test opts.startkey/opts.endkey", function() { 87 | initTestDB(this.name, function(err, db) { 88 | db.bulkDocs({docs: [{key: 'key1'},{key: 'key2'},{key: 'key3'},{key: 'key4'},{key: 'key5'}]}, {}, function() { 89 | var queryFun = { 90 | map: function(doc) { emit(doc.key, doc); } 91 | }; 92 | db.query(queryFun, {reduce: false, startkey: 'key2'}, function(_, res) { 93 | equal(res.rows.length, 4, 'Startkey is inclusive'); 94 | db.query(queryFun, {reduce: false, endkey: 'key3'}, function(_, res) { 95 | equal(res.rows.length, 3, 'Endkey is inclusive'); 96 | db.query(queryFun, {reduce: false, startkey: 'key2', endkey: 'key3'}, function(_, res) { 97 | equal(res.rows.length, 2, 'Startkey and endkey together'); 98 | db.query(queryFun, {reduce: false, startkey: 'key4', endkey: 'key4'}, function(_, res) { 99 | equal(res.rows.length, 1, 'Startkey=endkey'); 100 | start(); 101 | }); 102 | }); 103 | }); 104 | }); 105 | }); 106 | }); 107 | }); 108 | 109 | asyncTest("Test opts.key", function() { 110 | initTestDB(this.name, function(err, db) { 111 | db.bulkDocs({docs: [{key: 'key1'},{key: 'key2'},{key: 'key3'},{key: 'key3'}]}, {}, function() { 112 | var queryFun = { 113 | map: function(doc) { emit(doc.key, doc); } 114 | }; 115 | db.query(queryFun, {reduce: false, key: 'key2'}, function(_, res) { 116 | equal(res.rows.length, 1, 'Doc with key'); 117 | db.query(queryFun, {reduce: false, key: 'key3'}, function(_, res) { 118 | equal(res.rows.length, 2, 'Multiple docs with key'); 119 | start(); 120 | }); 121 | }); 122 | }); 123 | }); 124 | }); 125 | 126 | asyncTest("Test basic view collation", function() { 127 | 128 | var values = []; 129 | 130 | // special values sort before all other types 131 | values.push(null); 132 | values.push(false); 133 | values.push(true); 134 | 135 | // then numbers 136 | values.push(1); 137 | values.push(2); 138 | values.push(3.0); 139 | values.push(4); 140 | 141 | // then text, case sensitive 142 | // currently chrome uses ascii ordering and so wont handle capitals properly 143 | values.push("a"); 144 | //values.push("A"); 145 | values.push("aa"); 146 | values.push("b"); 147 | //values.push("B"); 148 | values.push("ba"); 149 | values.push("bb"); 150 | 151 | // then arrays. compared element by element until different. 152 | // Longer arrays sort after their prefixes 153 | values.push(["a"]); 154 | values.push(["b"]); 155 | values.push(["b","c"]); 156 | values.push(["b","c", "a"]); 157 | values.push(["b","d"]); 158 | values.push(["b","d", "e"]); 159 | 160 | // then object, compares each key value in the list until different. 161 | // larger objects sort after their subset objects. 162 | values.push({a:1}); 163 | values.push({a:2}); 164 | values.push({b:1}); 165 | values.push({b:2}); 166 | values.push({b:2, a:1}); // Member order does matter for collation. 167 | // CouchDB preserves member order 168 | // but doesn't require that clients will. 169 | // (this test might fail if used with a js engine 170 | // that doesn't preserve order) 171 | values.push({b:2, c:2}); 172 | 173 | initTestDB(this.name, function(err, db) { 174 | var docs = values.map(function(x, i) { 175 | return {_id: (i).toString(), foo: x}; 176 | }); 177 | db.bulkDocs({docs: docs}, {}, function() { 178 | var queryFun = { 179 | map: function(doc) { emit(doc.foo, null); } 180 | }; 181 | db.query(queryFun, {reduce: false}, function(_, res) { 182 | res.rows.forEach(function(x, i) { 183 | ok(JSON.stringify(x.key) === JSON.stringify(values[i]), 'keys collate'); 184 | }); 185 | db.query(queryFun, {descending: true, reduce: false}, function(_, res) { 186 | res.rows.forEach(function(x, i) { 187 | ok(JSON.stringify(x.key) === JSON.stringify(values[values.length - 1 - i]), 188 | 'keys collate descending'); 189 | }); 190 | start(); 191 | }); 192 | }); 193 | }); 194 | }); 195 | }); 196 | 197 | asyncTest("Test joins", function() { 198 | initTestDB(this.name, function(err, db) { 199 | db.bulkDocs({docs: [{_id: 'mydoc', foo: 'bar'}, { doc_id: 'mydoc' }]}, {}, function() { 200 | var queryFun = { 201 | map: function(doc) { 202 | if (doc.doc_id) { 203 | emit(doc._id, {_id: doc.doc_id}); 204 | } 205 | } 206 | }; 207 | db.query(queryFun, {include_docs: true, reduce: false}, function(_, res) { 208 | ok(res.rows[0].doc, 'doc included'); 209 | equal(res.rows[0].doc._id, 'mydoc', 'mydoc included'); 210 | start(); 211 | }); 212 | }); 213 | }); 214 | }); 215 | 216 | asyncTest("No reduce function", function() { 217 | initTestDB(this.name, function(err, db) { 218 | db.post({foo: 'bar'}, function(err, res) { 219 | var queryFun = { 220 | map: function(doc) { 221 | emit('key', 'val'); 222 | } 223 | }; 224 | db.query(queryFun, function(err, res) { 225 | expect(0); 226 | start(); 227 | }); 228 | }); 229 | }); 230 | }); 231 | 232 | asyncTest("No reduce function, passing just a function", function() { 233 | initTestDB(this.name, function(err, db) { 234 | db.post({foo: 'bar'}, function(err, res) { 235 | var queryFun = function(doc) { emit('key', 'val'); }; 236 | db.query(queryFun, function(err, res) { 237 | expect(0); 238 | start(); 239 | }); 240 | }); 241 | }); 242 | }); 243 | 244 | 245 | asyncTest('Views should include _conflicts', function() { 246 | var self = this; 247 | var doc1 = {_id: '1', foo: 'bar'}; 248 | var doc2 = {_id: '1', foo: 'baz'}; 249 | var queryFun = function(doc) { emit(doc._id, !!doc._conflicts); }; 250 | initDBPair(this.name, this.remote, function(db, remote) { 251 | db.post(doc1, function(err, res) { 252 | remote.post(doc2, function(err, res) { 253 | db.replicate.from(remote, function(err, res) { 254 | db.get(doc1._id, {conflicts: true}, function(err, res) { 255 | ok(res._conflicts,'Conflict exists in db'); 256 | db.query(queryFun, function(err, res) { 257 | ok(res.rows[0].value, 'Conflicts included.'); 258 | start(); 259 | }); 260 | }); 261 | }); 262 | }); 263 | }); 264 | }); 265 | }); 266 | 267 | asyncTest("Test view querying with limit option", function() { 268 | initTestDB(this.name, function(err, db) { 269 | db.bulkDocs({ 270 | docs: [ 271 | { foo: 'bar' }, 272 | { foo: 'bar' }, 273 | { foo: 'baz' } 274 | ] 275 | }, null, function() { 276 | 277 | db.query(function (doc) { 278 | if (doc.foo === 'bar') { 279 | emit(doc.foo); 280 | } 281 | }, { limit: 1 }, function (err, res) { 282 | equal(res.total_rows, 2, 'Correctly returns total rows'); 283 | equal(res.rows.length, 1, 'Correctly limits returned rows'); 284 | start(); 285 | }); 286 | 287 | }); 288 | }); 289 | }); 290 | 291 | }); 292 | -------------------------------------------------------------------------------- /tests/webrunner.js: -------------------------------------------------------------------------------- 1 | /*globals $:false, console: false */ 2 | 3 | "use strict"; 4 | 5 | // use query parameter testFiles if present, 6 | // eg: test.html?testFiles=test.basics.js 7 | var testFiles = window.location.search.match(/[?&]testFiles=([^&]+)/); 8 | testFiles = testFiles && testFiles[1].split(',') || []; 9 | var started = new Date(); 10 | if (!testFiles.length) { 11 | // If you want to run performance tests, uncomment these tests 12 | // and comment out the testFiles below 13 | //testFiles = [ 14 | // 'perf.attachments.js' 15 | //]; 16 | 17 | // Temporarily disable auth replication 18 | // 'test.auth_replication.js', 19 | testFiles = ['test.basics.js', 'test.all_dbs.js', 'test.changes.js', 20 | 'test.bulk_docs.js', 'test.all_docs.js', 'test.conflicts.js', 21 | 'test.merge_rev_tree.js', 'test.revs_diff.js', 22 | 'test.replication.js', 'test.views.js', 'test.taskqueue.js', 23 | 'test.design_docs.js', 'test.issue221.js', 'test.http.js', 24 | 'test.gql.js', 'test.compaction.js', 'test.get.js', 25 | 'test.attachments.js']; 26 | } 27 | 28 | testFiles.unshift('test.utils.js'); 29 | 30 | var sourceFiles = { 31 | 'dev': ['../src/deps/uuid.js', '../src/deps/extend.js', '../src/deps/ajax.js', 32 | '../src/pouch.js', '../src/pouch.adapter.js', '../src/pouch.merge.js', 33 | '../src/pouch.replicate.js', 34 | '../src/pouch.collate.js', '../src/pouch.utils.js', 35 | '../src/adapters/pouch.http.js', '../src/adapters/pouch.idb.js', 36 | '../src/adapters/pouch.websql.js', 37 | '../src/plugins/pouchdb.gql.js', 38 | '../src/plugins/pouchdb.mapreduce.js', 39 | '../src/plugins/pouchdb.spatial.js'], 40 | 'release': ['../dist/pouchdb-nightly.js', '../src/deps/extend.js', '../src/deps/ajax.js'], 41 | 'release-min': ['../dist/pouchdb-nightly.min.js', '../src/deps/extend.js', '../src/deps/ajax.js'] 42 | }; 43 | 44 | // Thanks to http://engineeredweb.com/blog/simple-async-javascript-loader/ 45 | function asyncLoadScript(url, callback) { 46 | 47 | // Create a new script and setup the basics. 48 | var script = document.createElement("script"), 49 | firstScript = document.getElementsByTagName('script')[0]; 50 | 51 | script.async = true; 52 | script.src = url; 53 | 54 | // Handle the case where an optional callback was passed in. 55 | if ( "function" === typeof(callback) ) { 56 | script.onload = function() { 57 | callback(); 58 | 59 | // Clear it out to avoid getting called more than once or any memory leaks. 60 | script.onload = script.onreadystatechange = undefined; 61 | }; 62 | script.onreadystatechange = function() { 63 | if ( "loaded" === script.readyState || "complete" === script.readyState ) { 64 | script.onload(); 65 | } 66 | }; 67 | } 68 | 69 | // Attach the script tag to the page (before the first script) so the 70 | //magic can happen. 71 | firstScript.parentNode.insertBefore(script, firstScript); 72 | } 73 | 74 | function startQUnit() { 75 | QUnit.config.reorder = false; 76 | QUnit.begin = function() { 77 | console.log('running!!!'); 78 | }; 79 | } 80 | 81 | function asyncParForEach(array, fn, callback) { 82 | if (array.length === 0) { 83 | callback(); // done immediately 84 | return; 85 | } 86 | var toLoad = array.shift(); 87 | fn(toLoad, function() { 88 | asyncParForEach(array, fn, callback); 89 | }); 90 | } 91 | 92 | var source = window.location.search.match(/[?&]test=([^&]+)/); 93 | source = source && source[1] || 'dev'; 94 | 95 | QUnit.config.testTimeout = 30000; 96 | 97 | /**** Test Result Support ***************/ 98 | function submitResults() { 99 | var notice = document.createElement('p'); 100 | var button = document.getElementById('submit-results'); 101 | button.textContent = 'uploading...'; 102 | button.setAttribute('disabled', 'disabled'); 103 | $.ajax({ 104 | type: 'POST', 105 | url: 'http://localhost:2020/_replicate', 106 | data: JSON.stringify({ 107 | source: 'test_suite_db1', 108 | target: 'http://reupholster.iriscouch.com/pouch_tests' 109 | }), 110 | success: function() { 111 | document.body.classList.add('completed'); 112 | button.style.display = 'none'; 113 | notice.appendChild(document.createTextNode('Submission Complete')); 114 | document.body.appendChild(notice); 115 | }, 116 | error: function() { 117 | document.body.classList.add('completed'); 118 | }, 119 | headers: { 120 | Accept: 'application/json' 121 | }, 122 | dataType: 'json', 123 | contentType: 'application/json' 124 | }); 125 | } 126 | 127 | document.getElementById('submit-results').addEventListener('click', submitResults); 128 | QUnit.jUnitReport = function(report) { 129 | report.started = started; 130 | report.completed = new Date(); 131 | report.passed = (report.results.failed === 0); 132 | window.testReport = report; 133 | }; 134 | 135 | asyncParForEach(sourceFiles[source], asyncLoadScript, function() { 136 | asyncParForEach(testFiles, asyncLoadScript, function() { 137 | startQUnit(); 138 | }); 139 | }); 140 | --------------------------------------------------------------------------------