├── .gitignore ├── .npmignore ├── .travis.yml ├── HISTORY.md ├── LICENSE.txt ├── README.md ├── clerk.js ├── dist ├── .gitkeep ├── clerk.js ├── clerk.js.map ├── clerk.min.js └── clerk.min.js.map ├── index.js ├── jsdoc.json ├── karma.conf.js ├── package.json ├── test ├── base_test.js ├── clerk_test.js ├── client_test.js ├── database_test.js ├── design.js └── shared.js └── webpack.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | # npm 2 | node_modules 3 | npm-debug.log 4 | 5 | # Mac OS X 6 | .DS_Store 7 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # npm 2 | node_modules 3 | npm-debug.log 4 | 5 | # Mac OS X 6 | .DS_Store 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 0.10 4 | - 0.11 5 | - 0.12 6 | - iojs 7 | services: 8 | - couchdb 9 | after_install: 10 | - npm install -g karma-cli 11 | after_script: 12 | - karma start --single-run --browsers Firefox 13 | -------------------------------------------------------------------------------- /HISTORY.md: -------------------------------------------------------------------------------- 1 | 0.8.3 / 2015-06-11 2 | ================== 3 | 4 | * [Added] Assign `clerk.Promise` for custom Promise implementations. 5 | * [Fixed] `db.exists()` resolves to false on 404. 6 | * Update dependencies. 7 | 8 | 9 | 0.8.2 / 2015-04-30 10 | ================== 11 | 12 | * Return HTTP status code on error 13 | 14 | 15 | 0.8.1 / 2015-04-27 16 | ================== 17 | 18 | * Catch emitted errors from superagent 19 | * Update dependencies 20 | 21 | 22 | 0.8.0 / 2015-02-15 23 | ================== 24 | 25 | * Update Travis-CI to test against node 0.10-12 and iojs 26 | * Change DB#update() to accept data before query 27 | 28 | 29 | 0.7.2 / 2015-02-11 30 | ================== 31 | 32 | * Add `abort` method to returned promises 33 | * Update tests to allow for parallelization 34 | * Fix a bug in the follow handler 35 | * Document use with promises 36 | 37 | 38 | 0.7.1 / 2015-02-09 39 | ================== 40 | 41 | * Add `browser` property to `package.json` 42 | 43 | 44 | 0.7.0 / 2015-02-04 45 | ================== 46 | 47 | * Use superagent to support clerk in the browser 48 | * Use Karma for browser testing 49 | * Remove node uuid generator 50 | * Beta: return promises when no callback given to support co 51 | 52 | 53 | 0.6.1 / 2015-02-01 54 | ================== 55 | 56 | * Only set prototypical _id and _rev 57 | 58 | 59 | 0.6.0 / 2015-02-01 60 | ================== 61 | 62 | * Update dependencies 63 | * Test against CouchDB 1.6.1 64 | 65 | 66 | 0.5.3 / 2014-01-04 67 | ================== 68 | 69 | * Fix error handling on HEAD requests 70 | 71 | 72 | 0.5.2 / 2013-12-31 73 | ================== 74 | 75 | * Ensure query Array values are JSON encoded 76 | 77 | 78 | 0.5.1 / 2013-12-30 79 | ================== 80 | 81 | * Update dependencies 82 | 83 | 84 | 0.5.0 / 2012-11-13 85 | ================== 86 | 87 | * Use ~ instead of . for URL-safe base64 IDs to support content-negotiation 88 | file extensions over HTTP 89 | 90 | 91 | 0.4.2 / 2012-11-04 92 | ================== 93 | 94 | * Define _id, id, _rev, and rev on prototypes to hide from JSON.stringify 95 | * Ignore missing callbacks 96 | 97 | 98 | 0.4.1 / 2012-05-19 99 | ================== 100 | 101 | * Return server errors as errors 102 | 103 | 104 | 0.4.0 / 2012-05-12 105 | ================== 106 | 107 | * Fixed URI parsing on node side 108 | * UUIDs use '.' instead of '_' to avoid generating IDs starting with '_' 109 | * Added uglify-js dependency 110 | * Removed useless zombie.js tests 111 | 112 | 113 | 0.3.2 / 2012-05-06 114 | ================== 115 | 116 | * `clerk#uuids()` accepts `nbytes` to use when generating base64 uuids 117 | 118 | 119 | 0.3.1 / 2012-05-06 120 | ================== 121 | 122 | * Fixed `clerk#uuids()` and added relevant tests 123 | 124 | 125 | 0.3.0 / 2012-05-05 126 | ================== 127 | 128 | * Renamed `DB#view()` as `DB#find()` 129 | 130 | 131 | 0.2.0 / 2012-05-04 132 | ================== 133 | 134 | * Overhauled library with experimental browser support 135 | 136 | 137 | 0.0.1 / 2011-10-07 138 | ================== 139 | 140 | * Initial release 141 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # clerk [![Build Status](https://travis-ci.org/mikepb/clerk.svg)](http://travis-ci.org/mikepb/clerk) 2 | 3 | ```js 4 | var clerk = require('clerk'); 5 | 6 | var client = clerk('http://127.0.0.1:5984'); 7 | var db = client.db('test'); 8 | 9 | db.info(function (err, info) { 10 | console.log(err || info); 11 | }); 12 | 13 | // if a Promise implementation is available, you may leave out the callback 14 | var promise = db.info() 15 | promise.then(function (info) { 16 | console.log(info); 17 | }).catch(function (err) { 18 | console.log(err); 19 | }); 20 | ``` 21 | 22 | ## Documentation 23 | 24 | Full documentation at http://mikepb.github.io/clerk/ 25 | 26 | For more information about promises, see 27 | [this Mozilla documentation][promises]. 28 | 29 | ## Installation 30 | 31 | ```sh 32 | $ npm install clerk 33 | ``` 34 | 35 | ## Browser Support 36 | 37 | Browser support is provided by [superagent][]. The browser versions of the 38 | library may be found under the `dist/` directory. The browser files are updated 39 | on each versioned release, but not for development. Modern browsers are 40 | generally supported, but not widely tested. [Karma][karma] is used to run the 41 | [mocha][] tests in the browser. 42 | 43 | Security restrictions on cross-domain requests currently limits the usefulness 44 | of the browser version. Using a local proxy or configuring [Cross-Origin 45 | Resource Sharing][cors] in CouchDB may allow you to use the library in the 46 | browser. Please see the configuration notes in the testing section for more 47 | information about CouchDB CORS support. 48 | 49 | To build the client files: 50 | 51 | ```sh 52 | $ npm run dist 53 | ``` 54 | 55 | ## Testing 56 | 57 | To run tests in node: 58 | 59 | ```sh 60 | $ npm test 61 | ``` 62 | 63 | To run tests with Karma, make sure that you have [enabled cors in 64 | CouchDB][couchdb_cors]. By default, CouchDB does not allow the `Authorization` 65 | header, so if you will be authenticating, you'll need to add it to the list as 66 | well. 67 | 68 | ``` 69 | [httpd] 70 | enable_cors = true 71 | 72 | [cors] 73 | credentials = true 74 | origins = * 75 | headers = Accept, Accept-Language, Authorization, Content-Length, Content-Range, Content-Type, Destination, Expires, If-Match, Last-Modified, Origin, Pragma, X-Requested-With, X-Http-Method-Override 76 | ``` 77 | 78 | Then, you may run Karma: 79 | 80 | ```sh 81 | $ npm run karma 82 | ``` 83 | 84 | ## Philosophy 85 | 86 | The philosophy of *clerk* is to provide a thin wrapper around the CouchDB API, 87 | making the database easier to use from JavaScript. *clerk* is designed to 88 | quickly allow you to get started with CouchDB, while still giving you full 89 | access to CouchDB's more advanced features. 90 | 91 | The library API generally follows the RESTful API, so you can use the CouchDB 92 | docs as well as the *clerk* docs to build your applications. If a feature is 93 | missing from *clerk* or you need to access more advanced features, the 94 | `request` method allows you to send custom requests directly to CouchDB. 95 | 96 | ## License 97 | 98 | Copyright 2012-2015 Michael Phan-Ba <michael@mikepb.com> 99 | 100 | Licensed under the Apache License, Version 2.0 (the "License"); 101 | you may not use this file except in compliance with the License. 102 | You may obtain a copy of the License at 103 | 104 | <http://www.apache.org/licenses/LICENSE-2.0> 105 | 106 | Unless required by applicable law or agreed to in writing, software 107 | distributed under the License is distributed on an "AS IS" BASIS, 108 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 109 | See the License for the specific language governing permissions and 110 | limitations under the License. 111 | 112 | [cors]: http://www.w3.org/TR/cors/ 113 | [couchdb_cors]: http://docs.couchdb.org/en/latest/config/http.html#cross-origin-resource-sharing 114 | [karma]: http://karma-runner.github.io 115 | [mocha]: http://mochajs.org 116 | [promises]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise 117 | [superagent]: https://github.com/visionmedia/superagent 118 | -------------------------------------------------------------------------------- /clerk.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | /*! 4 | 5 | clerk - CouchDB client for node and the browser. 6 | Copyright 2012-2015 Michael Phan-Ba 7 | 8 | Licensed under the Apache License, Version 2.0 (the "License"); 9 | you may not use this file except in compliance with the License. 10 | You may obtain a copy of the License at 11 | 12 | http://www.apache.org/licenses/LICENSE-2.0 13 | 14 | Unless required by applicable law or agreed to in writing, software 15 | distributed under the License is distributed on an "AS IS" BASIS, 16 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | See the License for the specific language governing permissions and 18 | limitations under the License. 19 | 20 | */ 21 | 22 | // Module dependencies. 23 | var request = require("superagent"); 24 | 25 | /** 26 | * Copy properties from sources to target. 27 | * 28 | * @param {Object} target The target object. 29 | * @param {...Object} sources The source object. 30 | * @return {Object} The target object. 31 | * @private 32 | */ 33 | 34 | var extend = function (target /* ...sources */) { 35 | var source, key, i = 1; 36 | while (source = arguments[i++]) { 37 | for (key in source) target[key] = source[key]; 38 | } 39 | return target; 40 | }; 41 | 42 | /** 43 | * Stringify value. 44 | * 45 | * @param {Object} that That value to stringify. 46 | * @return {String} The stringifyed value. 47 | * @private 48 | */ 49 | 50 | var asString = function (that) { 51 | return Object.prototype.toString.call(that); 52 | }; 53 | 54 | /** 55 | * Check if value is a string. 56 | * 57 | * @param {Object} that That value to check. 58 | * @return {Boolean} `true` if string, `false` otherwise. 59 | * @private 60 | */ 61 | 62 | var isString = function (that) { 63 | return asString(that) == "[object String]"; 64 | }; 65 | 66 | /** 67 | * Check if value is an object. 68 | * 69 | * @param {Object} that That value to check. 70 | * @return {Boolean} `true` if object, `false` otherwise. 71 | * @private 72 | */ 73 | 74 | var isObject = function (that) { 75 | return asString(that) == "[object Object]"; 76 | }; 77 | 78 | /** 79 | * Check if value is an array. 80 | * 81 | * @param {Object} that That value to check. 82 | * @return {Boolean} `true` if array, `false` otherwise. 83 | * @private 84 | */ 85 | 86 | var isArray = function (that) { 87 | return asString(that) == "[object Array]"; 88 | }; 89 | 90 | /** 91 | * Check if value is a function. 92 | * 93 | * @param {Object} that That value to check. 94 | * @return {Boolean} `true` if function, `false` otherwise. 95 | * @private 96 | */ 97 | 98 | var isFunction = function (that) { 99 | return asString(that) == "[object Function]"; 100 | }; 101 | 102 | /** 103 | * Clerk library entry point. 104 | * 105 | * @param {String} uri CouchDB server URI. 106 | * @return {Client|DB} If a URI path is given, returns a `DB`, otherwise 107 | * returns a `Client`. 108 | * @see {@link http://docs.couchdb.org|CouchDB Documentation} 109 | * @see {@link http://guide.couchdb.org/|CouchDB Guide} 110 | * @see {@link http://wiki.apache.org/couchdb/|CouchDB Wiki} 111 | */ 112 | 113 | function clerk (uri) { 114 | return clerk.make(uri); 115 | }; 116 | 117 | /** 118 | * Promise implementation. 119 | * @type {Promise} 120 | */ 121 | 122 | clerk.Promise = typeof Promise !== "undefined" && Promise; 123 | 124 | /** 125 | * Library version. 126 | * @type {String} 127 | */ 128 | 129 | clerk.version = "0.8.2"; 130 | 131 | /** 132 | * Default host. 133 | * @type {String} 134 | */ 135 | 136 | clerk.defaultHost = "http://127.0.0.1:5984"; 137 | 138 | /** 139 | * Create single CouchDB client. 140 | * 141 | * @param {String} uri Fully qualified URI. 142 | * @return {Client|DB} If `uri` has a path, the last segment of the 143 | * path is used as the database name and a `DB` instance is 144 | * returned. Otherwise, a `Client` instance is returned. 145 | */ 146 | 147 | clerk.make = function (uri) { 148 | if (!uri) return new Client(this.defaultHost); 149 | 150 | uri = clerk._parseURI(uri); 151 | 152 | var db = /\/*([^\/]+)\/*$/.exec(uri.path); 153 | if (db) { 154 | uri.path = uri.path.substr(0, db.index); 155 | db = db[1] && decodeURIComponent(db[1]); 156 | } 157 | 158 | // weird way of doing it, but it's more efficient... 159 | if (uri.auth) uri.auth = 'Basic ' + clerk.btoa(uri.auth); 160 | 161 | var client = new clerk.Client(uri.base + uri.path, uri.auth); 162 | return db ? client.db(db) : client; 163 | }; 164 | 165 | /** 166 | * Base64-encode a string. 167 | * 168 | * @param {String} str 169 | * @return {String} 170 | */ 171 | 172 | clerk.btoa = typeof Buffer != "undefined" ? function (str) { 173 | return new Buffer(str).toString("base64"); 174 | } : function (str) { 175 | return btoa(str); 176 | }; 177 | 178 | /** 179 | * Parse URI. 180 | * 181 | * The URI is normalized by removing extra `//` in the path. 182 | * 183 | * @param {String} uri Fully qualified URI. 184 | * @return {String} The normalized URI. 185 | * @private 186 | */ 187 | 188 | clerk._parseURI = function (uri) { 189 | var match; 190 | 191 | if (uri) { 192 | if (match = /^(https?:\/\/)(?:([^\/@]+)@)?([^\/]+)(.*)\/*$/.exec(uri)) { 193 | return { 194 | base: match[1] + match[3].replace(/\/+/g, "\/"), 195 | path: match[4], 196 | auth: match[2] && decodeURIComponent(match[2]) 197 | }; 198 | } 199 | } 200 | 201 | return { base: uri || "", path: "" }; 202 | }; 203 | 204 | /** 205 | * Base prototype for `Client` and `DB`. 206 | * Encapsulates HTTP methods, JSON handling, and response coersion. 207 | * 208 | * @constructor 209 | * @memberof clerk 210 | */ 211 | 212 | function Base () {}; 213 | 214 | /** 215 | * Service request and parse JSON response. 216 | * 217 | * @param {String} [method=GET] HTTP method. 218 | * @param {String} [path=this.uri] HTTP URI. 219 | * @param {Object} [query] HTTP query options. 220 | * @param {Object} [body] HTTP body. 221 | * @param {Object} [headers] HTTP headers. 222 | * @param {handler} [callback] Callback function. 223 | * @return {Promise} 224 | */ 225 | 226 | Base.prototype.request = function (/* [method], [path], [query], [body], [headers], [callback] */) { 227 | var args = [].slice.call(arguments); 228 | var callback = isFunction (args[args.length - 1]) && args.pop(); 229 | 230 | return this._request({ 231 | method: args[0], 232 | path: args[1], 233 | query: args[2], 234 | data: args[3], 235 | headers: args[4], 236 | fn: callback 237 | }); 238 | }; 239 | 240 | /** 241 | * Internal service request and parse JSON response handler. 242 | * 243 | * @param {String} options 244 | * @param {String} method HTTP method. 245 | * @param {String} path HTTP URI. 246 | * @param {Object} query HTTP query options. 247 | * @param {Object} data HTTP body data. 248 | * @param {Object} headers HTTP headers. 249 | * @param {handler} [callback] Callback function. 250 | * @private 251 | */ 252 | 253 | Base.prototype._request = function (options) { 254 | var self = this; 255 | 256 | if (options.method == null) options.method = "GET"; 257 | if (options.headers == null) options.headers = {}; 258 | if (options.auth == null) options.auth = this.auth; 259 | 260 | options.path = options.path ? "/" + options.path : ""; 261 | 262 | // set default headers 263 | if (options.headers["Content-Type"] == null) { 264 | options.headers["Content-Type"] = "application/json"; 265 | } 266 | if (options.headers["Accept"] == null) { 267 | options.headers["Accept"] = "application/json"; 268 | } 269 | if (this.auth && options.headers["Authorization"] == null) { 270 | options.headers["Authorization"] = this.auth; 271 | } 272 | 273 | options.uri = this.uri + options.path; 274 | options.body = options.data && JSON.stringify(options.data, 275 | /^\/_design/.test(options.path) && this._replacer 276 | ) || ""; 277 | 278 | // create promise if no callback given 279 | var promise, req; 280 | if (!options.fn && clerk.Promise) { 281 | promise = new clerk.Promise(function (resolve, reject) { 282 | options.fn = function (err, data, status, headers, res) { 283 | if (err) { 284 | err.body = data; 285 | err.status = status; 286 | err.headers = headers; 287 | err.res = res; 288 | reject(err); 289 | } else { 290 | if (isObject(data) && Object.defineProperties) { 291 | Object.defineProperties(data, { 292 | _status: { value: status }, 293 | _headers: { value: headers }, 294 | _response: { value: res }, 295 | }); 296 | } 297 | resolve(data); 298 | }; 299 | }; 300 | }); 301 | req = send(); 302 | promise.request = req; 303 | promise.abort = function () { 304 | req.abort(); 305 | options.fn(new Error("abort")); 306 | return promise; 307 | }; 308 | return promise; 309 | } 310 | 311 | send(); 312 | 313 | function send () { 314 | // apply response transforms 315 | var g = options._; 316 | var fn = options.fn; 317 | if (fn) { 318 | options.fn = g ? function () { 319 | fn.apply(self, g.apply(self, arguments) || arguments); 320 | } : fn; 321 | } 322 | return self._do(options); 323 | } 324 | }; 325 | 326 | /** 327 | * Provider for servicing requests and parsing JSON responses. 328 | * 329 | * @param {String} options 330 | * @param {String} method HTTP method. 331 | * @param {String} uri HTTP URI. 332 | * @param {Object} query HTTP query options. 333 | * @param {Object} body HTTP body. 334 | * @param {Object} headers HTTP headers. 335 | * @param {Object} auth HTTP authentication. 336 | * @param {handler} [callback] Callback function. 337 | * @private 338 | */ 339 | 340 | Base.prototype._do = function (options) { 341 | var self = this; 342 | var key, value; 343 | var fn = options.fn; 344 | 345 | // create request 346 | var req = request(options.method, options.uri); 347 | 348 | // query string 349 | if (options.query) { 350 | // ensure query Array values are JSON encoded 351 | for (key in options.query) { 352 | if (isObject(value = options.query[key])) { 353 | options.query[key] = JSON.stringify(value); 354 | } 355 | } 356 | // set query on request 357 | req.query(options.query); 358 | } 359 | 360 | // set headers 361 | if (options.headers) { 362 | req.set(options.headers); 363 | // if authenticating 364 | if (req.withCredentials && options.headers["Authorization"] != null) { 365 | req.withCredentials(); 366 | } 367 | } 368 | 369 | // send body 370 | if (options.body) req.send(options.body); 371 | 372 | // send request 373 | req.end(function (err, res) { 374 | var data; 375 | 376 | if (!err) { 377 | if (!(data = res.body)) { data = res.text; } 378 | else if (data.error) err = self._error(data); 379 | else data = self._response(data); 380 | } 381 | 382 | if (err && fn) { 383 | var response = res || {}; 384 | return fn(err, data, response.status, response.header, res); 385 | } 386 | 387 | res.data = data; 388 | if (fn) fn(err || null, data, res.status, res.header, res); 389 | }); 390 | 391 | return req; 392 | }; 393 | 394 | /** 395 | * Coerce response to normalize access to `_id` and `_rev`. 396 | * 397 | * @param {Object} json The response JSON. 398 | * @return The coerced JSON. 399 | * @private 400 | */ 401 | 402 | Base.prototype._response = function (json) { 403 | var data = json.rows || json.results || json.uuids || isArray(json) && json; 404 | var meta = this._meta; 405 | var i = 0, len, item; 406 | 407 | if (data) { 408 | extend(data, json).json = json; 409 | for (len = data.length; i < len; i++) { 410 | item = data[i] = meta(data[i]); 411 | if (item.doc) item.doc = meta(item.doc); 412 | } 413 | } else { 414 | data = meta(json); 415 | } 416 | 417 | return data; 418 | }; 419 | 420 | /** 421 | * Make an error out of the response. 422 | * 423 | * @param {Object} json The response JSON. 424 | * @return An `Error` object. 425 | * @private 426 | */ 427 | 428 | Base.prototype._error = function (json) { 429 | var err = new Error(json.reason); 430 | err.code = json.error; 431 | return extend(err, json); 432 | }; 433 | 434 | /** 435 | * JSON stringify functions. Used for encoding view documents to JSON. 436 | * 437 | * @param {String} key The key to stringify. 438 | * @param {Object} val The value to stringify. 439 | * @return {Object} The stringified function value or the value. 440 | * @private 441 | */ 442 | 443 | Base.prototype._replacer = function (key, val) { 444 | return isFunction (val) ? val.toString() : val; 445 | }; 446 | 447 | /** 448 | * Coerce documents with prototypical `_id` and `_rev` 449 | * values. 450 | * 451 | * @param {Object} doc The document to coerce. 452 | * @return {Object} The coerced document. 453 | * @private 454 | */ 455 | 456 | Base.prototype._meta = function (doc) { 457 | var hasId = !doc._id && doc.id; 458 | var hasRev = !doc._rev && doc.rev; 459 | var proto; 460 | 461 | if (hasId || hasRev) { 462 | proto = function (){}; 463 | doc = extend(new proto(), doc); 464 | proto = proto.prototype; 465 | if (hasId) proto._id = doc.id; 466 | if (hasRev) proto._rev = doc.rev; 467 | } 468 | 469 | return doc; 470 | }; 471 | 472 | /** 473 | * Parse arguments. 474 | * 475 | * @param {Array} args The arguments. 476 | * @param {Integer} [start] The index from which to start reading arguments. 477 | * @param {Boolean} [withBody] Set to `true` if the request body is given as a 478 | * parameter before HTTP query options. 479 | * @param {Boolean} [notDoc] The request body is not a document. 480 | * @return {Promise} A Promise, if no callback is provided, otherwise `null`. 481 | * @private 482 | */ 483 | 484 | Base.prototype._ = function (args, start, withBody, notDoc) { 485 | var self = this, doc, id, rev; 486 | 487 | function request(method, path, options) { 488 | if (!options) options = {}; 489 | return self._request({ 490 | method: method, 491 | path: path || request.p, 492 | query: options.q || request.q, 493 | data: options.b || request.b, 494 | headers: options.h || request.h, 495 | fn: options.f || request.f, 496 | _: options._ || request._ 497 | }); 498 | } 499 | 500 | // [id], [doc], [query], [header], [callback] 501 | args = [].slice.call(args, start || 0); 502 | 503 | request.f = isFunction(args[args.length - 1]) && args.pop(); 504 | request.p = isString(args[0]) && encodeURI(args.shift()); 505 | request.q = args[withBody ? 1 : 0] || {}; 506 | request.h = args[withBody ? 2 : 1] || {}; 507 | 508 | if (withBody) { 509 | doc = request.b = args[0]; 510 | if (!notDoc) { 511 | if (id = request.p || doc._id || doc.id) request.p = id; 512 | if (rev = request.q.rev || doc._rev || doc.rev) request.q.rev = rev; 513 | } 514 | } 515 | 516 | return request; 517 | }; 518 | 519 | /** 520 | * Clerk CouchDB client. 521 | * 522 | * @param {String} uri Fully qualified URI. 523 | * @param {String} [auth] Authentication header value. 524 | * @constructor 525 | * @memberof clerk 526 | * @see {@link http://wiki.apache.org/couchdb/Complete_HTTP_API_Reference|CouchDB Wiki} 527 | */ 528 | 529 | function Client (uri, auth) { 530 | this.uri = uri; 531 | this._db = {}; 532 | this.auth = auth; 533 | }; 534 | 535 | Client.prototype = new Base(); 536 | 537 | /** 538 | * Select database to manipulate. 539 | * 540 | * @param {String} name DB name. 541 | * @return {DB} DB object. 542 | */ 543 | 544 | Client.prototype.db = function (name) { 545 | var db = this._db; 546 | return db[name] || (db[name] = new DB(this, name, this.auth)); 547 | }; 548 | 549 | /** 550 | * List all databases. 551 | * 552 | * @param {Object} [query] HTTP query options. 553 | * @param {Object} [headers] HTTP headers. 554 | * @param {handler} [callback] Callback function. 555 | * @return {Promise} A Promise, if no callback is provided, 556 | * otherwise `null`. 557 | * @see {@link http://wiki.apache.org/couchdb/HttpGetAllDbs|CouchDB Wiki} 558 | */ 559 | 560 | Client.prototype.dbs = function (/* [query], [headers], [callback] */) { 561 | return this._(arguments)("GET", "_all_dbs"); 562 | }; 563 | 564 | /** 565 | * Get UUIDs. 566 | * 567 | * @param {Integer} [count=1] Number of UUIDs to get. 568 | * @param {Object} [query] HTTP query options. 569 | * @param {Object} [headers] HTTP headers. 570 | * @param {handler} [callback] Callback function. 571 | * @return {Promise} A Promise, if no callback is provided, 572 | * otherwise `null`. 573 | * @see {@link http://wiki.apache.org/couchdb/HttpGetUuids|CouchDB Wiki} 574 | */ 575 | 576 | Client.prototype.uuids = function (count /* [query], [headers], [callback] */) { 577 | var request = this._(arguments, +count == count ? 1 : 0); 578 | if (count > 1) request.q.count = count; 579 | return request("GET", "_uuids"); 580 | }; 581 | 582 | /** 583 | * Get server information. 584 | * 585 | * @param {Object} [query] HTTP query options. 586 | * @param {Object} [headers] HTTP headers. 587 | * @param {handler} [callback] Callback function. 588 | * @return {Promise} A Promise, if no callback is provided, 589 | * otherwise `null`. 590 | * @see {@link http://wiki.apache.org/couchdb/HttpGetRoot|CouchDB Wiki} 591 | */ 592 | 593 | Client.prototype.info = function (/* [query], [headers], [callback] */) { 594 | return this._(arguments)("GET"); 595 | }; 596 | 597 | /** 598 | * Get server stats. 599 | * 600 | * @param {Object} [query] HTTP query options. 601 | * @param {Object} [headers] HTTP headers. 602 | * @param {handler} [callback] Callback function. 603 | * @return {Promise} A Promise, if no callback is provided, 604 | * otherwise `null`. 605 | * @see {@link http://wiki.apache.org/couchdb/HttpGetLog|CouchDB Wiki} 606 | */ 607 | 608 | Client.prototype.stats = function (/* [query], [headers], [callback] */) { 609 | return this._(arguments)("GET", "_stats"); 610 | }; 611 | 612 | /** 613 | * Get tail of the server log file. 614 | * 615 | * @param {Object} [query] Query parameters. 616 | * @param {Integer} [query.bytes] Number of bytes to read. 617 | * @param {Integer} [query.offset] Number of bytes from the end of 618 | * log file to start reading. 619 | * @param {Object} [headers] HTTP headers. 620 | * @param {handler} [callback] Callback function. 621 | * @return {Promise} A Promise, if no callback is provided, 622 | * otherwise `null`. 623 | * @see {@link http://wiki.apache.org/couchdb/HttpGetLog|CouchDB Wiki} 624 | */ 625 | 626 | Client.prototype.log = function (/* [query], [headers], [callback] */) { 627 | return this._(arguments)("GET", "_log"); 628 | }; 629 | 630 | /** 631 | * List running tasks. 632 | * 633 | * @param {Object} [query] HTTP query options. 634 | * @param {Object} [headers] HTTP headers. 635 | * @param {handler} [callback] Callback function. 636 | * @return {Promise} A Promise, if no callback is provided, 637 | * otherwise `null`. 638 | * @see {@link http://wiki.apache.org/couchdb/HttpGetActiveTasks|CouchDB Wiki} 639 | */ 640 | 641 | Client.prototype.tasks = function (/* [query], [headers], [callback] */) { 642 | return this._(arguments)("GET", "_active_tasks"); 643 | }; 644 | 645 | /** 646 | * Get or set configuration values. 647 | * 648 | * @param {String} [key] Configuration section or key. 649 | * @param {String} [value] Configuration value. 650 | * @param {Object} [query] HTTP query options. 651 | * @param {Object} [headers] HTTP headers. 652 | * @param {handler} [callback] Callback function. 653 | * @return {Promise} A Promise, if no callback is provided, 654 | * otherwise `null`. 655 | */ 656 | 657 | Client.prototype.config = function (/* [key], [value], [query], [headers], [callback] */) { 658 | var args = [].slice.call(arguments); 659 | var key = isString(args[0]) && args.shift() || ""; 660 | var value = isString(args[0]) && args.shift(); 661 | var method = isString(value) ? "PUT" : "GET"; 662 | return this._(args)(method, "_config/" + key, { b: value }); 663 | }; 664 | 665 | /** 666 | * Replicate databases. 667 | * 668 | * @param {Object} options Options. 669 | * @param {String} options.source Source database URL or local name. 670 | * @param {String} options.target Target database URL or local name. 671 | * @param {Boolean} [options.cancel] Set to `true` to cancel replication. 672 | * @param {Boolean} [options.continuous] Set to `true` for continuous 673 | * replication. 674 | * @param {Boolean} [options.create_target] Set to `true` to create the 675 | * target database. 676 | * @param {String} [options.filter] Filter name for filtered replication. 677 | * Example: "mydesign/myfilter". 678 | * @param {Object} [options.query] Query parameters for filter. 679 | * @param {String[]} [options.doc_ids] Document IDs to replicate. 680 | * @param {String} [options.proxy] Proxy through which to replicate. 681 | * @param {Object} [query] HTTP query options. 682 | * @param {Object} [headers] HTTP headers. 683 | * @param {handler} [callback] Callback function. 684 | * @return {Promise} A Promise, if no callback is provided, 685 | * otherwise `null`. 686 | * @see {@link http://wiki.apache.org/couchdb/Replication|CouchDB Wiki} 687 | */ 688 | 689 | Client.prototype.replicate = function (options /* [query], [headers], [callback] */) { 690 | return this._(arguments, 1)("POST", "_replicate", { b: options }); 691 | }; 692 | 693 | /** 694 | * Methods for CouchDB database. 695 | * 696 | * @param {Client} client Clerk client. 697 | * @param {String} name DB name. 698 | * @param {String} [auth] Authentication header value. 699 | * @constructor 700 | * @memberof clerk 701 | * @return This object for chaining. 702 | */ 703 | 704 | function DB (client, name, auth) { 705 | this.client = client; 706 | this.name = name; 707 | this.uri = client.uri + "/" + encodeURIComponent(name); 708 | this.auth = auth; 709 | }; 710 | 711 | DB.prototype = new Base(); 712 | 713 | /** 714 | * Create database. 715 | * 716 | * @param {Object} [query] HTTP query options. 717 | * @param {Object} [headers] HTTP headers. 718 | * @param {handler} [callback] Callback function. 719 | * @return {Promise} A Promise, if no callback is provided, 720 | * otherwise `null`. 721 | */ 722 | 723 | DB.prototype.create = function (/* [query], [headers], [callback] */) { 724 | return this._(arguments)("PUT"); 725 | }; 726 | 727 | /** 728 | * Destroy database. 729 | * 730 | * @param {Object} [query] HTTP query options. 731 | * @param {Object} [headers] HTTP headers. 732 | * @param {handler} [callback] Callback function. 733 | * @return {Promise} A Promise, if no callback is provided, 734 | * otherwise `null`. 735 | */ 736 | 737 | DB.prototype.destroy = function (/* [query], [headers], [callback] */) { 738 | return this._(arguments)("DELETE"); 739 | }; 740 | 741 | /** 742 | * Get database info. 743 | * 744 | * @param {Object} [query] HTTP query options. 745 | * @param {Object} [headers] HTTP headers. 746 | * @param {handler} [callback] Callback function. 747 | * @return {Promise} A Promise, if no callback is provided, 748 | * otherwise `null`. 749 | */ 750 | 751 | DB.prototype.info = function (/* [query], [headers], callback */) { 752 | return this._(arguments)("GET"); 753 | }; 754 | 755 | /** 756 | * Check if database exists. 757 | * 758 | * @param {Object} [query] HTTP query options. 759 | * @param {Object} [headers] HTTP headers. 760 | * @param {handler} [callback] Callback function. 761 | * @return {Promise} A Promise, if no callback is provided, 762 | * otherwise `null`. 763 | */ 764 | 765 | DB.prototype.exists = function (/* [query], [headers], callback */) { 766 | var request = this._(arguments); 767 | request._ = function (err, body, status, headers, req) { 768 | if (status === 404) err = null; 769 | return [err, status === 200, status, headers, req]; 770 | }; 771 | return request("HEAD"); 772 | }; 773 | 774 | /** 775 | * Fetch document. 776 | * 777 | * Set `rev` in `query`. 778 | * 779 | * @param {String} id Document ID. 780 | * @param {Object} [query] HTTP query options. 781 | * @param {Boolean} [query.revs] Fetch list of revisions. 782 | * @param {Boolean} [query.revs_info] Fetch detailed revision information. 783 | * @param {Object} [headers] HTTP headers. 784 | * @param {handler} [callback] Callback function. 785 | * @return {Promise} A Promise, if no callback is provided, 786 | * otherwise `null`. 787 | * @see {@link http://wiki.apache.org/couchdb/HTTP_Document_API#GET|CouchDB Wiki} 788 | */ 789 | 790 | DB.prototype.get = function (/* [id], [query], [headers], [callback] */) { 791 | return this._(arguments)("GET"); 792 | }; 793 | 794 | /** 795 | * Get document metadata. 796 | * 797 | * @param {String} id Document ID. 798 | * @param {Object} [query] HTTP query options. 799 | * @param {Object} [headers] HTTP headers. 800 | * @param {handler} [callback] Callback function. 801 | * @return {Promise} A Promise, if no callback is provided, 802 | * otherwise `null`. 803 | * @see {@link http://wiki.apache.org/couchdb/HTTP_Document_API#HEAD|CouchDB Wiki} 804 | */ 805 | 806 | DB.prototype.head = function (/* [id], [query], [headers], callback */) { 807 | var self = this; 808 | var request = self._(arguments); 809 | request._ = function (err, body, status, headers, res) { 810 | return [err, err ? null : { 811 | _id: request.p, 812 | _rev: headers.etag && JSON.parse(headers.etag), 813 | contentType: headers["content-type"], 814 | contentLength: headers["content-length"] 815 | }, status, headers, res]; 816 | }; 817 | return request("HEAD"); 818 | }; 819 | 820 | /** 821 | * Post document(s) to database. 822 | * 823 | * If documents have no ID, a document ID will be automatically generated 824 | * on the server. Attachments are not currently supported. 825 | * 826 | * @param {Object|Object[]} doc Document or array of documents. 827 | * @param {String} [doc._id] Document ID. If set, uses given document ID. 828 | * @param {String} [doc._rev] Document revision. If set, allows update to 829 | * existing document. 830 | * @param {Object} [doc._attachments] Attachments. If given, must be a 831 | * map of filenames to attachment properties. 832 | * @param {String} [doc._attachments[filename]] Attachment filename, as 833 | * hash key. 834 | * @param {String} [doc._attachments[filename].contentType] Attachment 835 | * MIME content type. 836 | * @param {String|Object} [doc._attachments[filename].data] Attachment 837 | * data. Will be Base64 encoded. 838 | * @param {Object} [query] HTTP query options. 839 | * @param {Boolean} [query.batch] Allow server to write document in 840 | * batch mode. Documents will not be written to disk immediately, 841 | * increasing the chances of write failure. 842 | * @param {Boolean} [query.all_or_nothing] For batch updating of 843 | * documents, use all-or-nothing semantics. 844 | * @param {Object} [headers] HTTP headers. 845 | * @param {handler} [callback] Callback function. 846 | * @return {Promise} A Promise, if no callback is provided, 847 | * otherwise `null`. 848 | * @see {@link http://wiki.apache.org/couchdb/HTTP_Document_API#POST|CouchDB Wiki} 849 | * @see {@link http://wiki.apache.org/couchdb/HTTP_Bulk_Document_API|CouchDB Wiki} 850 | */ 851 | 852 | DB.prototype.post = function (docs /* [query], [headers], [callback] */) { 853 | var request = this._(arguments, 1); 854 | if (isArray(docs)) { 855 | request.p = "_bulk_docs"; 856 | request.b = extend({ docs: docs }, request.q); 857 | request.q = null 858 | } else { 859 | request.b = docs; 860 | } 861 | return request("POST"); 862 | }; 863 | 864 | /** 865 | * Put document in database. 866 | * 867 | * @param {Object} doc Document data. Requires `_id` and `_rev`. 868 | * @param {String} [options] Options. 869 | * @param {Object} [query] HTTP query options. 870 | * @param {Object} [headers] HTTP headers. 871 | * @param {handler} [callback] Callback function. 872 | * @return {Promise} A Promise, if no callback is provided, 873 | * otherwise `null`. 874 | * @see {@link http://wiki.apache.org/couchdb/HTTP_Document_API#PUT|CouchDB Wiki} 875 | */ 876 | 877 | DB.prototype.put = function (/* [id], [doc], [query], [headers], [callback] */) { 878 | var request = this._(arguments, 0, 1); 879 | // prevent acidentally creating database 880 | if (!request.p) request.p = request.b._id || request.b.id; 881 | if (!request.p) throw new Error("missing id"); 882 | return request("PUT"); 883 | }; 884 | 885 | /** 886 | * Delete document(s). 887 | * 888 | * @param {Object|Object[]} docs Document or array of documents. 889 | * @param {Object} [query] HTTP query options. 890 | * @param {Object} [headers] HTTP headers. 891 | * @param {handler} [callback] Callback function. 892 | * @return {Promise} A Promise, if no callback is provided, 893 | * otherwise `null`. 894 | * @see {@link http://wiki.apache.org/couchdb/HTTP_Document_API#DELETE|CouchDB Wiki} 895 | * @see {@link http://wiki.apache.org/couchdb/HTTP_Bulk_Document_API|CouchDB Wiki} 896 | */ 897 | 898 | DB.prototype.del = function (docs /* [query], [headers], [callback] */) { 899 | if (isArray(docs)) { 900 | var i = 0, len = docs.length, doc; 901 | for (; i < len; i++) { 902 | doc = docs[i], docs[i] = { 903 | _id: doc._id || doc.id, 904 | _rev: doc._rev || doc.rev, 905 | _deleted: true 906 | }; 907 | } 908 | return this.post.apply(this, arguments); 909 | } else { 910 | var request = this._(arguments, 0, 1); 911 | // prevent acidentally deleting database 912 | if (!request.p) throw new Error("missing id"); 913 | return request("DELETE"); 914 | } 915 | }; 916 | 917 | /** 918 | * Copy document. 919 | * 920 | * @param {Object} source Source document. 921 | * @param {String} source.id Source document ID. 922 | * @param {String} [source.rev] Source document revision. 923 | * @param {String} [source._id] Source document ID. Alternate key for 924 | * `source.id`. 925 | * @param {String} [source._rev] Source document revision. Alternate key 926 | * for `source.id`. 927 | * @param {Object} target Target document. 928 | * @param {String} target.id Target document ID. 929 | * @param {String} [target.rev] Target document revision. 930 | * @param {String} [target._id] Target document ID. Alternate key for 931 | * `target.id`. 932 | * @param {String} [target._rev] Target document revision. Alternate key 933 | * for `target.id`. 934 | * @param {Object} [query] HTTP query options. 935 | * @param {Object} [headers] HTTP headers. 936 | * @param {handler} [callback] Callback function. 937 | * @return {Promise} A Promise, if no callback is provided, 938 | * otherwise `null`. 939 | * @see {@link http://wiki.apache.org/couchdb/HTTP_Document_API#COPY|CouchDB Wiki} 940 | */ 941 | 942 | DB.prototype.copy = function (source, target /* [query], [headers], [callback] */) { 943 | var request = this._(arguments, 2); 944 | var sourcePath = encodeURIComponent(source.id || source._id || source); 945 | var targetPath = encodeURIComponent(target.id || target._id || target); 946 | var sourceRev = source.rev || source._rev; 947 | var targetRev = target.rev || target._rev; 948 | 949 | if (sourceRev) request.q.rev = sourceRev; 950 | if (targetRev) targetPath += "?rev=" + encodeURIComponent(targetRev); 951 | 952 | request.h.Destination = targetPath; 953 | 954 | return request("COPY", sourcePath); 955 | }; 956 | 957 | /** 958 | * Query all documents by ID. 959 | * 960 | * @param {Object} [query] HTTP query options. 961 | * @param {JSON} [query.startkey] Start returning results from this 962 | * document ID. 963 | * @param {JSON} [query.endkey] Stop returning results at this document 964 | * ID. 965 | * @param {Integer} [query.limit] Limit number of results returned. 966 | * @param {Boolean} [query.descending=false] Lookup results in reverse 967 | * order by key, returning documents in descending order by key. 968 | * @param {Integer} [query.skip] Skip this many records before 969 | * returning results. 970 | * @param {Boolean} [query.include_docs=false] Include document source for 971 | * each result. 972 | * @param {Boolean} [query.include_end=true] Include `query.endkey` 973 | * in results. 974 | * @param {Boolean} [query.update_seq=false] Include sequence value 975 | * of the database corresponding to the view. 976 | * @param {Object} [headers] HTTP headers. 977 | * @param {handler} [callback] Callback function. 978 | * @return {Promise} A Promise, if no callback is provided, 979 | * otherwise `null`. 980 | * @see {@link http://wiki.apache.org/couchdb/HTTP_Bulk_Document_API|CouchDB Wiki} 981 | */ 982 | 983 | DB.prototype.all = function (/* [query], [headers], [callback] */) { 984 | var request = this._(arguments); 985 | var body = this._viewOptions(request.q); 986 | return request(body ? "POST" : "GET", "_all_docs", { b: body }); 987 | }; 988 | 989 | /** 990 | * Query a view. 991 | * 992 | * @param {String|Object} view View name (e.g. mydesign/myview) or 993 | * temporary view definition. Using a temporary view is strongly not 994 | * recommended for production use. 995 | * @param {Object} [query] HTTP query options. 996 | * @param {JSON} [query.key] Key to lookup. 997 | * @param {JSON} [query.startkey] Start returning results from this key. 998 | * @param {String} [query.startkey_docid] Start returning results 999 | * from this document ID. Allows pagination with duplicate keys. 1000 | * @param {JSON} [query.endkey] Stop returning results at this key. 1001 | * @param {String} [query.endkey_docid] Stop returning results at 1002 | * this document ID. Allows pagination with duplicate keys. 1003 | * @param {Integer} [query.limit] Limit number of results returned. 1004 | * @param {Boolean|String} [query.stale] Do not refresh view even if 1005 | * stale. For CouchDB versions `1.1.0` and up, set to `update_after` to 1006 | * update view after results are returned. 1007 | * @param {Boolean} [query.descending=false] Lookup results in reverse 1008 | * order by key, returning documents in descending order by key. 1009 | * @param {Integer} [query.skip] Skip this many records before 1010 | * returning results. 1011 | * @param {Boolean|Integer} [query.group=false] Use the reduce function 1012 | * to group results by key. Set to an integer specify `group_level`. 1013 | * @param {Boolean|Integer} [query.reduce=true] Use the reduce function. 1014 | * @param {Boolean} [query.fetch=false] Include document source for 1015 | * each result. 1016 | * @param {Boolean} [query.include_end=true] Include `query.endkey` 1017 | * in results. 1018 | * @param {Boolean} [query.update_seq=false] Include sequence value 1019 | * of the database corresponding to the view. 1020 | * @param {Object} [headers] HTTP headers. 1021 | * @param {handler} [callback] Callback function. 1022 | * @return {Promise} A Promise, if no callback is provided, 1023 | * otherwise `null`. 1024 | * @see {@link http://wiki.apache.org/couchdb/HTTP_view_API|CouchDB Wiki} 1025 | */ 1026 | 1027 | DB.prototype.find = function (view /* [query], [headers], [callback] */) { 1028 | var request = this._(arguments, 1), path, body; 1029 | 1030 | if (isString(view)) { 1031 | path = view.split("/", 2); 1032 | path = "_design/" + encodeURIComponent(path[0]) + 1033 | "/_view/" + encodeURIComponent(path[1]); 1034 | } else { 1035 | path = "_temp_view"; 1036 | body = view; 1037 | } 1038 | 1039 | body = this._viewOptions(request.q, body); 1040 | return request(body ? "POST" : "GET", path, { b: body }); 1041 | }; 1042 | 1043 | /** 1044 | * Get database changes. 1045 | * 1046 | * The `feed` option determines how the callback is called: 1047 | * 1048 | * - `normal` calls the callback once. 1049 | * - `longpoll` waits for a response, then calls the callback once. 1050 | * - `continuous` calls the callback each time an update is received. 1051 | * Implemented as the `database#follow()` method. 1052 | * 1053 | * @param {Object} [query] HTTP query options. 1054 | * @param {String} [query.feed="normal"] Type of feed. See comments 1055 | * above. 1056 | * @param {String} [query.filter] Filter updates using this filter. 1057 | * @param {Integer} [query.limit] Maximum number of rows to return. 1058 | * @param {Integer} [query.since=0] Start results from this sequence 1059 | * number. 1060 | * @param {Boolean} [query.include_docs=false] Include documents with 1061 | * results. 1062 | * @param {Integer} [query.timeout=1000] Maximum period in milliseconds 1063 | * to wait for a change before sending a response, even if there are no 1064 | * results. 1065 | * @param {Integer} [query.heartbeat=1000] Period in milliseconds after 1066 | * which an empty line is sent. Applicable only to feed types 1067 | * `longpoll` and `continuous`. Overrides `query.timeout` to keep the 1068 | * feed alive indefinitely. 1069 | * @param {Object} [headers] HTTP headers. 1070 | * @param {handler} [callback] Callback function. 1071 | * @return {Promise} A Promise, if no callback is provided, 1072 | * otherwise `null`. 1073 | * @see {@link http://wiki.apache.org/couchdb/HTTP_database_API#Changes|CouchDB Wiki} 1074 | */ 1075 | 1076 | DB.prototype.changes = function (/* [query], [headers], [callback] */) { 1077 | var request = this._(arguments); 1078 | if (request.q.feed != "longpoll") delete request.q.feed; 1079 | return this._changes(request); 1080 | }; 1081 | 1082 | /** 1083 | * Follow database changes. 1084 | * 1085 | * @see `#changes()`. 1086 | */ 1087 | 1088 | DB.prototype.follow = function (/* [query], [headers], callback */) { 1089 | var self = this; 1090 | var request = this._(arguments); 1091 | var fn = request.f; 1092 | 1093 | if (!fn) return this; 1094 | 1095 | request.q.feed = "longpoll"; 1096 | request.f = function (err, body) { 1097 | var args = [].slice.call(arguments); 1098 | var done, i; 1099 | for (i = 0; i < body.length; i++) { 1100 | args[1] = body[i]; 1101 | if (done = fn.apply(self, args) === false || err) break; 1102 | } 1103 | if (!done) self._changes(request); 1104 | }; 1105 | 1106 | return this._changes(request); 1107 | }; 1108 | 1109 | /** 1110 | * Service a changes request. 1111 | * 1112 | * @private 1113 | */ 1114 | 1115 | DB.prototype._changes = function (request) { 1116 | return request("GET", "_changes"); 1117 | }; 1118 | 1119 | /** 1120 | * Update document using server-side handler. 1121 | * 1122 | * @param {String} handler Update handler. Example: mydesign/myhandler 1123 | * @param {String} [id] Document ID. 1124 | * @param {any} data Data. 1125 | * @param {Object} [query] HTTP query options. 1126 | * @param {Object} [headers] Headers. 1127 | * @param {handler} [callback] Callback function. 1128 | * @return {Promise} A Promise, if no callback is provided, 1129 | * otherwise `null`. 1130 | * @see {@link http://wiki.apache.org/couchdb/Document_Update_Handlers|CouchDB Wiki} 1131 | */ 1132 | 1133 | DB.prototype.update = function (handler /* [id], [data], [query], [headers], [callback] */) { 1134 | var request = this._(arguments, 1, 1, 1); 1135 | var path = handler.split("/", 2); 1136 | 1137 | path = "_design/" + encodeURIComponent(path[0]) + 1138 | "/_update/" + encodeURIComponent(path[1]); 1139 | 1140 | if (request.p) path += "/" + request.p; 1141 | 1142 | return request("POST", path); 1143 | }; 1144 | 1145 | /** 1146 | * Download attachment from document. 1147 | * 1148 | * @param {Object|String} docOrId Document or document ID. 1149 | * @param {String} attachmentName Attachment name. 1150 | * @param {Object} [query] HTTP query options. 1151 | * @param {Object} [headers] HTTP headers. 1152 | * @param {handler} [callback] Callback function. 1153 | * @return {Promise} A Promise, if no callback is provided, 1154 | * otherwise `null`. 1155 | */ 1156 | 1157 | DB.prototype.attachment = function (doc, attachmentName /* [query], [headers], [callback] */) { 1158 | var request = this._(arguments, 2); 1159 | var path = encodeURIComponent(doc._id || doc.id || doc) + "/" + 1160 | encodeURIComponent(attachmentName); 1161 | return request("GET", path, options); 1162 | }; 1163 | 1164 | /** 1165 | * Upload attachment to document. 1166 | * 1167 | * Set the `Content-Type` header. 1168 | * 1169 | * @param {Object} [doc] Document. Requires `id`. `rev` can be specified 1170 | * here or in `query`. 1171 | * @param {String} attachmentName Attachment name. 1172 | * @param {Object} data Data. 1173 | * @param {Object} [query] HTTP query options. 1174 | * @param {Object} [headers] HTTP headers. 1175 | * @param {handler} [callback] Callback function. 1176 | * @return {Promise} A Promise, if no callback is provided, 1177 | * otherwise `null`. 1178 | */ 1179 | 1180 | DB.prototype.attach = function (doc, attachmentName, data /* [query], [headers], [callback] */) { 1181 | var request = this._(arguments, 3); 1182 | request.p = encodeURIComponent(doc._id || doc.id) + "/" + 1183 | encodeURIComponent(attachmentName); 1184 | if (!request.q.rev) request.q.rev = doc._rev || doc.rev; 1185 | request.q.body = data; 1186 | return request("PUT", path); 1187 | }; 1188 | 1189 | /** 1190 | * Replicate database. 1191 | * 1192 | * This convenience function sets `options.source` and `options.target` to 1193 | * the selected database name. Either `options.source` or `options.target` 1194 | * must be overridden for a successful replication request. 1195 | * 1196 | * @param {Options} options Options. Accepts all options from 1197 | * `Client.replicate()`. 1198 | * @param {String} [options.source=this.name] Source database URL or 1199 | * local name. Defaults to the selected database name if not given. 1200 | * @param {String} [options.target=this.name] Target database URL or 1201 | * local name. Defaults to the selected database name if not given. 1202 | * @param {Object} [query] HTTP query options. 1203 | * @param {Object} [headers] HTTP headers. 1204 | * @param {handler} [callback] Callback function. 1205 | * @return {Promise} A Promise, if no callback is provided, 1206 | * otherwise `null`. 1207 | */ 1208 | 1209 | DB.prototype.replicate = function (options /* [query], [headers], [callback] */) { 1210 | if (!options.source) options.source = this.name; 1211 | if (!options.target) options.target = this.name; 1212 | return this.client.replicate.apply(this.client, arguments); 1213 | }; 1214 | 1215 | /** 1216 | * Ensure recent changes are committed to disk. 1217 | * 1218 | * @param {Object} [query] HTTP query options. 1219 | * @param {Object} [headers] HTTP headers. 1220 | * @param {handler} [callback] Callback function. 1221 | * @return {Promise} A Promise, if no callback is provided, 1222 | * otherwise `null`. 1223 | */ 1224 | 1225 | DB.prototype.commit = function (/* [query], [headers], [callback] */) { 1226 | return this._(arguments)("POST", "_ensure_full_commit"); 1227 | }; 1228 | 1229 | /** 1230 | * Purge deleted documents from database. 1231 | * 1232 | * @param {Object} revs Map of document IDs to revisions to be purged. 1233 | * @param {Object} [query] HTTP query options. 1234 | * @param {Object} [headers] HTTP headers. 1235 | * @param {handler} [callback] Callback function. 1236 | * @return {Promise} A Promise, if no callback is provided, 1237 | * otherwise `null`. 1238 | */ 1239 | 1240 | DB.prototype.purge = function (revs /* [query], [headers], [callback] */) { 1241 | return this._(arguments, 1)("POST", "_purge", { b: revs }); 1242 | }; 1243 | 1244 | /** 1245 | * Compact database or design. 1246 | * 1247 | * @param {String} [design] Design name if compacting design indexes. 1248 | * @param {Object} [query] HTTP query options. 1249 | * @param {Object} [headers] HTTP headers. 1250 | * @param {handler} [callback] Callback function. 1251 | * @return {Promise} A Promise, if no callback is provided, 1252 | * otherwise `null`. 1253 | * @see {@link http://wiki.apache.org/couchdb/Compaction|CouchDB Wiki} 1254 | */ 1255 | 1256 | DB.prototype.compact = function (/* [design], [query], [headers], [callback] */) { 1257 | var request = this._(arguments); 1258 | return request("POST", "_compact/" + (request.p || "")); 1259 | }; 1260 | 1261 | /** 1262 | * Remove unused views. 1263 | * 1264 | * @param {Object} [query] HTTP query options. 1265 | * @param {Object} [headers] HTTP headers. 1266 | * @param {handler} [callback] Callback function. 1267 | * @return {Promise} A Promise, if no callback is provided, 1268 | * otherwise `null`. 1269 | * @see {@link http://wiki.apache.org/couchdb/Compaction|CouchDB Wiki} 1270 | */ 1271 | 1272 | DB.prototype.vacuum = function (/* [query], [headers], [callback] */) { 1273 | return this._(arguments)("POST", "_view_cleanup"); 1274 | }; 1275 | 1276 | /** 1277 | * Parse view options. 1278 | * 1279 | * @param {Object} query The HTTP query options. 1280 | * @param {Object} body The body payload. 1281 | * @param {handler} [callback] Callback function. 1282 | * @return {Object} The body payload. 1283 | * @private 1284 | */ 1285 | 1286 | DB.prototype._viewOptions = function (q, body) { 1287 | if (q) { 1288 | if (q.key) q.key = JSON.stringify(q.key); 1289 | if (q.startkey) q.startkey = JSON.stringify(q.startkey); 1290 | if (q.endkey) q.endkey = JSON.stringify(q.endkey); 1291 | if (q.stale && q.stale != "update_after") q.stale = "ok"; 1292 | if (q.keys) { 1293 | if (!body) body = {}; 1294 | body.keys = q.keys; 1295 | delete q.keys; 1296 | } 1297 | } 1298 | return body; 1299 | }; 1300 | 1301 | /** 1302 | * Handle a clerk response. 1303 | * 1304 | * @callback handler 1305 | * @param {Error|null} error Error or `null` on success. 1306 | * @param {Object} data Response data. 1307 | * @param {Integer} status Response status code. 1308 | * @param {Object} headers Response headers. 1309 | * @param {superagent.Response} res Superagent response object. 1310 | */ 1311 | 1312 | clerk.Base = Base; 1313 | clerk.Client = Client; 1314 | clerk.DB = DB; 1315 | 1316 | // Export clerk. 1317 | module.exports = clerk; 1318 | -------------------------------------------------------------------------------- /dist/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikepb/clerk/b41b218b8e91b3f9fc62d411d51243f395b5bf83/dist/.gitkeep -------------------------------------------------------------------------------- /dist/clerk.js: -------------------------------------------------------------------------------- 1 | (function webpackUniversalModuleDefinition(root, factory) { 2 | if(typeof exports === 'object' && typeof module === 'object') 3 | module.exports = factory(); 4 | else if(typeof define === 'function' && define.amd) 5 | define(factory); 6 | else if(typeof exports === 'object') 7 | exports["clerk"] = factory(); 8 | else 9 | root["clerk"] = factory(); 10 | })(this, function() { 11 | return /******/ (function(modules) { // webpackBootstrap 12 | /******/ // The module cache 13 | /******/ var installedModules = {}; 14 | /******/ 15 | /******/ // The require function 16 | /******/ function __webpack_require__(moduleId) { 17 | /******/ 18 | /******/ // Check if module is in cache 19 | /******/ if(installedModules[moduleId]) 20 | /******/ return installedModules[moduleId].exports; 21 | /******/ 22 | /******/ // Create a new module (and put it into the cache) 23 | /******/ var module = installedModules[moduleId] = { 24 | /******/ exports: {}, 25 | /******/ id: moduleId, 26 | /******/ loaded: false 27 | /******/ }; 28 | /******/ 29 | /******/ // Execute the module function 30 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 31 | /******/ 32 | /******/ // Flag the module as loaded 33 | /******/ module.loaded = true; 34 | /******/ 35 | /******/ // Return the exports of the module 36 | /******/ return module.exports; 37 | /******/ } 38 | /******/ 39 | /******/ 40 | /******/ // expose the modules object (__webpack_modules__) 41 | /******/ __webpack_require__.m = modules; 42 | /******/ 43 | /******/ // expose the module cache 44 | /******/ __webpack_require__.c = installedModules; 45 | /******/ 46 | /******/ // __webpack_public_path__ 47 | /******/ __webpack_require__.p = ""; 48 | /******/ 49 | /******/ // Load entry module and return exports 50 | /******/ return __webpack_require__(0); 51 | /******/ }) 52 | /************************************************************************/ 53 | /******/ ([ 54 | /* 0 */ 55 | /***/ function(module, exports, __webpack_require__) { 56 | 57 | "use strict"; 58 | 59 | /*! 60 | 61 | clerk - CouchDB client for node and the browser. 62 | Copyright 2012-2015 Michael Phan-Ba 63 | 64 | Licensed under the Apache License, Version 2.0 (the "License"); 65 | you may not use this file except in compliance with the License. 66 | You may obtain a copy of the License at 67 | 68 | http://www.apache.org/licenses/LICENSE-2.0 69 | 70 | Unless required by applicable law or agreed to in writing, software 71 | distributed under the License is distributed on an "AS IS" BASIS, 72 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 73 | See the License for the specific language governing permissions and 74 | limitations under the License. 75 | 76 | */ 77 | 78 | // Module dependencies. 79 | var request = __webpack_require__(1); 80 | 81 | /** 82 | * Copy properties from sources to target. 83 | * 84 | * @param {Object} target The target object. 85 | * @param {...Object} sources The source object. 86 | * @return {Object} The target object. 87 | * @private 88 | */ 89 | 90 | var extend = function (target /* ...sources */) { 91 | var source, key, i = 1; 92 | while (source = arguments[i++]) { 93 | for (key in source) target[key] = source[key]; 94 | } 95 | return target; 96 | }; 97 | 98 | /** 99 | * Stringify value. 100 | * 101 | * @param {Object} that That value to stringify. 102 | * @return {String} The stringifyed value. 103 | * @private 104 | */ 105 | 106 | var asString = function (that) { 107 | return Object.prototype.toString.call(that); 108 | }; 109 | 110 | /** 111 | * Check if value is a string. 112 | * 113 | * @param {Object} that That value to check. 114 | * @return {Boolean} `true` if string, `false` otherwise. 115 | * @private 116 | */ 117 | 118 | var isString = function (that) { 119 | return asString(that) == "[object String]"; 120 | }; 121 | 122 | /** 123 | * Check if value is an object. 124 | * 125 | * @param {Object} that That value to check. 126 | * @return {Boolean} `true` if object, `false` otherwise. 127 | * @private 128 | */ 129 | 130 | var isObject = function (that) { 131 | return asString(that) == "[object Object]"; 132 | }; 133 | 134 | /** 135 | * Check if value is an array. 136 | * 137 | * @param {Object} that That value to check. 138 | * @return {Boolean} `true` if array, `false` otherwise. 139 | * @private 140 | */ 141 | 142 | var isArray = function (that) { 143 | return asString(that) == "[object Array]"; 144 | }; 145 | 146 | /** 147 | * Check if value is a function. 148 | * 149 | * @param {Object} that That value to check. 150 | * @return {Boolean} `true` if function, `false` otherwise. 151 | * @private 152 | */ 153 | 154 | var isFunction = function (that) { 155 | return asString(that) == "[object Function]"; 156 | }; 157 | 158 | /** 159 | * Clerk library entry point. 160 | * 161 | * @param {String} uri CouchDB server URI. 162 | * @return {Client|DB} If a URI path is given, returns a `DB`, otherwise 163 | * returns a `Client`. 164 | * @see {@link http://docs.couchdb.org|CouchDB Documentation} 165 | * @see {@link http://guide.couchdb.org/|CouchDB Guide} 166 | * @see {@link http://wiki.apache.org/couchdb/|CouchDB Wiki} 167 | */ 168 | 169 | function clerk (uri) { 170 | return clerk.make(uri); 171 | }; 172 | 173 | /** 174 | * Promise implementation. 175 | * @type {Promise} 176 | */ 177 | 178 | clerk.Promise = typeof Promise !== "undefined" && Promise; 179 | 180 | /** 181 | * Library version. 182 | * @type {String} 183 | */ 184 | 185 | clerk.version = "0.8.2"; 186 | 187 | /** 188 | * Default host. 189 | * @type {String} 190 | */ 191 | 192 | clerk.defaultHost = "http://127.0.0.1:5984"; 193 | 194 | /** 195 | * Create single CouchDB client. 196 | * 197 | * @param {String} uri Fully qualified URI. 198 | * @return {Client|DB} If `uri` has a path, the last segment of the 199 | * path is used as the database name and a `DB` instance is 200 | * returned. Otherwise, a `Client` instance is returned. 201 | */ 202 | 203 | clerk.make = function (uri) { 204 | if (!uri) return new Client(this.defaultHost); 205 | 206 | uri = clerk._parseURI(uri); 207 | 208 | var db = /\/*([^\/]+)\/*$/.exec(uri.path); 209 | if (db) { 210 | uri.path = uri.path.substr(0, db.index); 211 | db = db[1] && decodeURIComponent(db[1]); 212 | } 213 | 214 | // weird way of doing it, but it's more efficient... 215 | if (uri.auth) uri.auth = 'Basic ' + clerk.btoa(uri.auth); 216 | 217 | var client = new clerk.Client(uri.base + uri.path, uri.auth); 218 | return db ? client.db(db) : client; 219 | }; 220 | 221 | /** 222 | * Base64-encode a string. 223 | * 224 | * @param {String} str 225 | * @return {String} 226 | */ 227 | 228 | clerk.btoa = typeof Buffer != "undefined" ? function (str) { 229 | return new Buffer(str).toString("base64"); 230 | } : function (str) { 231 | return btoa(str); 232 | }; 233 | 234 | /** 235 | * Parse URI. 236 | * 237 | * The URI is normalized by removing extra `//` in the path. 238 | * 239 | * @param {String} uri Fully qualified URI. 240 | * @return {String} The normalized URI. 241 | * @private 242 | */ 243 | 244 | clerk._parseURI = function (uri) { 245 | var match; 246 | 247 | if (uri) { 248 | if (match = /^(https?:\/\/)(?:([^\/@]+)@)?([^\/]+)(.*)\/*$/.exec(uri)) { 249 | return { 250 | base: match[1] + match[3].replace(/\/+/g, "\/"), 251 | path: match[4], 252 | auth: match[2] && decodeURIComponent(match[2]) 253 | }; 254 | } 255 | } 256 | 257 | return { base: uri || "", path: "" }; 258 | }; 259 | 260 | /** 261 | * Base prototype for `Client` and `DB`. 262 | * Encapsulates HTTP methods, JSON handling, and response coersion. 263 | * 264 | * @constructor 265 | * @memberof clerk 266 | */ 267 | 268 | function Base () {}; 269 | 270 | /** 271 | * Service request and parse JSON response. 272 | * 273 | * @param {String} [method=GET] HTTP method. 274 | * @param {String} [path=this.uri] HTTP URI. 275 | * @param {Object} [query] HTTP query options. 276 | * @param {Object} [body] HTTP body. 277 | * @param {Object} [headers] HTTP headers. 278 | * @param {handler} [callback] Callback function. 279 | * @return {Promise} 280 | */ 281 | 282 | Base.prototype.request = function (/* [method], [path], [query], [body], [headers], [callback] */) { 283 | var args = [].slice.call(arguments); 284 | var callback = isFunction (args[args.length - 1]) && args.pop(); 285 | 286 | return this._request({ 287 | method: args[0], 288 | path: args[1], 289 | query: args[2], 290 | data: args[3], 291 | headers: args[4], 292 | fn: callback 293 | }); 294 | }; 295 | 296 | /** 297 | * Internal service request and parse JSON response handler. 298 | * 299 | * @param {String} options 300 | * @param {String} method HTTP method. 301 | * @param {String} path HTTP URI. 302 | * @param {Object} query HTTP query options. 303 | * @param {Object} data HTTP body data. 304 | * @param {Object} headers HTTP headers. 305 | * @param {handler} [callback] Callback function. 306 | * @private 307 | */ 308 | 309 | Base.prototype._request = function (options) { 310 | var self = this; 311 | 312 | if (options.method == null) options.method = "GET"; 313 | if (options.headers == null) options.headers = {}; 314 | if (options.auth == null) options.auth = this.auth; 315 | 316 | options.path = options.path ? "/" + options.path : ""; 317 | 318 | // set default headers 319 | if (options.headers["Content-Type"] == null) { 320 | options.headers["Content-Type"] = "application/json"; 321 | } 322 | if (options.headers["Accept"] == null) { 323 | options.headers["Accept"] = "application/json"; 324 | } 325 | if (this.auth && options.headers["Authorization"] == null) { 326 | options.headers["Authorization"] = this.auth; 327 | } 328 | 329 | options.uri = this.uri + options.path; 330 | options.body = options.data && JSON.stringify(options.data, 331 | /^\/_design/.test(options.path) && this._replacer 332 | ) || ""; 333 | 334 | // create promise if no callback given 335 | var promise, req; 336 | if (!options.fn && clerk.Promise) { 337 | promise = new clerk.Promise(function (resolve, reject) { 338 | options.fn = function (err, data, status, headers, res) { 339 | if (err) { 340 | err.body = data; 341 | err.status = status; 342 | err.headers = headers; 343 | err.res = res; 344 | reject(err); 345 | } else { 346 | if (isObject(data) && Object.defineProperties) { 347 | Object.defineProperties(data, { 348 | _status: { value: status }, 349 | _headers: { value: headers }, 350 | _response: { value: res }, 351 | }); 352 | } 353 | resolve(data); 354 | }; 355 | }; 356 | }); 357 | req = send(); 358 | promise.request = req; 359 | promise.abort = function () { 360 | req.abort(); 361 | options.fn(new Error("abort")); 362 | return promise; 363 | }; 364 | return promise; 365 | } 366 | 367 | send(); 368 | 369 | function send () { 370 | // apply response transforms 371 | var g = options._; 372 | var fn = options.fn; 373 | if (fn) { 374 | options.fn = g ? function () { 375 | fn.apply(self, g.apply(self, arguments) || arguments); 376 | } : fn; 377 | } 378 | return self._do(options); 379 | } 380 | }; 381 | 382 | /** 383 | * Provider for servicing requests and parsing JSON responses. 384 | * 385 | * @param {String} options 386 | * @param {String} method HTTP method. 387 | * @param {String} uri HTTP URI. 388 | * @param {Object} query HTTP query options. 389 | * @param {Object} body HTTP body. 390 | * @param {Object} headers HTTP headers. 391 | * @param {Object} auth HTTP authentication. 392 | * @param {handler} [callback] Callback function. 393 | * @private 394 | */ 395 | 396 | Base.prototype._do = function (options) { 397 | var self = this; 398 | var key, value; 399 | var fn = options.fn; 400 | 401 | // create request 402 | var req = request(options.method, options.uri); 403 | 404 | // query string 405 | if (options.query) { 406 | // ensure query Array values are JSON encoded 407 | for (key in options.query) { 408 | if (isObject(value = options.query[key])) { 409 | options.query[key] = JSON.stringify(value); 410 | } 411 | } 412 | // set query on request 413 | req.query(options.query); 414 | } 415 | 416 | // set headers 417 | if (options.headers) { 418 | req.set(options.headers); 419 | // if authenticating 420 | if (req.withCredentials && options.headers["Authorization"] != null) { 421 | req.withCredentials(); 422 | } 423 | } 424 | 425 | // send body 426 | if (options.body) req.send(options.body); 427 | 428 | // send request 429 | req.end(function (err, res) { 430 | var data; 431 | 432 | if (!err) { 433 | if (!(data = res.body)) { data = res.text; } 434 | else if (data.error) err = self._error(data); 435 | else data = self._response(data); 436 | } 437 | 438 | if (err && fn) { 439 | var response = res || {}; 440 | return fn(err, data, response.status, response.header, res); 441 | } 442 | 443 | res.data = data; 444 | if (fn) fn(err || null, data, res.status, res.header, res); 445 | }); 446 | 447 | return req; 448 | }; 449 | 450 | /** 451 | * Coerce response to normalize access to `_id` and `_rev`. 452 | * 453 | * @param {Object} json The response JSON. 454 | * @return The coerced JSON. 455 | * @private 456 | */ 457 | 458 | Base.prototype._response = function (json) { 459 | var data = json.rows || json.results || json.uuids || isArray(json) && json; 460 | var meta = this._meta; 461 | var i = 0, len, item; 462 | 463 | if (data) { 464 | extend(data, json).json = json; 465 | for (len = data.length; i < len; i++) { 466 | item = data[i] = meta(data[i]); 467 | if (item.doc) item.doc = meta(item.doc); 468 | } 469 | } else { 470 | data = meta(json); 471 | } 472 | 473 | return data; 474 | }; 475 | 476 | /** 477 | * Make an error out of the response. 478 | * 479 | * @param {Object} json The response JSON. 480 | * @return An `Error` object. 481 | * @private 482 | */ 483 | 484 | Base.prototype._error = function (json) { 485 | var err = new Error(json.reason); 486 | err.code = json.error; 487 | return extend(err, json); 488 | }; 489 | 490 | /** 491 | * JSON stringify functions. Used for encoding view documents to JSON. 492 | * 493 | * @param {String} key The key to stringify. 494 | * @param {Object} val The value to stringify. 495 | * @return {Object} The stringified function value or the value. 496 | * @private 497 | */ 498 | 499 | Base.prototype._replacer = function (key, val) { 500 | return isFunction (val) ? val.toString() : val; 501 | }; 502 | 503 | /** 504 | * Coerce documents with prototypical `_id` and `_rev` 505 | * values. 506 | * 507 | * @param {Object} doc The document to coerce. 508 | * @return {Object} The coerced document. 509 | * @private 510 | */ 511 | 512 | Base.prototype._meta = function (doc) { 513 | var hasId = !doc._id && doc.id; 514 | var hasRev = !doc._rev && doc.rev; 515 | var proto; 516 | 517 | if (hasId || hasRev) { 518 | proto = function (){}; 519 | doc = extend(new proto(), doc); 520 | proto = proto.prototype; 521 | if (hasId) proto._id = doc.id; 522 | if (hasRev) proto._rev = doc.rev; 523 | } 524 | 525 | return doc; 526 | }; 527 | 528 | /** 529 | * Parse arguments. 530 | * 531 | * @param {Array} args The arguments. 532 | * @param {Integer} [start] The index from which to start reading arguments. 533 | * @param {Boolean} [withBody] Set to `true` if the request body is given as a 534 | * parameter before HTTP query options. 535 | * @param {Boolean} [notDoc] The request body is not a document. 536 | * @return {Promise} A Promise, if no callback is provided, otherwise `null`. 537 | * @private 538 | */ 539 | 540 | Base.prototype._ = function (args, start, withBody, notDoc) { 541 | var self = this, doc, id, rev; 542 | 543 | function request(method, path, options) { 544 | if (!options) options = {}; 545 | return self._request({ 546 | method: method, 547 | path: path || request.p, 548 | query: options.q || request.q, 549 | data: options.b || request.b, 550 | headers: options.h || request.h, 551 | fn: options.f || request.f, 552 | _: options._ || request._ 553 | }); 554 | } 555 | 556 | // [id], [doc], [query], [header], [callback] 557 | args = [].slice.call(args, start || 0); 558 | 559 | request.f = isFunction(args[args.length - 1]) && args.pop(); 560 | request.p = isString(args[0]) && encodeURI(args.shift()); 561 | request.q = args[withBody ? 1 : 0] || {}; 562 | request.h = args[withBody ? 2 : 1] || {}; 563 | 564 | if (withBody) { 565 | doc = request.b = args[0]; 566 | if (!notDoc) { 567 | if (id = request.p || doc._id || doc.id) request.p = id; 568 | if (rev = request.q.rev || doc._rev || doc.rev) request.q.rev = rev; 569 | } 570 | } 571 | 572 | return request; 573 | }; 574 | 575 | /** 576 | * Clerk CouchDB client. 577 | * 578 | * @param {String} uri Fully qualified URI. 579 | * @param {String} [auth] Authentication header value. 580 | * @constructor 581 | * @memberof clerk 582 | * @see {@link http://wiki.apache.org/couchdb/Complete_HTTP_API_Reference|CouchDB Wiki} 583 | */ 584 | 585 | function Client (uri, auth) { 586 | this.uri = uri; 587 | this._db = {}; 588 | this.auth = auth; 589 | }; 590 | 591 | Client.prototype = new Base(); 592 | 593 | /** 594 | * Select database to manipulate. 595 | * 596 | * @param {String} name DB name. 597 | * @return {DB} DB object. 598 | */ 599 | 600 | Client.prototype.db = function (name) { 601 | var db = this._db; 602 | return db[name] || (db[name] = new DB(this, name, this.auth)); 603 | }; 604 | 605 | /** 606 | * List all databases. 607 | * 608 | * @param {Object} [query] HTTP query options. 609 | * @param {Object} [headers] HTTP headers. 610 | * @param {handler} [callback] Callback function. 611 | * @return {Promise} A Promise, if no callback is provided, 612 | * otherwise `null`. 613 | * @see {@link http://wiki.apache.org/couchdb/HttpGetAllDbs|CouchDB Wiki} 614 | */ 615 | 616 | Client.prototype.dbs = function (/* [query], [headers], [callback] */) { 617 | return this._(arguments)("GET", "_all_dbs"); 618 | }; 619 | 620 | /** 621 | * Get UUIDs. 622 | * 623 | * @param {Integer} [count=1] Number of UUIDs to get. 624 | * @param {Object} [query] HTTP query options. 625 | * @param {Object} [headers] HTTP headers. 626 | * @param {handler} [callback] Callback function. 627 | * @return {Promise} A Promise, if no callback is provided, 628 | * otherwise `null`. 629 | * @see {@link http://wiki.apache.org/couchdb/HttpGetUuids|CouchDB Wiki} 630 | */ 631 | 632 | Client.prototype.uuids = function (count /* [query], [headers], [callback] */) { 633 | var request = this._(arguments, +count == count ? 1 : 0); 634 | if (count > 1) request.q.count = count; 635 | return request("GET", "_uuids"); 636 | }; 637 | 638 | /** 639 | * Get server information. 640 | * 641 | * @param {Object} [query] HTTP query options. 642 | * @param {Object} [headers] HTTP headers. 643 | * @param {handler} [callback] Callback function. 644 | * @return {Promise} A Promise, if no callback is provided, 645 | * otherwise `null`. 646 | * @see {@link http://wiki.apache.org/couchdb/HttpGetRoot|CouchDB Wiki} 647 | */ 648 | 649 | Client.prototype.info = function (/* [query], [headers], [callback] */) { 650 | return this._(arguments)("GET"); 651 | }; 652 | 653 | /** 654 | * Get server stats. 655 | * 656 | * @param {Object} [query] HTTP query options. 657 | * @param {Object} [headers] HTTP headers. 658 | * @param {handler} [callback] Callback function. 659 | * @return {Promise} A Promise, if no callback is provided, 660 | * otherwise `null`. 661 | * @see {@link http://wiki.apache.org/couchdb/HttpGetLog|CouchDB Wiki} 662 | */ 663 | 664 | Client.prototype.stats = function (/* [query], [headers], [callback] */) { 665 | return this._(arguments)("GET", "_stats"); 666 | }; 667 | 668 | /** 669 | * Get tail of the server log file. 670 | * 671 | * @param {Object} [query] Query parameters. 672 | * @param {Integer} [query.bytes] Number of bytes to read. 673 | * @param {Integer} [query.offset] Number of bytes from the end of 674 | * log file to start reading. 675 | * @param {Object} [headers] HTTP headers. 676 | * @param {handler} [callback] Callback function. 677 | * @return {Promise} A Promise, if no callback is provided, 678 | * otherwise `null`. 679 | * @see {@link http://wiki.apache.org/couchdb/HttpGetLog|CouchDB Wiki} 680 | */ 681 | 682 | Client.prototype.log = function (/* [query], [headers], [callback] */) { 683 | return this._(arguments)("GET", "_log"); 684 | }; 685 | 686 | /** 687 | * List running tasks. 688 | * 689 | * @param {Object} [query] HTTP query options. 690 | * @param {Object} [headers] HTTP headers. 691 | * @param {handler} [callback] Callback function. 692 | * @return {Promise} A Promise, if no callback is provided, 693 | * otherwise `null`. 694 | * @see {@link http://wiki.apache.org/couchdb/HttpGetActiveTasks|CouchDB Wiki} 695 | */ 696 | 697 | Client.prototype.tasks = function (/* [query], [headers], [callback] */) { 698 | return this._(arguments)("GET", "_active_tasks"); 699 | }; 700 | 701 | /** 702 | * Get or set configuration values. 703 | * 704 | * @param {String} [key] Configuration section or key. 705 | * @param {String} [value] Configuration value. 706 | * @param {Object} [query] HTTP query options. 707 | * @param {Object} [headers] HTTP headers. 708 | * @param {handler} [callback] Callback function. 709 | * @return {Promise} A Promise, if no callback is provided, 710 | * otherwise `null`. 711 | */ 712 | 713 | Client.prototype.config = function (/* [key], [value], [query], [headers], [callback] */) { 714 | var args = [].slice.call(arguments); 715 | var key = isString(args[0]) && args.shift() || ""; 716 | var value = isString(args[0]) && args.shift(); 717 | var method = isString(value) ? "PUT" : "GET"; 718 | return this._(args)(method, "_config/" + key, { b: value }); 719 | }; 720 | 721 | /** 722 | * Replicate databases. 723 | * 724 | * @param {Object} options Options. 725 | * @param {String} options.source Source database URL or local name. 726 | * @param {String} options.target Target database URL or local name. 727 | * @param {Boolean} [options.cancel] Set to `true` to cancel replication. 728 | * @param {Boolean} [options.continuous] Set to `true` for continuous 729 | * replication. 730 | * @param {Boolean} [options.create_target] Set to `true` to create the 731 | * target database. 732 | * @param {String} [options.filter] Filter name for filtered replication. 733 | * Example: "mydesign/myfilter". 734 | * @param {Object} [options.query] Query parameters for filter. 735 | * @param {String[]} [options.doc_ids] Document IDs to replicate. 736 | * @param {String} [options.proxy] Proxy through which to replicate. 737 | * @param {Object} [query] HTTP query options. 738 | * @param {Object} [headers] HTTP headers. 739 | * @param {handler} [callback] Callback function. 740 | * @return {Promise} A Promise, if no callback is provided, 741 | * otherwise `null`. 742 | * @see {@link http://wiki.apache.org/couchdb/Replication|CouchDB Wiki} 743 | */ 744 | 745 | Client.prototype.replicate = function (options /* [query], [headers], [callback] */) { 746 | return this._(arguments, 1)("POST", "_replicate", { b: options }); 747 | }; 748 | 749 | /** 750 | * Methods for CouchDB database. 751 | * 752 | * @param {Client} client Clerk client. 753 | * @param {String} name DB name. 754 | * @param {String} [auth] Authentication header value. 755 | * @constructor 756 | * @memberof clerk 757 | * @return This object for chaining. 758 | */ 759 | 760 | function DB (client, name, auth) { 761 | this.client = client; 762 | this.name = name; 763 | this.uri = client.uri + "/" + encodeURIComponent(name); 764 | this.auth = auth; 765 | }; 766 | 767 | DB.prototype = new Base(); 768 | 769 | /** 770 | * Create database. 771 | * 772 | * @param {Object} [query] HTTP query options. 773 | * @param {Object} [headers] HTTP headers. 774 | * @param {handler} [callback] Callback function. 775 | * @return {Promise} A Promise, if no callback is provided, 776 | * otherwise `null`. 777 | */ 778 | 779 | DB.prototype.create = function (/* [query], [headers], [callback] */) { 780 | return this._(arguments)("PUT"); 781 | }; 782 | 783 | /** 784 | * Destroy database. 785 | * 786 | * @param {Object} [query] HTTP query options. 787 | * @param {Object} [headers] HTTP headers. 788 | * @param {handler} [callback] Callback function. 789 | * @return {Promise} A Promise, if no callback is provided, 790 | * otherwise `null`. 791 | */ 792 | 793 | DB.prototype.destroy = function (/* [query], [headers], [callback] */) { 794 | return this._(arguments)("DELETE"); 795 | }; 796 | 797 | /** 798 | * Get database info. 799 | * 800 | * @param {Object} [query] HTTP query options. 801 | * @param {Object} [headers] HTTP headers. 802 | * @param {handler} [callback] Callback function. 803 | * @return {Promise} A Promise, if no callback is provided, 804 | * otherwise `null`. 805 | */ 806 | 807 | DB.prototype.info = function (/* [query], [headers], callback */) { 808 | return this._(arguments)("GET"); 809 | }; 810 | 811 | /** 812 | * Check if database exists. 813 | * 814 | * @param {Object} [query] HTTP query options. 815 | * @param {Object} [headers] HTTP headers. 816 | * @param {handler} [callback] Callback function. 817 | * @return {Promise} A Promise, if no callback is provided, 818 | * otherwise `null`. 819 | */ 820 | 821 | DB.prototype.exists = function (/* [query], [headers], callback */) { 822 | var request = this._(arguments); 823 | request._ = function (err, body, status, headers, req) { 824 | if (status === 404) err = null; 825 | return [err, status === 200, status, headers, req]; 826 | }; 827 | return request("HEAD"); 828 | }; 829 | 830 | /** 831 | * Fetch document. 832 | * 833 | * Set `rev` in `query`. 834 | * 835 | * @param {String} id Document ID. 836 | * @param {Object} [query] HTTP query options. 837 | * @param {Boolean} [query.revs] Fetch list of revisions. 838 | * @param {Boolean} [query.revs_info] Fetch detailed revision information. 839 | * @param {Object} [headers] HTTP headers. 840 | * @param {handler} [callback] Callback function. 841 | * @return {Promise} A Promise, if no callback is provided, 842 | * otherwise `null`. 843 | * @see {@link http://wiki.apache.org/couchdb/HTTP_Document_API#GET|CouchDB Wiki} 844 | */ 845 | 846 | DB.prototype.get = function (/* [id], [query], [headers], [callback] */) { 847 | return this._(arguments)("GET"); 848 | }; 849 | 850 | /** 851 | * Get document metadata. 852 | * 853 | * @param {String} id Document ID. 854 | * @param {Object} [query] HTTP query options. 855 | * @param {Object} [headers] HTTP headers. 856 | * @param {handler} [callback] Callback function. 857 | * @return {Promise} A Promise, if no callback is provided, 858 | * otherwise `null`. 859 | * @see {@link http://wiki.apache.org/couchdb/HTTP_Document_API#HEAD|CouchDB Wiki} 860 | */ 861 | 862 | DB.prototype.head = function (/* [id], [query], [headers], callback */) { 863 | var self = this; 864 | var request = self._(arguments); 865 | request._ = function (err, body, status, headers, res) { 866 | return [err, err ? null : { 867 | _id: request.p, 868 | _rev: headers.etag && JSON.parse(headers.etag), 869 | contentType: headers["content-type"], 870 | contentLength: headers["content-length"] 871 | }, status, headers, res]; 872 | }; 873 | return request("HEAD"); 874 | }; 875 | 876 | /** 877 | * Post document(s) to database. 878 | * 879 | * If documents have no ID, a document ID will be automatically generated 880 | * on the server. Attachments are not currently supported. 881 | * 882 | * @param {Object|Object[]} doc Document or array of documents. 883 | * @param {String} [doc._id] Document ID. If set, uses given document ID. 884 | * @param {String} [doc._rev] Document revision. If set, allows update to 885 | * existing document. 886 | * @param {Object} [doc._attachments] Attachments. If given, must be a 887 | * map of filenames to attachment properties. 888 | * @param {String} [doc._attachments[filename]] Attachment filename, as 889 | * hash key. 890 | * @param {String} [doc._attachments[filename].contentType] Attachment 891 | * MIME content type. 892 | * @param {String|Object} [doc._attachments[filename].data] Attachment 893 | * data. Will be Base64 encoded. 894 | * @param {Object} [query] HTTP query options. 895 | * @param {Boolean} [query.batch] Allow server to write document in 896 | * batch mode. Documents will not be written to disk immediately, 897 | * increasing the chances of write failure. 898 | * @param {Boolean} [query.all_or_nothing] For batch updating of 899 | * documents, use all-or-nothing semantics. 900 | * @param {Object} [headers] HTTP headers. 901 | * @param {handler} [callback] Callback function. 902 | * @return {Promise} A Promise, if no callback is provided, 903 | * otherwise `null`. 904 | * @see {@link http://wiki.apache.org/couchdb/HTTP_Document_API#POST|CouchDB Wiki} 905 | * @see {@link http://wiki.apache.org/couchdb/HTTP_Bulk_Document_API|CouchDB Wiki} 906 | */ 907 | 908 | DB.prototype.post = function (docs /* [query], [headers], [callback] */) { 909 | var request = this._(arguments, 1); 910 | if (isArray(docs)) { 911 | request.p = "_bulk_docs"; 912 | request.b = extend({ docs: docs }, request.q); 913 | request.q = null 914 | } else { 915 | request.b = docs; 916 | } 917 | return request("POST"); 918 | }; 919 | 920 | /** 921 | * Put document in database. 922 | * 923 | * @param {Object} doc Document data. Requires `_id` and `_rev`. 924 | * @param {String} [options] Options. 925 | * @param {Object} [query] HTTP query options. 926 | * @param {Object} [headers] HTTP headers. 927 | * @param {handler} [callback] Callback function. 928 | * @return {Promise} A Promise, if no callback is provided, 929 | * otherwise `null`. 930 | * @see {@link http://wiki.apache.org/couchdb/HTTP_Document_API#PUT|CouchDB Wiki} 931 | */ 932 | 933 | DB.prototype.put = function (/* [id], [doc], [query], [headers], [callback] */) { 934 | var request = this._(arguments, 0, 1); 935 | // prevent acidentally creating database 936 | if (!request.p) request.p = request.b._id || request.b.id; 937 | if (!request.p) throw new Error("missing id"); 938 | return request("PUT"); 939 | }; 940 | 941 | /** 942 | * Delete document(s). 943 | * 944 | * @param {Object|Object[]} docs Document or array of documents. 945 | * @param {Object} [query] HTTP query options. 946 | * @param {Object} [headers] HTTP headers. 947 | * @param {handler} [callback] Callback function. 948 | * @return {Promise} A Promise, if no callback is provided, 949 | * otherwise `null`. 950 | * @see {@link http://wiki.apache.org/couchdb/HTTP_Document_API#DELETE|CouchDB Wiki} 951 | * @see {@link http://wiki.apache.org/couchdb/HTTP_Bulk_Document_API|CouchDB Wiki} 952 | */ 953 | 954 | DB.prototype.del = function (docs /* [query], [headers], [callback] */) { 955 | if (isArray(docs)) { 956 | var i = 0, len = docs.length, doc; 957 | for (; i < len; i++) { 958 | doc = docs[i], docs[i] = { 959 | _id: doc._id || doc.id, 960 | _rev: doc._rev || doc.rev, 961 | _deleted: true 962 | }; 963 | } 964 | return this.post.apply(this, arguments); 965 | } else { 966 | var request = this._(arguments, 0, 1); 967 | // prevent acidentally deleting database 968 | if (!request.p) throw new Error("missing id"); 969 | return request("DELETE"); 970 | } 971 | }; 972 | 973 | /** 974 | * Copy document. 975 | * 976 | * @param {Object} source Source document. 977 | * @param {String} source.id Source document ID. 978 | * @param {String} [source.rev] Source document revision. 979 | * @param {String} [source._id] Source document ID. Alternate key for 980 | * `source.id`. 981 | * @param {String} [source._rev] Source document revision. Alternate key 982 | * for `source.id`. 983 | * @param {Object} target Target document. 984 | * @param {String} target.id Target document ID. 985 | * @param {String} [target.rev] Target document revision. 986 | * @param {String} [target._id] Target document ID. Alternate key for 987 | * `target.id`. 988 | * @param {String} [target._rev] Target document revision. Alternate key 989 | * for `target.id`. 990 | * @param {Object} [query] HTTP query options. 991 | * @param {Object} [headers] HTTP headers. 992 | * @param {handler} [callback] Callback function. 993 | * @return {Promise} A Promise, if no callback is provided, 994 | * otherwise `null`. 995 | * @see {@link http://wiki.apache.org/couchdb/HTTP_Document_API#COPY|CouchDB Wiki} 996 | */ 997 | 998 | DB.prototype.copy = function (source, target /* [query], [headers], [callback] */) { 999 | var request = this._(arguments, 2); 1000 | var sourcePath = encodeURIComponent(source.id || source._id || source); 1001 | var targetPath = encodeURIComponent(target.id || target._id || target); 1002 | var sourceRev = source.rev || source._rev; 1003 | var targetRev = target.rev || target._rev; 1004 | 1005 | if (sourceRev) request.q.rev = sourceRev; 1006 | if (targetRev) targetPath += "?rev=" + encodeURIComponent(targetRev); 1007 | 1008 | request.h.Destination = targetPath; 1009 | 1010 | return request("COPY", sourcePath); 1011 | }; 1012 | 1013 | /** 1014 | * Query all documents by ID. 1015 | * 1016 | * @param {Object} [query] HTTP query options. 1017 | * @param {JSON} [query.startkey] Start returning results from this 1018 | * document ID. 1019 | * @param {JSON} [query.endkey] Stop returning results at this document 1020 | * ID. 1021 | * @param {Integer} [query.limit] Limit number of results returned. 1022 | * @param {Boolean} [query.descending=false] Lookup results in reverse 1023 | * order by key, returning documents in descending order by key. 1024 | * @param {Integer} [query.skip] Skip this many records before 1025 | * returning results. 1026 | * @param {Boolean} [query.include_docs=false] Include document source for 1027 | * each result. 1028 | * @param {Boolean} [query.include_end=true] Include `query.endkey` 1029 | * in results. 1030 | * @param {Boolean} [query.update_seq=false] Include sequence value 1031 | * of the database corresponding to the view. 1032 | * @param {Object} [headers] HTTP headers. 1033 | * @param {handler} [callback] Callback function. 1034 | * @return {Promise} A Promise, if no callback is provided, 1035 | * otherwise `null`. 1036 | * @see {@link http://wiki.apache.org/couchdb/HTTP_Bulk_Document_API|CouchDB Wiki} 1037 | */ 1038 | 1039 | DB.prototype.all = function (/* [query], [headers], [callback] */) { 1040 | var request = this._(arguments); 1041 | var body = this._viewOptions(request.q); 1042 | return request(body ? "POST" : "GET", "_all_docs", { b: body }); 1043 | }; 1044 | 1045 | /** 1046 | * Query a view. 1047 | * 1048 | * @param {String|Object} view View name (e.g. mydesign/myview) or 1049 | * temporary view definition. Using a temporary view is strongly not 1050 | * recommended for production use. 1051 | * @param {Object} [query] HTTP query options. 1052 | * @param {JSON} [query.key] Key to lookup. 1053 | * @param {JSON} [query.startkey] Start returning results from this key. 1054 | * @param {String} [query.startkey_docid] Start returning results 1055 | * from this document ID. Allows pagination with duplicate keys. 1056 | * @param {JSON} [query.endkey] Stop returning results at this key. 1057 | * @param {String} [query.endkey_docid] Stop returning results at 1058 | * this document ID. Allows pagination with duplicate keys. 1059 | * @param {Integer} [query.limit] Limit number of results returned. 1060 | * @param {Boolean|String} [query.stale] Do not refresh view even if 1061 | * stale. For CouchDB versions `1.1.0` and up, set to `update_after` to 1062 | * update view after results are returned. 1063 | * @param {Boolean} [query.descending=false] Lookup results in reverse 1064 | * order by key, returning documents in descending order by key. 1065 | * @param {Integer} [query.skip] Skip this many records before 1066 | * returning results. 1067 | * @param {Boolean|Integer} [query.group=false] Use the reduce function 1068 | * to group results by key. Set to an integer specify `group_level`. 1069 | * @param {Boolean|Integer} [query.reduce=true] Use the reduce function. 1070 | * @param {Boolean} [query.fetch=false] Include document source for 1071 | * each result. 1072 | * @param {Boolean} [query.include_end=true] Include `query.endkey` 1073 | * in results. 1074 | * @param {Boolean} [query.update_seq=false] Include sequence value 1075 | * of the database corresponding to the view. 1076 | * @param {Object} [headers] HTTP headers. 1077 | * @param {handler} [callback] Callback function. 1078 | * @return {Promise} A Promise, if no callback is provided, 1079 | * otherwise `null`. 1080 | * @see {@link http://wiki.apache.org/couchdb/HTTP_view_API|CouchDB Wiki} 1081 | */ 1082 | 1083 | DB.prototype.find = function (view /* [query], [headers], [callback] */) { 1084 | var request = this._(arguments, 1), path, body; 1085 | 1086 | if (isString(view)) { 1087 | path = view.split("/", 2); 1088 | path = "_design/" + encodeURIComponent(path[0]) + 1089 | "/_view/" + encodeURIComponent(path[1]); 1090 | } else { 1091 | path = "_temp_view"; 1092 | body = view; 1093 | } 1094 | 1095 | body = this._viewOptions(request.q, body); 1096 | return request(body ? "POST" : "GET", path, { b: body }); 1097 | }; 1098 | 1099 | /** 1100 | * Get database changes. 1101 | * 1102 | * The `feed` option determines how the callback is called: 1103 | * 1104 | * - `normal` calls the callback once. 1105 | * - `longpoll` waits for a response, then calls the callback once. 1106 | * - `continuous` calls the callback each time an update is received. 1107 | * Implemented as the `database#follow()` method. 1108 | * 1109 | * @param {Object} [query] HTTP query options. 1110 | * @param {String} [query.feed="normal"] Type of feed. See comments 1111 | * above. 1112 | * @param {String} [query.filter] Filter updates using this filter. 1113 | * @param {Integer} [query.limit] Maximum number of rows to return. 1114 | * @param {Integer} [query.since=0] Start results from this sequence 1115 | * number. 1116 | * @param {Boolean} [query.include_docs=false] Include documents with 1117 | * results. 1118 | * @param {Integer} [query.timeout=1000] Maximum period in milliseconds 1119 | * to wait for a change before sending a response, even if there are no 1120 | * results. 1121 | * @param {Integer} [query.heartbeat=1000] Period in milliseconds after 1122 | * which an empty line is sent. Applicable only to feed types 1123 | * `longpoll` and `continuous`. Overrides `query.timeout` to keep the 1124 | * feed alive indefinitely. 1125 | * @param {Object} [headers] HTTP headers. 1126 | * @param {handler} [callback] Callback function. 1127 | * @return {Promise} A Promise, if no callback is provided, 1128 | * otherwise `null`. 1129 | * @see {@link http://wiki.apache.org/couchdb/HTTP_database_API#Changes|CouchDB Wiki} 1130 | */ 1131 | 1132 | DB.prototype.changes = function (/* [query], [headers], [callback] */) { 1133 | var request = this._(arguments); 1134 | if (request.q.feed != "longpoll") delete request.q.feed; 1135 | return this._changes(request); 1136 | }; 1137 | 1138 | /** 1139 | * Follow database changes. 1140 | * 1141 | * @see `#changes()`. 1142 | */ 1143 | 1144 | DB.prototype.follow = function (/* [query], [headers], callback */) { 1145 | var self = this; 1146 | var request = this._(arguments); 1147 | var fn = request.f; 1148 | 1149 | if (!fn) return this; 1150 | 1151 | request.q.feed = "longpoll"; 1152 | request.f = function (err, body) { 1153 | var args = [].slice.call(arguments); 1154 | var done, i; 1155 | for (i = 0; i < body.length; i++) { 1156 | args[1] = body[i]; 1157 | if (done = fn.apply(self, args) === false || err) break; 1158 | } 1159 | if (!done) self._changes(request); 1160 | }; 1161 | 1162 | return this._changes(request); 1163 | }; 1164 | 1165 | /** 1166 | * Service a changes request. 1167 | * 1168 | * @private 1169 | */ 1170 | 1171 | DB.prototype._changes = function (request) { 1172 | return request("GET", "_changes"); 1173 | }; 1174 | 1175 | /** 1176 | * Update document using server-side handler. 1177 | * 1178 | * @param {String} handler Update handler. Example: mydesign/myhandler 1179 | * @param {String} [id] Document ID. 1180 | * @param {any} data Data. 1181 | * @param {Object} [query] HTTP query options. 1182 | * @param {Object} [headers] Headers. 1183 | * @param {handler} [callback] Callback function. 1184 | * @return {Promise} A Promise, if no callback is provided, 1185 | * otherwise `null`. 1186 | * @see {@link http://wiki.apache.org/couchdb/Document_Update_Handlers|CouchDB Wiki} 1187 | */ 1188 | 1189 | DB.prototype.update = function (handler /* [id], [data], [query], [headers], [callback] */) { 1190 | var request = this._(arguments, 1, 1, 1); 1191 | var path = handler.split("/", 2); 1192 | 1193 | path = "_design/" + encodeURIComponent(path[0]) + 1194 | "/_update/" + encodeURIComponent(path[1]); 1195 | 1196 | if (request.p) path += "/" + request.p; 1197 | 1198 | return request("POST", path); 1199 | }; 1200 | 1201 | /** 1202 | * Download attachment from document. 1203 | * 1204 | * @param {Object|String} docOrId Document or document ID. 1205 | * @param {String} attachmentName Attachment name. 1206 | * @param {Object} [query] HTTP query options. 1207 | * @param {Object} [headers] HTTP headers. 1208 | * @param {handler} [callback] Callback function. 1209 | * @return {Promise} A Promise, if no callback is provided, 1210 | * otherwise `null`. 1211 | */ 1212 | 1213 | DB.prototype.attachment = function (doc, attachmentName /* [query], [headers], [callback] */) { 1214 | var request = this._(arguments, 2); 1215 | var path = encodeURIComponent(doc._id || doc.id || doc) + "/" + 1216 | encodeURIComponent(attachmentName); 1217 | return request("GET", path, options); 1218 | }; 1219 | 1220 | /** 1221 | * Upload attachment to document. 1222 | * 1223 | * Set the `Content-Type` header. 1224 | * 1225 | * @param {Object} [doc] Document. Requires `id`. `rev` can be specified 1226 | * here or in `query`. 1227 | * @param {String} attachmentName Attachment name. 1228 | * @param {Object} data Data. 1229 | * @param {Object} [query] HTTP query options. 1230 | * @param {Object} [headers] HTTP headers. 1231 | * @param {handler} [callback] Callback function. 1232 | * @return {Promise} A Promise, if no callback is provided, 1233 | * otherwise `null`. 1234 | */ 1235 | 1236 | DB.prototype.attach = function (doc, attachmentName, data /* [query], [headers], [callback] */) { 1237 | var request = this._(arguments, 3); 1238 | request.p = encodeURIComponent(doc._id || doc.id) + "/" + 1239 | encodeURIComponent(attachmentName); 1240 | if (!request.q.rev) request.q.rev = doc._rev || doc.rev; 1241 | request.q.body = data; 1242 | return request("PUT", path); 1243 | }; 1244 | 1245 | /** 1246 | * Replicate database. 1247 | * 1248 | * This convenience function sets `options.source` and `options.target` to 1249 | * the selected database name. Either `options.source` or `options.target` 1250 | * must be overridden for a successful replication request. 1251 | * 1252 | * @param {Options} options Options. Accepts all options from 1253 | * `Client.replicate()`. 1254 | * @param {String} [options.source=this.name] Source database URL or 1255 | * local name. Defaults to the selected database name if not given. 1256 | * @param {String} [options.target=this.name] Target database URL or 1257 | * local name. Defaults to the selected database name if not given. 1258 | * @param {Object} [query] HTTP query options. 1259 | * @param {Object} [headers] HTTP headers. 1260 | * @param {handler} [callback] Callback function. 1261 | * @return {Promise} A Promise, if no callback is provided, 1262 | * otherwise `null`. 1263 | */ 1264 | 1265 | DB.prototype.replicate = function (options /* [query], [headers], [callback] */) { 1266 | if (!options.source) options.source = this.name; 1267 | if (!options.target) options.target = this.name; 1268 | return this.client.replicate.apply(this.client, arguments); 1269 | }; 1270 | 1271 | /** 1272 | * Ensure recent changes are committed to disk. 1273 | * 1274 | * @param {Object} [query] HTTP query options. 1275 | * @param {Object} [headers] HTTP headers. 1276 | * @param {handler} [callback] Callback function. 1277 | * @return {Promise} A Promise, if no callback is provided, 1278 | * otherwise `null`. 1279 | */ 1280 | 1281 | DB.prototype.commit = function (/* [query], [headers], [callback] */) { 1282 | return this._(arguments)("POST", "_ensure_full_commit"); 1283 | }; 1284 | 1285 | /** 1286 | * Purge deleted documents from database. 1287 | * 1288 | * @param {Object} revs Map of document IDs to revisions to be purged. 1289 | * @param {Object} [query] HTTP query options. 1290 | * @param {Object} [headers] HTTP headers. 1291 | * @param {handler} [callback] Callback function. 1292 | * @return {Promise} A Promise, if no callback is provided, 1293 | * otherwise `null`. 1294 | */ 1295 | 1296 | DB.prototype.purge = function (revs /* [query], [headers], [callback] */) { 1297 | return this._(arguments, 1)("POST", "_purge", { b: revs }); 1298 | }; 1299 | 1300 | /** 1301 | * Compact database or design. 1302 | * 1303 | * @param {String} [design] Design name if compacting design indexes. 1304 | * @param {Object} [query] HTTP query options. 1305 | * @param {Object} [headers] HTTP headers. 1306 | * @param {handler} [callback] Callback function. 1307 | * @return {Promise} A Promise, if no callback is provided, 1308 | * otherwise `null`. 1309 | * @see {@link http://wiki.apache.org/couchdb/Compaction|CouchDB Wiki} 1310 | */ 1311 | 1312 | DB.prototype.compact = function (/* [design], [query], [headers], [callback] */) { 1313 | var request = this._(arguments); 1314 | return request("POST", "_compact/" + (request.p || "")); 1315 | }; 1316 | 1317 | /** 1318 | * Remove unused views. 1319 | * 1320 | * @param {Object} [query] HTTP query options. 1321 | * @param {Object} [headers] HTTP headers. 1322 | * @param {handler} [callback] Callback function. 1323 | * @return {Promise} A Promise, if no callback is provided, 1324 | * otherwise `null`. 1325 | * @see {@link http://wiki.apache.org/couchdb/Compaction|CouchDB Wiki} 1326 | */ 1327 | 1328 | DB.prototype.vacuum = function (/* [query], [headers], [callback] */) { 1329 | return this._(arguments)("POST", "_view_cleanup"); 1330 | }; 1331 | 1332 | /** 1333 | * Parse view options. 1334 | * 1335 | * @param {Object} query The HTTP query options. 1336 | * @param {Object} body The body payload. 1337 | * @param {handler} [callback] Callback function. 1338 | * @return {Object} The body payload. 1339 | * @private 1340 | */ 1341 | 1342 | DB.prototype._viewOptions = function (q, body) { 1343 | if (q) { 1344 | if (q.key) q.key = JSON.stringify(q.key); 1345 | if (q.startkey) q.startkey = JSON.stringify(q.startkey); 1346 | if (q.endkey) q.endkey = JSON.stringify(q.endkey); 1347 | if (q.stale && q.stale != "update_after") q.stale = "ok"; 1348 | if (q.keys) { 1349 | if (!body) body = {}; 1350 | body.keys = q.keys; 1351 | delete q.keys; 1352 | } 1353 | } 1354 | return body; 1355 | }; 1356 | 1357 | /** 1358 | * Handle a clerk response. 1359 | * 1360 | * @callback handler 1361 | * @param {Error|null} error Error or `null` on success. 1362 | * @param {Object} data Response data. 1363 | * @param {Integer} status Response status code. 1364 | * @param {Object} headers Response headers. 1365 | * @param {superagent.Response} res Superagent response object. 1366 | */ 1367 | 1368 | clerk.Base = Base; 1369 | clerk.Client = Client; 1370 | clerk.DB = DB; 1371 | 1372 | // Export clerk. 1373 | module.exports = clerk; 1374 | 1375 | 1376 | /***/ }, 1377 | /* 1 */ 1378 | /***/ function(module, exports, __webpack_require__) { 1379 | 1380 | /** 1381 | * Module dependencies. 1382 | */ 1383 | 1384 | var Emitter = __webpack_require__(2); 1385 | var reduce = __webpack_require__(3); 1386 | 1387 | /** 1388 | * Root reference for iframes. 1389 | */ 1390 | 1391 | var root = 'undefined' == typeof window 1392 | ? (this || self) 1393 | : window; 1394 | 1395 | /** 1396 | * Noop. 1397 | */ 1398 | 1399 | function noop(){}; 1400 | 1401 | /** 1402 | * Check if `obj` is a host object, 1403 | * we don't want to serialize these :) 1404 | * 1405 | * TODO: future proof, move to compoent land 1406 | * 1407 | * @param {Object} obj 1408 | * @return {Boolean} 1409 | * @api private 1410 | */ 1411 | 1412 | function isHost(obj) { 1413 | var str = {}.toString.call(obj); 1414 | 1415 | switch (str) { 1416 | case '[object File]': 1417 | case '[object Blob]': 1418 | case '[object FormData]': 1419 | return true; 1420 | default: 1421 | return false; 1422 | } 1423 | } 1424 | 1425 | /** 1426 | * Determine XHR. 1427 | */ 1428 | 1429 | request.getXHR = function () { 1430 | if (root.XMLHttpRequest 1431 | && (!root.location || 'file:' != root.location.protocol 1432 | || !root.ActiveXObject)) { 1433 | return new XMLHttpRequest; 1434 | } else { 1435 | try { return new ActiveXObject('Microsoft.XMLHTTP'); } catch(e) {} 1436 | try { return new ActiveXObject('Msxml2.XMLHTTP.6.0'); } catch(e) {} 1437 | try { return new ActiveXObject('Msxml2.XMLHTTP.3.0'); } catch(e) {} 1438 | try { return new ActiveXObject('Msxml2.XMLHTTP'); } catch(e) {} 1439 | } 1440 | return false; 1441 | }; 1442 | 1443 | /** 1444 | * Removes leading and trailing whitespace, added to support IE. 1445 | * 1446 | * @param {String} s 1447 | * @return {String} 1448 | * @api private 1449 | */ 1450 | 1451 | var trim = ''.trim 1452 | ? function(s) { return s.trim(); } 1453 | : function(s) { return s.replace(/(^\s*|\s*$)/g, ''); }; 1454 | 1455 | /** 1456 | * Check if `obj` is an object. 1457 | * 1458 | * @param {Object} obj 1459 | * @return {Boolean} 1460 | * @api private 1461 | */ 1462 | 1463 | function isObject(obj) { 1464 | return obj === Object(obj); 1465 | } 1466 | 1467 | /** 1468 | * Serialize the given `obj`. 1469 | * 1470 | * @param {Object} obj 1471 | * @return {String} 1472 | * @api private 1473 | */ 1474 | 1475 | function serialize(obj) { 1476 | if (!isObject(obj)) return obj; 1477 | var pairs = []; 1478 | for (var key in obj) { 1479 | if (null != obj[key]) { 1480 | pairs.push(encodeURIComponent(key) 1481 | + '=' + encodeURIComponent(obj[key])); 1482 | } 1483 | } 1484 | return pairs.join('&'); 1485 | } 1486 | 1487 | /** 1488 | * Expose serialization method. 1489 | */ 1490 | 1491 | request.serializeObject = serialize; 1492 | 1493 | /** 1494 | * Parse the given x-www-form-urlencoded `str`. 1495 | * 1496 | * @param {String} str 1497 | * @return {Object} 1498 | * @api private 1499 | */ 1500 | 1501 | function parseString(str) { 1502 | var obj = {}; 1503 | var pairs = str.split('&'); 1504 | var parts; 1505 | var pair; 1506 | 1507 | for (var i = 0, len = pairs.length; i < len; ++i) { 1508 | pair = pairs[i]; 1509 | parts = pair.split('='); 1510 | obj[decodeURIComponent(parts[0])] = decodeURIComponent(parts[1]); 1511 | } 1512 | 1513 | return obj; 1514 | } 1515 | 1516 | /** 1517 | * Expose parser. 1518 | */ 1519 | 1520 | request.parseString = parseString; 1521 | 1522 | /** 1523 | * Default MIME type map. 1524 | * 1525 | * superagent.types.xml = 'application/xml'; 1526 | * 1527 | */ 1528 | 1529 | request.types = { 1530 | html: 'text/html', 1531 | json: 'application/json', 1532 | xml: 'application/xml', 1533 | urlencoded: 'application/x-www-form-urlencoded', 1534 | 'form': 'application/x-www-form-urlencoded', 1535 | 'form-data': 'application/x-www-form-urlencoded' 1536 | }; 1537 | 1538 | /** 1539 | * Default serialization map. 1540 | * 1541 | * superagent.serialize['application/xml'] = function(obj){ 1542 | * return 'generated xml here'; 1543 | * }; 1544 | * 1545 | */ 1546 | 1547 | request.serialize = { 1548 | 'application/x-www-form-urlencoded': serialize, 1549 | 'application/json': JSON.stringify 1550 | }; 1551 | 1552 | /** 1553 | * Default parsers. 1554 | * 1555 | * superagent.parse['application/xml'] = function(str){ 1556 | * return { object parsed from str }; 1557 | * }; 1558 | * 1559 | */ 1560 | 1561 | request.parse = { 1562 | 'application/x-www-form-urlencoded': parseString, 1563 | 'application/json': JSON.parse 1564 | }; 1565 | 1566 | /** 1567 | * Parse the given header `str` into 1568 | * an object containing the mapped fields. 1569 | * 1570 | * @param {String} str 1571 | * @return {Object} 1572 | * @api private 1573 | */ 1574 | 1575 | function parseHeader(str) { 1576 | var lines = str.split(/\r?\n/); 1577 | var fields = {}; 1578 | var index; 1579 | var line; 1580 | var field; 1581 | var val; 1582 | 1583 | lines.pop(); // trailing CRLF 1584 | 1585 | for (var i = 0, len = lines.length; i < len; ++i) { 1586 | line = lines[i]; 1587 | index = line.indexOf(':'); 1588 | field = line.slice(0, index).toLowerCase(); 1589 | val = trim(line.slice(index + 1)); 1590 | fields[field] = val; 1591 | } 1592 | 1593 | return fields; 1594 | } 1595 | 1596 | /** 1597 | * Return the mime type for the given `str`. 1598 | * 1599 | * @param {String} str 1600 | * @return {String} 1601 | * @api private 1602 | */ 1603 | 1604 | function type(str){ 1605 | return str.split(/ *; */).shift(); 1606 | }; 1607 | 1608 | /** 1609 | * Return header field parameters. 1610 | * 1611 | * @param {String} str 1612 | * @return {Object} 1613 | * @api private 1614 | */ 1615 | 1616 | function params(str){ 1617 | return reduce(str.split(/ *; */), function(obj, str){ 1618 | var parts = str.split(/ *= */) 1619 | , key = parts.shift() 1620 | , val = parts.shift(); 1621 | 1622 | if (key && val) obj[key] = val; 1623 | return obj; 1624 | }, {}); 1625 | }; 1626 | 1627 | /** 1628 | * Initialize a new `Response` with the given `xhr`. 1629 | * 1630 | * - set flags (.ok, .error, etc) 1631 | * - parse header 1632 | * 1633 | * Examples: 1634 | * 1635 | * Aliasing `superagent` as `request` is nice: 1636 | * 1637 | * request = superagent; 1638 | * 1639 | * We can use the promise-like API, or pass callbacks: 1640 | * 1641 | * request.get('/').end(function(res){}); 1642 | * request.get('/', function(res){}); 1643 | * 1644 | * Sending data can be chained: 1645 | * 1646 | * request 1647 | * .post('/user') 1648 | * .send({ name: 'tj' }) 1649 | * .end(function(res){}); 1650 | * 1651 | * Or passed to `.send()`: 1652 | * 1653 | * request 1654 | * .post('/user') 1655 | * .send({ name: 'tj' }, function(res){}); 1656 | * 1657 | * Or passed to `.post()`: 1658 | * 1659 | * request 1660 | * .post('/user', { name: 'tj' }) 1661 | * .end(function(res){}); 1662 | * 1663 | * Or further reduced to a single call for simple cases: 1664 | * 1665 | * request 1666 | * .post('/user', { name: 'tj' }, function(res){}); 1667 | * 1668 | * @param {XMLHTTPRequest} xhr 1669 | * @param {Object} options 1670 | * @api private 1671 | */ 1672 | 1673 | function Response(req, options) { 1674 | options = options || {}; 1675 | this.req = req; 1676 | this.xhr = this.req.xhr; 1677 | // responseText is accessible only if responseType is '' or 'text' and on older browsers 1678 | this.text = ((this.req.method !='HEAD' && (this.xhr.responseType === '' || this.xhr.responseType === 'text')) || typeof this.xhr.responseType === 'undefined') 1679 | ? this.xhr.responseText 1680 | : null; 1681 | this.statusText = this.req.xhr.statusText; 1682 | this.setStatusProperties(this.xhr.status); 1683 | this.header = this.headers = parseHeader(this.xhr.getAllResponseHeaders()); 1684 | // getAllResponseHeaders sometimes falsely returns "" for CORS requests, but 1685 | // getResponseHeader still works. so we get content-type even if getting 1686 | // other headers fails. 1687 | this.header['content-type'] = this.xhr.getResponseHeader('content-type'); 1688 | this.setHeaderProperties(this.header); 1689 | this.body = this.req.method != 'HEAD' 1690 | ? this.parseBody(this.text ? this.text : this.xhr.response) 1691 | : null; 1692 | } 1693 | 1694 | /** 1695 | * Get case-insensitive `field` value. 1696 | * 1697 | * @param {String} field 1698 | * @return {String} 1699 | * @api public 1700 | */ 1701 | 1702 | Response.prototype.get = function(field){ 1703 | return this.header[field.toLowerCase()]; 1704 | }; 1705 | 1706 | /** 1707 | * Set header related properties: 1708 | * 1709 | * - `.type` the content type without params 1710 | * 1711 | * A response of "Content-Type: text/plain; charset=utf-8" 1712 | * will provide you with a `.type` of "text/plain". 1713 | * 1714 | * @param {Object} header 1715 | * @api private 1716 | */ 1717 | 1718 | Response.prototype.setHeaderProperties = function(header){ 1719 | // content-type 1720 | var ct = this.header['content-type'] || ''; 1721 | this.type = type(ct); 1722 | 1723 | // params 1724 | var obj = params(ct); 1725 | for (var key in obj) this[key] = obj[key]; 1726 | }; 1727 | 1728 | /** 1729 | * Parse the given body `str`. 1730 | * 1731 | * Used for auto-parsing of bodies. Parsers 1732 | * are defined on the `superagent.parse` object. 1733 | * 1734 | * @param {String} str 1735 | * @return {Mixed} 1736 | * @api private 1737 | */ 1738 | 1739 | Response.prototype.parseBody = function(str){ 1740 | var parse = request.parse[this.type]; 1741 | return parse && str && (str.length || str instanceof Object) 1742 | ? parse(str) 1743 | : null; 1744 | }; 1745 | 1746 | /** 1747 | * Set flags such as `.ok` based on `status`. 1748 | * 1749 | * For example a 2xx response will give you a `.ok` of __true__ 1750 | * whereas 5xx will be __false__ and `.error` will be __true__. The 1751 | * `.clientError` and `.serverError` are also available to be more 1752 | * specific, and `.statusType` is the class of error ranging from 1..5 1753 | * sometimes useful for mapping respond colors etc. 1754 | * 1755 | * "sugar" properties are also defined for common cases. Currently providing: 1756 | * 1757 | * - .noContent 1758 | * - .badRequest 1759 | * - .unauthorized 1760 | * - .notAcceptable 1761 | * - .notFound 1762 | * 1763 | * @param {Number} status 1764 | * @api private 1765 | */ 1766 | 1767 | Response.prototype.setStatusProperties = function(status){ 1768 | // handle IE9 bug: http://stackoverflow.com/questions/10046972/msie-returns-status-code-of-1223-for-ajax-request 1769 | if (status === 1223) { 1770 | status = 204; 1771 | } 1772 | 1773 | var type = status / 100 | 0; 1774 | 1775 | // status / class 1776 | this.status = status; 1777 | this.statusType = type; 1778 | 1779 | // basics 1780 | this.info = 1 == type; 1781 | this.ok = 2 == type; 1782 | this.clientError = 4 == type; 1783 | this.serverError = 5 == type; 1784 | this.error = (4 == type || 5 == type) 1785 | ? this.toError() 1786 | : false; 1787 | 1788 | // sugar 1789 | this.accepted = 202 == status; 1790 | this.noContent = 204 == status; 1791 | this.badRequest = 400 == status; 1792 | this.unauthorized = 401 == status; 1793 | this.notAcceptable = 406 == status; 1794 | this.notFound = 404 == status; 1795 | this.forbidden = 403 == status; 1796 | }; 1797 | 1798 | /** 1799 | * Return an `Error` representative of this response. 1800 | * 1801 | * @return {Error} 1802 | * @api public 1803 | */ 1804 | 1805 | Response.prototype.toError = function(){ 1806 | var req = this.req; 1807 | var method = req.method; 1808 | var url = req.url; 1809 | 1810 | var msg = 'cannot ' + method + ' ' + url + ' (' + this.status + ')'; 1811 | var err = new Error(msg); 1812 | err.status = this.status; 1813 | err.method = method; 1814 | err.url = url; 1815 | 1816 | return err; 1817 | }; 1818 | 1819 | /** 1820 | * Expose `Response`. 1821 | */ 1822 | 1823 | request.Response = Response; 1824 | 1825 | /** 1826 | * Initialize a new `Request` with the given `method` and `url`. 1827 | * 1828 | * @param {String} method 1829 | * @param {String} url 1830 | * @api public 1831 | */ 1832 | 1833 | function Request(method, url) { 1834 | var self = this; 1835 | Emitter.call(this); 1836 | this._query = this._query || []; 1837 | this.method = method; 1838 | this.url = url; 1839 | this.header = {}; 1840 | this._header = {}; 1841 | this.on('end', function(){ 1842 | var err = null; 1843 | var res = null; 1844 | 1845 | try { 1846 | res = new Response(self); 1847 | } catch(e) { 1848 | err = new Error('Parser is unable to parse the response'); 1849 | err.parse = true; 1850 | err.original = e; 1851 | return self.callback(err); 1852 | } 1853 | 1854 | self.emit('response', res); 1855 | 1856 | if (err) { 1857 | return self.callback(err, res); 1858 | } 1859 | 1860 | if (res.status >= 200 && res.status < 300) { 1861 | return self.callback(err, res); 1862 | } 1863 | 1864 | var new_err = new Error(res.statusText || 'Unsuccessful HTTP response'); 1865 | new_err.original = err; 1866 | new_err.response = res; 1867 | new_err.status = res.status; 1868 | 1869 | self.callback(err || new_err, res); 1870 | }); 1871 | } 1872 | 1873 | /** 1874 | * Mixin `Emitter`. 1875 | */ 1876 | 1877 | Emitter(Request.prototype); 1878 | 1879 | /** 1880 | * Allow for extension 1881 | */ 1882 | 1883 | Request.prototype.use = function(fn) { 1884 | fn(this); 1885 | return this; 1886 | } 1887 | 1888 | /** 1889 | * Set timeout to `ms`. 1890 | * 1891 | * @param {Number} ms 1892 | * @return {Request} for chaining 1893 | * @api public 1894 | */ 1895 | 1896 | Request.prototype.timeout = function(ms){ 1897 | this._timeout = ms; 1898 | return this; 1899 | }; 1900 | 1901 | /** 1902 | * Clear previous timeout. 1903 | * 1904 | * @return {Request} for chaining 1905 | * @api public 1906 | */ 1907 | 1908 | Request.prototype.clearTimeout = function(){ 1909 | this._timeout = 0; 1910 | clearTimeout(this._timer); 1911 | return this; 1912 | }; 1913 | 1914 | /** 1915 | * Abort the request, and clear potential timeout. 1916 | * 1917 | * @return {Request} 1918 | * @api public 1919 | */ 1920 | 1921 | Request.prototype.abort = function(){ 1922 | if (this.aborted) return; 1923 | this.aborted = true; 1924 | this.xhr.abort(); 1925 | this.clearTimeout(); 1926 | this.emit('abort'); 1927 | return this; 1928 | }; 1929 | 1930 | /** 1931 | * Set header `field` to `val`, or multiple fields with one object. 1932 | * 1933 | * Examples: 1934 | * 1935 | * req.get('/') 1936 | * .set('Accept', 'application/json') 1937 | * .set('X-API-Key', 'foobar') 1938 | * .end(callback); 1939 | * 1940 | * req.get('/') 1941 | * .set({ Accept: 'application/json', 'X-API-Key': 'foobar' }) 1942 | * .end(callback); 1943 | * 1944 | * @param {String|Object} field 1945 | * @param {String} val 1946 | * @return {Request} for chaining 1947 | * @api public 1948 | */ 1949 | 1950 | Request.prototype.set = function(field, val){ 1951 | if (isObject(field)) { 1952 | for (var key in field) { 1953 | this.set(key, field[key]); 1954 | } 1955 | return this; 1956 | } 1957 | this._header[field.toLowerCase()] = val; 1958 | this.header[field] = val; 1959 | return this; 1960 | }; 1961 | 1962 | /** 1963 | * Remove header `field`. 1964 | * 1965 | * Example: 1966 | * 1967 | * req.get('/') 1968 | * .unset('User-Agent') 1969 | * .end(callback); 1970 | * 1971 | * @param {String} field 1972 | * @return {Request} for chaining 1973 | * @api public 1974 | */ 1975 | 1976 | Request.prototype.unset = function(field){ 1977 | delete this._header[field.toLowerCase()]; 1978 | delete this.header[field]; 1979 | return this; 1980 | }; 1981 | 1982 | /** 1983 | * Get case-insensitive header `field` value. 1984 | * 1985 | * @param {String} field 1986 | * @return {String} 1987 | * @api private 1988 | */ 1989 | 1990 | Request.prototype.getHeader = function(field){ 1991 | return this._header[field.toLowerCase()]; 1992 | }; 1993 | 1994 | /** 1995 | * Set Content-Type to `type`, mapping values from `request.types`. 1996 | * 1997 | * Examples: 1998 | * 1999 | * superagent.types.xml = 'application/xml'; 2000 | * 2001 | * request.post('/') 2002 | * .type('xml') 2003 | * .send(xmlstring) 2004 | * .end(callback); 2005 | * 2006 | * request.post('/') 2007 | * .type('application/xml') 2008 | * .send(xmlstring) 2009 | * .end(callback); 2010 | * 2011 | * @param {String} type 2012 | * @return {Request} for chaining 2013 | * @api public 2014 | */ 2015 | 2016 | Request.prototype.type = function(type){ 2017 | this.set('Content-Type', request.types[type] || type); 2018 | return this; 2019 | }; 2020 | 2021 | /** 2022 | * Set Accept to `type`, mapping values from `request.types`. 2023 | * 2024 | * Examples: 2025 | * 2026 | * superagent.types.json = 'application/json'; 2027 | * 2028 | * request.get('/agent') 2029 | * .accept('json') 2030 | * .end(callback); 2031 | * 2032 | * request.get('/agent') 2033 | * .accept('application/json') 2034 | * .end(callback); 2035 | * 2036 | * @param {String} accept 2037 | * @return {Request} for chaining 2038 | * @api public 2039 | */ 2040 | 2041 | Request.prototype.accept = function(type){ 2042 | this.set('Accept', request.types[type] || type); 2043 | return this; 2044 | }; 2045 | 2046 | /** 2047 | * Set Authorization field value with `user` and `pass`. 2048 | * 2049 | * @param {String} user 2050 | * @param {String} pass 2051 | * @return {Request} for chaining 2052 | * @api public 2053 | */ 2054 | 2055 | Request.prototype.auth = function(user, pass){ 2056 | var str = btoa(user + ':' + pass); 2057 | this.set('Authorization', 'Basic ' + str); 2058 | return this; 2059 | }; 2060 | 2061 | /** 2062 | * Add query-string `val`. 2063 | * 2064 | * Examples: 2065 | * 2066 | * request.get('/shoes') 2067 | * .query('size=10') 2068 | * .query({ color: 'blue' }) 2069 | * 2070 | * @param {Object|String} val 2071 | * @return {Request} for chaining 2072 | * @api public 2073 | */ 2074 | 2075 | Request.prototype.query = function(val){ 2076 | if ('string' != typeof val) val = serialize(val); 2077 | if (val) this._query.push(val); 2078 | return this; 2079 | }; 2080 | 2081 | /** 2082 | * Write the field `name` and `val` for "multipart/form-data" 2083 | * request bodies. 2084 | * 2085 | * ``` js 2086 | * request.post('/upload') 2087 | * .field('foo', 'bar') 2088 | * .end(callback); 2089 | * ``` 2090 | * 2091 | * @param {String} name 2092 | * @param {String|Blob|File} val 2093 | * @return {Request} for chaining 2094 | * @api public 2095 | */ 2096 | 2097 | Request.prototype.field = function(name, val){ 2098 | if (!this._formData) this._formData = new root.FormData(); 2099 | this._formData.append(name, val); 2100 | return this; 2101 | }; 2102 | 2103 | /** 2104 | * Queue the given `file` as an attachment to the specified `field`, 2105 | * with optional `filename`. 2106 | * 2107 | * ``` js 2108 | * request.post('/upload') 2109 | * .attach(new Blob(['hey!'], { type: "text/html"})) 2110 | * .end(callback); 2111 | * ``` 2112 | * 2113 | * @param {String} field 2114 | * @param {Blob|File} file 2115 | * @param {String} filename 2116 | * @return {Request} for chaining 2117 | * @api public 2118 | */ 2119 | 2120 | Request.prototype.attach = function(field, file, filename){ 2121 | if (!this._formData) this._formData = new root.FormData(); 2122 | this._formData.append(field, file, filename); 2123 | return this; 2124 | }; 2125 | 2126 | /** 2127 | * Send `data`, defaulting the `.type()` to "json" when 2128 | * an object is given. 2129 | * 2130 | * Examples: 2131 | * 2132 | * // querystring 2133 | * request.get('/search') 2134 | * .end(callback) 2135 | * 2136 | * // multiple data "writes" 2137 | * request.get('/search') 2138 | * .send({ search: 'query' }) 2139 | * .send({ range: '1..5' }) 2140 | * .send({ order: 'desc' }) 2141 | * .end(callback) 2142 | * 2143 | * // manual json 2144 | * request.post('/user') 2145 | * .type('json') 2146 | * .send('{"name":"tj"}) 2147 | * .end(callback) 2148 | * 2149 | * // auto json 2150 | * request.post('/user') 2151 | * .send({ name: 'tj' }) 2152 | * .end(callback) 2153 | * 2154 | * // manual x-www-form-urlencoded 2155 | * request.post('/user') 2156 | * .type('form') 2157 | * .send('name=tj') 2158 | * .end(callback) 2159 | * 2160 | * // auto x-www-form-urlencoded 2161 | * request.post('/user') 2162 | * .type('form') 2163 | * .send({ name: 'tj' }) 2164 | * .end(callback) 2165 | * 2166 | * // defaults to x-www-form-urlencoded 2167 | * request.post('/user') 2168 | * .send('name=tobi') 2169 | * .send('species=ferret') 2170 | * .end(callback) 2171 | * 2172 | * @param {String|Object} data 2173 | * @return {Request} for chaining 2174 | * @api public 2175 | */ 2176 | 2177 | Request.prototype.send = function(data){ 2178 | var obj = isObject(data); 2179 | var type = this.getHeader('Content-Type'); 2180 | 2181 | // merge 2182 | if (obj && isObject(this._data)) { 2183 | for (var key in data) { 2184 | this._data[key] = data[key]; 2185 | } 2186 | } else if ('string' == typeof data) { 2187 | if (!type) this.type('form'); 2188 | type = this.getHeader('Content-Type'); 2189 | if ('application/x-www-form-urlencoded' == type) { 2190 | this._data = this._data 2191 | ? this._data + '&' + data 2192 | : data; 2193 | } else { 2194 | this._data = (this._data || '') + data; 2195 | } 2196 | } else { 2197 | this._data = data; 2198 | } 2199 | 2200 | if (!obj || isHost(data)) return this; 2201 | if (!type) this.type('json'); 2202 | return this; 2203 | }; 2204 | 2205 | /** 2206 | * Invoke the callback with `err` and `res` 2207 | * and handle arity check. 2208 | * 2209 | * @param {Error} err 2210 | * @param {Response} res 2211 | * @api private 2212 | */ 2213 | 2214 | Request.prototype.callback = function(err, res){ 2215 | var fn = this._callback; 2216 | this.clearTimeout(); 2217 | fn(err, res); 2218 | }; 2219 | 2220 | /** 2221 | * Invoke callback with x-domain error. 2222 | * 2223 | * @api private 2224 | */ 2225 | 2226 | Request.prototype.crossDomainError = function(){ 2227 | var err = new Error('Origin is not allowed by Access-Control-Allow-Origin'); 2228 | err.crossDomain = true; 2229 | this.callback(err); 2230 | }; 2231 | 2232 | /** 2233 | * Invoke callback with timeout error. 2234 | * 2235 | * @api private 2236 | */ 2237 | 2238 | Request.prototype.timeoutError = function(){ 2239 | var timeout = this._timeout; 2240 | var err = new Error('timeout of ' + timeout + 'ms exceeded'); 2241 | err.timeout = timeout; 2242 | this.callback(err); 2243 | }; 2244 | 2245 | /** 2246 | * Enable transmission of cookies with x-domain requests. 2247 | * 2248 | * Note that for this to work the origin must not be 2249 | * using "Access-Control-Allow-Origin" with a wildcard, 2250 | * and also must set "Access-Control-Allow-Credentials" 2251 | * to "true". 2252 | * 2253 | * @api public 2254 | */ 2255 | 2256 | Request.prototype.withCredentials = function(){ 2257 | this._withCredentials = true; 2258 | return this; 2259 | }; 2260 | 2261 | /** 2262 | * Initiate request, invoking callback `fn(res)` 2263 | * with an instanceof `Response`. 2264 | * 2265 | * @param {Function} fn 2266 | * @return {Request} for chaining 2267 | * @api public 2268 | */ 2269 | 2270 | Request.prototype.end = function(fn){ 2271 | var self = this; 2272 | var xhr = this.xhr = request.getXHR(); 2273 | var query = this._query.join('&'); 2274 | var timeout = this._timeout; 2275 | var data = this._formData || this._data; 2276 | 2277 | // store callback 2278 | this._callback = fn || noop; 2279 | 2280 | // state change 2281 | xhr.onreadystatechange = function(){ 2282 | if (4 != xhr.readyState) return; 2283 | 2284 | // In IE9, reads to any property (e.g. status) off of an aborted XHR will 2285 | // result in the error "Could not complete the operation due to error c00c023f" 2286 | var status; 2287 | try { status = xhr.status } catch(e) { status = 0; } 2288 | 2289 | if (0 == status) { 2290 | if (self.timedout) return self.timeoutError(); 2291 | if (self.aborted) return; 2292 | return self.crossDomainError(); 2293 | } 2294 | self.emit('end'); 2295 | }; 2296 | 2297 | // progress 2298 | var handleProgress = function(e){ 2299 | if (e.total > 0) { 2300 | e.percent = e.loaded / e.total * 100; 2301 | } 2302 | self.emit('progress', e); 2303 | }; 2304 | if (this.hasListeners('progress')) { 2305 | xhr.onprogress = handleProgress; 2306 | } 2307 | try { 2308 | if (xhr.upload && this.hasListeners('progress')) { 2309 | xhr.upload.onprogress = handleProgress; 2310 | } 2311 | } catch(e) { 2312 | // Accessing xhr.upload fails in IE from a web worker, so just pretend it doesn't exist. 2313 | // Reported here: 2314 | // https://connect.microsoft.com/IE/feedback/details/837245/xmlhttprequest-upload-throws-invalid-argument-when-used-from-web-worker-context 2315 | } 2316 | 2317 | // timeout 2318 | if (timeout && !this._timer) { 2319 | this._timer = setTimeout(function(){ 2320 | self.timedout = true; 2321 | self.abort(); 2322 | }, timeout); 2323 | } 2324 | 2325 | // querystring 2326 | if (query) { 2327 | query = request.serializeObject(query); 2328 | this.url += ~this.url.indexOf('?') 2329 | ? '&' + query 2330 | : '?' + query; 2331 | } 2332 | 2333 | // initiate request 2334 | xhr.open(this.method, this.url, true); 2335 | 2336 | // CORS 2337 | if (this._withCredentials) xhr.withCredentials = true; 2338 | 2339 | // body 2340 | if ('GET' != this.method && 'HEAD' != this.method && 'string' != typeof data && !isHost(data)) { 2341 | // serialize stuff 2342 | var serialize = request.serialize[this.getHeader('Content-Type')]; 2343 | if (serialize) data = serialize(data); 2344 | } 2345 | 2346 | // set header fields 2347 | for (var field in this.header) { 2348 | if (null == this.header[field]) continue; 2349 | xhr.setRequestHeader(field, this.header[field]); 2350 | } 2351 | 2352 | // send stuff 2353 | this.emit('request', this); 2354 | xhr.send(data); 2355 | return this; 2356 | }; 2357 | 2358 | /** 2359 | * Expose `Request`. 2360 | */ 2361 | 2362 | request.Request = Request; 2363 | 2364 | /** 2365 | * Issue a request: 2366 | * 2367 | * Examples: 2368 | * 2369 | * request('GET', '/users').end(callback) 2370 | * request('/users').end(callback) 2371 | * request('/users', callback) 2372 | * 2373 | * @param {String} method 2374 | * @param {String|Function} url or callback 2375 | * @return {Request} 2376 | * @api public 2377 | */ 2378 | 2379 | function request(method, url) { 2380 | // callback 2381 | if ('function' == typeof url) { 2382 | return new Request('GET', method).end(url); 2383 | } 2384 | 2385 | // url first 2386 | if (1 == arguments.length) { 2387 | return new Request('GET', method); 2388 | } 2389 | 2390 | return new Request(method, url); 2391 | } 2392 | 2393 | /** 2394 | * GET `url` with optional callback `fn(res)`. 2395 | * 2396 | * @param {String} url 2397 | * @param {Mixed|Function} data or fn 2398 | * @param {Function} fn 2399 | * @return {Request} 2400 | * @api public 2401 | */ 2402 | 2403 | request.get = function(url, data, fn){ 2404 | var req = request('GET', url); 2405 | if ('function' == typeof data) fn = data, data = null; 2406 | if (data) req.query(data); 2407 | if (fn) req.end(fn); 2408 | return req; 2409 | }; 2410 | 2411 | /** 2412 | * HEAD `url` with optional callback `fn(res)`. 2413 | * 2414 | * @param {String} url 2415 | * @param {Mixed|Function} data or fn 2416 | * @param {Function} fn 2417 | * @return {Request} 2418 | * @api public 2419 | */ 2420 | 2421 | request.head = function(url, data, fn){ 2422 | var req = request('HEAD', url); 2423 | if ('function' == typeof data) fn = data, data = null; 2424 | if (data) req.send(data); 2425 | if (fn) req.end(fn); 2426 | return req; 2427 | }; 2428 | 2429 | /** 2430 | * DELETE `url` with optional callback `fn(res)`. 2431 | * 2432 | * @param {String} url 2433 | * @param {Function} fn 2434 | * @return {Request} 2435 | * @api public 2436 | */ 2437 | 2438 | request.del = function(url, fn){ 2439 | var req = request('DELETE', url); 2440 | if (fn) req.end(fn); 2441 | return req; 2442 | }; 2443 | 2444 | /** 2445 | * PATCH `url` with optional `data` and callback `fn(res)`. 2446 | * 2447 | * @param {String} url 2448 | * @param {Mixed} data 2449 | * @param {Function} fn 2450 | * @return {Request} 2451 | * @api public 2452 | */ 2453 | 2454 | request.patch = function(url, data, fn){ 2455 | var req = request('PATCH', url); 2456 | if ('function' == typeof data) fn = data, data = null; 2457 | if (data) req.send(data); 2458 | if (fn) req.end(fn); 2459 | return req; 2460 | }; 2461 | 2462 | /** 2463 | * POST `url` with optional `data` and callback `fn(res)`. 2464 | * 2465 | * @param {String} url 2466 | * @param {Mixed} data 2467 | * @param {Function} fn 2468 | * @return {Request} 2469 | * @api public 2470 | */ 2471 | 2472 | request.post = function(url, data, fn){ 2473 | var req = request('POST', url); 2474 | if ('function' == typeof data) fn = data, data = null; 2475 | if (data) req.send(data); 2476 | if (fn) req.end(fn); 2477 | return req; 2478 | }; 2479 | 2480 | /** 2481 | * PUT `url` with optional `data` and callback `fn(res)`. 2482 | * 2483 | * @param {String} url 2484 | * @param {Mixed|Function} data or fn 2485 | * @param {Function} fn 2486 | * @return {Request} 2487 | * @api public 2488 | */ 2489 | 2490 | request.put = function(url, data, fn){ 2491 | var req = request('PUT', url); 2492 | if ('function' == typeof data) fn = data, data = null; 2493 | if (data) req.send(data); 2494 | if (fn) req.end(fn); 2495 | return req; 2496 | }; 2497 | 2498 | /** 2499 | * Expose `request`. 2500 | */ 2501 | 2502 | module.exports = request; 2503 | 2504 | 2505 | /***/ }, 2506 | /* 2 */ 2507 | /***/ function(module, exports, __webpack_require__) { 2508 | 2509 | 2510 | /** 2511 | * Expose `Emitter`. 2512 | */ 2513 | 2514 | module.exports = Emitter; 2515 | 2516 | /** 2517 | * Initialize a new `Emitter`. 2518 | * 2519 | * @api public 2520 | */ 2521 | 2522 | function Emitter(obj) { 2523 | if (obj) return mixin(obj); 2524 | }; 2525 | 2526 | /** 2527 | * Mixin the emitter properties. 2528 | * 2529 | * @param {Object} obj 2530 | * @return {Object} 2531 | * @api private 2532 | */ 2533 | 2534 | function mixin(obj) { 2535 | for (var key in Emitter.prototype) { 2536 | obj[key] = Emitter.prototype[key]; 2537 | } 2538 | return obj; 2539 | } 2540 | 2541 | /** 2542 | * Listen on the given `event` with `fn`. 2543 | * 2544 | * @param {String} event 2545 | * @param {Function} fn 2546 | * @return {Emitter} 2547 | * @api public 2548 | */ 2549 | 2550 | Emitter.prototype.on = 2551 | Emitter.prototype.addEventListener = function(event, fn){ 2552 | this._callbacks = this._callbacks || {}; 2553 | (this._callbacks[event] = this._callbacks[event] || []) 2554 | .push(fn); 2555 | return this; 2556 | }; 2557 | 2558 | /** 2559 | * Adds an `event` listener that will be invoked a single 2560 | * time then automatically removed. 2561 | * 2562 | * @param {String} event 2563 | * @param {Function} fn 2564 | * @return {Emitter} 2565 | * @api public 2566 | */ 2567 | 2568 | Emitter.prototype.once = function(event, fn){ 2569 | var self = this; 2570 | this._callbacks = this._callbacks || {}; 2571 | 2572 | function on() { 2573 | self.off(event, on); 2574 | fn.apply(this, arguments); 2575 | } 2576 | 2577 | on.fn = fn; 2578 | this.on(event, on); 2579 | return this; 2580 | }; 2581 | 2582 | /** 2583 | * Remove the given callback for `event` or all 2584 | * registered callbacks. 2585 | * 2586 | * @param {String} event 2587 | * @param {Function} fn 2588 | * @return {Emitter} 2589 | * @api public 2590 | */ 2591 | 2592 | Emitter.prototype.off = 2593 | Emitter.prototype.removeListener = 2594 | Emitter.prototype.removeAllListeners = 2595 | Emitter.prototype.removeEventListener = function(event, fn){ 2596 | this._callbacks = this._callbacks || {}; 2597 | 2598 | // all 2599 | if (0 == arguments.length) { 2600 | this._callbacks = {}; 2601 | return this; 2602 | } 2603 | 2604 | // specific event 2605 | var callbacks = this._callbacks[event]; 2606 | if (!callbacks) return this; 2607 | 2608 | // remove all handlers 2609 | if (1 == arguments.length) { 2610 | delete this._callbacks[event]; 2611 | return this; 2612 | } 2613 | 2614 | // remove specific handler 2615 | var cb; 2616 | for (var i = 0; i < callbacks.length; i++) { 2617 | cb = callbacks[i]; 2618 | if (cb === fn || cb.fn === fn) { 2619 | callbacks.splice(i, 1); 2620 | break; 2621 | } 2622 | } 2623 | return this; 2624 | }; 2625 | 2626 | /** 2627 | * Emit `event` with the given args. 2628 | * 2629 | * @param {String} event 2630 | * @param {Mixed} ... 2631 | * @return {Emitter} 2632 | */ 2633 | 2634 | Emitter.prototype.emit = function(event){ 2635 | this._callbacks = this._callbacks || {}; 2636 | var args = [].slice.call(arguments, 1) 2637 | , callbacks = this._callbacks[event]; 2638 | 2639 | if (callbacks) { 2640 | callbacks = callbacks.slice(0); 2641 | for (var i = 0, len = callbacks.length; i < len; ++i) { 2642 | callbacks[i].apply(this, args); 2643 | } 2644 | } 2645 | 2646 | return this; 2647 | }; 2648 | 2649 | /** 2650 | * Return array of callbacks for `event`. 2651 | * 2652 | * @param {String} event 2653 | * @return {Array} 2654 | * @api public 2655 | */ 2656 | 2657 | Emitter.prototype.listeners = function(event){ 2658 | this._callbacks = this._callbacks || {}; 2659 | return this._callbacks[event] || []; 2660 | }; 2661 | 2662 | /** 2663 | * Check if this emitter has `event` handlers. 2664 | * 2665 | * @param {String} event 2666 | * @return {Boolean} 2667 | * @api public 2668 | */ 2669 | 2670 | Emitter.prototype.hasListeners = function(event){ 2671 | return !! this.listeners(event).length; 2672 | }; 2673 | 2674 | 2675 | /***/ }, 2676 | /* 3 */ 2677 | /***/ function(module, exports, __webpack_require__) { 2678 | 2679 | 2680 | /** 2681 | * Reduce `arr` with `fn`. 2682 | * 2683 | * @param {Array} arr 2684 | * @param {Function} fn 2685 | * @param {Mixed} initial 2686 | * 2687 | * TODO: combatible error handling? 2688 | */ 2689 | 2690 | module.exports = function(arr, fn, initial){ 2691 | var idx = 0; 2692 | var len = arr.length; 2693 | var curr = arguments.length == 3 2694 | ? initial 2695 | : arr[idx++]; 2696 | 2697 | while (idx < len) { 2698 | curr = fn.call(null, curr, arr[idx], ++idx, arr); 2699 | } 2700 | 2701 | return curr; 2702 | }; 2703 | 2704 | /***/ } 2705 | /******/ ]) 2706 | }); 2707 | ; 2708 | //# sourceMappingURL=clerk.js.map -------------------------------------------------------------------------------- /dist/clerk.min.js: -------------------------------------------------------------------------------- 1 | !function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):"object"==typeof exports?exports.clerk=e():t.clerk=e()}(this,function(){return function(t){function e(n){if(r[n])return r[n].exports;var o=r[n]={exports:{},id:n,loaded:!1};return t[n].call(o.exports,o,o.exports,e),o.loaded=!0,o.exports}var r={};return e.m=t,e.c=r,e.p="",e(0)}([function(t,e,r){"use strict";function n(t){return n.make(t)}function o(){}function i(t,e){this.uri=t,this._db={},this.auth=e}function s(t,e,r){this.client=t,this.name=e,this.uri=t.uri+"/"+encodeURIComponent(e),this.auth=r}/*! 2 | 3 | clerk - CouchDB client for node and the browser. 4 | Copyright 2012-2015 Michael Phan-Ba 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | 18 | */ 19 | var a=r(1),u=function(t){for(var e,r,n=1;e=arguments[n++];)for(r in e)t[r]=e[r];return t},p=function(t){return Object.prototype.toString.call(t)},h=function(t){return"[object String]"==p(t)},c=function(t){return"[object Object]"==p(t)},l=function(t){return"[object Array]"==p(t)},f=function(t){return"[object Function]"==p(t)};n.Promise="undefined"!=typeof Promise&&Promise,n.version="0.8.2",n.defaultHost="http://127.0.0.1:5984",n.make=function(t){if(!t)return new i(this.defaultHost);t=n._parseURI(t);var e=/\/*([^\/]+)\/*$/.exec(t.path);e&&(t.path=t.path.substr(0,e.index),e=e[1]&&decodeURIComponent(e[1])),t.auth&&(t.auth="Basic "+n.btoa(t.auth));var r=new n.Client(t.base+t.path,t.auth);return e?r.db(e):r},n.btoa="undefined"!=typeof Buffer?function(t){return new Buffer(t).toString("base64")}:function(t){return btoa(t)},n._parseURI=function(t){var e;return t&&(e=/^(https?:\/\/)(?:([^\/@]+)@)?([^\/]+)(.*)\/*$/.exec(t))?{base:e[1]+e[3].replace(/\/+/g,"/"),path:e[4],auth:e[2]&&decodeURIComponent(e[2])}:{base:t||"",path:""}},o.prototype.request=function(){var t=[].slice.call(arguments),e=f(t[t.length-1])&&t.pop();return this._request({method:t[0],path:t[1],query:t[2],data:t[3],headers:t[4],fn:e})},o.prototype._request=function(t){function e(){var e=t._,n=t.fn;return n&&(t.fn=e?function(){n.apply(r,e.apply(r,arguments)||arguments)}:n),r._do(t)}var r=this;null==t.method&&(t.method="GET"),null==t.headers&&(t.headers={}),null==t.auth&&(t.auth=this.auth),t.path=t.path?"/"+t.path:"",null==t.headers["Content-Type"]&&(t.headers["Content-Type"]="application/json"),null==t.headers.Accept&&(t.headers.Accept="application/json"),this.auth&&null==t.headers.Authorization&&(t.headers.Authorization=this.auth),t.uri=this.uri+t.path,t.body=t.data&&JSON.stringify(t.data,/^\/_design/.test(t.path)&&this._replacer)||"";var o,i;return!t.fn&&n.Promise?(o=new n.Promise(function(e,r){t.fn=function(t,n,o,i,s){t?(t.body=n,t.status=o,t.headers=i,t.res=s,r(t)):(c(n)&&Object.defineProperties&&Object.defineProperties(n,{_status:{value:o},_headers:{value:i},_response:{value:s}}),e(n))}}),i=e(),o.request=i,o.abort=function(){return i.abort(),t.fn(new Error("abort")),o},o):void e()},o.prototype._do=function(t){var e,r,n=this,o=t.fn,i=a(t.method,t.uri);if(t.query){for(e in t.query)c(r=t.query[e])&&(t.query[e]=JSON.stringify(r));i.query(t.query)}return t.headers&&(i.set(t.headers),i.withCredentials&&null!=t.headers.Authorization&&i.withCredentials()),t.body&&i.send(t.body),i.end(function(t,e){var r;if(t||((r=e.body)?r.error?t=n._error(r):r=n._response(r):r=e.text),t&&o){var i=e||{};return o(t,r,i.status,i.header,e)}e.data=r,o&&o(t||null,r,e.status,e.header,e)}),i},o.prototype._response=function(t){var e,r,n=t.rows||t.results||t.uuids||l(t)&&t,o=this._meta,i=0;if(n)for(u(n,t).json=t,e=n.length;e>i;i++)r=n[i]=o(n[i]),r.doc&&(r.doc=o(r.doc));else n=o(t);return n},o.prototype._error=function(t){var e=new Error(t.reason);return e.code=t.error,u(e,t)},o.prototype._replacer=function(t,e){return f(e)?e.toString():e},o.prototype._meta=function(t){var e,r=!t._id&&t.id,n=!t._rev&&t.rev;return(r||n)&&(e=function(){},t=u(new e,t),e=e.prototype,r&&(e._id=t.id),n&&(e._rev=t.rev)),t},o.prototype._=function(t,e,r,n){function o(t,e,r){return r||(r={}),u._request({method:t,path:e||o.p,query:r.q||o.q,data:r.b||o.b,headers:r.h||o.h,fn:r.f||o.f,_:r._||o._})}var i,s,a,u=this;return t=[].slice.call(t,e||0),o.f=f(t[t.length-1])&&t.pop(),o.p=h(t[0])&&encodeURI(t.shift()),o.q=t[r?1:0]||{},o.h=t[r?2:1]||{},r&&(i=o.b=t[0],n||((s=o.p||i._id||i.id)&&(o.p=s),(a=o.q.rev||i._rev||i.rev)&&(o.q.rev=a))),o},i.prototype=new o,i.prototype.db=function(t){var e=this._db;return e[t]||(e[t]=new s(this,t,this.auth))},i.prototype.dbs=function(){return this._(arguments)("GET","_all_dbs")},i.prototype.uuids=function(t){var e=this._(arguments,+t==t?1:0);return t>1&&(e.q.count=t),e("GET","_uuids")},i.prototype.info=function(){return this._(arguments)("GET")},i.prototype.stats=function(){return this._(arguments)("GET","_stats")},i.prototype.log=function(){return this._(arguments)("GET","_log")},i.prototype.tasks=function(){return this._(arguments)("GET","_active_tasks")},i.prototype.config=function(){var t=[].slice.call(arguments),e=h(t[0])&&t.shift()||"",r=h(t[0])&&t.shift(),n=h(r)?"PUT":"GET";return this._(t)(n,"_config/"+e,{b:r})},i.prototype.replicate=function(t){return this._(arguments,1)("POST","_replicate",{b:t})},s.prototype=new o,s.prototype.create=function(){return this._(arguments)("PUT")},s.prototype.destroy=function(){return this._(arguments)("DELETE")},s.prototype.info=function(){return this._(arguments)("GET")},s.prototype.exists=function(){var t=this._(arguments);return t._=function(t,e,r,n,o){return 404===r&&(t=null),[t,200===r,r,n,o]},t("HEAD")},s.prototype.get=function(){return this._(arguments)("GET")},s.prototype.head=function(){var t=this,e=t._(arguments);return e._=function(t,r,n,o,i){return[t,t?null:{_id:e.p,_rev:o.etag&&JSON.parse(o.etag),contentType:o["content-type"],contentLength:o["content-length"]},n,o,i]},e("HEAD")},s.prototype.post=function(t){var e=this._(arguments,1);return l(t)?(e.p="_bulk_docs",e.b=u({docs:t},e.q),e.q=null):e.b=t,e("POST")},s.prototype.put=function(){var t=this._(arguments,0,1);if(t.p||(t.p=t.b._id||t.b.id),!t.p)throw new Error("missing id");return t("PUT")},s.prototype.del=function(t){if(l(t)){for(var e,r=0,n=t.length;n>r;r++)e=t[r],t[r]={_id:e._id||e.id,_rev:e._rev||e.rev,_deleted:!0};return this.post.apply(this,arguments)}var o=this._(arguments,0,1);if(!o.p)throw new Error("missing id");return o("DELETE")},s.prototype.copy=function(t,e){var r=this._(arguments,2),n=encodeURIComponent(t.id||t._id||t),o=encodeURIComponent(e.id||e._id||e),i=t.rev||t._rev,s=e.rev||e._rev;return i&&(r.q.rev=i),s&&(o+="?rev="+encodeURIComponent(s)),r.h.Destination=o,r("COPY",n)},s.prototype.all=function(){var t=this._(arguments),e=this._viewOptions(t.q);return t(e?"POST":"GET","_all_docs",{b:e})},s.prototype.find=function(t){var e,r,n=this._(arguments,1);return h(t)?(e=t.split("/",2),e="_design/"+encodeURIComponent(e[0])+"/_view/"+encodeURIComponent(e[1])):(e="_temp_view",r=t),r=this._viewOptions(n.q,r),n(r?"POST":"GET",e,{b:r})},s.prototype.changes=function(){var t=this._(arguments);return"longpoll"!=t.q.feed&&delete t.q.feed,this._changes(t)},s.prototype.follow=function(){var t=this,e=this._(arguments),r=e.f;return r?(e.q.feed="longpoll",e.f=function(n,o){var i,s,a=[].slice.call(arguments);for(s=0;si;++i)r=o[i],e=r.split("="),n[decodeURIComponent(e[0])]=decodeURIComponent(e[1]);return n}function u(t){var e,r,n,o,i=t.split(/\r?\n/),s={};i.pop();for(var a=0,u=i.length;u>a;++a)r=i[a],e=r.indexOf(":"),n=r.slice(0,e).toLowerCase(),o=m(r.slice(e+1)),s[n]=o;return s}function p(t){return t.split(/ *; */).shift()}function h(t){return y(t.split(/ *; */),function(t,e){var r=e.split(/ *= */),n=r.shift(),o=r.shift();return n&&o&&(t[n]=o),t},{})}function c(t,e){e=e||{},this.req=t,this.xhr=this.req.xhr,this.text="HEAD"!=this.req.method&&(""===this.xhr.responseType||"text"===this.xhr.responseType)||"undefined"==typeof this.xhr.responseType?this.xhr.responseText:null,this.statusText=this.req.xhr.statusText,this.setStatusProperties(this.xhr.status),this.header=this.headers=u(this.xhr.getAllResponseHeaders()),this.header["content-type"]=this.xhr.getResponseHeader("content-type"),this.setHeaderProperties(this.header),this.body="HEAD"!=this.req.method?this.parseBody(this.text?this.text:this.xhr.response):null}function l(t,e){var r=this;d.call(this),this._query=this._query||[],this.method=t,this.url=e,this.header={},this._header={},this.on("end",function(){var t=null,e=null;try{e=new c(r)}catch(n){return t=new Error("Parser is unable to parse the response"),t.parse=!0,t.original=n,r.callback(t)}if(r.emit("response",e),t)return r.callback(t,e);if(e.status>=200&&e.status<300)return r.callback(t,e);var o=new Error(e.statusText||"Unsuccessful HTTP response");o.original=t,o.response=e,o.status=e.status,r.callback(t||o,e)})}function f(t,e){return"function"==typeof e?new l("GET",t).end(e):1==arguments.length?new l("GET",t):new l(t,e)}var d=r(2),y=r(3),_="undefined"==typeof window?this||self:window;f.getXHR=function(){if(!(!_.XMLHttpRequest||_.location&&"file:"==_.location.protocol&&_.ActiveXObject))return new XMLHttpRequest;try{return new ActiveXObject("Microsoft.XMLHTTP")}catch(t){}try{return new ActiveXObject("Msxml2.XMLHTTP.6.0")}catch(t){}try{return new ActiveXObject("Msxml2.XMLHTTP.3.0")}catch(t){}try{return new ActiveXObject("Msxml2.XMLHTTP")}catch(t){}return!1};var m="".trim?function(t){return t.trim()}:function(t){return t.replace(/(^\s*|\s*$)/g,"")};f.serializeObject=s,f.parseString=a,f.types={html:"text/html",json:"application/json",xml:"application/xml",urlencoded:"application/x-www-form-urlencoded",form:"application/x-www-form-urlencoded","form-data":"application/x-www-form-urlencoded"},f.serialize={"application/x-www-form-urlencoded":s,"application/json":JSON.stringify},f.parse={"application/x-www-form-urlencoded":a,"application/json":JSON.parse},c.prototype.get=function(t){return this.header[t.toLowerCase()]},c.prototype.setHeaderProperties=function(t){var e=this.header["content-type"]||"";this.type=p(e);var r=h(e);for(var n in r)this[n]=r[n]},c.prototype.parseBody=function(t){var e=f.parse[this.type];return e&&t&&(t.length||t instanceof Object)?e(t):null},c.prototype.setStatusProperties=function(t){1223===t&&(t=204);var e=t/100|0;this.status=t,this.statusType=e,this.info=1==e,this.ok=2==e,this.clientError=4==e,this.serverError=5==e,this.error=4==e||5==e?this.toError():!1,this.accepted=202==t,this.noContent=204==t,this.badRequest=400==t,this.unauthorized=401==t,this.notAcceptable=406==t,this.notFound=404==t,this.forbidden=403==t},c.prototype.toError=function(){var t=this.req,e=t.method,r=t.url,n="cannot "+e+" "+r+" ("+this.status+")",o=new Error(n);return o.status=this.status,o.method=e,o.url=r,o},f.Response=c,d(l.prototype),l.prototype.use=function(t){return t(this),this},l.prototype.timeout=function(t){return this._timeout=t,this},l.prototype.clearTimeout=function(){return this._timeout=0,clearTimeout(this._timer),this},l.prototype.abort=function(){return this.aborted?void 0:(this.aborted=!0,this.xhr.abort(),this.clearTimeout(),this.emit("abort"),this)},l.prototype.set=function(t,e){if(i(t)){for(var r in t)this.set(r,t[r]);return this}return this._header[t.toLowerCase()]=e,this.header[t]=e,this},l.prototype.unset=function(t){return delete this._header[t.toLowerCase()],delete this.header[t],this},l.prototype.getHeader=function(t){return this._header[t.toLowerCase()]},l.prototype.type=function(t){return this.set("Content-Type",f.types[t]||t),this},l.prototype.accept=function(t){return this.set("Accept",f.types[t]||t),this},l.prototype.auth=function(t,e){var r=btoa(t+":"+e);return this.set("Authorization","Basic "+r),this},l.prototype.query=function(t){return"string"!=typeof t&&(t=s(t)),t&&this._query.push(t),this},l.prototype.field=function(t,e){return this._formData||(this._formData=new _.FormData),this._formData.append(t,e),this},l.prototype.attach=function(t,e,r){return this._formData||(this._formData=new _.FormData),this._formData.append(t,e,r),this},l.prototype.send=function(t){var e=i(t),r=this.getHeader("Content-Type");if(e&&i(this._data))for(var n in t)this._data[n]=t[n];else"string"==typeof t?(r||this.type("form"),r=this.getHeader("Content-Type"),"application/x-www-form-urlencoded"==r?this._data=this._data?this._data+"&"+t:t:this._data=(this._data||"")+t):this._data=t;return!e||o(t)?this:(r||this.type("json"),this)},l.prototype.callback=function(t,e){var r=this._callback;this.clearTimeout(),r(t,e)},l.prototype.crossDomainError=function(){var t=new Error("Origin is not allowed by Access-Control-Allow-Origin");t.crossDomain=!0,this.callback(t)},l.prototype.timeoutError=function(){var t=this._timeout,e=new Error("timeout of "+t+"ms exceeded");e.timeout=t,this.callback(e)},l.prototype.withCredentials=function(){return this._withCredentials=!0,this},l.prototype.end=function(t){var e=this,r=this.xhr=f.getXHR(),i=this._query.join("&"),s=this._timeout,a=this._formData||this._data;this._callback=t||n,r.onreadystatechange=function(){if(4==r.readyState){var t;try{t=r.status}catch(n){t=0}if(0==t){if(e.timedout)return e.timeoutError();if(e.aborted)return;return e.crossDomainError()}e.emit("end")}};var u=function(t){t.total>0&&(t.percent=t.loaded/t.total*100),e.emit("progress",t)};this.hasListeners("progress")&&(r.onprogress=u);try{r.upload&&this.hasListeners("progress")&&(r.upload.onprogress=u)}catch(p){}if(s&&!this._timer&&(this._timer=setTimeout(function(){e.timedout=!0,e.abort()},s)),i&&(i=f.serializeObject(i),this.url+=~this.url.indexOf("?")?"&"+i:"?"+i),r.open(this.method,this.url,!0),this._withCredentials&&(r.withCredentials=!0),"GET"!=this.method&&"HEAD"!=this.method&&"string"!=typeof a&&!o(a)){var h=f.serialize[this.getHeader("Content-Type")];h&&(a=h(a))}for(var c in this.header)null!=this.header[c]&&r.setRequestHeader(c,this.header[c]);return this.emit("request",this),r.send(a),this},f.Request=l,f.get=function(t,e,r){var n=f("GET",t);return"function"==typeof e&&(r=e,e=null),e&&n.query(e),r&&n.end(r),n},f.head=function(t,e,r){var n=f("HEAD",t);return"function"==typeof e&&(r=e,e=null),e&&n.send(e),r&&n.end(r),n},f.del=function(t,e){var r=f("DELETE",t);return e&&r.end(e),r},f.patch=function(t,e,r){var n=f("PATCH",t);return"function"==typeof e&&(r=e,e=null),e&&n.send(e),r&&n.end(r),n},f.post=function(t,e,r){var n=f("POST",t);return"function"==typeof e&&(r=e,e=null),e&&n.send(e),r&&n.end(r),n},f.put=function(t,e,r){var n=f("PUT",t);return"function"==typeof e&&(r=e,e=null),e&&n.send(e),r&&n.end(r),n},t.exports=f},function(t,e,r){function n(t){return t?o(t):void 0}function o(t){for(var e in n.prototype)t[e]=n.prototype[e];return t}t.exports=n,n.prototype.on=n.prototype.addEventListener=function(t,e){return this._callbacks=this._callbacks||{},(this._callbacks[t]=this._callbacks[t]||[]).push(e),this},n.prototype.once=function(t,e){function r(){n.off(t,r),e.apply(this,arguments)}var n=this;return this._callbacks=this._callbacks||{},r.fn=e,this.on(t,r),this},n.prototype.off=n.prototype.removeListener=n.prototype.removeAllListeners=n.prototype.removeEventListener=function(t,e){if(this._callbacks=this._callbacks||{},0==arguments.length)return this._callbacks={},this;var r=this._callbacks[t];if(!r)return this;if(1==arguments.length)return delete this._callbacks[t],this;for(var n,o=0;on;++n)r[n].apply(this,e)}return this},n.prototype.listeners=function(t){return this._callbacks=this._callbacks||{},this._callbacks[t]||[]},n.prototype.hasListeners=function(t){return!!this.listeners(t).length}},function(t,e,r){t.exports=function(t,e,r){for(var n=0,o=t.length,i=3==arguments.length?r:t[n++];o>n;)i=e.call(null,i,t[n],++n,t);return i}}])}); 20 | //# sourceMappingURL=clerk.min.js.map -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | /*! 4 | 5 | clerk - CouchDB client for node and the browser. 6 | Copyright 2012-2016 Michael Phan-Ba 7 | 8 | Licensed under the Apache License, Version 2.0 (the "License"); 9 | you may not use this file except in compliance with the License. 10 | You may obtain a copy of the License at 11 | 12 | http://www.apache.org/licenses/LICENSE-2.0 13 | 14 | Unless required by applicable law or agreed to in writing, software 15 | distributed under the License is distributed on an "AS IS" BASIS, 16 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | See the License for the specific language governing permissions and 18 | limitations under the License. 19 | 20 | */ 21 | 22 | /** 23 | * Module dependencies. 24 | */ 25 | 26 | var clerk = require("./clerk"); 27 | var follow = require("follow"); 28 | 29 | /** 30 | * Node.js compatible follow method, based on `follow` package. 31 | */ 32 | 33 | clerk.follow = function (/* [query], [headers], [callback] */) { 34 | var self = this; 35 | var request = self._(arguments); 36 | var options = request.q; 37 | var feed; 38 | 39 | if (!request.f) return self; 40 | 41 | delete options.feed; 42 | options.db = self.uri; 43 | options.headers = request.h; 44 | feed = new follow.Feed(options); 45 | 46 | feed 47 | .on("change", function (body) { 48 | var stop = request.f.call(this, null, self._response(body), 200, {}, this); 49 | if (stop === false) feed.stop(); 50 | }) 51 | .on("error", function (err) { 52 | request.f.call(this, err, null, 0, {}, this); 53 | }) 54 | .follow(); 55 | 56 | return self; 57 | }; 58 | 59 | /** 60 | * Export clerk. 61 | */ 62 | 63 | module.exports = clerk; 64 | -------------------------------------------------------------------------------- /jsdoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "source": { 3 | "include": [ 4 | "clerk.js", 5 | "package.json", 6 | "README.md" 7 | ] 8 | }, 9 | "plugins": ["plugins/markdown"], 10 | "templates": { 11 | "applicationName": "clerk", 12 | "meta": { 13 | "title": "clerk", 14 | "description": "clerk - CouchDB library for node and the browser", 15 | "keyword": "clerk couchdb" 16 | }, 17 | "default": { 18 | "outputSourceFiles": true 19 | }, 20 | "linenums": true 21 | }, 22 | "opts": { 23 | "destination": "docs" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | /** 4 | * Karma configuration. 5 | */ 6 | 7 | module.exports = function (config) { 8 | config.set({ 9 | 10 | frameworks: ["mocha", "sinon"], 11 | 12 | files: [ 13 | "test/**_test.js" 14 | ], 15 | 16 | preprocessors: { 17 | "test/**_test.js": ["webpack"] 18 | }, 19 | 20 | reporters: ["progress"], 21 | 22 | browsers: ["Chrome"], 23 | 24 | webpack: require("./webpack.config"), 25 | 26 | plugins: [ 27 | "karma-chrome-launcher", 28 | "karma-firefox-launcher", 29 | "karma-mocha", 30 | "karma-sinon", 31 | "karma-webpack" 32 | ] 33 | 34 | }); 35 | }; 36 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "clerk", 3 | "description": "CouchDB library for Node and the browser", 4 | "version": "0.8.3", 5 | "author": { 6 | "name": "Michael Phan-Ba", 7 | "email": "michael@mikepb.com" 8 | }, 9 | "homepage": "https://github.com/mikepb/clerk", 10 | "keywords": [ 11 | "cloudant", 12 | "couchdb", 13 | "data", 14 | "database", 15 | "db", 16 | "json", 17 | "nosql", 18 | "request" 19 | ], 20 | "license": "Apache-2.0", 21 | "repository": { 22 | "type": "git", 23 | "url": "git://github.com/mikepb/clerk.git" 24 | }, 25 | "main": "./index.js", 26 | "browser": "./clerk.js", 27 | "engines": { 28 | "node": ">=0.10.0" 29 | }, 30 | "dependencies": { 31 | "follow": "^0.12.1", 32 | "superagent": "^1.2.0" 33 | }, 34 | "devDependencies": { 35 | "expect.js": "*", 36 | "jsdoc": "^3.3.1", 37 | "karma": "^0.12.31", 38 | "karma-chrome-launcher": "^0.1.12", 39 | "karma-firefox-launcher": "^0.1.6", 40 | "karma-mocha": "^0.1.10", 41 | "karma-sinon": "^1.0.4", 42 | "karma-webpack": "^1.5.1", 43 | "mocha": "^2.2.5", 44 | "node-libs-browser": "^0.5.2", 45 | "sinon": "^1.15.3", 46 | "uglify-js": "^2.4.23", 47 | "webpack": "^1.9.10" 48 | }, 49 | "scripts": { 50 | "test": "mocha --reporter dot", 51 | "dist": "webpack clerk.js dist/clerk.js && webpack --optimize-minimize clerk.js dist/clerk.min.js", 52 | "karma": "karma start --single-run", 53 | "doc": "jsdoc -c jsdoc.json" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /test/base_test.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var expect = require("expect.js"); 4 | var shared = require("./shared"); 5 | 6 | describe("Base", function () { 7 | before(shared.clerkFactory); 8 | 9 | describe("#request()", function () { 10 | 11 | it("should send a request", function (done) { 12 | this.client.request("GET", function (err, body, status) { 13 | if (!err) { 14 | expect(body).to.be.an("object"); 15 | shared.shouldHave2xxStatus(status); 16 | } 17 | done(err); 18 | }); 19 | }); 20 | 21 | if (typeof Promise != "undefined") describe("with promises", function () { 22 | 23 | it("should return a promise", function () { 24 | var promise = this.client.request("GET"); 25 | expect(promise).to.be.a(Promise); 26 | return promise; 27 | }); 28 | 29 | it("should abort a request", function () { 30 | var promise = this.client.request("GET"); 31 | expect(promise).to.be.a(Promise); 32 | return promise.abort().catch(function (err) { 33 | expect(err).to.have.property("message", "abort"); 34 | }); 35 | }); 36 | 37 | }); 38 | 39 | }); 40 | 41 | }); 42 | -------------------------------------------------------------------------------- /test/clerk_test.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var clerk = require("../clerk"); 4 | var expect = require("expect.js"); 5 | var shared = require("./shared"); 6 | 7 | var sinon = require("sinon"); 8 | 9 | describe("clerk", function () { 10 | before(shared.clerkFactory); 11 | 12 | describe("#Promise", function () { 13 | it("should default to the global Promise implementation", function () { 14 | var ThePromise = false; 15 | if (typeof Promise !== "undefined") ThePromise = Promise; 16 | expect(clerk.Promise).to.be(ThePromise); 17 | }); 18 | }); 19 | 20 | it("should delegate to clerk.make()", function () { 21 | sinon.spy(clerk, "make"); 22 | clerk(); 23 | expect(clerk.make.calledOnce).to.be.ok(); 24 | clerk.make.restore(); 25 | }); 26 | 27 | describe("#make", function () { 28 | it("should make client", function () { 29 | var client = clerk.make(); 30 | expect(client).to.be.a(clerk.Client); 31 | expect(client).to.have.property("uri", "http://127.0.0.1:5984"); 32 | }); 33 | 34 | it("should make client with URI", function () { 35 | var client = clerk.make("http://127.0.0.1:5984"); 36 | expect(client).to.be.a(clerk.Client); 37 | expect(client).to.have.property("uri", "http://127.0.0.1:5984"); 38 | }); 39 | 40 | it("should make db with URI", function () { 41 | var db = clerk.make("http://127.0.0.1:5984/test"); 42 | expect(db).to.be.a(clerk.DB); 43 | expect(db).to.have.property("uri", "http://127.0.0.1:5984/test"); 44 | }); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /test/client_test.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var clerk = require("../clerk"); 4 | var expect = require("expect.js"); 5 | var shared = require("./shared"); 6 | 7 | describe("Client", function () { 8 | 9 | before(shared.clerkFactory); 10 | 11 | describe("#db", function () { 12 | it("should return DB object", function () { 13 | expect(this.db).to.be.a(clerk.DB); 14 | var uri = this.baseURL.replace(/^(https?:\/\/)([^@\/]+@)/, "$1") + "/" + 15 | this.dbname; 16 | expect(this.db).to.have.property("uri", uri); 17 | }); 18 | }); 19 | 20 | describe("#dbs", function () { 21 | it("shoud list databases", function (done) { 22 | this.client.dbs(function (err, body, status, headers, res) { 23 | if (!err) { 24 | expect(body).to.be.an("array"); 25 | shared.shouldHave2xxStatus(status); 26 | } 27 | done(err); 28 | }); 29 | }); 30 | }); 31 | 32 | describe("#uuids", function () { 33 | 34 | it("shoud return 1 uuid by default", function (done) { 35 | this.client.uuids(having(1, done)); 36 | }); 37 | 38 | shouldReturnUUIDs(1); 39 | shouldReturnUUIDs(2); 40 | shouldReturnUUIDs(3); 41 | shouldReturnUUIDs(100); 42 | 43 | function having(n, done) { 44 | return function (err, body, status, headers, res) { 45 | if (!err) { 46 | expect(body).to.have.property("uuids"); 47 | expect(body).to.be.an("array"); 48 | expect(body).to.have.length(n); 49 | shared.shouldHave2xxStatus(status); 50 | } 51 | done(err); 52 | }; 53 | } 54 | 55 | function shouldReturnUUIDs(n) { 56 | it("shoud return " + n + " uuid", function (done) { 57 | this.client.uuids(n, having(n, done)); 58 | }); 59 | } 60 | 61 | }); 62 | 63 | describe("#info", function () { 64 | it("shoud return server info", function (done) { 65 | this.client.info(function (err, body, status, headers, res) { 66 | if (!err) { 67 | expect(body).to.have.property("couchdb", "Welcome"); 68 | expect(body).to.have.property("version"); 69 | shared.shouldHave2xxStatus(status); 70 | } 71 | done(err); 72 | }); 73 | }); 74 | }); 75 | 76 | describe("#stats", function () { 77 | it("shoud return server stats", function (done) { 78 | this.client.stats(function (err, body, status, headers, res) { 79 | if (!err) { 80 | expect(body).to.have.property("couchdb"); 81 | expect(body).to.have.property("httpd"); 82 | expect(body).to.have.property("httpd_request_methods"); 83 | expect(body).to.have.property("httpd_status_codes"); 84 | shared.shouldHave2xxStatus(status); 85 | } 86 | done(err); 87 | }); 88 | }); 89 | }); 90 | 91 | describe("#log", function () { 92 | it("shoud return server log lines", function (done) { 93 | this.client.log(function (err, body, status, headers, res) { 94 | if (!err) { 95 | expect(body).to.be.ok(); 96 | shared.shouldHave2xxStatus(status); 97 | } 98 | done(err); 99 | }); 100 | }); 101 | }); 102 | 103 | describe("#tasks", function () { 104 | it("shoud return server running tasks", function (done) { 105 | this.client.tasks(function (err, body, status, headers, res) { 106 | if (!err) { 107 | expect(body).to.be.an("array"); 108 | shared.shouldHave2xxStatus(status); 109 | } 110 | done(err); 111 | }); 112 | }); 113 | }); 114 | 115 | describe("#config", function () { 116 | 117 | it("shoud return server config", function (done) { 118 | this.client.config(function (err, body, status, headers, res) { 119 | if (!err) { 120 | expect(body).to.be.an("object"); 121 | expect(body).to.have.property("couchdb"); 122 | expect(body).to.have.property("daemons"); 123 | expect(body).to.have.property("httpd"); 124 | shared.shouldHave2xxStatus(status); 125 | } 126 | done(err); 127 | }); 128 | }); 129 | 130 | it("shoud return server config object", function (done) { 131 | this.client.config("couchdb", function (err, body, status, headers, res) { 132 | if (!err) { 133 | expect(body).to.be.an("object"); 134 | expect(body).to.have.property("database_dir"); 135 | expect(body).to.have.property("delayed_commits"); 136 | expect(body).to.have.property("max_document_size"); 137 | shared.shouldHave2xxStatus(status); 138 | } 139 | done(err); 140 | }); 141 | }); 142 | 143 | it("shoud return server config value", function (done) { 144 | var self = this; 145 | this.client.config("log/level", function (err, body, status, headers, res) { 146 | if (!err) { 147 | expect(body).to.be.a("string"); 148 | shared.shouldHave2xxStatus(status); 149 | self.value = body; 150 | } 151 | done(err); 152 | }); 153 | }); 154 | 155 | it("shoud set server config value", function (done) { 156 | this.client.config("log/level", this.value, function (err, body, status, headers, res) { 157 | if (!err) { 158 | shared.shouldHave2xxStatus(status); 159 | } 160 | done(err); 161 | }); 162 | }); 163 | 164 | }); 165 | 166 | describe("#replicate", function () { 167 | 168 | before(function () { 169 | this.replica = this.client.db(this.dbname + "-client-replica"); 170 | }); 171 | 172 | before(shared.createDB("db")); 173 | before(shared.createDB("replica")); 174 | after(shared.destroyDB("db")); 175 | after(shared.destroyDB("replica")); 176 | 177 | it("shoud be ok", function (done) { 178 | var options = { 179 | source: this.db.name, 180 | target: this.replica.name 181 | }; 182 | this.client.replicate(options, function (err, body, status, headers, res) { 183 | if (!err) { 184 | shared.shouldBeOk(body); 185 | shared.shouldHave2xxStatus(status); 186 | } 187 | done(err); 188 | }); 189 | }); 190 | 191 | }); 192 | 193 | }); 194 | -------------------------------------------------------------------------------- /test/database_test.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var clerk = require("../clerk"); 4 | var expect = require("expect.js"); 5 | var shared = require("./shared"); 6 | 7 | describe("DB", function () { 8 | before(shared.clerkFactory); 9 | 10 | describe("#create", function () { 11 | it("should create database", shared.createDB("db")); 12 | }); 13 | 14 | describe("#replicate", function () { 15 | 16 | before(function () { 17 | this.replica = this.client.db(this.dbname + "-replica"); 18 | }); 19 | 20 | before(shared.createDB("replica")); 21 | after(shared.destroyDB("replica")); 22 | 23 | it("should be ok", function (done) { 24 | var options = { 25 | target: this.replica.name 26 | }; 27 | this.db.replicate(options, function (err, body, status, headers, res) { 28 | if (!err) { 29 | shared.shouldBeOk(body); 30 | shared.shouldHave2xxStatus(status); 31 | } 32 | done(err); 33 | }); 34 | }); 35 | 36 | }); 37 | 38 | describe("utils", function () { 39 | 40 | describe("#exists", function () { 41 | it("should be true", function (done) { 42 | this.db.exists(function (err, exists, status, headers, res) { 43 | if (!err) { 44 | expect(exists).to.be(true); 45 | shared.shouldHave2xxStatus(status); 46 | } 47 | done(err); 48 | }); 49 | }); 50 | 51 | it("should be false", function (done) { 52 | var db = this.client.db(this.dbname + "-404-" + Date.now()); 53 | db.exists(function (err, exists, status, headers, res) { 54 | if (err) return done(err); 55 | expect(exists).to.be(false); 56 | expect(status).to.be(404); 57 | done(); 58 | }); 59 | }); 60 | }); 61 | 62 | describe("#info", function () { 63 | it("should return database info", function (done) { 64 | var self = this; 65 | this.db.info(function (err, body, status, headers, res) { 66 | if (!err) { 67 | expect(body).to.have.property("db_name", self.dbname); 68 | expect(body).to.have.property("doc_count", 0); 69 | expect(body).to.have.property("doc_del_count", 0); 70 | shared.shouldHave2xxStatus(status); 71 | } 72 | done(err); 73 | }); 74 | }); 75 | }); 76 | 77 | describe("#commit", function () { 78 | it("should be ok", function (done) { 79 | this.db.commit(function (err, body, status, headers, res) { 80 | if (!err) { 81 | shared.shouldBeOk(body); 82 | shared.shouldHave2xxStatus(status); 83 | } 84 | done(err); 85 | }); 86 | }); 87 | }); 88 | 89 | describe("#purge", function () { 90 | it("should be ok", function (done) { 91 | this.db.purge({}, function (err, body, status, headers, res) { 92 | if (!err) { 93 | expect(body).to.have.property("purged"); 94 | shared.shouldHave2xxStatus(status); 95 | } 96 | done(err); 97 | }); 98 | }); 99 | }); 100 | 101 | describe("#compact", function () { 102 | it("should be ok", function (done) { 103 | this.db.compact(function (err, body, status, headers, res) { 104 | if (!err) { 105 | shared.shouldBeOk(body); 106 | shared.shouldHave2xxStatus(status); 107 | } 108 | done(err); 109 | }); 110 | }); 111 | }); 112 | 113 | describe("#vacuum", function () { 114 | it("should be ok", function (done) { 115 | this.db.vacuum(function (err, body, status, headers, res) { 116 | if (!err) { 117 | shared.shouldBeOk(body); 118 | shared.shouldHave2xxStatus(status); 119 | } 120 | done(err); 121 | }); 122 | }); 123 | }); 124 | 125 | }); 126 | 127 | describe("putting documents", function () { 128 | beforeEach(shared.docFactory); 129 | 130 | describe("#post", function () { 131 | it("should store document", function (done) { 132 | this.db.post({}, function (err, body, status, headers, res) { 133 | if (!err) { 134 | shared.shouldBeOk(body); 135 | shouldHaveIdRev(body, body._id, body._rev); 136 | shared.shouldHave2xxStatus(status); 137 | } 138 | done(err); 139 | }); 140 | }); 141 | }); 142 | 143 | describe("#put", function () { 144 | it("should store document", function (done) { 145 | var doc = this.doc; 146 | this.db.put(doc, function (err, body, status, headers, res) { 147 | if (!err) { 148 | shared.shouldBeOk(body); 149 | shouldHaveIdRev(body, doc._id, "1-15f65339921e497348be384867bb940f"); 150 | shared.shouldHave2xxStatus(status); 151 | } 152 | done(err); 153 | }); 154 | }); 155 | }); 156 | 157 | }); 158 | 159 | describe("getting documents", function () { 160 | before(shared.docFactory); 161 | before(putDocument); 162 | 163 | describe("#get", function () { 164 | 165 | it("should return document", function (done) { 166 | var doc = this.doc; 167 | this.db.get(doc._id, function (err, body, status, headers, res) { 168 | if (!err) { 169 | shouldHaveIdRev(body, doc._id, doc._rev); 170 | shouldBeDocument(body, doc); 171 | shared.shouldHave2xxStatus(status); 172 | } 173 | done(err); 174 | }); 175 | }); 176 | 177 | it("should return not found", function (done) { 178 | this.db.get("notfound" + Math.random(), function (err, body, status, headers, res) { 179 | expect(err).to.be.an(Error); 180 | expect(err.status).to.be(404); 181 | expect(status).to.be(404); 182 | done(); 183 | }); 184 | }); 185 | 186 | }); 187 | 188 | describe("#head", function () { 189 | it("should return document metadata", function (done) { 190 | var doc = this.doc; 191 | this.db.head(doc._id, function (err, body, status, headers, res) { 192 | if (!err) { 193 | shouldHaveIdRev(body, doc._id, doc._rev); 194 | shared.shouldHave2xxStatus(status); 195 | } 196 | done(err); 197 | }); 198 | }); 199 | }); 200 | 201 | }); 202 | 203 | describe("updating documents", function () { 204 | beforeEach(shared.docFactory); 205 | beforeEach(putDocument); 206 | 207 | describe("#post", function () { 208 | it("should return document metadata", function (done) { 209 | var doc = this.doc; 210 | this.db.post(doc, function (err, body, status, headers, res) { 211 | if (!err) { 212 | shared.shouldBeOk(body); 213 | shouldHaveIdRev(body, doc._id, "2-47661acbb62a2a63704c803bc0152f2b"); 214 | shared.shouldHave2xxStatus(status); 215 | } 216 | done(err); 217 | }); 218 | }); 219 | }); 220 | 221 | describe("#del", function () { 222 | it("should be ok", function (done) { 223 | var doc = this.doc; 224 | this.db.del(doc, function (err, body, status, headers, res) { 225 | if (!err) { 226 | shared.shouldBeOk(body); 227 | shared.shouldHave2xxStatus(status); 228 | } 229 | done(err); 230 | }); 231 | }); 232 | }); 233 | 234 | describe("#copy", function () { 235 | 236 | it("should copy id to id", function (done) { 237 | var id = shared.randomId(); 238 | shouldCopy.call(this, done, this.doc._id, id, id, "1-"); 239 | }); 240 | 241 | it("should copy doc to id", function (done) { 242 | var id = shared.randomId(); 243 | shouldCopy.call(this, done, this.doc, id, id, "1-"); 244 | }); 245 | 246 | it("should copy doc to doc", function (done) { 247 | var id = shared.randomId(); 248 | var target = { _id: id }; 249 | shouldCopy.call(this, done, this.doc, target, target._id, "1-"); 250 | }); 251 | 252 | it("should copy doc to doc with rev", function (done) { 253 | var id = shared.randomId(); 254 | var target = { _id: id, _rev: this.doc._rev }; 255 | shouldCopy.call(this, done, this.doc, target, target._id, "2-"); 256 | }); 257 | 258 | function shouldCopy(done, source, target, id, rev) { 259 | this.db.copy(source, target, function (err, body, status, headers, res) { 260 | if (!err) { 261 | // CouchDB 1.2 changes the rev on COPY 262 | // https://issues.apache.org/jira/browse/COUCHDB-1485 263 | var proto = body.__proto__ || body; 264 | expect(proto._id).to.be(id); 265 | // shouldHaveIdRev(body, id, rev); 266 | shared.shouldHave2xxStatus(status); 267 | } 268 | done(err); 269 | }); 270 | } 271 | 272 | }); 273 | 274 | }); 275 | 276 | describe("batch", function () { 277 | before(shared.docFactory); 278 | 279 | describe("#post", function () { 280 | it("should be ok", function (done) { 281 | var docs = this.docs; 282 | this.db.post(docs, function (err, body, status, headers, res) { 283 | var i = 0, j, len, item, doc; 284 | if (!err) { 285 | for (len = body.length; i < len; i++) { 286 | item = body[i], doc = docs[i]; 287 | expect(item).to.have.property("id", doc._id); 288 | expect(item).to.have.property("rev"); 289 | doc._rev = item.rev; 290 | } 291 | shared.shouldBeOk(item); 292 | shared.shouldHave2xxStatus(status); 293 | } 294 | done(err); 295 | }); 296 | }); 297 | }); 298 | 299 | describe("#del", function () { 300 | it("should be ok", function (done) { 301 | var docs = this.docs; 302 | this.db.del(docs, function (err, body, status, headers, res) { 303 | var i = 0, 304 | len, item, doc; 305 | if (!err) { 306 | for (len = body.length; i < len; i++) { 307 | item = body[i], doc = docs[i]; 308 | shared.shouldBeOk(item); 309 | shouldHaveIdRev(item, doc._id, item._rev); 310 | } 311 | } 312 | shared.shouldHave2xxStatus(status); 313 | done(err); 314 | }); 315 | }); 316 | }); 317 | 318 | }); 319 | 320 | describe("querying documents", function () { 321 | 322 | describe("#all", function () { 323 | 324 | it("should return metadata", function (done) { 325 | this.db.all(function (err, body, status, headers, res) { 326 | var i = 0, len, item; 327 | if (!err) { 328 | expect(body).to.have.property("rows"); 329 | expect(body).to.have.property("total_rows"); 330 | expect(body.total_rows).greaterThan(0); 331 | expect(body).to.have.property("offset", 0); 332 | for (len = body.length; i < len; i++) { 333 | item = body[i]; 334 | expect(item).to.have.property("id"); 335 | expect(item).to.have.property("key"); 336 | expect(item.value).to.have.property("rev"); 337 | } 338 | shared.shouldHave2xxStatus(status); 339 | } 340 | done(err); 341 | }); 342 | }); 343 | 344 | it("should return documents", function (done) { 345 | this.db.all({ 346 | include_docs: true 347 | }, function (err, body, status, headers, res) { 348 | var i = 0, len, item; 349 | if (!err) { 350 | expect(body).to.have.property("rows"); 351 | expect(body).to.have.property("total_rows"); 352 | expect(body.total_rows).greaterThan(0); 353 | expect(body).to.have.property("offset", 0); 354 | for (len = body.length; i < len; i++) { 355 | item = body[i]; 356 | expect(item).to.have.property("id"); 357 | expect(item).to.have.property("key", item.id); 358 | expect(item.value).to.have.property("rev"); 359 | expect(item.doc).to.have.property("_id"); 360 | expect(item.doc).to.have.property("_rev"); 361 | if (/^clerk-test-/.test(item.id)) { 362 | expect(item.doc).to.have.property("hello"); 363 | } 364 | } 365 | shared.shouldHave2xxStatus(status); 366 | } 367 | done(err); 368 | }); 369 | }); 370 | 371 | }); 372 | 373 | describe("#find", function () { 374 | 375 | }); 376 | 377 | }); 378 | 379 | describe("realtime", function () { 380 | beforeEach(shared.docFactory); 381 | 382 | describe("#changes", function () { 383 | it("should get changes", function (done) { 384 | var db = this.db, 385 | doc = this.doc; 386 | putDocument.call(this, function (err) { 387 | if (err) return done(err); 388 | db.changes(function (err, body, status) { 389 | if (!err) { 390 | expect(body).to.have.property("results"); 391 | expect(body).to.be.an("array"); 392 | expect(body.length).to.be.greaterThan(0); 393 | var changes = body[body.length - 1]; 394 | expect(changes).to.have.property("changes"); 395 | expect(changes.changes).to.be.an("array"); 396 | expect(changes.changes[0]).to.have.property("rev", doc._rev); 397 | shared.shouldHave2xxStatus(status); 398 | } 399 | done(err); 400 | }); 401 | }); 402 | }); 403 | }); 404 | 405 | describe("#follow", function () { 406 | it("should follow changes", function (done) { 407 | var db = this.db; 408 | var docs = this.docs; 409 | 410 | db.follow(function (err, body, status) { 411 | if (err) return done(err); 412 | if (body.id != docs[0]._id) return; 413 | 414 | var doc = docs.shift(); 415 | 416 | expect(body).to.have.property("id", doc._id); 417 | expect(body.changes).to.be.an("array"); 418 | expect(body.changes[0]).to.have.property("rev", doc._rev); 419 | shared.shouldHave2xxStatus(status); 420 | 421 | if (!docs.length) { 422 | done(); 423 | return false; 424 | } 425 | }); 426 | 427 | bulkDocuments.call(this, function (err) { 428 | if (err) return done(err); 429 | }); 430 | }); 431 | }); 432 | 433 | }); 434 | 435 | describe("#update", function () { 436 | 437 | it("should create an update handler", function (done) { 438 | this.db.put("_design/test", require("./design"), done); 439 | }); 440 | 441 | it("should use an update handler", function (done) { 442 | this.db.update("test/test", "myrandomid", {Hello: "World"}, function (err, body) { 443 | if (!err) { 444 | expect(body).to.have.property("_id", "myrandomid"); 445 | expect(body).to.have.property("Hello", "World"); 446 | expect(body).to.have.property("dtcreated"); 447 | expect(body).to.have.property("dtupdated"); 448 | } 449 | done(err); 450 | }); 451 | }); 452 | 453 | it("should not set the path from the request body", function (done) { 454 | this.db.update("test/test", {_id: "foo", Hello: "World"}, function (err) { 455 | expect(err).to.be.an(Error); 456 | expect(err.status).to.be(400); 457 | expect(err.url).to.not.match(/\bfoo\b/) 458 | done(); 459 | }); 460 | }); 461 | 462 | }); 463 | 464 | 465 | describe("#destroy", function () { 466 | 467 | it("should destroy database", function (done) { 468 | this.db.destroy(function (err, body, status, headers, res) { 469 | if (!err) { 470 | shared.shouldBeOk(body); 471 | } 472 | done(err); 473 | }); 474 | }); 475 | 476 | }); 477 | 478 | function putDocument(done) { 479 | var doc = this.doc; 480 | this.db.put(doc, function (err, body, status, headers, res) { 481 | if (!err) { 482 | shouldHaveIdRev(body, doc._id, body._rev); 483 | shared.shouldHave2xxStatus(status); 484 | doc._rev = body._rev; 485 | } 486 | done(err); 487 | }); 488 | } 489 | 490 | function bulkDocuments(done) { 491 | var docs = this.docs; 492 | this.db.post(docs, function (err, body, status, headers, res) { 493 | var i = 0, 494 | j, len, item, doc; 495 | if (!err) { 496 | for (len = body.length; i < len; i++) { 497 | item = body[i], doc = docs[i]; 498 | expect(item).to.have.property("id", doc._id); 499 | expect(item).to.have.property("rev"); 500 | doc._rev = item.rev; 501 | } 502 | shared.shouldBeOk(item); 503 | shared.shouldHave2xxStatus(status); 504 | } 505 | done(err); 506 | }); 507 | } 508 | 509 | function shouldHaveIdRev(body, id, rev) { 510 | expect(body._id).to.be(id); 511 | expect(body._rev).to.be(rev); 512 | } 513 | 514 | function shouldBeDocument(body, doc) { 515 | for (var key in doc) { 516 | expect(body).to.have.property(key, doc[key]); 517 | } 518 | } 519 | 520 | }); 521 | -------------------------------------------------------------------------------- /test/design.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = { 4 | 5 | updates: { 6 | test: function (doc, req) { 7 | var now = new Date().toISOString(); 8 | 9 | doc = { 10 | _id: req.id, 11 | dtcreated: doc && doc.dtcreated || now, 12 | dtupdated: now 13 | } 14 | 15 | var data = JSON.parse(req.body); 16 | Object.keys(data).forEach(function (key) { 17 | if (key in doc) return; 18 | doc[key] = data[key]; 19 | }); 20 | 21 | var res = { 22 | headers: { 23 | "Content-Type": "application/json" 24 | }, 25 | body: toJSON(doc) 26 | }; 27 | 28 | return [doc, res]; 29 | } 30 | } 31 | 32 | }; 33 | -------------------------------------------------------------------------------- /test/shared.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var clerk = require("../clerk"); 4 | var expect = require("expect.js"); 5 | 6 | exports.clerkFactory = function () { 7 | this.baseURL = "http://127.0.0.1:5984"; 8 | this.dbname = "clerk-test-" + (1000 * Math.random() | 0); 9 | this.client = clerk(this.baseURL); 10 | this.db = this.client.db(this.dbname); 11 | }; 12 | 13 | exports.randomId = function () { 14 | return "clerk-test-" + Date.now() + (Math.random() * 1000000 | 0); 15 | }; 16 | 17 | exports.docFactory = function () { 18 | this.doc = { 19 | _id: exports.randomId(), 20 | hello: "world" 21 | }; 22 | this.docs = []; 23 | var i, j; 24 | for (i = 1; i < 10; i++) { 25 | this.docs.push({ 26 | _id: exports.randomId(), 27 | hello: "world" + i 28 | }); 29 | } 30 | }; 31 | 32 | exports.createDB = function (key) { 33 | return function (done) { 34 | this[key].create(function (err, body, status, headers, res) { 35 | if (!err) { 36 | exports.shouldBeOk(body); 37 | } else { 38 | expect(status).to.be(412); // database exists 39 | err = null; 40 | } 41 | done(err); 42 | }); 43 | }; 44 | }; 45 | 46 | exports.destroyDB = function (key) { 47 | return function (done) { 48 | this[key].destroy(done); 49 | }; 50 | }; 51 | 52 | exports.shouldBeOk = function (body) { 53 | expect(body).to.have.property("ok", true); 54 | }; 55 | 56 | exports.shouldHave2xxStatus = function (status) { 57 | expect(status).to.be.within(200, 299); 58 | }; 59 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | /** 4 | * Webpack configuration. 5 | */ 6 | 7 | exports = module.exports = { 8 | output: { 9 | library: "clerk", 10 | libraryTarget: "umd", 11 | sourcePrefix: "" 12 | }, 13 | externals: [{ 14 | "sinon": "sinon", 15 | "es6-promise": "window" 16 | }], 17 | devtool: "source-map", 18 | node: { 19 | Buffer: false 20 | } 21 | }; 22 | --------------------------------------------------------------------------------