├── .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 [](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 |
--------------------------------------------------------------------------------