Comparing version 0.0.4 to 0.0.5
423
cot.js
@@ -26,3 +26,6 @@ /* | ||
var querystring = require('querystring'); | ||
var Q = require('q'); | ||
module.exports = Cot; | ||
var viewQueryKeys = [ | ||
@@ -37,4 +40,2 @@ 'descending', 'endkey', 'endkey_docid', 'group', | ||
module.exports = Cot; | ||
function Cot(opts) { | ||
@@ -53,4 +54,13 @@ this.port = opts.port; | ||
Cot.prototype = { | ||
reqOpts: function (method, path, headers) { | ||
var opts = { | ||
jsonRequest: function(method, path, body) { | ||
var deferred = Q.defer(); | ||
var headers = {}; | ||
headers['accept'] = 'application/json'; | ||
headers['host'] = this.hostHeader; | ||
if (body) { | ||
headers['content-type'] = 'application/json'; | ||
} | ||
var request = this.http.request({ | ||
hostname: this.hostname, | ||
@@ -61,34 +71,28 @@ port: this.port, | ||
method: method, | ||
headers: headers || {} | ||
}; | ||
headers: headers | ||
}); | ||
opts.headers.host = this.hostHeader; | ||
return opts; | ||
}, | ||
processResponse: function (request, next) { | ||
waitForResponse(request, onWaitForResponse); | ||
request.on('error', deferred.reject.bind(deferred)); | ||
function onWaitForResponse(err, response) { | ||
if (err) { next(err); return; } | ||
request.on('response', function(response) { | ||
response.setEncoding('utf8'); | ||
if (response.statusCode >= 300 || response.headers['content-type'] === 'application/json') { | ||
readAllText(response, response.statusCode >= 300 ? 8192 : null, onReadAllText); | ||
} | ||
else { | ||
next(null, response); | ||
} | ||
var buffer = ''; | ||
response.on('data', function(data) { | ||
buffer += data; | ||
}); | ||
function onReadAllText(err, buffer) { | ||
if (err) { next(err); return; } | ||
response.on('error', deferred.reject.bind(deferred)); | ||
response.on('end', function() { | ||
var myResponse = { | ||
statusCode: response.statusCode, | ||
unparsedBody: buffer | ||
}; | ||
response.body = buffer; | ||
if (response.headers['content-type'] === 'application/json') { | ||
try { | ||
response.json = JSON.parse(response.body); | ||
} | ||
catch (err) { | ||
next(err); | ||
myResponse.body = JSON.parse(buffer); | ||
} catch (err) { | ||
deferred.reject(err); | ||
return; | ||
@@ -98,55 +102,16 @@ } | ||
next(null, response); | ||
} | ||
} | ||
}, | ||
GET: function (path, headers, next) { | ||
headers = headers || {}; | ||
if (!headers.accept) { | ||
headers.accept = 'application/json'; | ||
} | ||
var request = this.http.request(this.reqOpts('GET', path, headers)); | ||
this.processResponse(request, next); | ||
}, | ||
DELETE: function (path, headers, next) { | ||
headers = headers || {}; | ||
if (!headers.accept) { | ||
headers.accept = 'application/json'; | ||
} | ||
var request = this.http.request(this.reqOpts('DELETE', path, headers)); | ||
this.processResponse(request, next); | ||
}, | ||
putOrPost: function (which, path, body, headers, next) { | ||
headers = headers || {}; | ||
if (!headers.accept) { | ||
headers.accept = 'application/json'; | ||
} | ||
deferred.resolve(myResponse); | ||
}); | ||
}); | ||
if (typeof(body) === 'object' && !headers['content-type']) { | ||
body = JSON.stringify(body); | ||
headers['content-type'] = 'application/json'; | ||
if (body) { | ||
request.end(JSON.stringify(body)); | ||
} else { | ||
request.end(); | ||
} | ||
else if (typeof(body) === 'string') { | ||
body = new Buffer(body, 'utf8'); | ||
} | ||
var request = this.http.request(this.reqOpts(which, path, headers)); | ||
request.write(body); | ||
this.processResponse(request, next); | ||
return deferred.promise; | ||
}, | ||
PUT: function (path, body, headers, next) { | ||
this.putOrPost('PUT', path, body, headers, next); | ||
}, | ||
POST: function (path, body, headers, next) { | ||
this.putOrPost('POST', path, body, headers, next); | ||
}, | ||
db: function (name) { | ||
db: function(name) { | ||
return new DbHandle(this, name); | ||
@@ -162,35 +127,47 @@ } | ||
DbHandle.prototype = { | ||
docUrl: function (docId) { | ||
if (docId.indexOf('_design/') !== 0) { | ||
docId = encodeURIComponent(docId); | ||
docUrl: function(docId) { | ||
if (typeof docId !== 'string' || docId.length === 0) { | ||
throw new TypeError('doc id must be a non-empty string'); | ||
} | ||
return '/' + this.name + '/' + docId; | ||
return '/' + this.name + '/' + encodeURIComponent(docId); | ||
}, | ||
info: function (next) { | ||
this.cot.GET('/' + this.name, null, function (err, response) { | ||
if (err) { next(err); return; } | ||
next(null, response.json); | ||
info: function() { | ||
return this.cot.jsonRequest('GET', '/' + this.name) | ||
.then(function(response) { | ||
return response.body; | ||
}); | ||
}, | ||
getDoc: function (docId, next) { | ||
this.cot.GET(this.docUrl(docId), null, function (err, response) { | ||
if (err) { next(err); return; } | ||
if (response.statusCode === 404) { next(null, null); return; } | ||
if (response.statusCode !== 200) { next(new Error('error getting doc ' + docId + ': ' + response.body)); return; } | ||
next(null, response.json); | ||
get: function(docId) { | ||
return this.cot.jsonRequest('GET', this.docUrl(docId)) | ||
.then(function(response) { | ||
if (response.statusCode !== 200) { | ||
throw new Error('error getting doc ' + docId + ': ' + response.unparsedBody); | ||
} else { | ||
return response.body; | ||
} | ||
}); | ||
}, | ||
getDocWhere: function (docId, condition, next) { | ||
this.getDoc(docId, function (err, doc) { | ||
if (err) { next(err); return; } | ||
if (doc !== null && condition(doc)) { | ||
next(null, doc); | ||
exists: function(docId) { | ||
return this.cot.jsonRequest('GET', this.docUrl(docId)) | ||
.then(function(response) { | ||
if (response.statusCode === 404) { | ||
return null; | ||
} else if (response.statusCode !== 200) { | ||
throw new Error('error getting doc ' + docId + ': ' + response.unparsedBody); | ||
} else { | ||
return response.body; | ||
} | ||
else { | ||
next(null, null); | ||
}); | ||
}, | ||
put: function(doc) { | ||
return this.cot.jsonRequest('PUT', this.docUrl(doc._id), doc) | ||
.then(function(response) { | ||
if (response.statusCode === 201 || response.statusCode === 409) { | ||
return response.body; | ||
} else { | ||
throw new Error('error putting doc ' + doc._id + ': ' + response.unparsedBody); | ||
} | ||
@@ -200,21 +177,24 @@ }); | ||
putDoc: function (doc, opts, next) { | ||
if (typeof next === 'undefined') { | ||
next = opts; | ||
opts = null; | ||
} | ||
var url = this.docUrl(doc._id); | ||
if (opts && opts.batch) { | ||
url += '?batch=ok'; | ||
} | ||
this.cot.PUT(url, doc, null, function (err, response) { | ||
if (err) { next(err); return; } | ||
if (response.statusCode === 201 || response.statusCode === 202 || (response.statusCode === 409 && opts && opts.conflictOk)) { | ||
next(null, response.json); | ||
post: function(doc) { | ||
return this.cot.jsonRequest('POST', '/' + this.name, doc) | ||
.then(function(response) { | ||
if (response.statusCode === 201) { | ||
return response.body; | ||
} else if (doc._id) { | ||
throw new Error('error posting doc ' + doc._id + ': ' + response.unparsedBody); | ||
} else { | ||
throw new Error('error posting new doc: ' + response.unparsedBody); | ||
} | ||
else { | ||
next(new Error('error putting doc ' + doc._id + ': ' + response.body)); | ||
}); | ||
}, | ||
batch: function(doc) { | ||
return this.cot.jsonRequest('POST', '/' + this.name + '?batch=ok', doc) | ||
.then(function(response) { | ||
if (response.statusCode === 202) { | ||
return response.body; | ||
} else if (doc._id) { | ||
throw new Error('error batch posting doc ' + doc._id + ': ' + response.unparsedBody); | ||
} else { | ||
throw new Error('error batch posting new doc: ' + response.unparsedBody); | ||
} | ||
@@ -224,53 +204,46 @@ }); | ||
updateDoc: function (docId, fn, next) { | ||
update: function(docId, fn) { | ||
var db = this; | ||
tryIt(); | ||
return tryIt(); | ||
function tryIt() { | ||
db.getDoc(docId, onGot); | ||
return db.exists(docId) | ||
.then(function(doc) { | ||
return fn(doc || {_id: docId}); | ||
}) | ||
.then(function(doc) { | ||
return db.put(doc); | ||
}) | ||
.then(function(response) { | ||
if (response.ok) { | ||
return response | ||
} else { | ||
return tryIt(); | ||
} | ||
}); | ||
} | ||
function onGot(err, doc) { | ||
if (err) { next(err); return; } | ||
if (doc === null) { | ||
doc = {_id: docId}; | ||
} | ||
fn(doc, onApplied); | ||
} | ||
function onApplied(err, doc) { | ||
if (err) { next(err); return; } | ||
db.putDoc(doc, {conflictOk: true}, onPut); | ||
} | ||
function onPut(err, response) { | ||
if (err) { next(err); return; } | ||
if (response.ok) { | ||
next(null, response); | ||
} | ||
else { | ||
tryIt(); | ||
} | ||
} | ||
}, | ||
deleteDoc: function (docId, rev, opts, next) { | ||
if (typeof next === 'undefined') { | ||
next = opts; | ||
opts = null; | ||
} | ||
delete: function(docId, rev) { | ||
var url = this.docUrl(docId) + '?rev=' + encodeURIComponent(rev); | ||
this.cot.DELETE(url, null, function (err, response) { | ||
if (err) { next(err); return; } | ||
if (response.statusCode === 200 || (response.statusCode === 409 && opts && opts.conflictOk)) { | ||
next(null, response.json); | ||
return this.cot.jsonRequest('DELETE', url) | ||
.then(function(response) { | ||
if (response.statusCode === 200) { | ||
return response.body; | ||
} else { | ||
throw new Error('error deleting doc ' + docId + ': ' + response.unparsedBody); | ||
} | ||
else { | ||
next(new Error('error deleting doc ' + docId + ': ' + response.body)); | ||
}); | ||
}, | ||
bulk: function(docs) { | ||
var url = '/' + this.name + '/_bulk_docs'; | ||
return this.cot.jsonRequest('POST', url, {docs: docs}) | ||
.then(function(response) { | ||
if (response.statusCode !== 201) { | ||
throw new Error('error posting to _bulk_docs:' + response.unparsedBody); | ||
} else { | ||
return response.body; | ||
} | ||
@@ -280,17 +253,11 @@ }); | ||
viewQuery: function (path, query, next) { | ||
if (typeof next === 'undefined') { | ||
next = query; | ||
query = null; | ||
} | ||
viewQuery: function(path, query) { | ||
query = query || {}; | ||
var url = '/' + this.name + '/' + path; | ||
var q = {}; | ||
viewQueryKeys.forEach(function (key) { | ||
viewQueryKeys.forEach(function(key) { | ||
if (typeof query[key] !== 'undefined') { | ||
if (key === 'startkey_docid' || key === 'endkey_docid') { | ||
q[key] = query[key]; | ||
} | ||
else { | ||
} else { | ||
q[key] = JSON.stringify(query[key]); | ||
@@ -301,69 +268,41 @@ } | ||
this.cot.GET(url + '?' + querystring.stringify(q), null, function (err, response) { | ||
if (err) { next(err); return; } | ||
return this.cot.jsonRequest('GET', url + '?' + querystring.stringify(q)) | ||
.then(function(response) { | ||
if (response.statusCode !== 200) { | ||
next(new Error('error reading view ' + path + ': ' + response.body)); | ||
throw new Error('error reading view ' + path + ': ' + response.unparsedBody); | ||
} else { | ||
return response.body; | ||
} | ||
else { | ||
next(null, response.json); | ||
} | ||
}); | ||
}, | ||
view: function (designName, viewName, query, next) { | ||
this.viewQuery('_design/' + designName + '/_view/' + viewName, query, next); | ||
view: function(designName, viewName, query) { | ||
return this.viewQuery('_design/' + designName + '/_view/' + viewName, query); | ||
}, | ||
allDocs: function (query, next) { | ||
this.viewQuery('_all_docs', query, next); | ||
allDocs: function(query) { | ||
return this.viewQuery('_all_docs', query); | ||
}, | ||
viewKeysQuery: function (path, keys, next) { | ||
viewKeysQuery: function(path, keys) { | ||
var url = '/' + this.name + '/' + path; | ||
this.cot.POST(url, {keys: keys}, null, function (err, response) { | ||
if (err) { next(err); return; } | ||
return this.cot.jsonRequest('POST', url, {keys: keys}) | ||
.then(function(response) { | ||
if (response.statusCode !== 200) { | ||
next(new Error('error reading view ' + path + ': ' + response.body)); | ||
throw new Error('error reading view ' + path + ': ' + response.unparsedBody); | ||
} else { | ||
return response.body; | ||
} | ||
else { | ||
next(null, response.json); | ||
} | ||
}); | ||
}, | ||
viewKeys: function (designName, viewName, keys, next) { | ||
this.viewKeysQuery('_design/' + designName + '/_view/' + viewName, keys, next); | ||
viewKeys: function(designName, viewName, keys) { | ||
return this.viewKeysQuery('_design/' + designName + '/_view/' + viewName, keys); | ||
}, | ||
allDocsKeys: function (keys, next) { | ||
this.viewKeysQuery('_all_docs', keys, next); | ||
allDocsKeys: function(keys) { | ||
return this.viewKeysQuery('_all_docs', keys); | ||
}, | ||
postBulkDocs: function (docs, allOrNothing, next) { | ||
if (typeof next === 'undefined') { | ||
next = allOrNothing; | ||
allOrNothing = false; | ||
} | ||
var url = '/' + this.name + '/_bulk_docs'; | ||
this.cot.POST(url, {docs: docs, all_or_nothing: allOrNothing}, null, function (err, response) { | ||
if (err) { next(err); return; } | ||
if (response.statusCode !== 201) { | ||
next(new Error('error posting to _bulk_docs:' + response.body)); | ||
} | ||
else { | ||
next(null, response.json); | ||
} | ||
}); | ||
}, | ||
changes: function (query, next) { | ||
if (typeof next === 'undefined') { | ||
next = query; | ||
query = null; | ||
} | ||
changes: function(query) { | ||
query = query || {}; | ||
@@ -381,53 +320,11 @@ var q = {}; | ||
this.cot.GET('/' + this.name + '/_changes?' + querystring.stringify(q), null, function (err, response) { | ||
if (err) { next(err); return; } | ||
return this.cot.jsonRequest('GET', '/' + this.name + '/_changes?' + querystring.stringify(q)) | ||
.then(function(response) { | ||
if (response.statusCode !== 200) { | ||
next(new Error('error reading _changes: ' + response.body)); | ||
throw new Error('error reading _changes: ' + response.unparsedBody); | ||
} else { | ||
return response.body; | ||
} | ||
else { | ||
next(null, response.json); | ||
} | ||
}); | ||
} | ||
}; | ||
function waitForResponse(request, next) { | ||
next = once(next); | ||
request.on('error', next); | ||
request.on('response', function (response) { | ||
next(null, response); | ||
}); | ||
request.end(); | ||
} | ||
function readAllText(stream, limit, next) { | ||
next = once(next); | ||
var buffer = ''; | ||
stream.encoding = 'utf8'; | ||
stream.on('data', function (chunk) { | ||
if (!limit || buffer.length < limit) { | ||
buffer += chunk; | ||
} | ||
}); | ||
stream.on('error', next); | ||
stream.on('end', function () { | ||
next(null, buffer); | ||
}); | ||
} | ||
function once(f) { | ||
var called = false; | ||
return function() { | ||
if (!called) { | ||
called = true; | ||
return f.apply(this, arguments); | ||
} | ||
}; | ||
} |
{ | ||
"name": "cot", | ||
"description": "bare-bones couchdb interface for node", | ||
"keywords": ["couch", "couchdb"], | ||
"version": "0.0.4", | ||
"description": "promise-based CouchDB library for node with no surprises (in a good way)", | ||
"keywords": ["couch", "couchdb", "promise", "promises"], | ||
"version": "0.0.5", | ||
"main": "cot", | ||
"dependencies": {}, | ||
"dependencies": { | ||
"q": "0.9.x" | ||
}, | ||
"author": { | ||
@@ -9,0 +11,0 @@ "name": "Will Conant", |
219
README.md
# What is Cot? # | ||
Cot is a rather simple but quite pleasant interface for CouchDB. It doesn't attempt to implement everything, but it covers the important stuff for using couch as an effective database. | ||
Cot is a CouchDB library for nodejs with the following benefits: | ||
- It produces promises using the excellent Q module. | ||
- It has clear method names that map almost directly to CouchDB's HTTP API. | ||
- It elminates redundancies in CouchDB's API when there is a clear advantage. | ||
For instance, `post()` treats conflicts as errors, but `put()` treats | ||
conflicts as a normal state that callers can test for. | ||
- It supports view and `_all_docs` queries. | ||
- It supports regular changes queries and long-poll changes queries. | ||
- It doesn't have any weird ORM-like behavior or caching. Documents are just | ||
plain old javascript objects. Revs don't get updated on documents you pass | ||
in. | ||
- It doesn't implement the whole CouchDB API, but it has a generic method adequate | ||
for interacting with any CouchDB URL that expects JSON as input and produces | ||
JSON as output. | ||
# Installing # | ||
npm install cot | ||
# Examples # | ||
Here's a silly example that creates a new document and then updates it: | ||
var Cot = require('cot'); | ||
var cot = new Cot({port: 5984, hostname: 'localhost'}); | ||
var db = cot.db('my-db'); | ||
var db = new Cot({hostname: 'localhost', port: 5984}).db('my-db'); | ||
db.getDoc('counter', onGet); | ||
var doc = { | ||
title: 'So yeah, Cot is definitely another CouchDB library' | ||
}; | ||
function onGet(err, doc) { | ||
if (err) throw err; | ||
if (doc === null) { | ||
doc = { | ||
_id: 'counter', | ||
count: 0 | ||
}; | ||
} | ||
doc.count += 1; | ||
db.putDoc(doc, {conflictOk: true}, onPut); | ||
db.post(doc) | ||
.then(function(response) { | ||
doc._id = response.id; | ||
doc._rev = response.rev; | ||
doc.update = 'Time to update this document and save it again!'; | ||
return db.post(doc); | ||
}) | ||
.then(function(response) { | ||
// let's print out the rev because that would be cool | ||
console.log(response.rev); | ||
}) | ||
.fail(function(err) { | ||
// if anything goes wrong, we'll end up here | ||
console.log('errors are lame in examples'); | ||
}); | ||
Here's an example that increments a counter and is aware of conflicts: | ||
function incrementCounter(docId) { | ||
return db.get(docId) | ||
.then(function(doc) { | ||
doc.counter += 1; | ||
return db.put(doc); | ||
}) | ||
.then(function(response) { | ||
if (response.ok) { | ||
return response; | ||
} else { | ||
// there was a conflict... try again | ||
return incrementCounter(docId); | ||
} | ||
}) | ||
} | ||
function onPut(err, response) { | ||
if (err) throw err; | ||
if (response.conflict) { | ||
db.getDoc('counter', onGet); | ||
} | ||
else { | ||
console.log('done!'); | ||
} | ||
} | ||
There's actually a utility function for an optimistic update loop: | ||
This pattern is very common in CouchDB, so Cot comes with a quicker way to do it: | ||
db.updateDoc('counter', mutate, onUpdate); | ||
function mutate(doc, next) { | ||
doc.count = (doc.count || 0) + 1; | ||
next(); | ||
function incrementCounter(docId) { | ||
return db.update(docId, function(doc) { | ||
doc.counter += 1; | ||
return doc; | ||
}); | ||
} | ||
function onUpdate(err) { | ||
if (err) throw err; | ||
console.log('done!'); | ||
} | ||
@@ -69,36 +97,99 @@ # API Reference # | ||
## db.info(next(err, info)) ## | ||
Queries database info and calls next when done. | ||
## promise = db.info() ## | ||
## db.getDoc(docId, next(err, doc)) | ||
`GET /<dbName>` | ||
Gets a doc out of the database. If the doc is missing, err will be null and doc will be null. | ||
## db.getDocWhere(docId, condition(doc), next(err, doc)) | ||
## promise = db.get(docId) | ||
Gets a doc out of the database and passes it to condition. If condition returns false, next will be called with doc === null as if the doc did not exist. This is useful for avoiding the easy mistake of writing code that allows access to your entire database: | ||
`GET /<dbName>/<docId>` | ||
db.getDocWhere(query.docId, function(doc) { return doc.isPublic }, onGet); | ||
Missing documents are treated as an error. | ||
## db.putDoc(doc, [opts,] next(err, response)) | ||
Attempts to put doc into database. The doc must have an `_id` field. If you expect to handle update conflicts, send {conflictOk: true} in opts. Otherwise, conflicts will be treated as errors. In any case, the response from couch is passed to next so you can retrieve the new rev or detect a conflict if `conflictOk: true`. Response may be something like: | ||
## promise = db.exists(docId) | ||
{ok: true, rev: '2-whatever'} | ||
`GET /<dbName>/<docId>` | ||
Or, in case of a conflict where `conflictOk: true`: | ||
Returns whole document if docId exists and null if docId is missing | ||
{error: 'conflict'} | ||
## db.updateDoc(doc, fn, next(err, response)) | ||
## promise = db.post(doc) | ||
Reads doc from the database and calls `fn(doc, cb)`. `fn` may directly mutate doc and call `cb` when done. Then updateDoc will attempt to put the modified document back in the database. If there is an update conflict, the process will start over and repeat until success. | ||
`POST /<dbName>` | ||
## db.deleteDoc(docId, rev, [opts,] next) | ||
Creates a new document or updates an existing document. If `doc._id` is undefined, CouchDB will | ||
generate a new ID for you. | ||
Attempts to delete the document with the specified docId and rev. As with putDoc, you may pass `{conflictOk: true}` in opts. | ||
On 201, returns result from CouchDB which looks like: `{"ok":true, "id":"<docId>", "rev":"<docRev>"}` | ||
## db.view(designName, viewName, query, next(err, response)) | ||
All other status codes (including 409, conflict) are treated as errors | ||
## promise = db.put(doc) | ||
`PUT /<dbName>/<doc._id>` | ||
On 409 (conflict) returns result from CouchDB which looks like: `{"error":"conflict"}` | ||
On 201, returns result from CouchDB which looks like: `{"ok":true, "id":"<docId>", "rev":"<docRev>"}` | ||
All other status codes are treated as errors. | ||
## promise = db.batch(doc) | ||
`POST /<dbName>?batch=ok` | ||
Creates or updates a document but doesn't wait for success. Conflicts will not be detected. | ||
On 202, returns result from CouchDB which looks like: `{"ok":true, "id":"<docId>"}` | ||
The rev isn't returned because CouchDB returns before checking for conflicts. If there is a conflict, | ||
the update will be silently lost. | ||
All other status codes are treated as errors. | ||
## promise = db.delete(docId, rev) | ||
`DELETE /<dbName>/<docId>?rev=<rev>` | ||
On 200, returns result from CouchDB which looks like: `{"ok":true, "id":"<docId>", "rev":"<docRev>"}` | ||
All othe status codes are treated as errors. | ||
If you wish to gracefully handle update conflicts while deleting, use `db.put()` on a document with | ||
`_deleted` set to `true`: | ||
doc._deleted = true; | ||
db.put(doc).then(function(response) { | ||
if (!response.ok) { | ||
// there was a conflict | ||
} | ||
}); | ||
## promise = db.update(docId, updateFunction) | ||
Gets the specified document, passes it to `updateFunction`, and then saves the results of `updateFunction` | ||
over the document | ||
The process loops if there is an update conflict. | ||
If `updateFunction` needs to do asynchronous work, it may return a promise. | ||
## promise = db.bulk(arrayOfDocs) | ||
`POST /<dbName>/_bulk_docs` | ||
See CouchDB documentation for more information | ||
## promise = db.view(designName, viewName, query) | ||
`GET /<dbName>/_desgin/<designName>/_view/<viewName>?<properly encoded query>` | ||
Queries a view with the given name in the given design doc. `query` should be an object with any of the following keys: | ||
@@ -124,20 +215,22 @@ | ||
## db.allDocs(query, next(err, response)) | ||
Queries the _all_docs view. `query` supports the same keys as in `db.view`. | ||
## promise = db.allDocs(query) | ||
## db.viewKeys(designName, viewName, keys, next(err, response)) | ||
`GET /<dbName>/_all_docs?<properly encoded query>` | ||
Queries the `_all_docs` view. `query` supports the same keys as in `db.view`. | ||
## promise = db.viewKeys(designName, viewName, keys) | ||
Queries the specified keys from the specified view. Keys should be an array of keys. | ||
## db.allDocsKeys(keys, next(err, response)) | ||
## promise = db.allDocsKeys(keys) | ||
Loads documents with the specified keys. | ||
## db.postBulkDocs(docs, allOrNothing, next(err, response)) | ||
Posts multiple updates to the database in one request. `docs` should be an array of documents. `allOrNothing` should be a boolean. See http://wiki.apache.org/couchdb/HTTP_Bulk_Document_API for more information. | ||
## promise = db.changes(query) | ||
## db.changes(query, next) | ||
Queries the changes feed given the specified query. `query` may contain the following keys: | ||
@@ -144,0 +237,0 @@ |
@@ -24,34 +24,17 @@ var chai = require('chai'); | ||
beforeEach(function(done) { | ||
cot.DELETE('/' + config.dbName, {}, onDelete); | ||
function onDelete(err) { | ||
if (err) { | ||
done(err); | ||
} | ||
else { | ||
cot.PUT('/' + config.dbName, '', {}, onPut); | ||
} | ||
} | ||
function onPut(err) { | ||
if (err) { | ||
done(err); | ||
} | ||
else { | ||
db.putDoc({_id: 'person-1', type: 'person', name: 'Will Conant'}, onPutDoc); | ||
} | ||
} | ||
function onPutDoc(err) { | ||
if (err) { | ||
done(err); | ||
} | ||
else { | ||
db.putDoc({_id: '_design/test', views: { | ||
testView: { | ||
map: 'function(d) { emit(d.name, null) }' | ||
} | ||
}}, done); | ||
} | ||
} | ||
cot.jsonRequest('DELETE', '/' + config.dbName) | ||
.then(function() { | ||
return cot.jsonRequest('PUT', '/' + config.dbName); | ||
}) | ||
.then(function() { | ||
return db.post({_id: 'person-1', type: 'person', name: 'Will Conant'}); | ||
}) | ||
.then(function() { | ||
return db.post({_id: '_design/test', views: { | ||
testView: { | ||
map: 'function(d) { emit(d.name, null) }' | ||
} | ||
}}); | ||
}) | ||
.nodeify(done); | ||
}); | ||
@@ -61,83 +44,95 @@ | ||
it('should return database info', function(done) { | ||
db.info(onInfo); | ||
function onInfo(err, info) { | ||
if (err) { | ||
done(err); | ||
} | ||
else { | ||
expect(info).to.be.a('object'); | ||
expect(info.doc_count).to.equal(2); | ||
done(); | ||
} | ||
} | ||
db.info() | ||
.then(function(info) { | ||
expect(info).to.be.a('object'); | ||
expect(info.doc_count).to.equal(2); | ||
}) | ||
.nodeify(done); | ||
}); | ||
}); | ||
describe('#getDoc', function() { | ||
describe('#get', function() { | ||
it('should return test document from database', function(done) { | ||
db.getDoc('person-1', onGet); | ||
function onGet(err, doc) { | ||
if (err) { | ||
done(err); | ||
} | ||
else { | ||
expect(doc).to.be.a('object'); | ||
expect(doc.name).to.equal('Will Conant'); | ||
done(); | ||
} | ||
} | ||
db.get('person-1') | ||
.then(function(doc) { | ||
expect(doc).to.be.a('object'); | ||
expect(doc.name).to.equal('Will Conant'); | ||
}) | ||
.nodeify(done); | ||
}); | ||
}); | ||
describe('#getDocWhere', function() { | ||
it('should return null when condition does not match', function(done) { | ||
db.getDocWhere('person-1', function(doc) { return doc.type === 'clown' }, onGetDoc) | ||
function onGetDoc(err, doc) { | ||
if (err) { | ||
done(err); | ||
} | ||
else { | ||
expect(doc).to.be.null; | ||
done(); | ||
} | ||
} | ||
describe('#view', function() { | ||
it('should return a single row', function(done) { | ||
db.view('test', 'testView', {}) | ||
.then(function(response) { | ||
expect(response).to.be.object; | ||
expect(response.rows).to.be.array; | ||
expect(response.rows.length).to.equal(1); | ||
expect(response.rows[0].key).to.equal('Will Conant'); | ||
}) | ||
.nodeify(done); | ||
}); | ||
it('should return doc when condition matches', function(done) { | ||
db.getDocWhere('person-1', function(doc) { return doc.type === 'person' }, onGetDoc) | ||
function onGetDoc(err, doc) { | ||
if (err) { | ||
done(err); | ||
} | ||
else { | ||
expect(doc).to.be.a('object'); | ||
expect(doc.name).to.equal('Will Conant'); | ||
done(); | ||
} | ||
} | ||
}); | ||
describe('#put', function() { | ||
it('should treat conflicts as expected', function(done) { | ||
var doc = {_id: 'put-test'}; | ||
db.put(doc) | ||
.then(function(response) { | ||
return db.put(doc); | ||
}) | ||
.then(function(response) { | ||
expect(response.error).to.equal('conflict'); | ||
}) | ||
.nodeify(done); | ||
}); | ||
}); | ||
describe('#view', function() { | ||
it('should return a single row', function(done) { | ||
db.view('test', 'testView', {}, onView); | ||
function onView(err, response) { | ||
if (err) { | ||
done(err) | ||
} | ||
else { | ||
expect(response).to.be.object; | ||
expect(response.rows).to.be.array; | ||
expect(response.rows.length).to.equal(1); | ||
expect(response.rows[0].key).to.equal('Will Conant'); | ||
done(); | ||
} | ||
} | ||
describe('#post', function() { | ||
it('should treat conflicts as errors', function(done) { | ||
var doc = {_id: 'post-test'}; | ||
db.post(doc) | ||
.then(function(response) { | ||
return db.post(doc); | ||
}) | ||
.then(function(response) { | ||
done(new Error('should not have resolved')); | ||
}) | ||
.fail(function() { | ||
done(); | ||
}) | ||
.done(); | ||
}); | ||
}); | ||
describe('#batch', function() { | ||
it('should ignore conflicts', function(done) { | ||
var doc = {_id: 'batch-test'}; | ||
var origRev; | ||
db.post(doc) | ||
.then(function(response) { | ||
origRev = response.rev; | ||
return db.batch(doc); | ||
}) | ||
.delay(100) | ||
.then(function(response) { | ||
return db.get(doc._id); | ||
}) | ||
.then(function(response) { | ||
expect(response._rev).to.equal(origRev); | ||
}) | ||
.nodeify(done); | ||
}); | ||
}); | ||
describe('#exists', function() { | ||
it('should return null for nonexistent doc', function(done) { | ||
db.exists('does-not-exist') | ||
.then(function(doc) { | ||
expect(doc).to.be.null; | ||
}) | ||
.nodeify(done); | ||
}); | ||
}); | ||
}); |
@@ -5,2 +5,3 @@ var chai = require('chai'); | ||
var config = require('./config'); | ||
var Q = require('q'); | ||
@@ -12,33 +13,22 @@ describe('DbHandle', function() { | ||
beforeEach(function(done) { | ||
cot.DELETE('/' + config.dbName, {}, thenPutDB); | ||
function thenPutDB(err) { | ||
if (err) { | ||
done(err); | ||
cot.jsonRequest('DELETE', '/' + config.dbName) | ||
.then(function() { | ||
return cot.jsonRequest('PUT', '/' + config.dbName); | ||
}) | ||
.then(function() { | ||
var docPromises = []; | ||
for (var i = 1; i < 10; i++) { | ||
docPromises.push(db.post({_id: 'doc-' + i, key: 'key-' + i})); | ||
} | ||
else { | ||
cot.PUT('/' + config.dbName, '', {}, thenPutDoc); | ||
} | ||
} | ||
var docNumber = 0; | ||
function thenPutDoc(err) { | ||
if (err) { | ||
done(err); | ||
} | ||
else { | ||
docNumber += 1; | ||
if (docNumber === 10) { | ||
db.putDoc({_id: '_design/test', views: { | ||
testView: { | ||
map: 'function(d) { emit(d.key, null); emit("z", null); }' | ||
} | ||
}}, done); | ||
} | ||
else { | ||
db.putDoc({_id: 'doc-' + docNumber, key: 'key-' + docNumber}, thenPutDoc); | ||
} | ||
} | ||
} | ||
var designDoc = {_id: '_design/test', views: { | ||
testView: { | ||
map: 'function(d) { emit(d.key, null); emit("z", null); }' | ||
} | ||
}}; | ||
docPromises.push(db.post(designDoc)); | ||
return Q.all(docPromises); | ||
}) | ||
.nodeify(done); | ||
}); | ||
@@ -48,19 +38,13 @@ | ||
it('should return doc-3 thru doc-6 using startkey_docid and endkey_docid', function(done) { | ||
db.view('test', 'testView', {key: 'z', startkey_docid: 'doc-3', endkey_docid: 'doc-6'}, onView); | ||
function onView(err, response) { | ||
if (err) { | ||
done(err) | ||
} | ||
else { | ||
expect(response.rows.length).to.equal(4); | ||
expect(response.rows[0].id).to.equal('doc-3'); | ||
expect(response.rows[1].id).to.equal('doc-4'); | ||
expect(response.rows[2].id).to.equal('doc-5'); | ||
expect(response.rows[3].id).to.equal('doc-6'); | ||
done(); | ||
} | ||
} | ||
db.view('test', 'testView', {key: 'z', startkey_docid: 'doc-3', endkey_docid: 'doc-6'}) | ||
.then(function(response) { | ||
expect(response.rows.length).to.equal(4); | ||
expect(response.rows[0].id).to.equal('doc-3'); | ||
expect(response.rows[1].id).to.equal('doc-4'); | ||
expect(response.rows[2].id).to.equal('doc-5'); | ||
expect(response.rows[3].id).to.equal('doc-6'); | ||
}) | ||
.nodeify(done); | ||
}); | ||
}); | ||
}); |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
20345
245
1
446
3