blob: c325d68be9baac95d6f795f0b9060b5553e60eb8 [file] [log] [blame]
// 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.
// A simple class to represent a database. Uses XMLHttpRequest to interface with
// the CouchDB server.
function CouchDB(name, httpHeaders, globalRequestOptions) {
this.globalRequestOptions = globalRequestOptions || {}
this.name = name;
this.uri = "/" + encodeURIComponent(name) + "/";
// The XMLHttpRequest object from the most recent request. Callers can
// use this to check result http status and headers.
this.last_req = null;
this.request = function(method, uri, requestOptions) {
requestOptions = requestOptions || {};
requestOptions.headers = combine(requestOptions.headers, httpHeaders);
requestOptions.url = globalRequestOptions;
return CouchDB.request(method, uri, requestOptions);
};
// Creates the database on the server
this.createDb = function() {
this.last_req = this.request("PUT", this.uri);
CouchDB.maybeThrowError(this.last_req);
return JSON.parse(this.last_req.responseText);
};
// Deletes the database on the server
this.deleteDb = function() {
this.last_req = this.request("DELETE", this.uri + "?sync=true");
if (this.last_req.status == 404) {
return false;
}
CouchDB.maybeThrowError(this.last_req);
return JSON.parse(this.last_req.responseText);
};
// Save a document to the database
this.save = function(doc, options, http_headers) {
if (doc._id == undefined) {
doc._id = CouchDB.newUuids(1)[0];
}
http_headers = http_headers || {};
this.last_req = this.request("PUT", this.uri +
encodeURIComponent(doc._id) + encodeOptions(options),
{body: JSON.stringify(doc), headers: http_headers});
CouchDB.maybeThrowError(this.last_req);
var result = JSON.parse(this.last_req.responseText);
doc._rev = result.rev;
return result;
};
// Open a document from the database
this.open = function(docId, url_params, http_headers) {
this.last_req = this.request("GET", this.uri + encodeURIComponent(docId)
+ encodeOptions(url_params), {headers:http_headers});
if (this.last_req.status == 404) {
return null;
}
CouchDB.maybeThrowError(this.last_req);
return JSON.parse(this.last_req.responseText);
};
// Deletes a document from the database
this.deleteDoc = function(doc) {
this.last_req = this.request("DELETE", this.uri + encodeURIComponent(doc._id)
+ "?rev=" + doc._rev);
CouchDB.maybeThrowError(this.last_req);
var result = JSON.parse(this.last_req.responseText);
doc._rev = result.rev; //record rev in input document
doc._deleted = true;
return result;
};
// Deletes an attachment from a document
this.deleteDocAttachment = function(doc, attachment_name) {
this.last_req = this.request("DELETE", this.uri + encodeURIComponent(doc._id)
+ "/" + attachment_name + "?rev=" + doc._rev);
CouchDB.maybeThrowError(this.last_req);
var result = JSON.parse(this.last_req.responseText);
doc._rev = result.rev; //record rev in input document
return result;
};
this.bulkSave = function(docs, options) {
// first prepoulate the UUIDs for new documents
var newCount = 0;
for (var i=0; i<docs.length; i++) {
if (docs[i]._id == undefined) {
newCount++;
}
}
var newUuids = CouchDB.newUuids(newCount);
var newCount = 0;
for (var i=0; i<docs.length; i++) {
if (docs[i]._id == undefined) {
docs[i]._id = newUuids.pop();
}
}
var json = {"docs": docs};
// put any options in the json
for (var option in options) {
json[option] = options[option];
}
this.last_req = this.request("POST", this.uri + "_bulk_docs", {
body: JSON.stringify(json)
});
if (this.last_req.status == 417) {
return {errors: JSON.parse(this.last_req.responseText)};
}
else {
CouchDB.maybeThrowError(this.last_req);
var results = JSON.parse(this.last_req.responseText);
for (var i = 0; i < docs.length; i++) {
if(results[i] && results[i].rev && results[i].ok) {
docs[i]._rev = results[i].rev;
}
}
return results;
}
};
this.ensureFullCommit = function() {
this.last_req = this.request("POST", this.uri + "_ensure_full_commit");
CouchDB.maybeThrowError(this.last_req);
return JSON.parse(this.last_req.responseText);
};
// Applies the map function to the contents of database and returns the results.
this.query = function(mapFun, reduceFun, options, keys, language) {
//var body = {language: language || "javascript"};
var body = {}
if(keys) {
options.keys = keys ;
}
if (typeof(mapFun) != "string") {
mapFun = mapFun.toSource ? mapFun.toSource() : "(" + mapFun.toString() + ")";
}
if ((!language) || language.toLowerCase() == "javascript") {
mapFun = mapFun + "/" + "* avoid race cond " + (new Date().getTime()) + " *" + "/";
}
body.map = mapFun;
if (reduceFun != null) {
if (typeof(reduceFun) != "string") {
reduceFun = reduceFun.toSource ?
reduceFun.toSource() : "(" + reduceFun.toString() + ")";
}
body.reduce = reduceFun;
}
if (options && options.options != undefined) {
body.options = options.options;
delete options.options;
}
var ddoc = {
language: language || "javascript",
views: {
view: body
}
};
var ddoc_name = "_design/temp_" + get_random_string();
this.last_req = this.request("PUT", this.uri + ddoc_name, {
headers: {"Content-Type": "application/json"},
body: JSON.stringify(ddoc)
});
CouchDB.maybeThrowError(this.last_req);
var ddoc_result = JSON.parse(this.last_req.responseText)
this.last_req = this.request("GET", this.uri + ddoc_name + "/_view/view"
+ encodeOptions(options));
CouchDB.maybeThrowError(this.last_req);
var query_result = JSON.parse(this.last_req.responseText);
var res = this.request("DELETE", this.uri + ddoc_name + '?rev=' + ddoc_result.rev);
return query_result;
};
this.view = function(viewname, options, keys) {
var viewParts = viewname.split('/');
var viewPath = this.uri + "_design/" + viewParts[0] + "/_view/"
+ viewParts[1] + encodeOptions(options);
if(!keys) {
this.last_req = this.request("GET", viewPath);
} else {
this.last_req = this.request("POST", viewPath, {
headers: {"Content-Type": "application/json"},
body: JSON.stringify({keys:keys})
});
}
if (this.last_req.status == 404) {
return null;
}
CouchDB.maybeThrowError(this.last_req);
return JSON.parse(this.last_req.responseText);
};
// gets information about the database
this.info = function() {
this.last_req = this.request("GET", this.uri);
CouchDB.maybeThrowError(this.last_req);
return JSON.parse(this.last_req.responseText);
};
// gets information about a design doc
this.designInfo = function(docid) {
this.last_req = this.request("GET", this.uri + docid + "/_info");
CouchDB.maybeThrowError(this.last_req);
return JSON.parse(this.last_req.responseText);
};
this.allDocs = function(options,keys) {
if(!keys) {
this.last_req = this.request("GET", this.uri + "_all_docs"
+ encodeOptions(options));
} else {
this.last_req = this.request("POST", this.uri + "_all_docs"
+ encodeOptions(options), {
headers: {"Content-Type": "application/json"},
body: JSON.stringify({keys:keys})
});
}
CouchDB.maybeThrowError(this.last_req);
return JSON.parse(this.last_req.responseText);
};
this.designDocs = function() {
return this.allDocs({startkey:"_design", endkey:"_design0"});
};
this.changes = function(options) {
this.last_req = this.request("GET", this.uri + "_changes"
+ encodeOptions(options));
CouchDB.maybeThrowError(this.last_req);
return JSON.parse(this.last_req.responseText);
};
this.compact = function() {
this.last_req = this.request("POST", this.uri + "_compact");
CouchDB.maybeThrowError(this.last_req);
return JSON.parse(this.last_req.responseText);
};
this.viewCleanup = function() {
this.last_req = this.request("POST", this.uri + "_view_cleanup");
CouchDB.maybeThrowError(this.last_req);
return JSON.parse(this.last_req.responseText);
};
this.setDbProperty = function(propId, propValue) {
this.last_req = this.request("PUT", this.uri + propId,{
body:JSON.stringify(propValue)
});
CouchDB.maybeThrowError(this.last_req);
return JSON.parse(this.last_req.responseText);
};
this.getDbProperty = function(propId) {
this.last_req = this.request("GET", this.uri + propId);
CouchDB.maybeThrowError(this.last_req);
return JSON.parse(this.last_req.responseText);
};
this.setSecObj = function(secObj) {
this.last_req = this.request("PUT", this.uri + "_security",{
body:JSON.stringify(secObj)
});
CouchDB.maybeThrowError(this.last_req);
return JSON.parse(this.last_req.responseText);
};
this.getSecObj = function() {
this.last_req = this.request("GET", this.uri + "_security");
CouchDB.maybeThrowError(this.last_req);
return JSON.parse(this.last_req.responseText);
};
// 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)) { continue; };
var value = options[name];
if (name == "key" || name == "keys" || name == "startkey" || name == "endkey" || (name == "open_revs" && value !== "all")) {
value = toJSON(value);
}
buf.push(encodeURIComponent(name) + "=" + encodeURIComponent(value));
}
}
if (!buf.length) {
return "";
}
return "?" + buf.join("&");
}
function toJSON(obj) {
return obj !== null ? JSON.stringify(obj) : null;
}
function combine(object1, object2) {
if (!object2) {
return object1;
}
if (!object1) {
return object2;
}
for (var name in object2) {
object1[name] = object2[name];
}
return object1;
}
}
// this is the XMLHttpRequest object from last request made by the following
// CouchDB.* functions (except for calls to request itself).
// Use this from callers to check HTTP status or header values of requests.
CouchDB.last_req = null;
CouchDB.urlPrefix = '';
CouchDB.login = function(name, password) {
CouchDB.last_req = CouchDB.request("POST", "/_session", {
headers: {"Content-Type": "application/x-www-form-urlencoded",
"X-CouchDB-WWW-Authenticate": "Cookie"},
body: "name=" + encodeURIComponent(name) + "&password="
+ encodeURIComponent(password)
});
return JSON.parse(CouchDB.last_req.responseText);
}
CouchDB.logout = function() {
CouchDB.last_req = CouchDB.request("DELETE", "/_session", {
headers: {"Content-Type": "application/x-www-form-urlencoded",
"X-CouchDB-WWW-Authenticate": "Cookie"}
});
return JSON.parse(CouchDB.last_req.responseText);
};
CouchDB.session = function(options) {
options = options || {};
CouchDB.last_req = CouchDB.request("GET", "/_session", options);
CouchDB.maybeThrowError(CouchDB.last_req);
return JSON.parse(CouchDB.last_req.responseText);
};
CouchDB.allDbs = function() {
CouchDB.last_req = CouchDB.request("GET", "/_all_dbs");
CouchDB.maybeThrowError(CouchDB.last_req);
return JSON.parse(CouchDB.last_req.responseText);
};
CouchDB.allDesignDocs = function() {
var ddocs = {}, dbs = CouchDB.allDbs();
for (var i=0; i < dbs.length; i++) {
var db = new CouchDB(dbs[i]);
ddocs[dbs[i]] = db.designDocs();
};
return ddocs;
};
CouchDB.getVersion = function() {
CouchDB.last_req = CouchDB.request("GET", "/");
CouchDB.maybeThrowError(CouchDB.last_req);
return JSON.parse(CouchDB.last_req.responseText).version;
};
CouchDB.reloadConfig = function() {
// diabled until cluser port gets /_config
return {};
CouchDB.last_req = CouchDB.request("POST", "/_config/_reload");
CouchDB.maybeThrowError(CouchDB.last_req);
return JSON.parse(CouchDB.last_req.responseText);
};
CouchDB.replicate = function(source, target, rep_options) {
rep_options = rep_options || {};
var headers = rep_options.headers || {};
var body = rep_options.body || {};
body.source = source;
body.target = target;
CouchDB.last_req = CouchDB.request("POST", "/_replicate", {
headers: headers,
body: JSON.stringify(body)
});
CouchDB.maybeThrowError(CouchDB.last_req);
return JSON.parse(CouchDB.last_req.responseText);
};
CouchDB.newXhr = function() {
if (typeof(XMLHttpRequest) != "undefined") {
return new XMLHttpRequest();
} else if (typeof(ActiveXObject) != "undefined") {
return new ActiveXObject("Microsoft.XMLHTTP");
} else {
throw new Error("No XMLHTTPRequest support detected");
}
};
CouchDB.xhrbody = function(xhr) {
if (xhr.responseText) {
return xhr.responseText;
} else if (xhr.body) {
return xhr.body
} else {
throw new Error("No XMLHTTPRequest support detected");
}
}
CouchDB.xhrheader = function(xhr, header) {
if(xhr.getResponseHeader) {
return xhr.getResponseHeader(header);
} else if(xhr.headers) {
return xhr.headers[header] || null;
} else {
throw new Error("No XMLHTTPRequest support detected");
}
}
CouchDB.proxyUrl = function(uri) {
if(uri.substr(0, CouchDB.protocol.length) != CouchDB.protocol) {
uri = CouchDB.urlPrefix + uri;
}
return uri;
}
CouchDB.request = function(method, uri, options) {
options = typeof(options) == 'object' ? options : {};
options.headers = typeof(options.headers) == 'object' ? options.headers : {};
options.headers["Content-Type"] = options.headers["Content-Type"] || options.headers["content-type"] || "application/json";
options.headers["Accept"] = options.headers["Accept"] || options.headers["accept"] || "application/json";
var req = CouchDB.newXhr();
uri = CouchDB.proxyUrl(uri);
if (options.url) {
var params = '';
for (var key in options.url) {
var value = options.url[key]
params += key + '=' + value + '&'
}
// if uri already has a ? append with &
if (uri.indexOf('?') === -1) {
uri += '?' + params;
} else {
uri += '&' + params;
}
}
// console.log(uri);
// console.log(JSON.stringify(options, null, 2));
req.open(method, uri, false);
if (options.headers) {
var headers = options.headers;
for (var headerName in headers) {
if (!headers.hasOwnProperty(headerName)) { continue; }
req.setRequestHeader(headerName, headers[headerName]);
}
}
req.send(options.body || "");
return req;
};
CouchDB.requestStats = function(path, test) {
var query_arg = "";
if(test !== null) {
query_arg = "?flush=true";
}
var url = "/_node/_local/_stats/" + path.join("/") + query_arg;
var stat = CouchDB.request("GET", url).responseText;
return JSON.parse(stat);
};
CouchDB.uuids_cache = [];
CouchDB.newUuids = function(n, buf) {
buf = buf || 100;
if (CouchDB.uuids_cache.length >= n) {
var uuids = CouchDB.uuids_cache.slice(CouchDB.uuids_cache.length - n);
if(CouchDB.uuids_cache.length - n == 0) {
CouchDB.uuids_cache = [];
} else {
CouchDB.uuids_cache =
CouchDB.uuids_cache.slice(0, CouchDB.uuids_cache.length - n);
}
return uuids;
} else {
CouchDB.last_req = CouchDB.request("GET", "/_uuids?count=" + (buf + n));
CouchDB.maybeThrowError(CouchDB.last_req);
var result = JSON.parse(CouchDB.last_req.responseText);
CouchDB.uuids_cache =
CouchDB.uuids_cache.concat(result.uuids.slice(0, buf));
return result.uuids.slice(buf);
}
};
CouchDB.maybeThrowError = function(req) {
if (req.status >= 400) {
try {
var result = JSON.parse(req.responseText);
} catch (ParseError) {
var result = {error:"unknown", reason:req.responseText};
}
throw (new CouchError(result));
}
}
CouchDB.params = function(options) {
options = options || {};
var returnArray = [];
for(var key in options) {
var value = options[key];
returnArray.push(key + "=" + value);
}
return returnArray.join("&");
};
// Used by replication test
if (typeof window == 'undefined' || !window) {
var hostRE = RegExp("https?://([^\/]+)");
var getter = function () {
return (new CouchHTTP).base_url.match(hostRE)[1];
};
if(Object.defineProperty) {
Object.defineProperty(CouchDB, "host", {
get : getter,
enumerable : true
});
} else {
CouchDB.__defineGetter__("host", getter);
}
CouchDB.protocol = "http://";
CouchDB.inBrowser = false;
} else {
CouchDB.host = window.location.host;
CouchDB.inBrowser = true;
CouchDB.protocol = window.location.protocol + "//";
}
// Turns an {error: ..., reason: ...} response into an Error instance
function CouchError(error) {
var inst = new Error(error.reason);
inst.name = 'CouchError';
inst.error = error.error;
inst.reason = error.reason;
return inst;
}
CouchError.prototype.constructor = CouchError;