PageRenderTime 56ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/couch.js

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