├── .gitignore ├── .jshintrc ├── .npmignore ├── .travis.yml ├── README.markdown ├── bin ├── dev-server.js ├── run-test.sh └── test-browser.js ├── index.js ├── package.json ├── pouch-utils.js ├── test ├── bind-polyfill.js ├── index.html ├── test.js └── webrunner.js └── vendor └── selenium-server-standalone-2.38.0.jar /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | *~ 4 | coverage 5 | test/test-bundle.js 6 | npm-debug.log 7 | dist 8 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "curly": true, 3 | "eqeqeq": true, 4 | "immed": true, 5 | "newcap": true, 6 | "noarg": true, 7 | "sub": true, 8 | "undef": true, 9 | "unused": true, 10 | "eqnull": true, 11 | "browser": true, 12 | "node": true, 13 | "strict": true, 14 | "globalstrict": true, 15 | "globals": { "Pouch": true}, 16 | "white": true, 17 | "indent": 2, 18 | "maxlen": 100, 19 | "predef": [ 20 | "process", 21 | "global", 22 | "require", 23 | "console", 24 | "describe", 25 | "beforeEach", 26 | "afterEach", 27 | "it", 28 | "emit" 29 | ] 30 | } -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .git* 2 | node_modules 3 | .DS_Store 4 | *~ 5 | coverage 6 | npm-debug.log 7 | vendor/ 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | services: 4 | - couchdb 5 | 6 | node_js: 7 | - "0.10" 8 | script: npm run $COMMAND 9 | before_script: 10 | - "export DISPLAY=:99.0" 11 | - "sh -e /etc/init.d/xvfb start" 12 | - "sleep 5" 13 | 14 | # Workaround for Selenium #3280 issue 15 | - "sudo sed -i 's/^127\\.0\\.0\\.1.*$/127.0.0.1 localhost/' /etc/hosts" 16 | 17 | # Lets know what CouchDB we are playing with 18 | # and make sure its logging debug 19 | - "curl -X GET http://127.0.0.1:5984/" 20 | - "curl -X PUT http://127.0.0.1:5984/_config/log/level -d '\"debug\"'" 21 | 22 | after_failure: 23 | - "curl -X GET http://127.0.0.1:5984/_log?bytes=1000000" 24 | 25 | env: 26 | matrix: 27 | - COMMAND=test 28 | - CLIENT=selenium:firefox COMMAND=test 29 | - CLIENT=selenium:phantomjs COMMAND=test 30 | - COMMAND=coverage 31 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | # pouch-datalog 2 | 3 | Datomic-like Datalog queries for PouchDB! (See [Learn Datalog Today](http://www.learndatalogtoday.org/) for some examples of how Datalog works.) 4 | 5 | The query engine is a fork from [Datascript](https://github.com/tonsky/datascript). 6 | 7 | # Ready? 8 | 9 | Probably not. Give it a spin, make pull requests, and we'll see if we can't get ready! I expect I've added bugs galore to the basic query engine. Contributions gladly welcome. 10 | 11 | # Usage 12 | 13 | ``` 14 | var PouchDB = require('pouchdb'); 15 | PouchDB.plugin(require('pouch-datalog')); 16 | 17 | db.dataquery('[:find ?id \ 18 | :in \ 19 | :where [?id "last_name" "Benson"]]') 20 | .then(function (response) { 21 | console.log( response ); // [['1']], i.e. the id of the document that matched 22 | }); 23 | ``` 24 | 25 | More examples will be forthcoming, but, for now, please see [Learn Datalog Today](http://www.learndatalogtoday.org/), [Datascript](https://github.com/tonsky/datascript), and the [Datomic documentation](http://docs.datomic.com/query.html) for examples that may or may not work with `pouch-datalog`. 26 | 27 | # How? 28 | 29 | [Datomic](http://www.datomic.com/) and [Datascript](https://github.com/tonsky/datascript) make use of the Datalog query engine against `EAV` and `AVE` indexes.[1] They are shorthand for `entity`-`attribute`-`value` and `attribute`-`value`-`entity`. (`entity` corresponds to `id` and `attribute` to `key` in more common Javascript parlance.) We can use Datalog against CouchDB if we also provide such indexes via views. 30 | 31 | `pouch-datalog` expects two views: once called `ave` in a design document called `ave`, and another called `eav` in a design document called `eav`. 32 | 33 | For instance, say that you've emulated [triple store](https://en.wikipedia.org/wiki/Triplestore) in PouchDB, and have documents that look like the following: 34 | 35 | ``` 36 | { 37 | _id: ________, 38 | id: 0, 39 | key: "last_name", 40 | value: "Benson" 41 | } 42 | ``` 43 | 44 | Then, you'd want to have a `map` function in your `ave` view something like the following: 45 | 46 | ``` 47 | function( doc ) { 48 | emit( [doc.key, doc.value, doc.id], [doc.id, doc.key, doc.value] ); // [2] 49 | } 50 | ``` 51 | 52 | and the `eav` something like: 53 | 54 | ``` 55 | function( doc ) { 56 | emit( [doc.id, doc.key, doc.value], [doc.id, doc.key, doc.value] ); // [2] 57 | } 58 | ``` 59 | 60 | Voila! Datalog at your disposal. 61 | 62 | # Do I need to store my data in triples? 63 | 64 | No, you just need indexes in triples. For instance, if you have documents of the form: 65 | 66 | ``` 67 | { 68 | _id: _________, 69 | first_name: "Philip", 70 | last_name: "King", 71 | car: "fast" 72 | } 73 | ``` 74 | 75 | You could create a `ave` view with something like the following: 76 | 77 | ``` 78 | function( doc ) { 79 | for (var key in doc) { 80 | if ( key !== "_id" ) { 81 | emit( [doc._id, key, doc[ key ]], [doc._id, key, doc[ key ]] ); // [2] 82 | } 83 | } 84 | } 85 | ``` 86 | 87 | # Customizing Indexes 88 | 89 | The sky is the limit as to customizing the indexes. Don't include some documents or parameters, transform values before putting them in the index, whatever. 90 | 91 | 92 | # Drawbacks 93 | 94 | - **Speed.** At the very least, you will likely be making several HTTP requests per query. It is very possible there are some possible optimizations here. Additionally, I've read that views are not necessarily as fast as you wish they would be. 95 | - **Indexes**. The indexes will dramatically inflate the amount of storage space your database requires. The trade-off may or may not be worth it, depending on your application. 96 | 97 | # Comparison Against Cloudant Query? 98 | 99 | I don't have any experience with Cloudant query, but I'm guessing that it would have smaller indexes (since they aren't indexing everything, and wouldn't have some of the cool logic programming bits that Datalog does. Cloudant Query is probably far more production-ready, though not generally available just yet. Cloudant query will, of course, eventually be the standard for CouchDB. 100 | 101 | [1] They may actually use 3 or 4 indexes, all of which typically have a fourth term 'T' in them, but we can ignore that for now. 102 | [2] Yes, I realize one shouldn't need to emit anything for the `value` here. Coming soon…pull requests welcome. -------------------------------------------------------------------------------- /bin/dev-server.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | 'use strict'; 4 | 5 | var PouchDB = require('pouchdb'); 6 | var COUCH_HOST = process.env.COUCH_HOST || 'http://127.0.0.1:5984'; 7 | var HTTP_PORT = 8001; 8 | 9 | var Promise = require('bluebird'); 10 | var request = require('request'); 11 | var http_server = require("http-server"); 12 | var fs = require('fs'); 13 | var indexfile = "./test/test.js"; 14 | var dotfile = "./test/.test-bundle.js"; 15 | var outfile = "./test/test-bundle.js"; 16 | var watchify = require("watchify"); 17 | var w = watchify(indexfile); 18 | 19 | w.on('update', bundle); 20 | bundle(); 21 | 22 | var filesWritten = false; 23 | var serverStarted = false; 24 | var readyCallback; 25 | 26 | function bundle() { 27 | var wb = w.bundle(); 28 | wb.on('error', function (err) { 29 | console.error(String(err)); 30 | }); 31 | wb.on("end", end); 32 | wb.pipe(fs.createWriteStream(dotfile)); 33 | 34 | function end() { 35 | fs.rename(dotfile, outfile, function (err) { 36 | if (err) { return console.error(err); } 37 | console.log('Updated:', outfile); 38 | filesWritten = true; 39 | checkReady(); 40 | }); 41 | } 42 | } 43 | 44 | function startServers(callback) { 45 | readyCallback = callback; 46 | // enable CORS globally, because it's easier this way 47 | 48 | var corsValues = { 49 | '/_config/httpd/enable_cors': 'true', 50 | '/_config/cors/origins': '*', 51 | '/_config/cors/credentials': 'true', 52 | '/_config/cors/methods': 'PROPFIND, PROPPATCH, COPY, MOVE, DELETE, ' + 53 | 'MKCOL, LOCK, UNLOCK, PUT, GETLIB, VERSION-CONTROL, CHECKIN, ' + 54 | 'CHECKOUT, UNCHECKOUT, REPORT, UPDATE, CANCELUPLOAD, HEAD, ' + 55 | 'OPTIONS, GET, POST', 56 | '/_config/cors/headers': 57 | 'Cache-Control, Content-Type, Depth, Destination, ' + 58 | 'If-Modified-Since, Overwrite, User-Agent, X-File-Name, ' + 59 | 'X-File-Size, X-Requested-With, accept, accept-encoding, ' + 60 | 'accept-language, authorization, content-type, origin, referer' 61 | }; 62 | 63 | Promise.all(Object.keys(corsValues).map(function (key) { 64 | var value = corsValues[key]; 65 | return request({ 66 | method: 'put', 67 | url: COUCH_HOST + key, 68 | body: JSON.stringify(value) 69 | }); 70 | })).then(function () { 71 | return http_server.createServer().listen(HTTP_PORT); 72 | }).then(function () { 73 | console.log('Tests: http://127.0.0.1:' + HTTP_PORT + '/test/index.html'); 74 | serverStarted = true; 75 | checkReady(); 76 | }).catch(function (err) { 77 | if (err) { 78 | console.log(err); 79 | process.exit(1); 80 | } 81 | }); 82 | } 83 | 84 | function checkReady() { 85 | if (filesWritten && serverStarted && readyCallback) { 86 | readyCallback(); 87 | } 88 | } 89 | 90 | if (require.main === module) { 91 | startServers(); 92 | } else { 93 | module.exports.start = startServers; 94 | } 95 | -------------------------------------------------------------------------------- /bin/run-test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | : ${CLIENT:="node"} 4 | 5 | if [ "$CLIENT" == "node" ]; then 6 | npm run test-node 7 | else 8 | npm run test-browser 9 | fi 10 | -------------------------------------------------------------------------------- /bin/test-browser.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | 4 | var path = require('path'); 5 | var spawn = require('child_process').spawn; 6 | 7 | var wd = require('wd'); 8 | var sauceConnectLauncher = require('sauce-connect-launcher'); 9 | var querystring = require("querystring"); 10 | var request = require('request').defaults({json: true}); 11 | 12 | var devserver = require('./dev-server.js'); 13 | 14 | var SELENIUM_PATH = '../vendor/selenium-server-standalone-2.38.0.jar'; 15 | var SELENIUM_HUB = 'http://localhost:4444/wd/hub/status'; 16 | 17 | var testTimeout = 30 * 60 * 1000; 18 | 19 | var username = process.env.SAUCE_USERNAME; 20 | var accessKey = process.env.SAUCE_ACCESS_KEY; 21 | 22 | // process.env.CLIENT is a colon seperated list of 23 | // (saucelabs|selenium):browserName:browserVerion:platform 24 | var tmp = (process.env.CLIENT || 'selenium:firefox').split(':'); 25 | var client = { 26 | runner: tmp[0] || 'selenium', 27 | browser: tmp[1] || 'firefox', 28 | version: tmp[2] || null, // Latest 29 | platform: tmp[3] || null 30 | }; 31 | 32 | var testUrl = 'http://127.0.0.1:8001/test/index.html'; 33 | var qs = {}; 34 | 35 | var sauceClient; 36 | var sauceConnectProcess; 37 | var tunnelId = process.env.TRAVIS_JOB_NUMBER || 'tunnel-' + Date.now(); 38 | 39 | if (client.runner === 'saucelabs') { 40 | qs.saucelabs = true; 41 | } 42 | if (process.env.GREP) { 43 | qs.grep = process.env.GREP; 44 | } 45 | if (process.env.ADAPTERS) { 46 | qs.adapters = process.env.ADAPTERS; 47 | } 48 | if (process.env.ES5_SHIM || process.env.ES5_SHIMS) { 49 | qs.es5shim = true; 50 | } 51 | testUrl += '?'; 52 | testUrl += querystring.stringify(qs); 53 | 54 | if (process.env.TRAVIS && 55 | client.browser !== 'firefox' && 56 | client.browser !== 'phantomjs' && 57 | process.env.TRAVIS_SECURE_ENV_VARS === 'false') { 58 | console.error('Not running test, cannot connect to saucelabs'); 59 | process.exit(1); 60 | return; 61 | } 62 | 63 | function testError(e) { 64 | console.error(e); 65 | console.error('Doh, tests failed'); 66 | sauceClient.quit(); 67 | process.exit(3); 68 | } 69 | 70 | function postResult(result) { 71 | process.exit(!process.env.PERF && result.failed ? 1 : 0); 72 | } 73 | 74 | function testComplete(result) { 75 | console.log(result); 76 | 77 | sauceClient.quit().then(function () { 78 | if (sauceConnectProcess) { 79 | sauceConnectProcess.close(function () { 80 | postResult(result); 81 | }); 82 | } else { 83 | postResult(result); 84 | } 85 | }); 86 | } 87 | 88 | function startSelenium(callback) { 89 | 90 | // Start selenium 91 | spawn('java', ['-jar', path.resolve(__dirname, SELENIUM_PATH)], {}); 92 | 93 | var retries = 0; 94 | var started = function () { 95 | 96 | if (++retries > 30) { 97 | console.error('Unable to connect to selenium'); 98 | process.exit(1); 99 | return; 100 | } 101 | 102 | request(SELENIUM_HUB, function (err, resp) { 103 | if (resp && resp.statusCode === 200) { 104 | sauceClient = wd.promiseChainRemote(); 105 | callback(); 106 | } else { 107 | setTimeout(started, 1000); 108 | } 109 | }); 110 | }; 111 | 112 | started(); 113 | 114 | } 115 | 116 | function startSauceConnect(callback) { 117 | 118 | var options = { 119 | username: username, 120 | accessKey: accessKey, 121 | tunnelIdentifier: tunnelId 122 | }; 123 | 124 | sauceConnectLauncher(options, function (err, process) { 125 | if (err) { 126 | console.error('Failed to connect to saucelabs'); 127 | console.error(err); 128 | return process.exit(1); 129 | } 130 | sauceConnectProcess = process; 131 | sauceClient = wd.promiseChainRemote("localhost", 4445, username, accessKey); 132 | callback(); 133 | }); 134 | } 135 | 136 | function startTest() { 137 | 138 | console.log('Starting', client); 139 | 140 | var opts = { 141 | browserName: client.browser, 142 | version: client.version, 143 | platform: client.platform, 144 | tunnelTimeout: testTimeout, 145 | name: client.browser + ' - ' + tunnelId, 146 | 'max-duration': 60 * 30, 147 | 'command-timeout': 599, 148 | 'idle-timeout': 599, 149 | 'tunnel-identifier': tunnelId 150 | }; 151 | 152 | sauceClient.init(opts).get(testUrl, function () { 153 | 154 | /* jshint evil: true */ 155 | var interval = setInterval(function () { 156 | sauceClient.eval('window.results', function (err, results) { 157 | if (err) { 158 | clearInterval(interval); 159 | testError(err); 160 | } else if (results.completed || results.failures.length) { 161 | clearInterval(interval); 162 | testComplete(results); 163 | } else { 164 | console.log('=> ', results); 165 | } 166 | }); 167 | }, 10 * 1000); 168 | }); 169 | } 170 | 171 | devserver.start(function () { 172 | if (client.runner === 'saucelabs') { 173 | startSauceConnect(startTest); 174 | } else { 175 | startSelenium(startTest); 176 | } 177 | }); 178 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var utils = require('./pouch-utils'); 4 | var datascript = require('datascript-async'); 5 | 6 | var lowerCase = function (key) { 7 | if (!key) { 8 | return key; 9 | } 10 | return key.toLowerCase(); 11 | }; 12 | 13 | exports.dataquery = utils.toPromise(function (query, dataquery_callback) { 14 | var self = this; 15 | var searchPouchIndex = function (db, index) { 16 | return function (startkey, endkey, index_callback) { 17 | var view = index + "/" + index; 18 | if (index === "eav") { 19 | startkey = [startkey.e, startkey.a, startkey.v]; 20 | endkey = [endkey.e, endkey.a, endkey.v]; 21 | } else if (index === "ave") { 22 | startkey = [startkey.a, startkey.v, startkey.e]; 23 | endkey = [endkey.a, endkey.v, endkey.e]; 24 | } 25 | 26 | startkey = startkey.map(lowerCase); 27 | endkey = endkey.map(lowerCase); 28 | 29 | endkey = endkey.map(function (el) { 30 | if (el === null) { 31 | return {}; 32 | } 33 | return el; 34 | }); 35 | return db.query(view, { 36 | startkey: startkey, 37 | endkey: endkey 38 | }).then(function (data) { 39 | var result = data.rows.map(function (el) { 40 | return el.value; 41 | }); 42 | index_callback(result); 43 | }).catch(function (error) { 44 | console.error("An error occured.", error); 45 | }); 46 | }; 47 | }; 48 | 49 | var db = datascript.set_indexes(searchPouchIndex(self, "eav"), searchPouchIndex(self, "ave")); 50 | 51 | return datascript.q(function (err, data) { 52 | if (err) { 53 | return err; 54 | } 55 | return dataquery_callback(null, data); 56 | }, query, db); 57 | }); 58 | 59 | /* istanbul ignore next */ 60 | if (typeof window !== 'undefined' && window.PouchDB) { 61 | window.PouchDB.plugin(exports); 62 | } 63 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pouch-datalog", 3 | "version": "1.0.0", 4 | "description": "Datalog query engine for PouchDB.", 5 | "main": "index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "git://github.com/dahjelle/pouch-datalog.git" 9 | }, 10 | "keywords": [ 11 | "pouch", 12 | "pouchdb", 13 | "datalog", 14 | "query", 15 | "couch", 16 | "couchdb" 17 | ], 18 | "author": "David Alan Hjelle dist/pouchdb.datalog.min.js", 30 | "dev": "browserify test/test.js > test/test-bundle.js && npm run dev-server", 31 | "dev-server": "./bin/dev-server.js", 32 | "coverage": "npm test --coverage && istanbul check-coverage --lines 100 --function 100 --statements 100 --branches 100" 33 | }, 34 | "dependencies": { 35 | "argsarray": "0.0.1", 36 | "datascript-async": "0.1.3", 37 | "es3ify": "^0.1.3", 38 | "inherits": "~2.0.1", 39 | "istanbul": "^0.3.2", 40 | "lie": "^2.6.0" 41 | }, 42 | "devDependencies": { 43 | "bluebird": "^1.0.7", 44 | "browserify": "~2.36.0", 45 | "chai": "~1.8.1", 46 | "chai-as-promised": "~4.1.0", 47 | "http-server": "~0.5.5", 48 | "istanbul": "^0.2.7", 49 | "jshint": "~2.3.0", 50 | "mocha": "~1.18", 51 | "phantomjs": "^1.9.7-5", 52 | "pouchdb": "pouchdb/pouchdb", 53 | "request": "^2.36.0", 54 | "sauce-connect-launcher": "^0.4.2", 55 | "uglify-js": "^2.4.13", 56 | "watchify": "~0.4.1", 57 | "wd": "^0.2.21" 58 | }, 59 | "browser": { 60 | "crypto": false 61 | }, 62 | "browserify": { 63 | "transform": [ 64 | "es3ify" 65 | ] 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /pouch-utils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Promise; 4 | /* istanbul ignore next */ 5 | if (typeof window !== 'undefined' && window.PouchDB) { 6 | Promise = window.PouchDB.utils.Promise; 7 | } else { 8 | Promise = typeof global.Promise === 'function' ? global.Promise : require('lie'); 9 | } 10 | /* istanbul ignore next */ 11 | exports.once = function (fun) { 12 | var called = false; 13 | return exports.getArguments(function (args) { 14 | if (called) { 15 | console.trace(); 16 | throw new Error('once called more than once'); 17 | } else { 18 | called = true; 19 | fun.apply(this, args); 20 | } 21 | }); 22 | }; 23 | /* istanbul ignore next */ 24 | exports.getArguments = function (fun) { 25 | return function () { 26 | var len = arguments.length; 27 | var args = new Array(len); 28 | var i = -1; 29 | while (++i < len) { 30 | args[i] = arguments[i]; 31 | } 32 | return fun.call(this, args); 33 | }; 34 | }; 35 | /* istanbul ignore next */ 36 | exports.toPromise = function (func) { 37 | //create the function we will be returning 38 | return exports.getArguments(function (args) { 39 | var self = this; 40 | var tempCB = (typeof args[args.length - 1] === 'function') ? args.pop() : false; 41 | // if the last argument is a function, assume its a callback 42 | var usedCB; 43 | if (tempCB) { 44 | // if it was a callback, create a new callback which calls it, 45 | // but do so async so we don't trap any errors 46 | usedCB = function (err, resp) { 47 | process.nextTick(function () { 48 | tempCB(err, resp); 49 | }); 50 | }; 51 | } 52 | var promise = new Promise(function (fulfill, reject) { 53 | try { 54 | var callback = exports.once(function (err, mesg) { 55 | if (err) { 56 | reject(err); 57 | } else { 58 | fulfill(mesg); 59 | } 60 | }); 61 | // create a callback for this invocation 62 | // apply the function in the orig context 63 | args.push(callback); 64 | func.apply(self, args); 65 | } catch (e) { 66 | reject(e); 67 | } 68 | }); 69 | // if there is a callback, call it back 70 | if (usedCB) { 71 | promise.then(function (result) { 72 | usedCB(null, result); 73 | }, usedCB); 74 | } 75 | promise.cancel = function () { 76 | return this; 77 | }; 78 | return promise; 79 | }); 80 | }; 81 | 82 | exports.inherits = require('inherits'); 83 | exports.Promise = Promise; 84 | -------------------------------------------------------------------------------- /test/bind-polyfill.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | // minimal polyfill for phantomjs; in the future, we should do ES5_SHIM=true like pouchdb 4 | if (!Function.prototype.bind) { 5 | Function.prototype.bind = function (oThis) { 6 | if (typeof this !== "function") { 7 | // closest thing possible to the ECMAScript 5 8 | // internal IsCallable function 9 | throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable"); 10 | } 11 | 12 | var aArgs = Array.prototype.slice.call(arguments, 1), 13 | fToBind = this, 14 | fNOP = function () {}, 15 | fBound = function () { 16 | return fToBind.apply(this instanceof fNOP && oThis 17 | ? this 18 | : oThis, 19 | aArgs.concat(Array.prototype.slice.call(arguments))); 20 | }; 21 | 22 | fNOP.prototype = this.prototype; 23 | fBound.prototype = new fNOP(); 24 | 25 | return fBound; 26 | }; 27 | } 28 | })(); 29 | -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Mocha Tests 6 | 7 | 8 | 9 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | /*jshint expr:true,multistr:true */ 2 | 'use strict'; 3 | 4 | var Pouch = require('pouchdb'); 5 | 6 | // 7 | // your plugin goes here 8 | // 9 | var dataqueryPlugin = require('../'); 10 | Pouch.plugin(dataqueryPlugin); 11 | 12 | var chai = require('chai'); 13 | chai.use(require("chai-as-promised")); 14 | 15 | // 16 | // more variables you might want 17 | // 18 | chai.should(); // var should = chai.should(); 19 | require('bluebird'); // var Promise = require('bluebird'); 20 | 21 | var dbs; 22 | if (process.browser) { 23 | dbs = 'testdb' + Math.random() + 24 | ',http://localhost:5984/testdb' + Math.round(Math.random() * 100000); 25 | } else { 26 | dbs = process.env.TEST_DB; 27 | } 28 | 29 | dbs.split(',').forEach(function (db) { 30 | var dbType = /^http/.test(db) ? 'http' : 'local'; 31 | tests(db, dbType); 32 | }); 33 | 34 | function tests(dbName, dbType) { 35 | 36 | var db; 37 | 38 | beforeEach(function (done) { 39 | db = new Pouch(dbName); 40 | db.bulkDocs([ 41 | { 42 | _id: "1", 43 | label: "last_name", 44 | value: "Benson" 45 | }, 46 | { 47 | _id: "2", 48 | label: "first_name", 49 | value: "George" 50 | }, 51 | { 52 | _id: "3", 53 | label: "last_name", 54 | value: "Henderson" 55 | }, 56 | { 57 | _id: "4", 58 | label: "first_name", 59 | value: "george" 60 | } 61 | ]).then(function () { 62 | return db.put({ 63 | _id: "_design/eav", 64 | language: "javascript", 65 | views: { 66 | eav: { 67 | map: "function(doc) { \ 68 | emit([ \ 69 | doc._id, doc.label.toLowerCase(), doc.value.toLowerCase() \ 70 | ], [ \ 71 | doc._id, doc.label, doc.value \ 72 | ]); \ 73 | }" 74 | } 75 | } 76 | }); 77 | }).then(function () { 78 | return db.put({ 79 | _id: "_design/ave", 80 | language: "javascript", 81 | views: { 82 | ave: { 83 | map: "function(doc) { \ 84 | emit([ \ 85 | doc.label.toLowerCase(), doc.value.toLowerCase(), doc._id \ 86 | ], [ \ 87 | doc._id, doc.label, doc.value \ 88 | ]); \ 89 | }" 90 | } 91 | } 92 | }); 93 | }).then(function () { 94 | done(); 95 | }); 96 | }); 97 | afterEach(function () { 98 | return db.destroy(); 99 | }); 100 | describe(dbType + ': dataquery test suite', function () { 101 | it('should have a dataquery method that works', function (done) { 102 | db.dataquery('[:find ?id \ 103 | :in \ 104 | :where [?id "last_name" "Benson"]]').then(function (response) { 105 | response.should.eql([['1']]); 106 | done(); 107 | }).catch(function (err) { 108 | console.error(err); 109 | }); 110 | }); 111 | it('case insensitivity works (if views are set up with lowercasing as above)', function (done) { 112 | db.dataquery('[:find ?id ?first_name \ 113 | :in \ 114 | :where [?id "first_name" "george"] \ 115 | [?id "first_name" ?first_name]]').then(function (response) { 116 | response.should.eql([['2', 'George'], ['4', 'george']]); 117 | done(); 118 | }).catch(function (err) { 119 | console.error(err); 120 | }); 121 | }); 122 | }); 123 | } 124 | -------------------------------------------------------------------------------- /test/webrunner.js: -------------------------------------------------------------------------------- 1 | /* global mocha: true */ 2 | 3 | (function () { 4 | 'use strict'; 5 | var runner = mocha.run(); 6 | window.results = { 7 | lastPassed: '', 8 | passed: 0, 9 | failed: 0, 10 | failures: [] 11 | }; 12 | 13 | runner.on('pass', function (e) { 14 | window.results.lastPassed = e.title; 15 | window.results.passed++; 16 | }); 17 | 18 | runner.on('fail', function (e) { 19 | window.results.failed++; 20 | window.results.failures.push({ 21 | title: e.title, 22 | message: e.err.message, 23 | stack: e.err.stack 24 | }); 25 | }); 26 | 27 | runner.on('end', function () { 28 | window.results.completed = true; 29 | window.results.passed++; 30 | }); 31 | })(); 32 | 33 | 34 | -------------------------------------------------------------------------------- /vendor/selenium-server-standalone-2.38.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dahjelle/pouch-datalog/ee4826ed0fce93eeca6879c02ec33806b8a2ce34/vendor/selenium-server-standalone-2.38.0.jar --------------------------------------------------------------------------------