8/3/2019 couch-js
1/21
// 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.
/**
* @namespace* $.couch is used to communicate with a CouchDB server, the server methods can* be called directly without creating an instance. Typically all methods are* passed an options object which defines a success callback which* is called with the data returned from the http request to CouchDB, you can* find the other settings that can be used in the options object
* from * jQuery.ajax settings* $.couch.activeTasks({* success: function (data) {
* console.log(data);* }
* });* Outputs (for example):* [* {
* "pid" : "",* "status" : "Copied 0 of 18369 changes (0%)",* "task" : "recipes",* "type" : "Database Compaction"
* }*]
*/(function($) {
$.couch = $.couch || {};
/** @lends $.couch */
/*** @private*/function encodeDocId(docID) {
var parts = docID.split("/");if (parts[0] == "_design") {parts.shift();return "_design/" + encodeURIComponent(parts.join('/'));
}return encodeURIComponent(docID);
}
/**
8/3/2019 couch-js
2/21
* @private*/
var uuidCache = [];
$.extend($.couch, {
urlPrefix: '',
/**
* You can obtain a list of active tasks by using the /_active_tasks URL.* The result is a JSON array of the currently running tasks, with each task* being described with a single object.* @see docs for /_active_tasks* @param {ajaxSettings} options jQuery ajax settings*/activeTasks: function(options) {
ajax({url: this.urlPrefix + "/_active_tasks"},options,"Active task status could not be retrieved"
);},
/*** Returns a list of all the databases in the CouchDB instance* @see docs for /_all_dbs* @param {ajaxSettings} options jQuery ajax settings
*/allDbs: function(options) {
ajax({url: this.urlPrefix + "/_all_dbs"},options,"An error occurred retrieving the list of all databases"
);
},
/*** View and edit the CouchDB configuration, called with just the options* parameter the entire config is returned, you can be more specific by
* passing the section and option parameters, if you specify a value that* value will be stored in the configuration.* @see docs for /_config* @param {ajaxSettings} options
* * jQuery ajax settings* @param {String} [section] the section of the config
8/3/2019 couch-js
3/21
* @param {String} [option] the particular config option* @param {String} [value] value to be set*/
config: function(options, section, option, value) {var req = {url: this.urlPrefix + "/_config/"};if (section) {
req.url += encodeURIComponent(section) + "/";if (option) {req.url += encodeURIComponent(option);
}}if (value === null) {req.type = "DELETE";
} else if (value !== undefined) {req.type = "PUT";req.data = toJSON(value);req.contentType = "application/json";req.processData = false
}
ajax(req, options,"An error occurred retrieving/updating the server configuration"
);
},
/*** Returns the session information for the currently logged in user.* @param {ajaxSettings} options*
* jQuery ajax settings*/session: function(options) {options = options || {};
$.ajax({type: "GET", url: this.urlPrefix + "/_session",
beforeSend: function(xhr) {xhr.setRequestHeader('Accept', 'application/json');
},complete: function(req) {
var resp = $.parseJSON(req.responseText);
if (req.status == 200) {if (options.success) options.success(resp);
} else if (options.error) {options.error(req.status, resp.error, resp.reason);
} else {
alert("An error occurred getting session info: " + resp.reason);}
}});
},
/*** @private*/
8/3/2019 couch-js
4/21
userDb : function(callback) {$.couch.session({success : function(resp) {
var userDb = $.couch.db(resp.info.authentication_db);callback(userDb);
}
});},
/*** Create a new user on the CouchDB server, user_doc is an* object with a name field and other information you want* to store relating to that user, for example
* {"name": "daleharvey"}* @param {Object} user_doc Users details* @param {String} password Users password* @param {ajaxSettings} options* * jQuery ajax settings
*/signup: function(user_doc, password, options) {options = options || {};// prepare user doc based on name and password
user_doc = this.prepareUserDoc(user_doc, password);$.couch.userDb(function(db) {
db.saveDoc(user_doc, options);});
},
/*** Populates a user doc with a new password.* @param {Object} user_doc User details* @param {String} new_password New Password
*/prepareUserDoc: function(user_doc, new_password) {
if (typeof hex_sha1 == "undefined") {alert("creating a user doc requires sha1.js to be loaded in the page");return;
}
var user_prefix = "org.couchdb.user:";
user_doc._id = user_doc._id || user_prefix + user_doc.name;if (new_password) {// handle the password cryptouser_doc.salt = $.couch.newUUID();user_doc.password_sha = hex_sha1(new_password + user_doc.salt);
}user_doc.type = "user";if (!user_doc.roles) {user_doc.roles = [];
}return user_doc;
},
/**
8/3/2019 couch-js
5/21
* Authenticate against CouchDB, the options parameter is*expected to have name and password fields.
* @param {ajaxSettings} options
* * jQuery ajax settings*/
login: function(options) {options = options || {};$.ajax({
type: "POST", url: this.urlPrefix + "/_session", dataType: "json",data: {name: options.name, password: options.password},beforeSend: function(xhr) {
xhr.setRequestHeader('Accept', 'application/json');
},complete: function(req) {var resp = $.parseJSON(req.responseText);if (req.status == 200) {if (options.success) options.success(resp);
} else if (options.error) {
options.error(req.status, resp.error, resp.reason);} else {alert("An error occurred logging in: " + resp.reason);
}
}});
},
/**
* Delete your current CouchDB user session* @param {ajaxSettings} options* * jQuery ajax settings
*/logout: function(options) {
options = options || {};$.ajax({type: "DELETE", url: this.urlPrefix + "/_session", dataType: "json",username : "_", password : "_",
beforeSend: function(xhr) {
xhr.setRequestHeader('Accept', 'application/json');},complete: function(req) {var resp = $.parseJSON(req.responseText);if (req.status == 200) {
if (options.success) options.success(resp);} else if (options.error) {options.error(req.status, resp.error, resp.reason);
} else {
alert("An error occurred logging out: " + resp.reason);}
}});
},
8/3/2019 couch-js
6/21
8/3/2019 couch-js
7/21
);},
/*** Cleans up the cached view output on disk for a given view.* @see docs for /db/_compact* @param {ajaxSettings} options jQuery ajax settings*/viewCleanup: function(options) {$.extend(options, {successStatus: 202});
ajax({type: "POST", url: this.uri + "_view_cleanup",data: "", processData: false
},options,"The views could not be cleaned up"
);},
/**
* Compacts the view indexes associated with the specified design* document. You can use this in place of the full database compaction
* if you know a specific set of view indexes have been affected by a* recent database change.* @see docs for /db/_compact/design-doc* @param {String} groupname Name of design-doc to compact* @param {ajaxSettings} options jQuery ajax settings
*/compactView: function(groupname, options) {
$.extend(options, {successStatus: 202});ajax({
type: "POST", url: this.uri + "_compact/" + groupname,data: "", processData: false
},
options,"The view could not be compacted"
);},
/*** Create a new database* @see docs for PUT /db/* @param {ajaxSettings} options jQuery ajax settings*/create: function(options) {
8/3/2019 couch-js
8/21
$.extend(options, {successStatus: 201});ajax({
type: "PUT", url: this.uri, contentType: "application/json",
data: "", processData: false},options,
"The database could not be created");},
/*** Deletes the specified database, and all the documents and* attachments contained within it.
* @see docs for DELETE /db/* @param {ajaxSettings} options jQuery ajax settings*/
drop: function(options) {ajax({type: "DELETE", url: this.uri},options,
"The database could not be deleted");
},
/*** Gets information about the specified database.
* @see docs for GET /db/* @param {ajaxSettings} options jQuery ajax settings*/
info: function(options) {ajax({url: this.uri},options,
"Database information could not be retrieved"
);},
/*** @namespace
* $.couch.db.changes provides an API for subscribing to the changes* feed* var $changes = $.couch.db("mydatabase").changes();*$changes.onChange = function (data) {
* ... process data ...* }
* $changes.stop();* */
8/3/2019 couch-js
9/21
changes: function(since, options) {
options = options || {};
// set up the promise object within a closure for this handlervar timeout = 100, db = this, active = true,listeners = [],
promise = /** @lends $.couch.db.changes */ {/*** Add a listener callback
* @see docs for /db/_changes* @param {Function} fun Callback function to run when
* notified of changes.*/
onChange : function(fun) {listeners.push(fun);
},/**
* Stop subscribing to the changes feed*/
stop : function() {active = false;
}};
// call each listener when there is a changefunction triggerListeners(resp) {$.each(listeners, function() {this(resp);
});};// when there is a change, call any listeners, then check for// another change
options.success = function(resp) {timeout = 100;
if (active) {since = resp.last_seq;triggerListeners(resp);getChangesSince();
};
};options.error = function() {if (active) {setTimeout(getChangesSince, timeout);timeout = timeout * 2;
}};// actually make the changes requestfunction getChangesSince() {
var opts = $.extend({heartbeat : 10 * 1000}, options, {feed : "longpoll",
since : since});ajax(
8/3/2019 couch-js
10/21
{url: db.uri + "_changes"+encodeOptions(opts)},options,"Error connecting to "+db.uri+"/_changes."
);}// start the first request
if (since) {getChangesSince();} else {
db.info({success : function(info) {since = info.update_seq;getChangesSince();
}});
}return promise;
},
/*** Fetch all the docs in this db, you can specify an array of keys to* fetch by passing the keys field in the* options
* parameter.* @see docs for /db/all_docs/* @param {ajaxSettings} options jQuery ajax settings
*/allDocs: function(options) {var type = "GET";var data = null;
if (options["keys"]) {type = "POST";
var keys = options["keys"];delete options["keys"];data = toJSON({ "keys": keys });
}
ajax({
type: type,data: data,url: this.uri + "_all_docs" + encodeOptions(options)
},options,
"An error occurred retrieving a list of all documents");
},
/*** Fetch all the design docs in this db
* @param {ajaxSettings} options jQuery ajax settings*/
8/3/2019 couch-js
11/21
allDesignDocs: function(options) {this.allDocs($.extend({startkey:"_design", endkey:"_design0"}, options));
},
/**
* Fetch all the design docs with an index.html, options* parameter expects an eachApp field which is a callback* called on each app found.
* @param {ajaxSettings} options jQuery ajax settings*/allApps: function(options) {
options = options || {};var self = this;if (options.eachApp) {this.allDesignDocs({success: function(resp) {$.each(resp.rows, function() {
self.openDoc(this.id, {success: function(ddoc) {var index, appPath, appName = ddoc._id.split('/');appName.shift();
appName = appName.join('/');index = ddoc.couchapp && ddoc.couchapp.index;
if (index) {appPath = ['', name, ddoc._id, index].join('/');
} else if (ddoc._attachments &&ddoc._attachments["index.html"]) {
appPath = ['', name, ddoc._id, "index.html"].join('/');}if (appPath) options.eachApp(appName, appPath, ddoc);
}
});});
}});
} else {alert("Please provide an eachApp function for allApps()");
}
},
/*** Returns the specified doc from the specified db.* @see docs for GET /db/doc* @param {String} docId id of document to fetch* @param {ajaxSettings} options jQuery ajax settings* @param {ajaxSettings} ajaxOptions jQuery ajax settings*/openDoc: function(docId, options, ajaxOptions) {
8/3/2019 couch-js
12/21
options = options || {};if (db_opts.attachPrevRev || options.attachPrevRev) {$.extend(options, {
beforeSuccess : function(req, doc) {rawDocs[doc._id] = {rev : doc._rev,
raw : req.responseText};}
});} else {$.extend(options, {beforeSuccess : function(req, doc) {
if (doc["jquery.couch.attachPrevRev"]) {rawDocs[doc._id] = {rev : doc._rev,raw : req.responseText
};}
}});
}ajax({url: this.uri + encodeDocId(docId) + encodeOptions(options)},
options,"The document could not be retrieved",
ajaxOptions);
},
/*** Create a new document in the specified database, using the supplied* JSON document structure. If the JSON structure includes the _id* field, then the document will be created with the specified document
* ID. If the _id field is not specified, a new unique ID will be* generated.
* @see docs for GET /db/doc* @param {String} doc document to save
* @param {ajaxSettings} options jQuery ajax settings*/saveDoc: function(doc, options) {options = options || {};var db = this;
var beforeSend = fullCommit(options);if (doc._id === undefined) {var method = "POST";var uri = this.uri;
} else {var method = "PUT";
var uri = this.uri + encodeDocId(doc._id);}var versioned = maybeApplyVersion(doc);
8/3/2019 couch-js
13/21
$.ajax({type: method, url: uri + encodeOptions(options),contentType: "application/json",
dataType: "json", data: toJSON(doc),beforeSend : beforeSend,complete: function(req) {
var resp = $.parseJSON(req.responseText);if (req.status == 200 || req.status == 201 || req.status == 202) {doc._id = resp.id;
doc._rev = resp.rev;if (versioned) {db.openDoc(doc._id, {attachPrevRev : true,
success : function(d) {doc._attachments = d._attachments;if (options.success) options.success(resp);
}});
} else {
if (options.success) options.success(resp);}
} else if (options.error) {options.error(req.status, resp.error, resp.reason);
} else {alert("The document could not be saved: " + resp.reason);
}}
});},
/*** Save a list of documents* @see docs for /db/_bulk_docs
* @param {Object[]} docs List of documents to save* @param {ajaxSettings} options jQuery ajax settings*/
bulkSave: function(docs, options) {
var beforeSend = fullCommit(options);$.extend(options, {successStatus: 201, beforeSend : beforeSend});ajax({
type: "POST",url: this.uri + "_bulk_docs" + encodeOptions(options),
contentType: "application/json", data: toJSON(docs)},options,"The documents could not be saved"
);},
/*** Deletes the specified document from the database. You must supply
8/3/2019 couch-js
14/21
8/3/2019 couch-js
15/21
* @param {ajaxSettings} options jQuery ajax settings* @param {ajaxSettings} options jQuery ajax settings*/copyDoc: function(docId, options, ajaxOptions) {
ajaxOptions = $.extend(ajaxOptions, {complete: function(req) {var resp = $.parseJSON(req.responseText);
if (req.status == 201) {if (options.success) options.success(resp);
} else if (options.error) {options.error(req.status, resp.error, resp.reason);
} else {alert("The document could not be copied: " + resp.reason);
}}
});ajax({
type: "COPY",url: this.uri + encodeDocId(docId)
},options,
"The document could not be copied",ajaxOptions
);},
/**
* Creates (and executes) a temporary view based on the view function* supplied in the JSON request.* @see docs for /db/_temp_view* @param {Function} mapFun Map function
* @param {Function} reduceFun Reduce function* @param {Function} language Language the map / reduce funs are* implemented in* @param {ajaxSettings} options jQuery ajax settings
*/query: function(mapFun, reduceFun, language, options) {language = language || "javascript";if (typeof(mapFun) !== "string") {mapFun = mapFun.toSource ? mapFun.toSource()
: "(" + mapFun.toString() + ")";}var body = {language: language, map: mapFun};if (reduceFun != null) {
if (typeof(reduceFun) !== "string")reduceFun = reduceFun.toSource ? reduceFun.toSource()
: "(" + reduceFun.toString() + ")";body.reduce = reduceFun;
}
8/3/2019 couch-js
16/21
ajax({type: "POST",url: this.uri + "_temp_view" + encodeOptions(options),
contentType: "application/json", data: toJSON(body)},options,
"An error occurred querying the database");},
/*** Fetch a _list view output, you can specify a list of* keys in the options object to recieve only those keys.
* @see * docs for /db/_design/design-doc/_list/l1/v1* @param {String} list Listname in the form of ddoc/listname* @param {String} view View to run list against
* @param {options} CouchDB View Options* @param {ajaxSettings} options jQuery ajax settings
*/list: function(list, view, options, ajaxOptions) {
var list = list.split('/');var options = options || {};var type = 'GET';var data = null;
if (options['keys']) {type = 'POST';var keys = options['keys'];delete options['keys'];
data = toJSON({'keys': keys });}
ajax({type: type,data: data,url: this.uri + '_design/' + list[0] +
'/_list/' + list[1] + '/' + view + encodeOptions(options)
},ajaxOptions, 'An error occured accessing the list'
);},
/*** Executes the specified view-name from the specified design-doc* design document, you can specify a list of keys* in the options object to recieve only those keys.
* @see docs for /db/* _design/design-doc/_list/l1/v1* @param {String} name View to run list against
8/3/2019 couch-js
17/21
* @param {ajaxSettings} options jQuery ajax settings*/
view: function(name, options) {var name = name.split('/');var options = options || {};
var type = "GET";var data= null;if (options["keys"]) {
type = "POST";var keys = options["keys"];delete options["keys"];data = toJSON({ "keys": keys });
}ajax({
type: type,data: data,url: this.uri + "_design/" + name[0] +
"/_view/" + name[1] + encodeOptions(options)
},options, "An error occurred accessing the view"
);},
/**
* Fetch an arbitrary CouchDB database property* @see docs for /db/_prop* @param {String} propName Propery name to fetch
* @param {ajaxSettings} options jQuery ajax settings* @param {ajaxSettings} ajaxOptions jQuery ajax settings
*/getDbProperty: function(propName, options, ajaxOptions) {
ajax({url: this.uri + propName + encodeOptions(options)},options,"The property could not be retrieved",ajaxOptions
);
},
/*** Set an arbitrary CouchDB database property* @see docs for /db/_prop* @param {String} propName Propery name to fetch* @param {String} propValue Propery value to set* @param {ajaxSettings} options jQuery ajax settings* @param {ajaxSettings} ajaxOptions jQuery ajax settings*/setDbProperty: function(propName, propValue, options, ajaxOptions) {
8/3/2019 couch-js
18/21
ajax({type: "PUT",url: this.uri + propName + encodeOptions(options),
data : JSON.stringify(propValue)},options,
"The property could not be updated",ajaxOptions);
}};
},
encodeDocId: encodeDocId,
/*** Accessing the root of a CouchDB instance returns meta information about* the instance. The response is a JSON structure containing information* about the server, including a welcome message and the version of the
* server.* @see * docs for GET /
* @param {ajaxSettings} options jQuery ajax settings
*/info: function(options) {ajax({url: this.urlPrefix + "/"},
options,"Server information could not be retrieved"
);},
/**
* Request, configure, or stop, a replication operation.* @see docs for POST /_replicate
* @param {String} source Path or url to source database
* @param {String} target Path or url to target database* @param {ajaxSettings} ajaxOptions jQuery ajax settings* @param {Object} repOpts Additional replication options*/
replicate: function(source, target, ajaxOptions, repOpts) {repOpts = $.extend({source: source, target: target}, repOpts);if (repOpts.continuous && !repOpts.cancel) {ajaxOptions.successStatus = 202;
}ajax({
type: "POST", url: this.urlPrefix + "/_replicate",data: JSON.stringify(repOpts),contentType: "application/json"
8/3/2019 couch-js
19/21
},ajaxOptions,"Replication failed"
);},
/*** Fetch a new UUID* @see docs for /_uuids* @param {Int} cacheNum Number of uuids to keep cached for future use*/
newUUID: function(cacheNum) {if (cacheNum === undefined) {cacheNum = 1;
}if (!uuidCache.length) {ajax({url: this.urlPrefix + "/_uuids", data: {count: cacheNum}, async:
false}, {success: function(resp) {uuidCache = resp.uuids;
}
},"Failed to retrieve UUID batch."
);}return uuidCache.shift();
}
});
/*** @private
*/function ajax(obj, options, errorMessage, ajaxOptions) {
var defaultAjaxOpts = {contentType: "application/json",headers:{"Accept": "application/json"}
};
options = $.extend({successStatus: 200}, options);ajaxOptions = $.extend(defaultAjaxOpts, ajaxOptions);errorMessage = errorMessage || "Unknown error";$.ajax($.extend($.extend({
type: "GET", dataType: "json", cache : !$.browser.msie,beforeSend: function(xhr){if(ajaxOptions && ajaxOptions.headers){for (var header in ajaxOptions.headers){
xhr.setRequestHeader(header, ajaxOptions.headers[header]);}
}},complete: function(req) {
8/3/2019 couch-js
20/21
try {var resp = $.parseJSON(req.responseText);
} catch(e) {
if (options.error) {options.error(req.status, req, e);
} else {
alert(errorMessage + ": " + e);}return;
}if (options.ajaxStart) {options.ajaxStart(resp);
}
if (req.status == options.successStatus) {if (options.beforeSuccess) options.beforeSuccess(req, resp);if (options.success) options.success(resp);
} else if (options.error) {options.error(req.status, resp && resp.error ||
errorMessage, resp && resp.reason || "no response");
} else {alert(errorMessage + ": " + resp.reason);
}}
}, obj), ajaxOptions));}
/*** @private*/
function fullCommit(options) {var options = options || {};if (typeof options.ensure_full_commit !== "undefined") {var commit = options.ensure_full_commit;
delete options.ensure_full_commit;return function(xhr) {
xhr.setRequestHeader('Accept', 'application/json');xhr.setRequestHeader("X-Couch-Full-Commit", commit.toString());
};}
};
/*** @private*/// 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 ($.inArray(name,
["error", "success", "beforeSuccess", "ajaxStart"]) >= 0)continue;
var value = options[name];
8/3/2019 couch-js
21/21
if ($.inArray(name, ["key", "startkey", "endkey"]) >= 0) {value = toJSON(value);
}
buf.push(encodeURIComponent(name) + "=" + encodeURIComponent(value));}
}
return buf.length ? "?" + buf.join("&") : "";}
/*** @private*/function toJSON(obj) {
return obj !== null ? JSON.stringify(obj) : null;}
})(jQuery);