Created
December 25, 2010 23:07
-
-
Save joemccann/755100 to your computer and use it in GitHub Desktop.
The bug in current node-couchdb module throws an error with version 0.4.0 of traits module. Line 25 is the change.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| (function (exports) { | |
| if (typeof JSON === "undefined") { | |
| throw new Error("JSON is required. Plesae include json2.js if you are running this in the browser"); | |
| } | |
| var | |
| Q = require("promised-io/promise"), | |
| HttpClient = require("promised-io/http-client").Client, | |
| Trait = require("traits").Trait, | |
| base64 = require("./base64"), | |
| when = Q.when, | |
| defineProperty = Object.defineProperty, | |
| httpMethod = { | |
| POST: "POST", | |
| GET: "GET", | |
| DELETE: "DELETE", | |
| PUT: "PUT" | |
| }; | |
| exports.createClient = function(opts) { | |
| opts = opts || {}; | |
| var | |
| client = new HttpClient(), | |
| couchClient = new CouchClient(client); // add this line | |
| // this line is throwing the error -> couchClient = Trait.create(new CouchClient(client), Trait(opts)); | |
| return couchClient; | |
| }; | |
| function CouchClient(httpClient){ | |
| this.httpClient = httpClient; | |
| } | |
| /** | |
| * Get options for http client request | |
| * | |
| * @api private | |
| * @return {Object} Request options | |
| */ | |
| CouchClient.prototype.getRequestOptions = function(opts) { | |
| var defaults = { | |
| hostname: this.host, | |
| port: this.port, | |
| headers: { "Content-Type": "application/json" } | |
| }; | |
| opts = complete(opts, defaults); | |
| if (opts.headers["Content-Type"] === undefined || opts.headers["Content-Type"] === null) { | |
| opts.headers["Content-Type"] = "application/json"; | |
| } | |
| if (this.user) { | |
| opts.headers["Authorization"] = "Basic "+base64.encode(this.user+":"+this.password); | |
| } else if (this.authSession) { | |
| if (opts.headers.cookie) { | |
| opts.headers.cookie += ";"; | |
| } else { | |
| opts.headers.cookie = ""; | |
| } | |
| opts.headers.cookie += "AuthSession="+this.authSession; | |
| } | |
| if (typeof opts.body === "string") { | |
| opts.body = [opts.body]; | |
| } | |
| return opts; | |
| }; | |
| var parseBody = exports.parseBody = function(resp) { | |
| var body = ""; | |
| return when(resp.body.forEach(function(chunk) { body += chunk; }), function() { | |
| var val; | |
| try { | |
| val = JSON.parse(body); | |
| } catch (err) { | |
| console.log("parse error:" + body); | |
| throw { error: 'ParseError', reason: body }; | |
| } | |
| if (resp.status >= 400) { | |
| val.status = resp.status; | |
| throw val; | |
| } | |
| return val; | |
| }); | |
| } | |
| CouchClient.prototype.requestRaw = function(opts) { | |
| var opts = this.getRequestOptions(opts); | |
| return this.httpClient.request(opts); | |
| };; | |
| CouchClient.prototype.request = function(opts) { | |
| var opts = this.getRequestOptions(opts); | |
| return when(this.httpClient.request(opts), function success(resp) { | |
| var body = ""; | |
| return parseBody(resp); | |
| }); | |
| }; | |
| CouchClient.prototype.prepareUserDoc = function(doc, password) { | |
| doc._id = doc._id || USER_PREFIX+doc.name; | |
| doc.type = "user"; | |
| doc.roles = doc.roles || []; | |
| if (password) { | |
| return when(this.uuids(1), function(uuids) { | |
| doc.salt = uuids[0]; | |
| doc.password_sha = require("./sha1").hex_sha1(password + doc.salt); | |
| return doc; | |
| }); | |
| } | |
| return doc; | |
| }; | |
| /** | |
| * Get the users DB for this CouchDB Server | |
| * @returns {Promise} A promise that resolves to the users database | |
| */ | |
| CouchClient.prototype.userDb = function() { | |
| var self = this; | |
| return when(this.session(), function(resp) { | |
| var | |
| userDbName = resp.info.authentication_db, | |
| userDb = self.db(userDbName); | |
| userDb.hasUser = function(name) { | |
| return this.exists(USER_PREFIX+name); | |
| }; | |
| return userDb; | |
| }); | |
| }; | |
| CouchClient.prototype.signup = function(user, newPassword, opts) { | |
| var self = this; | |
| opts = opts || {}; | |
| return when(this.userDb(), function(db) { | |
| return when(self.prepareUserDoc(user, newPassword), function(doc) { | |
| return db.saveDoc(doc); | |
| }); | |
| }); | |
| }; | |
| CouchClient.prototype.login = function(name, password) { | |
| return this.requestRaw({ | |
| pathInfo: "/_session", | |
| method: httpMethod.POST, | |
| headers: { "Content-Type": "application/x-www-form-urlencoded", "X-CouchDB-WWW-Authenticate": "Cookie" }, | |
| body: "name="+encodeURIComponent(name)+"&password="+encodeURIComponent(password) | |
| }); | |
| }; | |
| CouchClient.prototype.logout = CouchClient.prototype.logoff = function() { | |
| return this.request({ | |
| pathInfo: "/_session", | |
| method: httpMethod.DELETE, | |
| headers: { | |
| "Content-Type": "application/x-www-form-urlencoded", | |
| "X-CouchDB-WWW-Authenticate": "Cookie" | |
| } | |
| }); | |
| }; | |
| CouchClient.prototype.allDbs = function() { | |
| return this.request({ | |
| pathInfo: "/_all_dbs" | |
| }); | |
| }; | |
| CouchClient.prototype.config = function() { | |
| return this.request({ | |
| pathInfo: "/_config" | |
| }); | |
| }; | |
| CouchClient.prototype.session = function() { | |
| return this.request({ | |
| pathInfo: "/_session" | |
| }); | |
| }; | |
| /** | |
| * Retrieve unique identifiers generated by CouchDB | |
| * | |
| * @param {Number|Function} count If this is a function, it becomes the callback. If it is a number, it is the number of unique identifiers to retrieve | |
| * @param {Function} cb Callback | |
| * @api public | |
| */ | |
| CouchClient.prototype.uuids = function(count) { | |
| var jsgiRequest = { | |
| pathInfo: "/_uuids" | |
| }; | |
| if (count) { | |
| jsgiRequest.queryString = "count="+count; | |
| } | |
| return when(this.request(jsgiRequest), function(resp) { | |
| return resp.uuids; | |
| }); | |
| }; | |
| CouchClient.prototype.replicate = function(source, target, opts) { | |
| opts = complete({}, { | |
| source: source, | |
| target: target | |
| }, opts); | |
| var req = { | |
| method: httpMethod.POST, | |
| pathInfo: "/_replicate", | |
| body: [JSON.stringify(opts)] | |
| }; | |
| if (opts.queryString) { | |
| req.queryString = opts.queryString; | |
| delete opts.queryString; | |
| } | |
| return this.request(req); | |
| }; | |
| CouchClient.prototype.stats = function() { | |
| var args = Array.prototype.slice.call(arguments); | |
| return this.request({ | |
| pathInfo: "/_stats" + ((args && args.length > 0) ? "/" + args.join("/") : "") | |
| }); | |
| }; | |
| CouchClient.prototype.activeTasks = function() { | |
| return this.request({ | |
| path: "/_active_tasks" | |
| }); | |
| }; | |
| CouchClient.prototype.session = function() { | |
| return this.request({ | |
| pathInfo: "/_session" | |
| }); | |
| }; | |
| CouchClient.prototype.db = function(name) { | |
| if (name === undefined || name === null || name === "") { | |
| throw new Error("Name must contain a value"); | |
| } | |
| var | |
| couchClient = this; | |
| db = Object.create(new Database(), | |
| { | |
| "name": { | |
| get: function() { return name; } | |
| }, | |
| "client": { | |
| get: function() { return couchClient; } | |
| } | |
| }); | |
| db.request = function(opts) { | |
| opts = opts || {}; | |
| opts.pathInfo = "/"+name+(opts.path || ""); | |
| return couchClient.request(opts); | |
| }; | |
| db.view = function(designDoc, viewName, opts){ | |
| opts = opts || {}; | |
| opts.pathInfo = "/" + this.name +"/_design/" + designDoc + "/_view/" + viewName + encodeOptions(opts); | |
| return couchClient.request(opts); | |
| }; | |
| return db; | |
| }; | |
| var Database = exports.Database = function() {}; | |
| Database.prototype.exists = function() { | |
| return when(this.request({ pathInfo: "" }), function(resp) { | |
| return true; | |
| }, function(err) { | |
| return !(err.error && err.error === "not_found"); | |
| }); | |
| }; | |
| Database.prototype.info = function() { | |
| return this.request({}, cb); | |
| }; | |
| Database.prototype.create = function() { | |
| return this.request({ | |
| method: httpMethod.PUT | |
| }); | |
| }; | |
| /** | |
| * Permanently delete database | |
| * | |
| * @return {Promise} a promise | |
| * @api public | |
| */ | |
| Database.prototype.remove = function() { | |
| return this.request({ | |
| method: httpMethod.DELETE | |
| }); | |
| }; | |
| Database.prototype.allDocs = function() { | |
| return this.request({ | |
| path: "/_all_docs" | |
| }); | |
| }; | |
| /** | |
| * Retrieve document by unique identifier | |
| * | |
| * @param {String} id Unique identifier of the document | |
| * @param {Function} cb Callback | |
| * @api public | |
| */ | |
| Database.prototype.getDoc = Database.prototype.openDoc = function(id) { | |
| return when(this.request({ | |
| path: "/"+id | |
| }), function(doc) { | |
| return doc; | |
| }, function(err) { | |
| if (err.status === 404) { | |
| return null; | |
| } | |
| throw err; | |
| }); | |
| }; | |
| Database.prototype.getAttachmentResponse = function(doc, attachmentName){ | |
| return this.request({ | |
| method:httpMethod.GET, | |
| path:"/"+this.name + "/" + doc._id + "/" + attachmentName | |
| }) | |
| }; | |
| Database.prototype.saveDoc = function(doc, opts) { | |
| var | |
| method = httpMethod.PUT | |
| , path = "/"; | |
| if (doc._id === undefined) { | |
| method = httpMethod.POST; | |
| } else { | |
| path += doc._id; | |
| } | |
| return this.request({ | |
| method: method, | |
| path: path, | |
| body: typeof doc === "string" ? [doc] : [JSON.stringify(doc)] | |
| }); | |
| }; | |
| Database.prototype.removeDoc = function(id, rev) { | |
| return this.request({ | |
| method: httpMethod.DELETE, | |
| path: "/"+id, | |
| queryString: "rev="+rev | |
| }); | |
| }; | |
| Database.prototype.security = function(obj) { | |
| if (obj === undefined || obj === null) { | |
| return this.request({ | |
| method: httpMethod.GET, | |
| path: "/_security" | |
| }); | |
| } | |
| return this.request({ | |
| method: httpMethod.PUT, | |
| path: "/_security", | |
| body: [ JSON.stringify(obj) ] | |
| }); | |
| }; | |
| /** | |
| * @api private | |
| */ | |
| function getRequestOptions(args) { | |
| args = Array.prototype.slice.call(args); | |
| var | |
| cb = args.pop(), | |
| method = args.shift(), | |
| path = args.shift(), | |
| data = args.shift(), | |
| opts; | |
| if (typeof method === "object") { | |
| opts = method; | |
| } else if (typeof method === "string" && typeof path !== "string") { | |
| opts = { | |
| path: method, | |
| query: path | |
| }; | |
| } else { | |
| opts = { | |
| method: method, | |
| path: path, | |
| data: data | |
| }; | |
| } | |
| opts.cb = cb; | |
| return opts; | |
| } | |
| function removeAttr(attr) { | |
| var val = this[attr]; | |
| delete this[attr]; | |
| return val; | |
| } | |
| /** | |
| * Stringify function embedded inside of objects. Useful for couch views. | |
| * @api private | |
| */ | |
| function toJSON(data) { | |
| return JSON.stringify(data, function(key, val) { | |
| if (typeof val == 'function') { | |
| return val.toString(); | |
| } | |
| return val; | |
| }); | |
| } | |
| // Convert a options object to an url query string. | |
| // ex: {key:'value',key2:'value2'} becomes '?key="value"&key2="value2"' | |
| function encodeOptions(options) { | |
| var buf = []; | |
| if (typeof(options) == "object" && options !== null) { | |
| for (var name in options) { | |
| if (options.hasOwnProperty(name)) { | |
| var value = options[name]; | |
| if (name == "key" || name == "startkey" || name == "endkey") { | |
| value = toJSON(value); | |
| } | |
| buf.push(encodeURIComponent(name) + "=" + encodeURIComponent(value)); | |
| } | |
| } | |
| } | |
| if (!buf.length) { | |
| return ""; | |
| } | |
| return "?" + buf.join("&"); | |
| } | |
| /** | |
| * Updates an object with the properties of another object(s) if those | |
| * properties are not already defined for the target object. First argument is | |
| * the object to complete, the remaining arguments are considered sources to | |
| * complete from. If multiple sources contain the same property, the value of | |
| * the first source with that property will be the one inserted in to the | |
| * target. | |
| * | |
| * example usage: | |
| * util.complete({}, { hello: "world" }); // -> { hello: "world" } | |
| * util.complete({ hello: "narwhal" }, { hello: "world" }); // -> { hello: "narwhal" } | |
| * util.complete({}, { hello: "world" }, { hello: "le monde" }); // -> { hello: "world" } | |
| * | |
| * @returns Completed object | |
| * @type Object | |
| * @api private | |
| */ | |
| function complete() { | |
| return variadicHelper(arguments, function(target, source) { | |
| var key; | |
| for (key in source) { | |
| if ( | |
| Object.prototype.hasOwnProperty.call(source, key) && | |
| !Object.prototype.hasOwnProperty.call(target, key) | |
| ) { | |
| target[key] = source[key]; | |
| } | |
| } | |
| }); | |
| } | |
| /** | |
| * @param args Arguments list of the calling function | |
| * First argument should be a callback that takes target and source parameters. | |
| * Second argument should be target. | |
| * Remaining arguments are treated a sources. | |
| * | |
| * @returns Target | |
| * @type Object | |
| * @api private | |
| */ | |
| function variadicHelper(args, callback) { | |
| var sources = Array.prototype.slice.call(args); | |
| var target = sources.shift(); | |
| sources.forEach(function(source) { | |
| callback(target, source); | |
| }); | |
| return target; | |
| } | |
| var USER_PREFIX = exports.USER_PREFIX = "org.couchdb.user:"; | |
| })(exports); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment