├── .gitignore ├── .npmignore ├── LICENSE-Apache ├── .travis.yml ├── package.json ├── README.md ├── lib └── coax.js └── test └── coax_test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | -------------------------------------------------------------------------------- /LICENSE-Apache: -------------------------------------------------------------------------------- 1 | What is this I don’t even -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 0.8 4 | - 0.6 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "coax", 3 | "description": "Couch client using pax for path currying and request for HTTP.", 4 | "version": "0.4.2", 5 | "homepage": "https://github.com/jchris/coax", 6 | "author": { 7 | "name": "Chris Anderson", 8 | "email": "jchris@couchbase.com", 9 | "url": "http://www.couchbase.com" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git://github.com/jchris/coax.git" 14 | }, 15 | "bugs": { 16 | "url": "https://github.com/jchris/coax/issues" 17 | }, 18 | "licenses": [ 19 | { 20 | "type": "Apache", 21 | "url": "https://github.com/jchris/coax/blob/master/LICENSE-Apache" 22 | } 23 | ], 24 | "main": "lib/coax", 25 | "engines": { 26 | "node": "~0.8.16" 27 | }, 28 | "scripts": { 29 | "test": "grunt test" 30 | }, 31 | "dependencies": { 32 | "hoax" : "0.2", 33 | "pax" : "0.2" 34 | }, 35 | "devDependencies": { 36 | "grunt": "~0.3.6", 37 | "request" : "2.12", 38 | "grunt-browserify": "~0.1" 39 | }, 40 | "keywords": [] 41 | } 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # coax 2 | 3 | Couch client using pax for path currying and request for HTTP. 4 | 5 | [![Build Status](https://travis-ci.org/jchris/coax.png?branch=master)](https://travis-ci.org/jchris/coax) 6 | 7 | ## Getting Started 8 | Install the module with: `npm install coax` 9 | 10 | ### Example code illustrating URL currying 11 | 12 | ```javascript 13 | var server = coax("http://server.com") 14 | 15 | // just for example 16 | var database = server("my-database") 17 | 18 | 19 | database.get(function(error, data){ 20 | // did a get to http://server.com/my-database 21 | }) 22 | 23 | database.get("foobar", function(error, data){ 24 | // did a get to http://server.com/my-database/foobar 25 | }) 26 | 27 | // note the array 28 | database.get(["foobar", "baz"], function(error, data){ 29 | // did a get to http://server.com/my-database/foobar/baz 30 | }) 31 | 32 | // still an array, with query options 33 | database.get(["foobar", "baz", {"x" : "y"}], function(error, data){ 34 | // did a get to http://server.com/my-database/foobar/baz?x=y 35 | }) 36 | 37 | // also we can curry it again 38 | var doc = database(["foobar","baz"]) 39 | doc.get(function(error, data){ 40 | // did a get to http://server.com/my-database/foobar/baz 41 | }) 42 | 43 | ``` 44 | 45 | 46 | See [hoax](https://github.com/jchris/hoax) for documentation. Coax is like hoax, but with some Couch-specific things (like JSON encoding key / startkey / endkey). 47 | 48 | ## License 49 | Copyright (c) 2013 Chris Anderson 50 | Licensed under the Apache license. 51 | -------------------------------------------------------------------------------- /lib/coax.js: -------------------------------------------------------------------------------- 1 | /* 2 | * coax 3 | * https://github.com/jchris/coax 4 | * 5 | * Copyright (c) 2013 Chris Anderson 6 | * Licensed under the Apache license. 7 | */ 8 | var pax = require("pax"), 9 | hoax = require("hoax"); 10 | 11 | var coaxPax = pax(); 12 | 13 | coaxPax.extend("getQuery", function(params) { 14 | params = JSON.parse(JSON.stringify(params)); 15 | var key, keys = ["key", "startkey", "endkey", "start_key", "end_key"]; 16 | for (var i = 0; i < keys.length; i++) { 17 | key = keys[i]; 18 | if (params[key]) { 19 | params[key] = JSON.stringify(params[key]); 20 | } 21 | } 22 | return params; 23 | }); 24 | 25 | var Coax = module.exports = hoax.makeHoax(coaxPax()); 26 | 27 | Coax.extend("changes", function(opts, cb) { 28 | if (typeof opts === "function") { 29 | cb = opts; 30 | opts = {}; 31 | } 32 | var self = this; 33 | opts = opts || {}; 34 | 35 | 36 | if (opts.feed == "continuous") { 37 | var listener = self(["_changes", opts], function(err, ok) { 38 | if (err && err.code == "ETIMEDOUT") { 39 | return self.changes(opts, cb); // TODO retry limit? 40 | } else if (err) { 41 | return cb(err); 42 | } 43 | }); 44 | listener.on("data", function(data){ 45 | var sep = "\n"; 46 | 47 | // re-emit chunked json data 48 | eom = data.toString().indexOf(sep) 49 | msg = data.toString().substring(0, eom) 50 | remaining = data.toString().substring(eom + 1, data.length) 51 | if (remaining.length > 0){ 52 | // console.log(data.toString()) 53 | listener.emit("data", remaining) 54 | } 55 | 56 | var json = JSON.parse(msg); 57 | cb(false, json) 58 | }) 59 | return listener; 60 | } else { 61 | opts.feed = "longpoll"; 62 | // opts.since = opts.since || 0; 63 | // console.log("change opts "+JSON.stringify(opts)); 64 | return self(["_changes", opts], function(err, ok) { 65 | if (err && err.code == "ETIMEDOUT") { 66 | return self.changes(opts, cb); // TODO retry limit? 67 | } else if (err) { 68 | return cb(err); 69 | } 70 | // console.log("changes", ok) 71 | ok.results.forEach(function(row){ 72 | cb(null, row); 73 | }); 74 | opts.since = ok.last_seq; 75 | self.changes(opts, cb); 76 | }); 77 | } 78 | }); 79 | 80 | Coax.extend("forceSave", function(doc, cb) { 81 | var api = this(doc._id); 82 | // console.log("forceSave "+api.pax); 83 | api.get(function(err, old) { 84 | if (err && err.error !== "not_found") { 85 | return cb(err); 86 | } 87 | if (!err) { 88 | doc._rev = old._rev; 89 | } 90 | // console.log("forceSave put", api.pax, doc._rev) 91 | api.put(doc, cb); 92 | }); 93 | }); 94 | 95 | 96 | Coax.extend("channels", function(channels, opts) { 97 | var self = this; 98 | var opts = opts || {}; 99 | 100 | opts.filter = "sync_gateway/bychannel"; 101 | opts.feed = "continuous" 102 | opts.channels = channels.join(',') 103 | 104 | // console.log(self.pax.toString()) 105 | var x = function(){}; 106 | x.request = true; 107 | var changes = self(['_changes', opts], x); 108 | changes.on("data", function(data) { 109 | var json; 110 | try{ 111 | var json = JSON.parse(data.toString()) 112 | }catch(e){ 113 | console.log("not json", data.toString()) 114 | } 115 | if (json) { 116 | changes.emit("json", json) 117 | } 118 | }) 119 | return changes; 120 | }); 121 | -------------------------------------------------------------------------------- /test/coax_test.js: -------------------------------------------------------------------------------- 1 | var coax = require('../lib/coax.js'), 2 | request = require("request"); 3 | 4 | var http = require("http"), url = require("url"), qs = require("querystring"); 5 | 6 | var handlers = {}; 7 | 8 | var testServer = function() { 9 | http.createServer(function(req, res){ 10 | // your custom error-handling logic: 11 | function error(status, err) { 12 | res.statusCode = status || 500; 13 | res.end(err.toString()); 14 | } 15 | var path = url.parse(req.url).pathname; 16 | console.log("test server", req.method, req.url); 17 | if (handlers[path]) { 18 | handlers[path](req, res); 19 | } else { 20 | error(404, "no handler for "+path); 21 | } 22 | }).listen(3001); 23 | }; 24 | 25 | testServer(); 26 | 27 | exports['/awesome'] = { 28 | setUp: function(done) { 29 | // setup here 30 | handlers['/awesome'] = function(req, res) { 31 | res.statusCode = 200; 32 | res.end(JSON.stringify({awesome:true})); 33 | }; 34 | handlers['/very/awesome'] = function(req, res) { 35 | res.statusCode = 200; 36 | res.end(JSON.stringify({awesome:true, method : req.method})); 37 | }; 38 | handlers['/very/awesome/coat'] = function(req, res) { 39 | res.statusCode = 200; 40 | res.end(JSON.stringify({coat:true, method : req.method})); 41 | }; 42 | done(); 43 | }, 44 | '200 get': function(test) { 45 | test.expect(2); 46 | // tests here 47 | coax("http://localhost:3001/awesome", function(err, json){ 48 | // console.log(ok.statusCode, body); 49 | test.equal(err, null); 50 | test.equal(json.awesome, true, 'should be awesome.'); 51 | test.done(); 52 | }); 53 | }, 54 | '200 array' : function(test) { 55 | // test.expect() 56 | coax(["http://localhost:3001/","very","awesome"], function(err, json){ 57 | test.equal(err, null); 58 | test.equal(json.awesome, true, 'should be awesome.'); 59 | test.equal(json.method, 'GET', 'should be get.'); 60 | test.done(); 61 | }); 62 | }, 63 | '200 put' : function(test) { 64 | // test.expect() 65 | coax.put("http://localhost:3001/very/awesome", function(err, json){ 66 | test.equal(err, null); 67 | test.equal(json.awesome, true, 'should be awesome.'); 68 | test.equal(json.method, 'PUT', 'should be put.'); 69 | test.done(); 70 | }); 71 | }, 72 | '200 array put' : function(test) { 73 | // test.expect() 74 | coax.put(["http://localhost:3001/","very","awesome"], function(err, json){ 75 | test.equal(err, null); 76 | test.equal(json.awesome, true, 'should be awesome.'); 77 | test.equal(json.method, 'PUT', 'should be put.'); 78 | test.done(); 79 | }); 80 | }, 81 | '200 post' : function(test) { 82 | test.expect(3); 83 | coax.post("http://localhost:3001/very/awesome", function(err, json){ 84 | test.equal(err, null); 85 | test.equal(json.awesome, true, 'should be awesome.'); 86 | test.equal(json.method, 'POST', 'should be put.'); 87 | test.done(); 88 | }); 89 | }, 90 | '200 array curry' : function(test) { 91 | // test.expect() 92 | var host = coax("http://localhost:3001/"), 93 | resource = host(["very","awesome"]); 94 | resource("coat", function(err, json){ 95 | test.equal(err, null); 96 | test.equal(json.coat, true, 'should be coat.'); 97 | test.equal(json.method, 'GET', 'should be get.'); 98 | test.done(); 99 | }); 100 | }, 101 | '200 array no path' : function(test) { 102 | // test.expect() 103 | var host = coax("http://localhost:3001/"), 104 | resource = host(["very","awesome"]); 105 | resource(function(err, json){ 106 | test.equal(err, null); 107 | test.equal(json.awesome, true, 'should be awesome.'); 108 | test.equal(json.method, 'GET', 'should be get.'); 109 | test.done(); 110 | }); 111 | }, 112 | '200 array curry put' : function(test) { 113 | // test.expect() 114 | var host = coax("http://localhost:3001/"), 115 | resource = host(["very","awesome"]); 116 | resource.put(function(err, json){ 117 | test.equal(err, null); 118 | test.equal(json.awesome, true, 'should be awesome.'); 119 | test.equal(json.method, 'PUT', 'should be put.'); 120 | test.done(); 121 | }); 122 | }, 123 | 'put curry' : function(test) { 124 | // test.expect() 125 | var host = coax("http://localhost:3001/"), 126 | resource = host.put(["very","awesome"]); 127 | resource("coat",function(err, json){ 128 | test.equal(err, null); 129 | test.equal(json.coat, true, 'should be awesome.'); 130 | test.equal(json.method, 'PUT', 'should be put.'); 131 | test.done(); 132 | }); 133 | } 134 | }; 135 | 136 | var query = coax("http://localhost:3001/query"); 137 | exports['/query'] = { 138 | setUp: function(done) { 139 | // setup here 140 | handlers['/query'] = function(req, res) { 141 | res.statusCode = 200; 142 | res.end(JSON.stringify({url:req.url, method : req.method})); 143 | }; 144 | done(); 145 | }, 146 | 'get': function(test) { 147 | // test.expect(2); 148 | // tests here 149 | query({myquery:"exciting"}, function(err, json){ 150 | // console.log(ok.statusCode, body); 151 | test.equal(err, null); 152 | test.ok(json.url, 'should have query'); 153 | // test.ok(json.query.myquery, 'should have query'); 154 | test.deepEqual(json.url, "/query?myquery=exciting", 'should be "exciting".'); 155 | test.done(); 156 | }); 157 | }, 158 | 'get startkey etc': function(test) { 159 | // test.expect(2); 160 | // tests here 161 | 162 | query({startkey:[1,2], endkey:1, key:"ok",other:"ok"}, function(err, json){ 163 | // console.log(ok.statusCode, body); 164 | test.equal(err, null); 165 | test.ok(json.url, 'should have query'); 166 | // test.ok(json.query.myquery, 'should have query'); 167 | test.deepEqual(json.url, "/query?startkey=%5B1%2C2%5D&endkey=1&key=%22ok%22&other=ok", 'should be "[1,2]".'); 168 | test.done(); 169 | }); 170 | } 171 | }; 172 | 173 | exports["/body"] = { 174 | setUp: function(done) { 175 | // setup here 176 | handlers['/body'] = function(req, res) { 177 | var body = ""; 178 | req.on("data", function(data) {body = body + data;}); 179 | req.on("end", function(){ 180 | res.statusCode = 200; 181 | res.end(JSON.stringify({url:req.url, method : req.method, body:body})); 182 | }); 183 | }; 184 | done(); 185 | }, 186 | 'post': function(test) { 187 | // test.expect(2); 188 | // tests here 189 | var db = coax("http://localhost:3001/body"); 190 | db.post({'my':"doc"}, 191 | function(errJSON, res){ 192 | // console.log(ok.statusCode, body); 193 | test.equal(res.url, "/body", 'body'); 194 | test.equal(res.method, "POST", 'post'); 195 | test.equal(res.body, '{"my":"doc"}'); 196 | test.done(); 197 | }); 198 | } 199 | }; 200 | 201 | exports['/error'] = { 202 | setUp: function(done) { 203 | // setup here 204 | handlers['/error'] = function(req, res) { 205 | res.statusCode = 404; 206 | res.end(JSON.stringify({error:true})); 207 | }; 208 | done(); 209 | }, 210 | '404': function(test) { 211 | // test.expect(2); 212 | // tests here 213 | coax("http://localhost:3001/error?status=404", 214 | function(errJSON, res){ 215 | // console.log(ok.statusCode, body); 216 | test.equal(res.statusCode, 404, 'response is second argument on error'); 217 | test.equal(errJSON.error, true, 'error should be body when parsed json and error code'); 218 | test.done(); 219 | }); 220 | } 221 | }; 222 | 223 | exports['_changes'] = { 224 | setUp: function(done) { 225 | // setup here 226 | handlers['/db/_changes'] = function(req, res) { 227 | res.statusCode = 200; 228 | var uri = qs.parse(url.parse(req.url).query); 229 | if (uri.since === "0") { 230 | res.end(JSON.stringify({last_seq:10, results:[1,2,3], url:req.url})); 231 | } else { 232 | res.end(JSON.stringify({last_seq:12, results:[4,5], url:req.url})); 233 | } 234 | }; 235 | done(); 236 | }, 237 | 'basics': function(test) { 238 | test.expect(5); 239 | // tests here 240 | var db = coax("http://localhost:3001/db"); 241 | db.changes(function(err, change){ 242 | test.ok(true, "change"); 243 | if (change === 5) { 244 | test.done(); 245 | } 246 | }); 247 | } 248 | }; 249 | 250 | 251 | --------------------------------------------------------------------------------