PageRenderTime 58ms CodeModel.GetById 18ms RepoModel.GetById 1ms app.codeStats 0ms

/src/js/couch.js

https://github.com/redeemedfadi/harmony
JavaScript | 474 lines | 387 code | 56 blank | 31 comment | 88 complexity | ff65585696b7905474502bf5e77ec727 MD5 | raw file
  1. // Licensed under the Apache License, Version 2.0 (the "License"); you may not
  2. // use this file except in compliance with the License. You may obtain a copy of
  3. // the License at
  4. //
  5. // http://www.apache.org/licenses/LICENSE-2.0
  6. //
  7. // Unless required by applicable law or agreed to in writing, software
  8. // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  9. // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  10. // License for the specific language governing permissions and limitations under
  11. // the License.
  12. // A simple class to represent a database. Uses XMLHttpRequest to interface with
  13. // the CouchDB server.
  14. function CouchDB(name, httpHeaders) {
  15. this.name = name;
  16. this.uri = "/" + encodeURIComponent(name) + "/";
  17. // The XMLHttpRequest object from the most recent request. Callers can
  18. // use this to check result http status and headers.
  19. this.last_req = null;
  20. this.request = function(method, uri, requestOptions) {
  21. requestOptions = requestOptions || {}
  22. requestOptions.headers = combine(requestOptions.headers, httpHeaders)
  23. return CouchDB.request(method, uri, requestOptions);
  24. }
  25. // Creates the database on the server
  26. this.createDb = function() {
  27. this.last_req = this.request("PUT", this.uri);
  28. CouchDB.maybeThrowError(this.last_req);
  29. return JSON.parse(this.last_req.responseText);
  30. }
  31. // Deletes the database on the server
  32. this.deleteDb = function() {
  33. this.last_req = this.request("DELETE", this.uri);
  34. if (this.last_req.status == 404)
  35. return false;
  36. CouchDB.maybeThrowError(this.last_req);
  37. return JSON.parse(this.last_req.responseText);
  38. }
  39. // Save a document to the database
  40. this.save = function(doc, options) {
  41. if (doc._id == undefined)
  42. doc._id = CouchDB.newUuids(1)[0];
  43. this.last_req = this.request("PUT", this.uri +
  44. encodeURIComponent(doc._id) + encodeOptions(options),
  45. {body: JSON.stringify(doc)});
  46. CouchDB.maybeThrowError(this.last_req);
  47. var result = JSON.parse(this.last_req.responseText);
  48. doc._rev = result.rev;
  49. return result;
  50. }
  51. // Open a document from the database
  52. this.open = function(docId, options) {
  53. this.last_req = this.request("GET", this.uri + encodeURIComponent(docId) + encodeOptions(options));
  54. if (this.last_req.status == 404)
  55. return null;
  56. CouchDB.maybeThrowError(this.last_req);
  57. return JSON.parse(this.last_req.responseText);
  58. }
  59. // Deletes a document from the database
  60. this.deleteDoc = function(doc) {
  61. this.last_req = this.request("DELETE", this.uri + encodeURIComponent(doc._id) + "?rev=" + doc._rev);
  62. CouchDB.maybeThrowError(this.last_req);
  63. var result = JSON.parse(this.last_req.responseText);
  64. doc._rev = result.rev; //record rev in input document
  65. doc._deleted = true;
  66. return result;
  67. }
  68. // Deletes an attachment from a document
  69. this.deleteDocAttachment = function(doc, attachment_name) {
  70. this.last_req = this.request("DELETE", this.uri + encodeURIComponent(doc._id) + "/" + attachment_name + "?rev=" + doc._rev);
  71. CouchDB.maybeThrowError(this.last_req);
  72. var result = JSON.parse(this.last_req.responseText);
  73. doc._rev = result.rev; //record rev in input document
  74. return result;
  75. }
  76. this.bulkSave = function(docs, options) {
  77. // first prepoulate the UUIDs for new documents
  78. var newCount = 0
  79. for (var i=0; i<docs.length; i++) {
  80. if (docs[i]._id == undefined)
  81. newCount++;
  82. }
  83. var newUuids = CouchDB.newUuids(docs.length);
  84. var newCount = 0
  85. for (var i=0; i<docs.length; i++) {
  86. if (docs[i]._id == undefined)
  87. docs[i]._id = newUuids.pop();
  88. }
  89. var json = {"docs": docs};
  90. // put any options in the json
  91. for (var option in options) {
  92. json[option] = options[option];
  93. }
  94. this.last_req = this.request("POST", this.uri + "_bulk_docs", {
  95. body: JSON.stringify(json)
  96. });
  97. if (this.last_req.status == 417) {
  98. return {errors: JSON.parse(this.last_req.responseText)};
  99. }
  100. else {
  101. CouchDB.maybeThrowError(this.last_req);
  102. var results = JSON.parse(this.last_req.responseText);
  103. for (var i = 0; i < docs.length; i++) {
  104. if(results[i].rev)
  105. docs[i]._rev = results[i].rev;
  106. }
  107. return results;
  108. }
  109. }
  110. this.ensureFullCommit = function() {
  111. this.last_req = this.request("POST", this.uri + "_ensure_full_commit");
  112. CouchDB.maybeThrowError(this.last_req);
  113. return JSON.parse(this.last_req.responseText);
  114. }
  115. // Applies the map function to the contents of database and returns the results.
  116. this.query = function(mapFun, reduceFun, options, keys, language) {
  117. var body = {language: language || "javascript"};
  118. if(keys) {
  119. body.keys = keys ;
  120. }
  121. if (typeof(mapFun) != "string")
  122. mapFun = mapFun.toSource ? mapFun.toSource() : "(" + mapFun.toString() + ")";
  123. body.map = mapFun;
  124. if (reduceFun != null) {
  125. if (typeof(reduceFun) != "string")
  126. reduceFun = reduceFun.toSource ? reduceFun.toSource() : "(" + reduceFun.toString() + ")";
  127. body.reduce = reduceFun;
  128. }
  129. if (options && options.options != undefined) {
  130. body.options = options.options;
  131. delete options.options;
  132. }
  133. this.last_req = this.request("POST", this.uri + "_temp_view" + encodeOptions(options), {
  134. headers: {"Content-Type": "application/json"},
  135. body: JSON.stringify(body)
  136. });
  137. CouchDB.maybeThrowError(this.last_req);
  138. return JSON.parse(this.last_req.responseText);
  139. }
  140. this.view = function(viewname, options, keys) {
  141. var viewParts = viewname.split('/');
  142. var viewPath = this.uri + "_design/" + viewParts[0] + "/_view/"
  143. + viewParts[1] + encodeOptions(options);
  144. if(!keys) {
  145. this.last_req = this.request("GET", viewPath);
  146. } else {
  147. this.last_req = this.request("POST", viewPath, {
  148. headers: {"Content-Type": "application/json"},
  149. body: JSON.stringify({keys:keys})
  150. });
  151. }
  152. if (this.last_req.status == 404)
  153. return null;
  154. CouchDB.maybeThrowError(this.last_req);
  155. return JSON.parse(this.last_req.responseText);
  156. }
  157. // gets information about the database
  158. this.info = function() {
  159. this.last_req = this.request("GET", this.uri);
  160. CouchDB.maybeThrowError(this.last_req);
  161. return JSON.parse(this.last_req.responseText);
  162. }
  163. // gets information about a design doc
  164. this.designInfo = function(docid) {
  165. this.last_req = this.request("GET", this.uri + docid + "/_info");
  166. CouchDB.maybeThrowError(this.last_req);
  167. return JSON.parse(this.last_req.responseText);
  168. }
  169. this.viewCleanup = function() {
  170. this.last_req = this.request("POST", this.uri + "_view_cleanup");
  171. CouchDB.maybeThrowError(this.last_req);
  172. return JSON.parse(this.last_req.responseText);
  173. }
  174. this.allDocs = function(options,keys) {
  175. if(!keys) {
  176. this.last_req = this.request("GET", this.uri + "_all_docs" + encodeOptions(options));
  177. } else {
  178. this.last_req = this.request("POST", this.uri + "_all_docs" + encodeOptions(options), {
  179. headers: {"Content-Type": "application/json"},
  180. body: JSON.stringify({keys:keys})
  181. });
  182. }
  183. CouchDB.maybeThrowError(this.last_req);
  184. return JSON.parse(this.last_req.responseText);
  185. }
  186. this.designDocs = function() {
  187. return this.allDocs({startkey:"_design", endkey:"_design0"});
  188. };
  189. this.allDocsBySeq = function(options,keys) {
  190. var req = null;
  191. if(!keys) {
  192. req = this.request("GET", this.uri + "_all_docs_by_seq" + encodeOptions(options));
  193. } else {
  194. req = this.request("POST", this.uri + "_all_docs_by_seq" + encodeOptions(options), {
  195. headers: {"Content-Type": "application/json"},
  196. body: JSON.stringify({keys:keys})
  197. });
  198. }
  199. CouchDB.maybeThrowError(req);
  200. return JSON.parse(req.responseText);
  201. }
  202. this.compact = function() {
  203. this.last_req = this.request("POST", this.uri + "_compact");
  204. CouchDB.maybeThrowError(this.last_req);
  205. return JSON.parse(this.last_req.responseText);
  206. }
  207. this.setDbProperty = function(propId, propValue) {
  208. this.last_req = this.request("PUT", this.uri + propId,{
  209. body:JSON.stringify(propValue)
  210. });
  211. CouchDB.maybeThrowError(this.last_req);
  212. return JSON.parse(this.last_req.responseText);
  213. }
  214. this.getDbProperty = function(propId) {
  215. this.last_req = this.request("GET", this.uri + propId);
  216. CouchDB.maybeThrowError(this.last_req);
  217. return JSON.parse(this.last_req.responseText);
  218. }
  219. this.setAdmins = function(adminsArray) {
  220. this.last_req = this.request("PUT", this.uri + "_admins",{
  221. body:JSON.stringify(adminsArray)
  222. });
  223. CouchDB.maybeThrowError(this.last_req);
  224. return JSON.parse(this.last_req.responseText);
  225. }
  226. this.getAdmins = function() {
  227. this.last_req = this.request("GET", this.uri + "_admins");
  228. CouchDB.maybeThrowError(this.last_req);
  229. return JSON.parse(this.last_req.responseText);
  230. }
  231. // Convert a options object to an url query string.
  232. // ex: {key:'value',key2:'value2'} becomes '?key="value"&key2="value2"'
  233. function encodeOptions(options) {
  234. var buf = []
  235. if (typeof(options) == "object" && options !== null) {
  236. for (var name in options) {
  237. if (!options.hasOwnProperty(name)) continue;
  238. var value = options[name];
  239. if (name == "key" || name == "startkey" || name == "endkey") {
  240. value = toJSON(value);
  241. }
  242. buf.push(encodeURIComponent(name) + "=" + encodeURIComponent(value));
  243. }
  244. }
  245. if (!buf.length) {
  246. return "";
  247. }
  248. return "?" + buf.join("&");
  249. }
  250. function toJSON(obj) {
  251. return obj !== null ? JSON.stringify(obj) : null;
  252. }
  253. function combine(object1, object2) {
  254. if (!object2)
  255. return object1;
  256. if (!object1)
  257. return object2;
  258. for (var name in object2)
  259. object1[name] = object2[name];
  260. return object1;
  261. }
  262. }
  263. // this is the XMLHttpRequest object from last request made by the following
  264. // CouchDB.* functions (except for calls to request itself).
  265. // Use this from callers to check HTTP status or header values of requests.
  266. CouchDB.last_req = null;
  267. CouchDB.login = function(username, password) {
  268. CouchDB.last_req = CouchDB.request("POST", "/_session", {
  269. headers: {"Content-Type": "application/x-www-form-urlencoded",
  270. "X-CouchDB-WWW-Authenticate": "Cookie"},
  271. body: "username=" + encodeURIComponent(username) + "&password=" + encodeURIComponent(password)
  272. });
  273. return JSON.parse(CouchDB.last_req.responseText);
  274. }
  275. CouchDB.logout = function() {
  276. CouchDB.last_req = CouchDB.request("DELETE", "/_session", {
  277. headers: {"Content-Type": "application/x-www-form-urlencoded",
  278. "X-CouchDB-WWW-Authenticate": "Cookie"}
  279. });
  280. return JSON.parse(CouchDB.last_req.responseText);
  281. }
  282. CouchDB.createUser = function(username, password, email, roles, basicAuth) {
  283. var roles_str = ""
  284. if (roles) {
  285. for (var i=0; i< roles.length; i++) {
  286. roles_str += "&roles=" + encodeURIComponent(roles[i]);
  287. }
  288. }
  289. var headers = {"Content-Type": "application/x-www-form-urlencoded"};
  290. if (basicAuth) {
  291. headers['Authorization'] = basicAuth
  292. } else {
  293. headers['X-CouchDB-WWW-Authenticate'] = 'Cookie';
  294. }
  295. CouchDB.last_req = CouchDB.request("POST", "/_user/", {
  296. headers: headers,
  297. body: "username=" + encodeURIComponent(username) + "&password=" + encodeURIComponent(password)
  298. + "&email="+ encodeURIComponent(email)+ roles_str
  299. });
  300. return JSON.parse(CouchDB.last_req.responseText);
  301. }
  302. CouchDB.updateUser = function(username, email, roles, password, old_password) {
  303. var roles_str = ""
  304. if (roles) {
  305. for (var i=0; i< roles.length; i++) {
  306. roles_str += "&roles=" + encodeURIComponent(roles[i]);
  307. }
  308. }
  309. var body = "email="+ encodeURIComponent(email)+ roles_str;
  310. if (typeof(password) != "undefined" && password)
  311. body += "&password=" + password;
  312. if (typeof(old_password) != "undefined" && old_password)
  313. body += "&old_password=" + old_password;
  314. CouchDB.last_req = CouchDB.request("PUT", "/_user/"+encodeURIComponent(username), {
  315. headers: {"Content-Type": "application/x-www-form-urlencoded",
  316. "X-CouchDB-WWW-Authenticate": "Cookie"},
  317. body: body
  318. });
  319. return JSON.parse(CouchDB.last_req.responseText);
  320. }
  321. CouchDB.allDbs = function() {
  322. CouchDB.last_req = CouchDB.request("GET", "/_all_dbs");
  323. CouchDB.maybeThrowError(CouchDB.last_req);
  324. return JSON.parse(CouchDB.last_req.responseText);
  325. }
  326. CouchDB.allDesignDocs = function() {
  327. var ddocs = {}, dbs = CouchDB.allDbs();
  328. for (var i=0; i < dbs.length; i++) {
  329. var db = new CouchDB(dbs[i]);
  330. ddocs[dbs[i]] = db.designDocs();
  331. };
  332. return ddocs;
  333. };
  334. CouchDB.getVersion = function() {
  335. CouchDB.last_req = CouchDB.request("GET", "/");
  336. CouchDB.maybeThrowError(CouchDB.last_req);
  337. return JSON.parse(CouchDB.last_req.responseText).version;
  338. }
  339. CouchDB.replicate = function(source, target, rep_options) {
  340. rep_options = rep_options || {};
  341. var headers = rep_options.headers || {};
  342. CouchDB.last_req = CouchDB.request("POST", "/_replicate", {
  343. headers: headers,
  344. body: JSON.stringify({source: source, target: target})
  345. });
  346. CouchDB.maybeThrowError(CouchDB.last_req);
  347. return JSON.parse(CouchDB.last_req.responseText);
  348. }
  349. CouchDB.newXhr = function() {
  350. if (typeof(XMLHttpRequest) != "undefined") {
  351. return new XMLHttpRequest();
  352. } else if (typeof(ActiveXObject) != "undefined") {
  353. return new ActiveXObject("Microsoft.XMLHTTP");
  354. } else {
  355. throw new Error("No XMLHTTPRequest support detected");
  356. }
  357. }
  358. CouchDB.request = function(method, uri, options) {
  359. options = options || {};
  360. var req = CouchDB.newXhr();
  361. req.open(method, uri, false);
  362. if (options.headers) {
  363. var headers = options.headers;
  364. for (var headerName in headers) {
  365. if (!headers.hasOwnProperty(headerName)) continue;
  366. req.setRequestHeader(headerName, headers[headerName]);
  367. }
  368. }
  369. req.send(options.body || "");
  370. return req;
  371. }
  372. CouchDB.requestStats = function(module, key, test) {
  373. var query_arg = "";
  374. if(test !== null) {
  375. query_arg = "?flush=true";
  376. }
  377. var stat = CouchDB.request("GET", "/_stats/" + module + "/" + key + query_arg).responseText;
  378. return JSON.parse(stat)[module][key];
  379. }
  380. CouchDB.uuids_cache = [];
  381. CouchDB.newUuids = function(n) {
  382. if (CouchDB.uuids_cache.length >= n) {
  383. var uuids = CouchDB.uuids_cache.slice(CouchDB.uuids_cache.length - n);
  384. if(CouchDB.uuids_cache.length - n == 0) {
  385. CouchDB.uuids_cache = [];
  386. } else {
  387. CouchDB.uuids_cache =
  388. CouchDB.uuids_cache.slice(0, CouchDB.uuids_cache.length - n);
  389. }
  390. return uuids;
  391. } else {
  392. CouchDB.last_req = CouchDB.request("GET", "/_uuids?count=" + (100 + n));
  393. CouchDB.maybeThrowError(CouchDB.last_req);
  394. var result = JSON.parse(CouchDB.last_req.responseText);
  395. CouchDB.uuids_cache =
  396. CouchDB.uuids_cache.concat(result.uuids.slice(0, 100));
  397. return result.uuids.slice(100);
  398. }
  399. }
  400. CouchDB.maybeThrowError = function(req) {
  401. if (req.status >= 400) {
  402. try {
  403. var result = JSON.parse(req.responseText);
  404. } catch (ParseError) {
  405. var result = {error:"unknown", reason:req.responseText};
  406. }
  407. throw result;
  408. }
  409. }
  410. CouchDB.params = function(options) {
  411. options = options || {};
  412. var returnArray = [];
  413. for(var key in options) {
  414. var value = options[key];
  415. returnArray.push(key + "=" + value);
  416. }
  417. return returnArray.join("&");
  418. }