ffbad34303de9345feb827a322fcf11f03a304ed
[couchdb.git] / share / www / script / jquery.couch.js
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
13 /**
14  * @namespace
15  * $.couch is used to communicate with a CouchDB server, the server methods can
16  * be called directly without creating an instance. Typically all methods are
17  * passed an <code>options</code> object which defines a success callback which
18  * is called with the data returned from the http request to CouchDB, you can
19  * find the other settings that can be used in the <code>options</code> object
20  * from <a href="http://api.jquery.com/jQuery.ajax/#jQuery-ajax-settings">
21  * jQuery.ajax settings</a>
22  * <pre><code>$.couch.activeTasks({
23  *   success: function (data) {
24  *     console.log(data);
25  *   }
26  * });</code></pre>
27  * Outputs (for example):
28  * <pre><code>[
29  *  {
30  *   "pid" : "<0.11599.0>",
31  *   "status" : "Copied 0 of 18369 changes (0%)",
32  *   "task" : "recipes",
33  *   "type" : "Database Compaction"
34  *  }
35  *]</code></pre>
36  */
37 (function($) {
38
39   $.couch = $.couch || {};
40   /** @lends $.couch */
41
42   /**
43    * @private
44    */
45   function encodeDocId(docID) {
46     var parts = docID.split("/");
47     if (parts[0] == "_design") {
48       parts.shift();
49       return "_design/" + encodeURIComponent(parts.join('/'));
50     }
51     return encodeURIComponent(docID);
52   }
53
54   /**
55    * @private
56    */
57
58   var uuidCache = [];
59
60   $.extend($.couch, {
61     urlPrefix: '',
62
63     /**
64      * You can obtain a list of active tasks by using the /_active_tasks URL.
65      * The result is a JSON array of the currently running tasks, with each task
66      * being described with a single object.
67      * @see <a href="http://docs.couchdb.org/en/latest/api/server/common.html#
68      * active-tasks">docs for /_active_tasks</a>
69      * @param {ajaxSettings} options <a href="http://api.jquery.com/jQuery.ajax
70      * /#jQuery-ajax-settings">jQuery ajax settings</a>
71      */
72     activeTasks: function(options) {
73       return ajax(
74         {url: this.urlPrefix + "/_active_tasks"},
75         options,
76         "Active task status could not be retrieved"
77       );
78     },
79
80     /**
81      * Returns a list of all the databases in the CouchDB instance
82      * @see <a href="http://docs.couchdb.org/en/latest/api/server/common.html
83      * #all-dbs">docs for /_all_dbs</a>
84      * @param {ajaxSettings} options <a href="http://api.jquery.com/jQuery.ajax
85      * /#jQuery-ajax-settings">jQuery ajax settings</a>
86      */
87     allDbs: function(options) {
88       return ajax(
89         {url: this.urlPrefix + "/_all_dbs"},
90         options,
91         "An error occurred retrieving the list of all databases"
92       );
93     },
94
95     /**
96      * View and edit the CouchDB configuration, called with just the options
97      * parameter the entire config is returned, you can be more specific by
98      * passing the section and option parameters, if you specify a value that
99      * value will be stored in the configuration.
100      * @see <a href="http://docs.couchdb.org/en/latest/api/server
101      * /configuration.html#config-section-key">docs for /_config</a>
102      * @param {ajaxSettings} options
103      * <a href="http://api.jquery.com/jQuery.ajax/#jQuery-ajax-settings">
104      * jQuery ajax settings</a>
105      * @param {String} [section] the section of the config
106      * @param {String} [option] the particular config option
107      * @param {String} [value] value to be set
108      */
109     config: function(options, section, option, value) {
110       var req = {url: this.urlPrefix + "/_config/"};
111       if (section) {
112         req.url += encodeURIComponent(section) + "/";
113         if (option) {
114           req.url += encodeURIComponent(option);
115         }
116       }
117       if (value === null) {
118         req.type = "DELETE";
119       } else if (value !== undefined) {
120         req.type = "PUT";
121         req.data = toJSON(value);
122         req.contentType = "application/json";
123         req.processData = false
124       }
125
126       return ajax(req, options,
127         "An error occurred retrieving/updating the server configuration"
128       );
129     },
130
131     /**
132      * Returns the session information for the currently logged in user.
133      * @see <a href="http://docs.couchdb.org/en/latest/api/server/authn.html
134      * #get--_session">docs for GET /_session</a>
135      * @param {ajaxSettings} options
136      * <a href="http://api.jquery.com/jQuery.ajax/#jQuery-ajax-settings">
137      * jQuery ajax settings</a>
138      */
139     session: function(options) {
140       options = options || {};
141       return ajax({
142         type: "GET", url: this.urlPrefix + "/_session",
143         beforeSend: function(xhr) {
144             xhr.setRequestHeader('Accept', 'application/json');
145         },
146         complete: function(req) {
147           var resp = $.parseJSON(req.responseText);
148           if (req.status == 200) {
149             if (options.success) options.success(resp);
150           } else if (options.error) {
151             options.error(req.status, resp.error, resp.reason);
152           } else {
153             throw "An error occurred getting session info: " + resp.reason;
154           }
155         }
156       });
157     },
158
159     /**
160      * @private
161      */
162     userDb : function(callback) {
163       return $.couch.session({
164         success : function(resp) {
165           var userDb = $.couch.db(resp.info.authentication_db);
166           callback(userDb);
167         }
168       });
169     },
170
171     /**
172      * Create a new user on the CouchDB server, <code>user_doc</code> is an
173      * object with a <code>name</code> field and other information you want
174      * to store relating to that user, for example
175      * <code>{"name": "daleharvey"}</code>
176      * @param {Object} user_doc Users details
177      * @param {String} password Users password
178      * @param {ajaxSettings} options
179      * <a href="http://api.jquery.com/jQuery.ajax/#jQuery-ajax-settings">
180       * jQuery ajax settings</a>
181      */
182     signup: function(user_doc, password, options) {
183       options = options || {};
184       user_doc.password = password;
185       user_doc.roles = user_doc.roles || [];
186       user_doc.type = "user";
187       var user_prefix = "org.couchdb.user:";
188       user_doc._id = user_doc._id || user_prefix + user_doc.name;
189
190       return $.couch.userDb(function(db) {
191         db.saveDoc(user_doc, options);
192       });
193     },
194
195     /**
196      * Authenticate against CouchDB, the <code>options</code> parameter is
197       *expected to have <code>name</code> and <code>password</code> fields.
198      * @see <a href="http://docs.couchdb.org/en/latest/api/server/authn.html
199      * #post--_session">docs for POST /_session</a>
200      * @param {ajaxSettings} options
201      * <a href="http://api.jquery.com/jQuery.ajax/#jQuery-ajax-settings">
202      * jQuery ajax settings</a>
203      */
204     login: function(options) {
205       options = options || {};
206       return $.ajax({
207         type: "POST", url: this.urlPrefix + "/_session", dataType: "json",
208         data: {name: options.name, password: options.password},
209         beforeSend: function(xhr) {
210             xhr.setRequestHeader('Accept', 'application/json');
211         },
212         complete: function(req) {
213           var resp = $.parseJSON(req.responseText);
214           if (req.status == 200) {
215             if (options.success) options.success(resp);
216           } else if (options.error) {
217             options.error(req.status, resp.error, resp.reason);
218           } else {
219             throw 'An error occurred logging in: ' + resp.reason;
220           }
221         }
222       });
223     },
224
225
226     /**
227      * Delete your current CouchDB user session
228      * @see <a href="http://docs.couchdb.org/en/latest/api/server/authn.html
229      * #delete--_session">docs for DELETE /_session</a>
230      * @param {ajaxSettings} options
231      * <a href="http://api.jquery.com/jQuery.ajax/#jQuery-ajax-settings">
232      * jQuery ajax settings</a>
233      */
234     logout: function(options) {
235       options = options || {};
236       return $.ajax({
237         type: "DELETE", url: this.urlPrefix + "/_session", dataType: "json",
238         username : "_", password : "_",
239         beforeSend: function(xhr) {
240             xhr.setRequestHeader('Accept', 'application/json');
241         },
242         complete: function(req) {
243           var resp = $.parseJSON(req.responseText);
244           if (req.status == 200) {
245             if (options.success) options.success(resp);
246           } else if (options.error) {
247             options.error(req.status, resp.error, resp.reason);
248           } else {
249             throw 'An error occurred logging out: ' + resp.reason;
250           }
251         }
252       });
253     },
254
255     /**
256      * @namespace
257      * $.couch.db is used to communicate with a specific CouchDB database
258      * <pre><code>var $db = $.couch.db("mydatabase");
259      *$db.allApps({
260      *  success: function (data) {
261      *    ... process data ...
262      *  }
263      *});
264      * </code></pre>
265      */
266     db: function(name, db_opts) {
267       db_opts = db_opts || {};
268       var rawDocs = {};
269       function maybeApplyVersion(doc) {
270         if (doc._id && doc._rev && rawDocs[doc._id] &&
271             rawDocs[doc._id].rev == doc._rev) {
272           // todo: can we use commonjs require here?
273           if (typeof Base64 == "undefined") {
274             throw 'Base64 support not found.';
275           } else {
276             doc._attachments = doc._attachments || {};
277             doc._attachments["rev-"+doc._rev.split("-")[0]] = {
278               content_type :"application/json",
279               data : Base64.encode(rawDocs[doc._id].raw)
280             };
281             return true;
282           }
283         }
284       }
285       return /** @lends $.couch.db */{
286         name: name,
287         uri: this.urlPrefix + "/" + encodeURIComponent(name) + "/",
288
289         /**
290          * Request compaction of the specified database.
291          * @see <a href="http://docs.couchdb.org/en/latest/api/database
292          * /compact.html#db-compact">docs for /db/_compact</a>
293          * @param {ajaxSettings} options
294          * <a href="http://api.jquery.com/jQuery.ajax/#jQuery-ajax-settings">
295          * jQuery ajax settings</a>
296          */
297         compact: function(options) {
298           $.extend(options, {successStatus: 202});
299           return ajax({
300               type: "POST", url: this.uri + "_compact",
301               data: "", processData: false
302             },
303             options,
304             "The database could not be compacted"
305           );
306         },
307
308         /**
309          * Cleans up the cached view output on disk for a given view.
310          * @see <a href="http://docs.couchdb.org/en/latest/api/database
311          * /compact.html#db-view-cleanup">docs for /db/_view_cleanup</a>
312          * @param {ajaxSettings} options <a href="http://api.jquery.com/
313          * jQuery.ajax/#jQuery-ajax-settings">jQuery ajax settings</a>
314          */
315         viewCleanup: function(options) {
316           $.extend(options, {successStatus: 202});
317           return ajax({
318               type: "POST", url: this.uri + "_view_cleanup",
319               data: "", processData: false
320             },
321             options,
322             "The views could not be cleaned up"
323           );
324         },
325
326         /**
327          * Compacts the view indexes associated with the specified design
328          * document. You can use this in place of the full database compaction
329          * if you know a specific set of view indexes have been affected by a
330          * recent database change.
331          * @see <a href="http://docs.couchdb.org/en/latest/api/database
332          * /compact.html#db-compact-design-doc">
333          * docs for /db/_compact/design-doc</a>
334          * @param {String} groupname Name of design-doc to compact
335          * @param {ajaxSettings} options <a href="http://api.jquery.com/
336          * jQuery.ajax/#jQuery-ajax-settings">jQuery ajax settings</a>
337          */
338         compactView: function(groupname, options) {
339           $.extend(options, {successStatus: 202});
340           return ajax({
341               type: "POST", url: this.uri + "_compact/" + groupname,
342               data: "", processData: false
343             },
344             options,
345             "The view could not be compacted"
346           );
347         },
348
349         /**
350          * Create a new database
351          * @see <a href="http://docs.couchdb.org/en/latest/api/database
352          * /common.html#put--db">docs for PUT /db/</a>
353          * @param {ajaxSettings} options <a href="http://api.jquery.com/
354          * jQuery.ajax/#jQuery-ajax-settings">jQuery ajax settings</a>
355          */
356         create: function(options) {
357           $.extend(options, {successStatus: 201});
358           return ajax({
359               type: "PUT", url: this.uri, contentType: "application/json",
360               data: "", processData: false
361             },
362             options,
363             "The database could not be created"
364           );
365         },
366
367         /**
368          * Deletes the specified database, and all the documents and
369          * attachments contained within it.
370          * @see <a href="http://docs.couchdb.org/en/latest/api/database
371          * /common.html#delete--db">docs for DELETE /db/</a>
372          * @param {ajaxSettings} options <a href="http://api.jquery.com/
373          * jQuery.ajax/#jQuery-ajax-settings">jQuery ajax settings</a>
374          */
375         drop: function(options) {
376           return ajax(
377             {type: "DELETE", url: this.uri},
378             options,
379             "The database could not be deleted"
380           );
381         },
382
383         /**
384          * Gets information about the specified database.
385          * @see <a href="http://docs.couchdb.org/en/latest/api/database
386          * /common.html#get--db">docs for GET /db/</a>
387          * @param {ajaxSettings} options <a href="http://api.jquery.com/
388          * jQuery.ajax/#jQuery-ajax-settings">jQuery ajax settings</a>
389          */
390         info: function(options) {
391           return ajax(
392             {url: this.uri},
393             options,
394             "Database information could not be retrieved"
395           );
396         },
397
398         /**
399          * @namespace
400          * $.couch.db.changes provides an API for subscribing to the changes
401          * feed
402          * <pre><code>var $changes = $.couch.db("mydatabase").changes();
403          *$changes.onChange = function (data) {
404          *    ... process data ...
405          * }
406          * $changes.stop();
407          * </code></pre>
408          */
409         changes: function(since, options) {
410
411           options = options || {};
412           // set up the promise object within a closure for this handler
413           var timeout = 100, db = this, active = true,
414             listeners = [],
415             xhr = null,
416             promise = /** @lends $.couch.db.changes */ {
417               /**
418                * Add a listener callback
419                * @see <a href="http://docs.couchdb.org/en/latest/api/database
420                * /changes.html#db-changes">docs for /db/_changes</a>
421                * @param {Function} fun Callback function to run when
422                * notified of changes.
423                */
424             onChange : function(fun) {
425               listeners.push(fun);
426             },
427               /**
428                * Stop subscribing to the changes feed
429                */
430             stop : function() {
431               active = false;
432               if (xhr){
433                 xhr.abort();
434               }
435             }
436           };
437           // call each listener when there is a change
438           function triggerListeners(resp) {
439             $.each(listeners, function() {
440               this(resp);
441             });
442           }
443           // when there is a change, call any listeners, then check for
444           // another change
445           options.success = function(resp) {
446             timeout = 100;
447             if (active) {
448               since = resp.last_seq;
449               triggerListeners(resp);
450               getChangesSince();
451             }
452           };
453           options.error = function() {
454             if (active) {
455               setTimeout(getChangesSince, timeout);
456               timeout = timeout * 2;
457             }
458           };
459           // actually make the changes request
460           function getChangesSince() {
461             var opts = $.extend({heartbeat : 10 * 1000}, options, {
462               feed : "longpoll",
463               since : since
464             });
465             xhr = ajax(
466               {url: db.uri + "_changes"+encodeOptions(opts)},
467               options,
468               "Error connecting to "+db.uri+"/_changes."
469             );
470           }
471           // start the first request
472           if (since) {
473             getChangesSince();
474           } else {
475             db.info({
476               success : function(info) {
477                 since = info.update_seq;
478                 getChangesSince();
479               }
480             });
481           }
482           return promise;
483         },
484
485         /**
486          * Fetch all the docs in this db, you can specify an array of keys to
487          * fetch by passing the <code>keys</code> field in the
488          * <code>options</code>
489          * parameter.
490          * @see <a href="http://docs.couchdb.org/en/latest/api/database
491          * /bulk-api.html#db-all-docs">docs for /db/all_docs/</a>
492          * @param {ajaxSettings} options <a href="http://api.jquery.com/
493          * jQuery.ajax/#jQuery-ajax-settings">jQuery ajax settings</a>
494          */
495         allDocs: function(options) {
496           var type = "GET";
497           var data = null;
498           if (options["keys"]) {
499             type = "POST";
500             var keys = options["keys"];
501             delete options["keys"];
502             data = toJSON({ "keys": keys });
503           }
504           return ajax({
505               type: type,
506               data: data,
507               url: this.uri + "_all_docs" + encodeOptions(options)
508             },
509             options,
510             "An error occurred retrieving a list of all documents"
511           );
512         },
513
514         /**
515          * Fetch all the design docs in this db
516          * @param {ajaxSettings} options <a href="http://api.jquery.com/
517          * jQuery.ajax/#jQuery-ajax-settings">jQuery ajax settings</a>
518          */
519         allDesignDocs: function(options) {
520           return this.allDocs($.extend(
521             {startkey:"_design", endkey:"_design0"}, options));
522         },
523
524         /**
525          * Fetch all the design docs with an index.html, <code>options</code>
526          * parameter expects an <code>eachApp</code> field which is a callback
527          * called on each app found.
528          * @param {ajaxSettings} options <a href="http://api.jquery.com/
529          * jQuery.ajax/#jQuery-ajax-settings">jQuery ajax settings</a>
530          */
531         allApps: function(options) {
532           options = options || {};
533           var self = this;
534           if (options.eachApp) {
535             return this.allDesignDocs({
536               success: function(resp) {
537                 $.each(resp.rows, function() {
538                   self.openDoc(this.id, {
539                     success: function(ddoc) {
540                       var index, appPath, appName = ddoc._id.split('/');
541                       appName.shift();
542                       appName = appName.join('/');
543                       index = ddoc.couchapp && ddoc.couchapp.index;
544                       if (index) {
545                         appPath = ['', name, ddoc._id, index].join('/');
546                       } else if (ddoc._attachments &&
547                                  ddoc._attachments["index.html"]) {
548                         appPath = ['', name, ddoc._id, "index.html"].join('/');
549                       }
550                       if (appPath) options.eachApp(appName, appPath, ddoc);
551                     }
552                   });
553                 });
554               }
555             });
556           } else {
557             throw 'Please provide an eachApp function for allApps()';
558           }
559         },
560
561         /**
562          * Returns the specified doc from the specified db.
563          * @see <a href="http://docs.couchdb.org/en/latest/api/document
564          * /common.html#get--db-docid">docs for GET /db/doc</a>
565          * @param {String} docId id of document to fetch
566          * @param {ajaxSettings} options <a href="http://api.jquery.com/
567          * jQuery.ajax/#jQuery-ajax-settings">jQuery ajax settings</a>
568          * @param {ajaxSettings} ajaxOptions <a href="http://api.jquery.com/
569          * jQuery.ajax/#jQuery-ajax-settings">jQuery ajax settings</a>
570          */
571         openDoc: function(docId, options, ajaxOptions) {
572           options = options || {};
573           if (db_opts.attachPrevRev || options.attachPrevRev) {
574             $.extend(options, {
575               beforeSuccess : function(req, doc) {
576                 rawDocs[doc._id] = {
577                   rev : doc._rev,
578                   raw : req.responseText
579                 };
580               }
581             });
582           } else {
583             $.extend(options, {
584               beforeSuccess : function(req, doc) {
585                 if (doc["jquery.couch.attachPrevRev"]) {
586                   rawDocs[doc._id] = {
587                     rev : doc._rev,
588                     raw : req.responseText
589                   };
590                 }
591               }
592             });
593           }
594           return ajax(
595             {url: this.uri + encodeDocId(docId) + encodeOptions(options)},
596             options,
597             "The document could not be retrieved",
598             ajaxOptions
599           );
600         },
601
602         /**
603          * Create a new document in the specified database, using the supplied
604          * JSON document structure. If the JSON structure includes the _id
605          * field, then the document will be created with the specified document
606          * ID. If the _id field is not specified, a new unique ID will be
607          * generated.
608          * @see <a href="http://docs.couchdb.org/en/latest/api/document
609          * /common.html#put--db-docid">docs for PUT /db/doc</a>
610          * @param {String} doc document to save
611          * @param {ajaxSettings} options <a href="http://api.jquery.com/
612          * jQuery.ajax/#jQuery-ajax-settings">jQuery ajax settings</a>
613          */
614         saveDoc: function(doc, options) {
615           options = options || {};
616           var db = this;
617           var beforeSend = fullCommit(options);
618           if (doc._id === undefined) {
619             var method = "POST";
620             var uri = this.uri;
621           } else {
622             var method = "PUT";
623             var uri = this.uri + encodeDocId(doc._id);
624           }
625           var versioned = maybeApplyVersion(doc);
626           return $.ajax({
627             type: method, url: uri + encodeOptions(options),
628             contentType: "application/json",
629             dataType: "json", data: toJSON(doc),
630             beforeSend : beforeSend,
631             complete: function(req) {
632               var resp = $.parseJSON(req.responseText);
633               if (req.status == 200 || req.status == 201 || req.status == 202) {
634                 doc._id = resp.id;
635                 doc._rev = resp.rev;
636                 if (versioned) {
637                   db.openDoc(doc._id, {
638                     attachPrevRev : true,
639                     success : function(d) {
640                       doc._attachments = d._attachments;
641                       if (options.success) options.success(resp);
642                     }
643                   });
644                 } else {
645                   if (options.success) options.success(resp);
646                 }
647               } else if (options.error) {
648                 options.error(req.status, resp.error, resp.reason);
649               } else {
650                 throw "The document could not be saved: " + resp.reason;
651               }
652             }
653           });
654         },
655
656         /**
657          * Save a list of documents
658          * @see <a href="http://docs.couchdb.org/en/latest/api/database
659          * /bulk-api.html#db-bulk-docs">docs for /db/_bulk_docs</a>
660          * @param {Object[]} docs List of documents to save
661          * @param {ajaxSettings} options <a href="http://api.jquery.com/
662          * jQuery.ajax/#jQuery-ajax-settings">jQuery ajax settings</a>
663          */
664         bulkSave: function(docs, options) {
665           var beforeSend = fullCommit(options);
666           $.extend(options, {successStatus: 201, beforeSend : beforeSend});
667           return ajax({
668               type: "POST",
669               url: this.uri + "_bulk_docs" + encodeOptions(options),
670               contentType: "application/json", data: toJSON(docs)
671             },
672             options,
673             "The documents could not be saved"
674           );
675         },
676
677         /**
678          * Deletes the specified document from the database. You must supply
679          * the current (latest) revision and <code>id</code> of the document
680          * to delete eg <code>removeDoc({_id:"mydoc", _rev: "1-2345"})</code>
681          * @see <a href="http://docs.couchdb.org/en/latest/api/document
682          * /common.html#delete--db-docid">docs for DELETE /db/doc</a>
683          * @param {Object} doc Document to delete
684          * @param {ajaxSettings} options <a href="http://api.jquery.com/
685          * jQuery.ajax/#jQuery-ajax-settings">jQuery ajax settings</a>
686          */
687         removeDoc: function(doc, options) {
688           return ajax({
689               type: "DELETE",
690               url: this.uri +
691                    encodeDocId(doc._id) +
692                    encodeOptions({rev: doc._rev})
693             },
694             options,
695             "The document could not be deleted"
696           );
697         },
698
699         /**
700          * Remove a set of documents
701          * @see <a href="http://docs.couchdb.org/en/latest/api/database
702          * /bulk-api.html#db-bulk-docs">docs for /db/_bulk_docs</a>
703          * @param {String[]} docs List of document id's to remove
704          * @param {ajaxSettings} options <a href="http://api.jquery.com/
705          * jQuery.ajax/#jQuery-ajax-settings">jQuery ajax settings</a>
706          */
707         bulkRemove: function(docs, options){
708           docs.docs = $.each(
709             docs.docs, function(i, doc){
710               doc._deleted = true;
711             }
712           );
713           $.extend(options, {successStatus: 201});
714           return ajax({
715               type: "POST",
716               url: this.uri + "_bulk_docs" + encodeOptions(options),
717               data: toJSON(docs)
718             },
719             options,
720             "The documents could not be deleted"
721           );
722         },
723
724         /**
725          * The COPY command (which is non-standard HTTP) copies an existing
726          * document to a new or existing document.
727          * @see <a href="http://docs.couchdb.org/en/latest/api/document
728          * /common.html#copy--db-docid">docs for COPY /db/doc</a>
729          * @param {String[]} docId document id to copy
730          * @param {ajaxSettings} options <a href="http://api.jquery.com/
731          * jQuery.ajax/#jQuery-ajax-settings">jQuery ajax settings</a>
732          * @param {ajaxSettings} ajaxOptions <a href="http://api.jquery.com/
733          * jQuery.ajax/#jQuery-ajax-settings">jQuery ajax settings</a>
734          */
735         copyDoc: function(docId, options, ajaxOptions) {
736           ajaxOptions = $.extend(ajaxOptions, {
737             complete: function(req) {
738               var resp = $.parseJSON(req.responseText);
739               if (req.status == 201) {
740                 if (options.success) options.success(resp);
741               } else if (options.error) {
742                 options.error(req.status, resp.error, resp.reason);
743               } else {
744                 throw "The document could not be copied: " + resp.reason;
745               }
746             }
747           });
748           return ajax({
749               type: "COPY",
750               url: this.uri + encodeDocId(docId)
751             },
752             options,
753             "The document could not be copied",
754             ajaxOptions
755           );
756         },
757
758         /**
759          * Creates (and executes) a temporary view based on the view function
760          * supplied in the JSON request.
761          * @see <a href="http://docs.couchdb.org/en/latest/api/database
762          * /temp-views.html#db-temp-view">docs for /db/_temp_view</a>
763          * @param {Function} mapFun Map function
764          * @param {Function} reduceFun Reduce function
765          * @param {String} language Language the map / reduce funs are
766          * implemented in
767          * @param {ajaxSettings} options <a href="http://api.jquery.com/
768          * jQuery.ajax/#jQuery-ajax-settings">jQuery ajax settings</a>
769          */
770         query: function(mapFun, reduceFun, language, options) {
771           language = language || "javascript";
772           if (typeof(mapFun) !== "string") {
773             mapFun = mapFun.toSource ? mapFun.toSource()
774               : "(" + mapFun.toString() + ")";
775           }
776           var body = {language: language, map: mapFun};
777           if (reduceFun != null) {
778             if (typeof(reduceFun) !== "string")
779               reduceFun = reduceFun.toSource ? reduceFun.toSource()
780                 : "(" + reduceFun.toString() + ")";
781             body.reduce = reduceFun;
782           }
783           return ajax({
784               type: "POST",
785               url: this.uri + "_temp_view" + encodeOptions(options),
786               contentType: "application/json", data: toJSON(body)
787             },
788             options,
789             "An error occurred querying the database"
790           );
791         },
792
793         /**
794          * Fetch a _list view output, you can specify a list of
795          * <code>keys</code> in the options object to receive only those keys.
796          * @see <a href="http://docs.couchdb.org/en/latest/api/ddoc/render.html
797          * #db-design-design-doc-list-list-name-view-name">
798          * docs for /db/_design/design-doc/_list/list/view</a>
799          * @param {String} list Listname in the form of ddoc/listname
800          * @param {String} view View to run list against
801          * @param {Object} options CouchDB <a href="http://docs.couchdb.org/en
802          * /latest/api/ddoc/views.html#get--db-_design-ddoc-_view-view">
803          * View Options</a>
804          * @param {ajaxSettings} ajaxOptions <a href="http://api.jquery.com/
805          * jQuery.ajax/#jQuery-ajax-settings">jQuery ajax settings</a>
806          */
807         list: function(list, view, options, ajaxOptions) {
808           var list = list.split('/');
809           var options = options || {};
810           var type = 'GET';
811           var data = null;
812           if (options['keys']) {
813             type = 'POST';
814             var keys = options['keys'];
815             delete options['keys'];
816             data = toJSON({'keys': keys });
817           }
818           return ajax({
819               type: type,
820               data: data,
821               url: this.uri + '_design/' + list[0] +
822                    '/_list/' + list[1] + '/' + view + encodeOptions(options)
823               },
824               ajaxOptions, 'An error occurred accessing the list'
825           );
826         },
827
828         /**
829          * Executes the specified view-name from the specified design-doc
830          * design document, you can specify a list of <code>keys</code>
831          * in the options object to receive only those keys.
832          * @see <a href="http://docs.couchdb.org/en/latest/api/ddoc/views.html
833          * #db-design-design-doc-view-view-name">docs for /db/
834          * _design/design-doc/_view/name</a>
835          * @param {String} name View to run list against (string should have
836          * the design-doc name followed by a slash and the view name)
837          * @param {ajaxSettings} options <a href="http://api.jquery.com/
838          * jQuery.ajax/#jQuery-ajax-settings">jQuery ajax settings</a>
839          */
840         view: function(name, options) {
841           var name = name.split('/');
842           var options = options || {};
843           var type = "GET";
844           var data= null;
845           if (options["keys"]) {
846             type = "POST";
847             var keys = options["keys"];
848             delete options["keys"];
849             data = toJSON({ "keys": keys });
850           }
851           return ajax({
852               type: type,
853               data: data,
854               url: this.uri + "_design/" + name[0] +
855                    "/_view/" + name[1] + encodeOptions(options)
856             },
857             options, "An error occurred accessing the view"
858           );
859         },
860
861         /**
862          * Fetch an arbitrary CouchDB database property
863          * @param {String} propName Property name to fetch
864          * @param {ajaxSettings} options <a href="http://api.jquery.com/
865          * jQuery.ajax/#jQuery-ajax-settings">jQuery ajax settings</a>
866          * @param {ajaxSettings} ajaxOptions <a href="http://api.jquery.com/
867          * jQuery.ajax/#jQuery-ajax-settings">jQuery ajax settings</a>
868          */
869         getDbProperty: function(propName, options, ajaxOptions) {
870           return ajax({url: this.uri + propName + encodeOptions(options)},
871             options,
872             "The property could not be retrieved",
873             ajaxOptions
874           );
875         },
876
877         /**
878          * Set an arbitrary CouchDB database property
879          * @param {String} propName Property name to fetch
880          * @param {String} propValue Property value to set
881          * @param {ajaxSettings} options <a href="http://api.jquery.com/
882          * jQuery.ajax/#jQuery-ajax-settings">jQuery ajax settings</a>
883          * @param {ajaxSettings} ajaxOptions <a href="http://api.jquery.com/
884          * jQuery.ajax/#jQuery-ajax-settings">jQuery ajax settings</a>
885          */
886         setDbProperty: function(propName, propValue, options, ajaxOptions) {
887           return ajax({
888             type: "PUT",
889             url: this.uri + propName + encodeOptions(options),
890             data : JSON.stringify(propValue)
891           },
892             options,
893             "The property could not be updated",
894             ajaxOptions
895           );
896         }
897       };
898     },
899
900     encodeDocId: encodeDocId,
901
902     /**
903      * Accessing the root of a CouchDB instance returns meta information about
904      * the instance. The response is a JSON structure containing information
905      * about the server, including a welcome message and the version of the
906      * server.
907      * @see <a href="http://docs.couchdb.org/en/latest/api/server/common.html
908      * #api-server-root">
909      * docs for GET /</a>
910      * @param {ajaxSettings} options <a href="http://api.jquery.com/
911      * jQuery.ajax/#jQuery-ajax-settings">jQuery ajax settings</a>
912      */
913     info: function(options) {
914       return ajax(
915         {url: this.urlPrefix + "/"},
916         options,
917         "Server information could not be retrieved"
918       );
919     },
920
921     /**
922      * Request, configure, or stop, a replication operation.
923      * @see <a href="http://docs.couchdb.org/en/latest/api/server/common.html
924      * #replicate">docs for POST /_replicate</a>
925      * @param {String} source Path or url to source database
926      * @param {String} target Path or url to target database
927      * @param {ajaxSettings} ajaxOptions <a href="http://api.jquery.com/
928      * jQuery.ajax/#jQuery-ajax-settings">jQuery ajax settings</a>
929      * @param {Object} repOpts Additional replication options
930      */
931     replicate: function(source, target, ajaxOptions, repOpts) {
932       repOpts = $.extend({source: source, target: target}, repOpts);
933       if (repOpts.continuous && !repOpts.cancel) {
934         ajaxOptions.successStatus = 202;
935       }
936       return ajax({
937           type: "POST", url: this.urlPrefix + "/_replicate",
938           data: JSON.stringify(repOpts),
939           contentType: "application/json"
940         },
941         ajaxOptions,
942         "Replication failed"
943       );
944     },
945
946     /**
947      * Fetch a new UUID
948      * @see <a href="http://docs.couchdb.org/en/latest/api/server/common.html
949      * #uuids">docs for /_uuids</a>
950      * @param {Integer} cacheNum Number of uuids to keep cached for future use
951      */
952     newUUID: function(cacheNum) {
953       if (cacheNum === undefined) {
954         cacheNum = 1;
955       }
956       if (!uuidCache.length) {
957         ajax({url: this.urlPrefix + "/_uuids", data: {count: cacheNum}, async:
958               false}, {
959             success: function(resp) {
960               uuidCache = resp.uuids;
961             }
962           },
963           "Failed to retrieve UUID batch."
964         );
965       }
966       return uuidCache.shift();
967     }
968   });
969
970   /**
971    * @private
972    */
973   function ajax(obj, options, errorMessage, ajaxOptions) {
974     var timeStart;
975     var defaultAjaxOpts = {
976       contentType: "application/json",
977       headers:{"Accept": "application/json"}
978     };
979
980     options = $.extend({successStatus: 200}, options);
981     ajaxOptions = $.extend(defaultAjaxOpts, ajaxOptions);
982     errorMessage = errorMessage || "Unknown error";
983     timeStart = (new Date()).getTime();
984     return $.ajax($.extend($.extend({
985       type: "GET", dataType: "json", cache : maybeUseCache(),
986       beforeSend: function(xhr){
987         if(ajaxOptions && ajaxOptions.headers){
988           for (var header in ajaxOptions.headers){
989             xhr.setRequestHeader(header, ajaxOptions.headers[header]);
990           }
991         }
992       },
993       complete: function(req) {
994         var reqDuration = (new Date()).getTime() - timeStart;
995         try {
996           var resp = $.parseJSON(req.responseText);
997         } catch(e) {
998           if (options.error) {
999             options.error(req.status, req, e);
1000           } else {
1001             throw errorMessage + ': ' + e;
1002           }
1003           return;
1004         }
1005         if (options.ajaxStart) {
1006           options.ajaxStart(resp);
1007         }
1008         if (req.status == options.successStatus) {
1009           if (options.beforeSuccess) options.beforeSuccess(req, resp, reqDuration);
1010           if (options.success) options.success(resp, reqDuration);
1011         } else if (options.error) {
1012           options.error(req.status, resp && resp.error ||
1013                         errorMessage, resp && resp.reason || "no response",
1014                         reqDuration);
1015         } else {
1016           throw errorMessage + ": " + resp.reason;
1017         }
1018       }
1019     }, obj), ajaxOptions));
1020   }
1021
1022   /**
1023    * @private
1024    */
1025   function fullCommit(options) {
1026     var options = options || {};
1027     if (typeof options.ensure_full_commit !== "undefined") {
1028       var commit = options.ensure_full_commit;
1029       delete options.ensure_full_commit;
1030       return function(xhr) {
1031         xhr.setRequestHeader('Accept', 'application/json');
1032         xhr.setRequestHeader("X-Couch-Full-Commit", commit.toString());
1033       };
1034     }
1035   }
1036
1037   /**
1038    * @private
1039    */
1040   // Convert a options object to an url query string.
1041   // ex: {key:'value',key2:'value2'} becomes '?key="value"&key2="value2"'
1042   function encodeOptions(options) {
1043     var buf = [];
1044     if (typeof(options) === "object" && options !== null) {
1045       for (var name in options) {
1046         if ($.inArray(name,
1047                       ["error", "success", "beforeSuccess", "ajaxStart"]) >= 0)
1048           continue;
1049         var value = options[name];
1050         if ($.inArray(name, ["key", "startkey", "endkey"]) >= 0) {
1051           value = toJSON(value);
1052         }
1053         buf.push(encodeURIComponent(name) + "=" + encodeURIComponent(value));
1054       }
1055     }
1056     return buf.length ? "?" + buf.join("&") : "";
1057   }
1058
1059   /**
1060    * @private
1061    */
1062   function toJSON(obj) {
1063     return obj !== null ? JSON.stringify(obj) : null;
1064   }
1065
1066   /**
1067    * @private
1068    */
1069   function maybeUseCache() {
1070     if (!navigator){
1071       return true;
1072     }
1073     else if (/(MSIE|Trident)/.test(navigator.userAgent)){
1074       return false;
1075     }
1076     return true;
1077   }
1078
1079 })(jQuery);