Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

# 0.7.0

* added support for arbitrary paths in
* document paths are now EncodeURIComponent by default, use path to override
* new error handling code, hopefully more readable
* added auto db.use when a database is present in the uri that was provided
* updated to request 2.1.1 on frozen deps
  • Loading branch information...
commit ecd5b1a1d9ac3fe1255ac8d3e374dcb3a4fb4a37 1 parent 403949c
@dscape authored
7 CHANGELOG.md
@@ -0,0 +1,7 @@
+# 0.7.0
+
+* added support for arbitrary paths in `nano.request`
+* document paths are now EncodeURIComponent by default, use path to override
+* new error handling code, hopefully more readable
+* added auto db.use when a database is present in the uri that was provided
+* updated to request 2.1.1 on frozen deps
View
18 LICENSE
@@ -1,13 +1,13 @@
-Copyright 2011 Nuno Job <nunojob.com>
+copyright 2011 nuno job <nunojob.com> (oO)--',--
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
+licensed under the apache license, version 2.0 (the "license");
+you may not use this file except in compliance with the license.
+you may obtain a copy of the license at
http://www.apache.org/licenses/LICENSE-2.0
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
+unless required by applicable law or agreed to in writing, software
+distributed under the license is distributed on an "as is" basis,
+without warranties or conditions of any kind, either express or implied.
+see the license for the specific language governing permissions and
+limitations under the license.
View
2  README.md
@@ -123,7 +123,7 @@ to get a document in a specific rev an advanced user might do:
function (_,_,b) { console.log(b) }
);
-this is the same as (assuming `alice = nano.use("alice");`):
+this is the same as (assuming `alice = require('nano')('http://localhost:5984/alice')`):
alice.get("rabbit", {rev: "1-967a00dff5e02add41819138abb3284d"},
function (_,_,b) { console.log(b) }
View
4 cfg/tests.js
@@ -30,4 +30,8 @@ cfg.url = function (){
":" + cfg.port;
}();
+cfg.db_url = function (postfix) {
+ return cfg.url + '/' + postfix;
+};
+
module.exports = exports = cfg;
View
123 error.js
@@ -1,55 +1,56 @@
-var STATUS_CODES = { '100': 'Continue',
- '101': 'Switching Protocols',
- '102': 'Processing',
- '200': 'OK',
- '201': 'Created',
- '202': 'Accepted',
- '203': 'Non-Authoritative Information',
- '204': 'No Content',
- '205': 'Reset Content',
- '206': 'Partial Content',
- '207': 'Multi-Status',
- '300': 'Multiple Choices',
- '301': 'Moved Permanently',
- '302': 'Moved Temporarily',
- '303': 'See Other',
- '304': 'Not Modified',
- '305': 'Use Proxy',
- '307': 'Temporary Redirect',
- '400': 'Bad Request',
- '401': 'Unauthorized',
- '402': 'Payment Required',
- '403': 'Forbidden',
- '404': 'Not Found',
- '405': 'Method Not Allowed',
- '406': 'Not Acceptable',
- '407': 'Proxy Authentication Required',
- '408': 'Request Time-out',
- '409': 'Conflict',
- '410': 'Gone',
- '411': 'Length Required',
- '412': 'Precondition Failed',
- '413': 'Request Entity Too Large',
- '414': 'Request-URI Too Large',
- '415': 'Unsupported Media Type',
- '416': 'Requested Range Not Satisfiable',
- '417': 'Expectation Failed',
- '418': 'I\'m a teapot',
- '422': 'Unprocessable Entity',
- '423': 'Locked',
- '424': 'Failed Dependency',
- '425': 'Unordered Collection',
- '426': 'Upgrade Required',
- '500': 'Internal Server Error',
- '501': 'Not Implemented',
- '502': 'Bad Gateway',
- '503': 'Service Unavailable',
- '504': 'Gateway Time-out',
- '505': 'HTTP Version not supported',
- '506': 'Variant Also Negotiates',
- '507': 'Insufficient Storage',
- '509': 'Bandwidth Limit Exceeded',
- '510': 'Not Extended' };
+var STATUS_CODES = { '100': 'Continue'
+ ,'101': 'Switching Protocols'
+ ,'102': 'Processing'
+ ,'200': 'OK'
+ ,'201': 'Created'
+ ,'202': 'Accepted'
+ ,'203': 'Non-Authoritative Information'
+ ,'204': 'No Content'
+ ,'205': 'Reset Content'
+ ,'206': 'Partial Content'
+ ,'207': 'Multi-Status'
+ ,'300': 'Multiple Choices'
+ ,'301': 'Moved Permanently'
+ ,'302': 'Moved Temporarily'
+ ,'303': 'See Other'
+ ,'304': 'Not Modified'
+ ,'305': 'Use Proxy'
+ ,'307': 'Temporary Redirect'
+ ,'400': 'Bad Request'
+ ,'401': 'Unauthorized'
+ ,'402': 'Payment Required'
+ ,'403': 'Forbidden'
+ ,'404': 'Not Found'
+ ,'405': 'Method Not Allowed'
+ ,'406': 'Not Acceptable'
+ ,'407': 'Proxy Authentication Required'
+ ,'408': 'Request Time-out'
+ ,'409': 'Conflict'
+ ,'410': 'Gone'
+ ,'411': 'Length Required'
+ ,'412': 'Precondition Failed'
+ ,'413': 'Request Entity Too Large'
+ ,'414': 'Request-URI Too Large'
+ ,'415': 'Unsupported Media Type'
+ ,'416': 'Requested Range Not Satisfiable'
+ ,'417': 'Expectation Failed'
+ ,'418': 'I\'m a teapot'
+ ,'422': 'Unprocessable Entity'
+ ,'423': 'Locked'
+ ,'424': 'Failed Dependency'
+ ,'425': 'Unordered Collection'
+ ,'426': 'Upgrade Required'
+ ,'500': 'Internal Server Error'
+ ,'501': 'Not Implemented'
+ ,'502': 'Bad Gateway'
+ ,'503': 'Service Unavailable'
+ ,'504': 'Gateway Time-out'
+ ,'505': 'HTTP Version not supported'
+ ,'506': 'Variant Also Negotiates'
+ ,'507': 'Insufficient Storage'
+ ,'509': 'Bandwidth Limit Exceeded'
+ ,'510': 'Not Extended'
+ };
/*
* generic error
*
@@ -84,18 +85,18 @@ var STATUS_CODES = { '100': 'Continue',
*
* @return an augmented error that helps you know more than the stack trace
*/
-function gen_err(error,code,request,http_code,type) {
- if(!type) { type = http_code; http_code = null; }
- if(!error) { error = new Error(STATUS_CODES[http_code] || "Unknown Error"); }
- if(typeof http_code !== "number") { http_code = 500; }
+function gen_err(scope,error,code,request,status_code) {
+ error = error || STATUS_CODES[status_code] || 'Unknown Error';
+ code = code || 'unknown';
+ status_code = typeof status_code === 'number' && status_code || 500;
+ request = request || {};
if(typeof error === 'string') { error = new Error(error); }
-
error.error = code;
- error.status_code = http_code;
- error.namespace = type;
+ error.status_code = status_code;
+ error.scope = scope;
error.request = request;
return error;
}
-exports.request_err = function (e,c,r,h) { return gen_err(e,c,r,h,"request");};
-exports.couch_err = function (e,c,r,h) { return gen_err(e,c,r,h,"couch"); };
+exports.request = function (e,c,r,h) { return gen_err('request',e,c,r,h);};
+exports.couch = function (e,c,r,h) { return gen_err('couch',e,c,r,h); };
View
41 nano.js
@@ -18,6 +18,7 @@ var request = require('request')
, fs = require('fs')
, qs = require('querystring')
, _ = require('underscore')
+ , u = require('url')
, error = require('./error')
, default_url = "http://localhost:5984"
, nano;
@@ -32,7 +33,7 @@ var request = require('request')
* dinosaurs spaceships!
*/
module.exports = exports = nano = function database_module(cfg) {
- var public_functions = {};
+ var public_functions = {}, path, db;
if(typeof cfg === "string") {
if(/^https?:/.test(cfg)) { cfg = {url: cfg}; } // url
else {
@@ -40,6 +41,10 @@ module.exports = exports = nano = function database_module(cfg) {
catch(e) { console.error("bad cfg: couldn't load file"); }
}
}
+ if(!cfg) {
+ console.error("bad cfg: you passed undefined");
+ cfg = {};
+ }
if(cfg.proxy) {
request = request.defaults({proxy: cfg.proxy}); // proxy support
}
@@ -47,9 +52,10 @@ module.exports = exports = nano = function database_module(cfg) {
console.error("bad cfg: using default=" + default_url);
cfg = {url: default_url}; // if everything else fails, use default
}
+ path = u.parse(cfg.url);
/****************************************************************************
- * relax *
+ * relax *
****************************************************************************/
/*
* relax
@@ -71,6 +77,7 @@ module.exports = exports = nano = function database_module(cfg) {
* @param {opts:object} request options; e.g. {db: "test", method: "GET"}
* {opts.db:string} database name
* {opts.method:string:optional} http method, defaults to "GET"
+ * {opts.path:string:optional} a full path, override `doc` and `att`
* {opts.doc:string:optional} document name
* {opts.att:string:optional} attachment name
* {opts.content_type:string:optional} content type, default to json
@@ -88,10 +95,13 @@ module.exports = exports = nano = function database_module(cfg) {
, status_code
, parsed
, rh;
- if(opts.doc) {
- url += "/" + opts.doc; // add the document to the url
+ if(opts.path) {
+ url += "/" + opts.path;
+ }
+ else if(opts.doc) {
+ url += "/" + encodeURIComponent(opts.doc); // add the document to the url
if(opts.att) { url += "/" + opts.att; } // add the attachment to the url
- }
+ }
if(opts.encoding && callback) {
req.encoding = opts.encoding;
delete req.headers["content-type"];
@@ -112,7 +122,7 @@ module.exports = exports = nano = function database_module(cfg) {
request(req, function(e,h,b){
rh = (h && h.headers || {});
rh['status-code'] = status_code = (h && h.statusCode || 500);
- if(e) { return callback(error.request_err(e,"socket",req,status_code),rh,b); }
+ if(e) { return callback(error.request(e,"socket",req,status_code),rh,b); }
delete rh.server; // prevent security vunerabilities related to couchdb
delete rh["content-length"]; // prevent problems with trims and stalled responses
try { parsed = JSON.parse(b); } catch (err) { parsed = b; } // did we get json or binary?
@@ -120,7 +130,7 @@ module.exports = exports = nano = function database_module(cfg) {
callback(null,rh,parsed);
}
else { // proxy the error directly from couchdb
- callback(error.couch_err(parsed.reason,parsed.error,req,status_code),rh,parsed);
+ callback(error.couch(parsed.reason,parsed.error,req,status_code),rh,parsed);
}
});
}
@@ -211,7 +221,7 @@ module.exports = exports = nano = function database_module(cfg) {
callback = design_name;
design_name = null;
}
- return relax({db: db_name, doc: "_compact", att: design_name, method: "POST"},callback);
+ return relax({db: db_name, path: ("_compact" + design_name), method: "POST"},callback);
}
/*
@@ -231,7 +241,7 @@ module.exports = exports = nano = function database_module(cfg) {
callback = params;
params = {};
}
- return relax({db: db_name, doc: "_changes", params: params, method: "GET"},callback);
+ return relax({db: db_name, path: "_changes", params: params, method: "GET"},callback);
}
/*
@@ -331,7 +341,7 @@ module.exports = exports = nano = function database_module(cfg) {
callback = params;
params = {};
}
- return relax({db: db_name, doc: "_all_docs", method: "GET", params: params},callback);
+ return relax({db: db_name, path: "_all_docs", method: "GET", params: params},callback);
}
/*
@@ -344,7 +354,7 @@ module.exports = exports = nano = function database_module(cfg) {
* @see relax
*/
function bulk_docs(docs,callback) {
- return relax({db: db_name, doc: "_bulk_docs", body: docs, method: "POST"},callback);
+ return relax({db: db_name, path: "_bulk_docs", body: docs, method: "POST"},callback);
}
/**************************************************************************
@@ -458,7 +468,14 @@ module.exports = exports = nano = function database_module(cfg) {
, relax: relax // alias
, dinosaur: relax // alias
};
- return public_functions;
+
+ // does the user want a database, or nano?
+ if(!_.isEmpty(path.pathname.split('/')[1])) {
+ db = path.pathname.split('/')[1];
+ cfg.url = path.protocol + '//' + path.host; // reset url
+ return document_module(db);
+ }
+ else { return public_functions; }
};
/*
View
10 node_modules/request/main.js
@@ -57,7 +57,7 @@ function isReadStream (rs) {
function copy (obj) {
var o = {}
- for (i in obj) o[i] = obj[i]
+ for (var i in obj) o[i] = obj[i]
return o
}
@@ -74,7 +74,7 @@ function Request (options) {
options = {uri:options}
}
- for (i in options) {
+ for (var i in options) {
this[i] = options[i]
}
if (!this.pool) this.pool = globalPool
@@ -281,7 +281,7 @@ Request.prototype.request = function () {
}
}
if (dest.setHeader) {
- for (i in response.headers) {
+ for (var i in response.headers) {
dest.setHeader(i, response.headers[i])
}
dest.statusCode = response.statusCode
@@ -337,7 +337,7 @@ Request.prototype.request = function () {
options.headers['content-type'] = mimetypes.lookup(src.path.slice(src.path.lastIndexOf('.')+1))
} else {
if (src.headers) {
- for (i in src.headers) {
+ for (var i in src.headers) {
if (!options.headers[i]) {
options.headers[i] = src.headers[i]
}
@@ -405,7 +405,7 @@ request.defaults = function (options) {
var def = function (method) {
var d = function (opts, callback) {
if (typeof opts === 'string') opts = {uri:opts}
- for (i in options) {
+ for (var i in options) {
if (opts[i] === undefined) opts[i] = options[i]
}
return method(opts, callback)
View
4 package.json
@@ -1,10 +1,10 @@
{ "name": "nano"
, "description": "minimalistic couchdb driver for node.js"
, "homepage": "http://github.com/dscape/nano"
-, "version": "0.6.6"
+, "version": "0.7.0"
, "author": "Nuno Job <nunojobpinto@gmail.com> (http://nunojob.com)"
, "keywords": ["couchdb", "data", "request", "json", "nosql", "micro", "nano"]
-, "dependencies": {"request": "~2.0.5", "underscore": "~1.1.7"}
+, "dependencies": {"request": "~2.1.1", "underscore": "~1.1.7"}
, "devDependencies": { "async": "~0.1.9", "vows": "~0.5.10" }
, "main": "./nano.js"
, "engines" : { "node": ">=0.3.6" }
View
36 tests/doc/insert.js
@@ -1,30 +1,50 @@
var vows = require('vows')
, assert = require('assert')
, cfg = require('../../cfg/tests.js')
- , nano = require('../../nano')(cfg)
- , db_name = "doc_in1"
- , db = nano.use(db_name);
+ , nano = require('../../nano')(cfg);
+
+function db_name(i) { return "doc_in" + i; }
+function db(i) { return nano.use(db_name(i)); }
/*****************************************************************************
* insert_doc *
*****************************************************************************/
function insert_doc(callback) {
- nano.db.create(db_name, function () {
- db.insert({foo: "bar"}, callback);
+ nano.db.create(db_name("a"), function () {
+ db("a").insert({foo: "bar"}, callback);
});
}
function insert_doc_ok(e,h,b) {
- nano.db.destroy(db_name);
+ nano.db.destroy(db_name("a"));
assert.isNull(e);
assert.ok(b.ok);
assert.ok(b.rev);
assert.ok(b.id);
}
+/*****************************************************************************
+ * insert_doc_path *
+ *****************************************************************************/
+function insert_doc_path(callback) {
+ nano.db.create(db_name("b"), function () {
+ db("b").insert({foo: "bar"}, 'some/path', callback);
+ });
+}
+
+function insert_doc_path_ok(e,h,b) {
+ nano.db.destroy(db_name("b"));
+ assert.isNull(e);
+ assert.ok(b.ok);
+ assert.ok(b.rev);
+ assert.equal(b.id, "some/path");
+}
+
vows.describe('db.insert').addBatch({
"insert_doc": {
topic: function () { insert_doc(this.callback); }
- , "=": insert_doc_ok
- }
+ , "=": insert_doc_ok },
+ "insert_doc_path": {
+ topic: function () { insert_doc_path(this.callback); }
+ , "=": insert_doc_path_ok }
}).exportTo(module);
View
82 tests/shared/cfg.js
@@ -10,6 +10,60 @@ function url(callback) { callback(null,nano('http://someurl.com')); }
function url_ok(_,n) { assert.equal(n.config.url, "http://someurl.com"); }
/*****************************************************************************
+ * url2 *
+ *****************************************************************************/
+function url2(callback) { callback(null,nano('http://someurl.com/')); }
+function url2_ok(_,n) { assert.equal(n.config.url, "http://someurl.com/"); }
+
+/*****************************************************************************
+ * url_db *
+ *****************************************************************************/
+function url_db(callback) {
+ nano(cfg.db_url("adb")).info(callback);
+}
+function url_db_ok(e,h,b) {
+ assert.equal(e.message,"no_db_file");
+ assert.equal(e.error, "not_found");
+ assert.equal(e.request.uri, (cfg.url + '/adb'));
+}
+
+/*****************************************************************************
+ * url_db2 *
+ *****************************************************************************/
+function url_db2(callback) {
+ nano(cfg.db_url("adb/")).info(callback);
+}
+function url_db2_ok(e,h,b) {
+ assert.equal(e.message,"no_db_file");
+ assert.equal(e.error, "not_found");
+ assert.equal(e.request.uri, (cfg.url + '/adb'));
+}
+
+/*****************************************************************************
+ * url_db3 *
+ *****************************************************************************/
+function url_db3(callback) {
+ nano(cfg.db_url("adb/blergh")).info(callback);
+}
+function url_db3_ok(e,h,b) {
+ assert.equal(e.message,"no_db_file");
+ assert.equal(e.error, "not_found");
+ assert.equal(e.request.uri, (cfg.url + '/adb'));
+}
+
+/*****************************************************************************
+ * url_db4 *
+ *****************************************************************************/
+function url_db4(callback) {
+ nano(cfg.db_url("a/b/d/c/")).info(callback);
+}
+function url_db4_ok(e,h,b) {
+ assert.equal(e.message,"no_db_file");
+ assert.equal(e.error, "not_found");
+ assert.equal(e.request.uri, (cfg.url + '/a'));
+}
+
+/*****************************************************************************
* file *
*****************************************************************************/
function file(callback) { callback(null,nano(__dirname+ '/../../cfg/tests.js')); }
@@ -38,11 +92,36 @@ function not_string_or_object(callback) {
function not_string_or_object_ok(_,e) {
assert.equal(e.config.url,"http://localhost:5984");
}
+
+/*****************************************************************************
+ * nano_undefined *
+ *****************************************************************************/
+function nano_undefined(callback) {
+ callback(null,nano());
+}
+function nano_undefined_ok(_,e) {
+ assert.equal(e.config.url,"http://localhost:5984");
+}
vows.describe('config').addBatch({
"url": {
topic: function () { url(this.callback); }
, "=": url_ok },
+ "url2": {
+ topic: function () { url2(this.callback); }
+ , "=": url2_ok },
+ "url_db": {
+ topic: function () { url_db(this.callback); }
+ , "=": url_db_ok },
+ "url_db2": {
+ topic: function () { url_db2(this.callback); }
+ , "=": url_db2_ok },
+ "url_db3": {
+ topic: function () { url_db3(this.callback); }
+ , "=": url_db3_ok },
+ "url_db4": {
+ topic: function () { url_db4(this.callback); }
+ , "=": url_db4_ok },
"file": {
topic: function () { file(this.callback); }
, "=": file_ok },
@@ -52,6 +131,9 @@ vows.describe('config').addBatch({
"object": {
topic: function () { object(this.callback); }
, "=": object_ok },
+ "nano_undefined": {
+ topic: function () { nano_undefined(this.callback); }
+ , "=": nano_undefined_ok },
"not_string_or_object": {
topic: function () { not_string_or_object(this.callback); }
, "=": not_string_or_object_ok }
View
12 tests/shared/error.js
@@ -6,28 +6,28 @@ var vows = require('vows')
* empty_error *
*****************************************************************************/
function empty_error(callback) {
- callback(null,err.couch_err(null,null,null,null));
+ callback(null,err.couch(null,null,null,null));
}
function empty_error_ok(_,e) {
assert.equal(e.message, "Unknown Error");
assert.equal(e.status_code, 500);
- assert.isNull(e.error);
- assert.isNull(e.request);
+ assert.equal(e.error, "unknown");
+ assert.ok(typeof e.request === 'object');
}
/*****************************************************************************
* error_412 *
*****************************************************************************/
function error_412(callback) {
- callback(null,err.couch_err(null,null,null,412));
+ callback(null,err.couch(null,null,null,412));
}
function error_412_ok(_,e) {
assert.equal(e.message, "Precondition Failed");
assert.equal(e.status_code, 412);
- assert.isNull(e.error);
- assert.isNull(e.request);
+ assert.equal(e.error, "unknown");
+ assert.ok(typeof e.request === 'object');
}
vows.describe('error').addBatch({
Please sign in to comment.
Something went wrong with that request. Please try again.