Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

install cradle

  • Loading branch information...
commit cec655e007bd2cd2d5a8f044aefd689dcc0d203e 1 parent 71bd5bc
Mike Amundsen mamund authored

Showing 49 changed files with 5,041 additions and 0 deletions. Show diff stats Hide diff stats

  1. +20 0 node_modules/cradle/LICENSE
  2. +10 0 node_modules/cradle/Makefile
  3. +260 0 node_modules/cradle/README.md
  4. +628 0 node_modules/cradle/lib/cradle.js
  5. +102 0 node_modules/cradle/lib/cradle/cache.js
  6. +123 0 node_modules/cradle/lib/cradle/response.js
  7. +1 0  node_modules/cradle/node_modules/.bin/vows
  8. +20 0 node_modules/cradle/node_modules/vargs/LICENSE
  9. +34 0 node_modules/cradle/node_modules/vargs/README.md
  10. +70 0 node_modules/cradle/node_modules/vargs/lib/vargs.js
  11. +12 0 node_modules/cradle/node_modules/vargs/package.json
  12. +1 0  node_modules/cradle/node_modules/vows/.gitignore
  13. +20 0 node_modules/cradle/node_modules/vows/LICENSE
  14. +7 0 node_modules/cradle/node_modules/vows/Makefile
  15. +39 0 node_modules/cradle/node_modules/vows/README.md
  16. +506 0 node_modules/cradle/node_modules/vows/bin/vows
  17. +27 0 node_modules/cradle/node_modules/vows/lib/assert/error.js
  18. +205 0 node_modules/cradle/node_modules/vows/lib/assert/macros.js
  19. +77 0 node_modules/cradle/node_modules/vows/lib/assert/utils.js
  20. +193 0 node_modules/cradle/node_modules/vows/lib/vows.js
  21. +131 0 node_modules/cradle/node_modules/vows/lib/vows/console.js
  22. +55 0 node_modules/cradle/node_modules/vows/lib/vows/context.js
  23. +29 0 node_modules/cradle/node_modules/vows/lib/vows/coverage/file.js
  24. +2 0  node_modules/cradle/node_modules/vows/lib/vows/coverage/fragments/coverage-foot.html
  25. +61 0 node_modules/cradle/node_modules/vows/lib/vows/coverage/fragments/coverage-head.html
  26. +54 0 node_modules/cradle/node_modules/vows/lib/vows/coverage/report-html.js
  27. +38 0 node_modules/cradle/node_modules/vows/lib/vows/coverage/report-plain.js
  28. +28 0 node_modules/cradle/node_modules/vows/lib/vows/extras.js
  29. +67 0 node_modules/cradle/node_modules/vows/lib/vows/reporters/dot-matrix.js
  30. +16 0 node_modules/cradle/node_modules/vows/lib/vows/reporters/json.js
  31. +8 0 node_modules/cradle/node_modules/vows/lib/vows/reporters/silent.js
  32. +44 0 node_modules/cradle/node_modules/vows/lib/vows/reporters/spec.js
  33. +39 0 node_modules/cradle/node_modules/vows/lib/vows/reporters/watch.js
  34. +90 0 node_modules/cradle/node_modules/vows/lib/vows/reporters/xunit.js
  35. +319 0 node_modules/cradle/node_modules/vows/lib/vows/suite.js
  36. +14 0 node_modules/cradle/node_modules/vows/package.json
  37. +119 0 node_modules/cradle/node_modules/vows/test/assert-test.js
  38. +18 0 node_modules/cradle/node_modules/vows/test/fixtures/isolate/failing.js
  39. +18 0 node_modules/cradle/node_modules/vows/test/fixtures/isolate/log.js
  40. +17 0 node_modules/cradle/node_modules/vows/test/fixtures/isolate/passing.js
  41. +18 0 node_modules/cradle/node_modules/vows/test/fixtures/isolate/stderr.js
  42. +140 0 node_modules/cradle/node_modules/vows/test/isolate-test.js
  43. +133 0 node_modules/cradle/node_modules/vows/test/testInherit.js
  44. +374 0 node_modules/cradle/node_modules/vows/test/vows-test.js
  45. +13 0 node_modules/cradle/package.json
  46. +104 0 node_modules/cradle/test/cache-test.js
  47. +612 0 node_modules/cradle/test/cradle-test.js
  48. +76 0 node_modules/cradle/test/response-test.js
  49. +49 0 node_modules/cradle/test/scripts/prepare-db.js
20 node_modules/cradle/LICENSE
... ... @@ -0,0 +1,20 @@
  1 +Copyright (c) 2009 cloudhead
  2 +
  3 +Permission is hereby granted, free of charge, to any person obtaining
  4 +a copy of this software and associated documentation files (the
  5 +"Software"), to deal in the Software without restriction, including
  6 +without limitation the rights to use, copy, modify, merge, publish,
  7 +distribute, sublicense, and/or sell copies of the Software, and to
  8 +permit persons to whom the Software is furnished to do so, subject to
  9 +the following conditions:
  10 +
  11 +The above copyright notice and this permission notice shall be
  12 +included in all copies or substantial portions of the Software.
  13 +
  14 +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  15 +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  16 +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  17 +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  18 +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  19 +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  20 +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
10 node_modules/cradle/Makefile
... ... @@ -0,0 +1,10 @@
  1 +default: test
  2 +
  3 +#
  4 +# Run all tests
  5 +#
  6 +test:
  7 + node test/scripts/prepare-db.js
  8 + vows test/*-test.js
  9 +
  10 +.PHONY: test
260 node_modules/cradle/README.md
Source Rendered
... ... @@ -0,0 +1,260 @@
  1 +cradle
  2 +======
  3 +
  4 +A high-level, caching, CouchDB client for Node.js
  5 +
  6 +introduction
  7 +------------
  8 +
  9 +Cradle is an asynchronous javascript client for [CouchDB](http://couchdb.apache.org).
  10 +It is somewhat higher-level than most other CouchDB clients, requiring a little less knowledge of CouchDB's REST API.
  11 +Cradle also has built-in write-through caching, giving you an extra level of speed, and making document _updates_ and _deletion_ easier.
  12 +Cradle was built from the love of CouchDB and Node.js, and tries to make the most out of this wonderful marriage of technologies.
  13 +
  14 +philosophy
  15 +----------
  16 +
  17 +The key concept here is the common ground shared by CouchDB and Node.js, that is, _javascript_. The other important aspect of this marriage is the asynchronous behaviors of both these technologies. Cradle tries to make use of these symmetries, whenever it can.
  18 +Cradle's API, although closely knit with CouchDB's, isn't overly so. Whenever the API can be abstracted in a friendlier, simpler way, that's the route it takes. So even though a large part of the `Cradle <--> CouchDB` mappings are one to one, some Cradle functions, such as `save()`, can perform more than one operation, depending on how they are used.
  19 +
  20 +synopsis
  21 +--------
  22 +
  23 + var cradle = require('cradle');
  24 + var db = new(cradle.Connection)().database('starwars');
  25 +
  26 + db.get('vader', function (err, doc) {
  27 + doc.name; // 'Darth Vader'
  28 + assert.equal(doc.force, 'dark');
  29 + });
  30 +
  31 + db.save('skywalker', {
  32 + force: 'light',
  33 + name: 'Luke Skywalker'
  34 + }, function (err, res) {
  35 + if (err) {
  36 + // Handle error
  37 + } else {
  38 + // Handle success
  39 + }
  40 + });
  41 +
  42 +installation
  43 +------------
  44 +
  45 + $ npm install cradle
  46 +
  47 +API
  48 +---
  49 +
  50 +Cradle's API builds right on top of Node's asynch API. Every asynch method takes a callback as its last argument. The return value is an `event.EventEmitter`, so listeners can also be optionally added.
  51 +
  52 +### Opening a connection ###
  53 +
  54 + new(cradle.Connection)('http://living-room.couch', 5984, {
  55 + cache: true,
  56 + raw: false
  57 + });
  58 +
  59 +_Defaults to `127.0.0.1:5984`_
  60 +
  61 +Note that you can also use `cradle.setup` to set a global configuration:
  62 +
  63 + cradle.setup({host: 'living-room.couch',
  64 + options: {cache: true, raw: false}});
  65 + var c = new(cradle.Connection),
  66 + cc = new(cradle.Connection)('173.45.66.92');
  67 +
  68 +### creating a database ###
  69 +
  70 + var db = c.database('starwars');
  71 + db.create();
  72 +
  73 +> You can check if a database exists with the `exists()` method.
  74 +
  75 +### fetching a document _(GET)_ ###
  76 +
  77 + db.get('vader', function (err, doc) {
  78 + sys.puts(doc);
  79 + });
  80 +
  81 +> If you want to get a specific revision for that document, you can pass it as the 2nd parameter to `get()`.
  82 +
  83 +Cradle is also able to fetch multiple documents if you have a list of ids, just pass an array to `get`:
  84 +
  85 + db.get(['luke', 'vader'], function (err, doc) { ... });
  86 +
  87 +### Querying a view ###
  88 +
  89 + db.view('characters/all', function (err, res) {
  90 + res.forEach(function (row) {
  91 + sys.puts(row.name + " is on the " +
  92 + row.force + " side of the force.");
  93 + });
  94 + });
  95 +
  96 +### creating/updating documents ###
  97 +
  98 +In general, document creation is done with the `save()` method, while updating is done with `merge()`.
  99 +
  100 +#### creating with an id _(PUT)_ ####
  101 +
  102 + db.save('vader', {
  103 + name: 'darth', force: 'dark'
  104 + }, function (err, res) {
  105 + // Handle response
  106 + });
  107 +
  108 +#### creating without an id _(POST)_ ####
  109 +
  110 + db.save({
  111 + force: 'dark', name: 'Darth'
  112 + }, function (err, res) {
  113 + // Handle response
  114 + });
  115 +
  116 +#### updating an existing document with the revision ####
  117 +
  118 + db.save('luke', '1-94B6F82', {
  119 + force: 'dark', name: 'Luke'
  120 + }, function (err, res) {
  121 + // Handle response
  122 + });
  123 +
  124 +Note that when saving a document this way, CouchDB overwrites the existing document with the new one. If you want to update only certain fields of the document, you have to fetch it first (with `get`), make your changes, then resave the modified document with the above method.
  125 +
  126 +If you only want to update one or more attributes, and leave the others untouched, you can use the `merge()` method:
  127 +
  128 + db.merge('luke', {jedi: true}, function (err, res) {
  129 + // Luke is now a jedi,
  130 + // but remains on the dark side of the force.
  131 + });
  132 +
  133 +Note that we didn't pass a `_rev`, this only works because we previously saved a full version of 'luke', and the `cache` option is enabled.
  134 +
  135 +#### bulk insertion ####
  136 +
  137 +If you want to insert more than one document at a time, for performance reasons, you can pass an array to `save()`:
  138 +
  139 + db.save([
  140 + {name: 'Yoda'},
  141 + {name: 'Han Solo'},
  142 + {name: 'Leia'}
  143 + ], function (err, res) {
  144 + // Handle response
  145 + });
  146 +
  147 +#### creating views ####
  148 +
  149 +Here we create a design document named 'characters', with two views: 'all' and 'darkside'.
  150 +
  151 + db.save('_design/characters', {
  152 + all: {
  153 + map: function (doc) {
  154 + if (doc.name) emit(doc.name, doc);
  155 + }
  156 + },
  157 + darkside: {
  158 + map: function (doc) {
  159 + if (doc.name && doc.force == 'dark') {
  160 + emit(null, doc);
  161 + }
  162 + }
  163 + }
  164 + });
  165 +
  166 +These views can later be queried with `db.view('characters/all')`, for example.
  167 +
  168 +Here we create a temporary view. WARNING: do not use this in production as it is extremely slow (use it to test views).
  169 + db.temporaryView({
  170 + map: function(doc) {
  171 + if (doc.color) emit(doc._id, doc);
  172 + }
  173 + }, function (err, res) {
  174 + if (err) console.log(err);
  175 + console.log(res);
  176 + });
  177 +
  178 +### removing documents _(DELETE)_ ###
  179 +
  180 +To remove a document, you call the `remove()` method, passing the latest document revision.
  181 +
  182 + db.remove('luke', '1-94B6F82', function (err, res) {
  183 + // Handle response
  184 + });
  185 +
  186 +
  187 +If `remove` is called without a revision, and the document was recently fetched from the database, it will attempt to use the cached document's revision, providing caching is enabled.
  188 +
  189 +Connecting with authentication and SSL
  190 +--------------------------------------
  191 +
  192 + var connection = new(cradle.Connection)('https://couch.io', 443, {
  193 + auth: { username: 'john', password: 'fha82l' }
  194 + });
  195 +
  196 +or
  197 +
  198 + var connection = new(cradle.Connection)('couch.io', 443, {
  199 + secure: true,
  200 + auth: { username: 'john', password: 'fha82l' }
  201 + });
  202 +
  203 +Changes API
  204 +-----------
  205 +
  206 +For a one-time `_changes` query, simply call `db.changes` with a callback:
  207 +
  208 + db.changes(function (list) {
  209 + list.forEach(function (change) { console.log(change) });
  210 + });
  211 +
  212 +Or if you want to see changes since a specific sequence number:
  213 +
  214 + db.changes({ since: 42 }, function (list) {
  215 + ...
  216 + });
  217 +
  218 +The callback will receive the list of changes as an *Array*. If you want to include
  219 +the affected documents, simply pass `include_docs: true` in the options.
  220 +
  221 +### Streaming #
  222 +
  223 +You can also *stream* changes, by calling `db.changes` without the callback:
  224 +
  225 + db.changes({ since: 42 }).on('response', function (res) {
  226 + res.on('data', function (change) {
  227 + console.log(change);
  228 + });
  229 + res.on('end', function () { ... });
  230 + });
  231 +
  232 +In this case, it returns an `EventEmitter`, which behaves very similarly to node's `Stream` API.
  233 +
  234 +
  235 +Other API methods
  236 +-----------------
  237 +
  238 +### CouchDB Server level ###
  239 +
  240 + new(cradle.Connection)().*
  241 +
  242 +- `databases()`: Get list of databases
  243 +- `config()`: Get server config
  244 +- `info()`: Get server information
  245 +- `stats()`: Statistics overview
  246 +- `activeTasks()`: Get list of currently active tasks
  247 +- `uuids(count)`: Get _count_ list of UUIDs
  248 +- `replicate(options)`: Replicate a database.
  249 +
  250 +### database level ###
  251 +
  252 + new(cradle.Connection)().database('starwars').*
  253 +
  254 +- `info()`: Database information
  255 +- `all()`: Get all documents
  256 +- `allBySeq()`: Get all documents by sequence
  257 +- `compact()`: Compact database
  258 +- `viewCleanup()`: Cleanup old view data
  259 +- `replicate(target, options)`: Replicate this database to `target`.
  260 +
628 node_modules/cradle/lib/cradle.js
... ... @@ -0,0 +1,628 @@
  1 +var path = require('path');
  2 +
  3 +require.paths.unshift(path.join(__dirname, 'cradle'));
  4 +
  5 +var sys = require("sys"),
  6 + http = require("http"),
  7 + https = require("https"),
  8 + events = require('events'),
  9 + fs = require("fs"),
  10 + url = require('url'),
  11 + buffer = require('buffer');
  12 +
  13 +var querystring = require('querystring');
  14 +var Args = require('vargs').Constructor;
  15 +
  16 +var cradle = exports;
  17 +
  18 +cradle.extend = require('response').extend;
  19 +cradle.Response = require('response').Response;
  20 +cradle.Cache = require('cache').Cache;
  21 +
  22 +cradle.host = '127.0.0.1';
  23 +cradle.port = 5984;
  24 +cradle.auth = null;
  25 +cradle.options = {
  26 + cache: true,
  27 + raw: false,
  28 + timeout: 0,
  29 + secure: false,
  30 + headers: {}
  31 +};
  32 +
  33 +cradle.setup = function (settings) {
  34 + this.host = settings.host;
  35 + this.auth = settings.auth;
  36 + this.port = parseInt(settings.port);
  37 + cradle.merge(this.options, settings);
  38 +
  39 + return this;
  40 +};
  41 +
  42 +var protocolPattern = /^(https?):\/\//;
  43 +
  44 +cradle.Connection = function Connection(/* variable args */) {
  45 + var args = Array.prototype.slice.call(arguments),
  46 + host, port, remote, auth, options = {};
  47 +
  48 + args.forEach(function (a) {
  49 + if (typeof(a) === 'number' || (typeof(a) === 'string' && /^\d{2,5}$/.test(a))) {
  50 + port = parseInt(a);
  51 + } else if (typeof(a) === 'object') {
  52 + options = a;
  53 + host = host || options.host;
  54 + port = port || options.port;
  55 + auth = options.auth;
  56 + } else {
  57 + host = a;
  58 + }
  59 + });
  60 +
  61 + this.host = host || cradle.host;
  62 + this.port = port || cradle.port;
  63 + this.auth = auth || cradle.auth;
  64 + this.options = cradle.merge({}, cradle.options, options);
  65 +
  66 + this.options.secure = this.options.secure || this.options.ssl;
  67 +
  68 + if (protocolPattern.test(this.host)) {
  69 + this.protocol = this.host.match(protocolPattern)[1];
  70 + this.host = this.host.replace(protocolPattern, '');
  71 + }
  72 +
  73 + if (this.protocol === 'https') this.options.secure = true;
  74 +
  75 + if (this.auth && this.auth.user) { // Deprecation warning
  76 + console.log('Warning: "user" & "pass" parameters ignored. Use "username" & "password"');
  77 + }
  78 + if (this.options.ssl) { // Deprecation warning
  79 + console.log('Warning: "ssl" option is deprecated. Use "secure" instead.');
  80 + }
  81 +
  82 + this.socket = (this.options.secure) ? https : http;
  83 +};
  84 +
  85 +//
  86 +// Connection.rawRequest()
  87 +//
  88 +// This is a base wrapper around connections to CouchDB. Given that it handles
  89 +// *all* requests, including those for attachments, it knows nothing about
  90 +// JSON serialization and does not presuppose it is sending or receiving JSON
  91 +// content
  92 +//
  93 +cradle.Connection.prototype.rawRequest = function (method, path, options, data, headers) {
  94 + var promise = new(events.EventEmitter), request, that = this;
  95 +
  96 + // HTTP Headers
  97 + headers = headers || {};
  98 +
  99 + // Set HTTP Basic Auth
  100 + if (this.auth) {
  101 + headers['Authorization'] = "Basic " + new Buffer(this.auth.username + ':' + this.auth.password).toString('base64');
  102 + }
  103 +
  104 + // Set client-wide headers
  105 + for (var h in this.options.headers) {
  106 + headers[h] = this.options.headers[h];
  107 + }
  108 +
  109 + path = (path || '/').replace(/https?:\/\//, '').replace(/\/{2,}/g, '/');
  110 + if (path.charAt(0) !== '/') { path = '/' + path }
  111 +
  112 + if (options) {
  113 + for (var k in options) {
  114 + if (typeof(options[k]) === 'boolean') {
  115 + options[k] = String(options[k]);
  116 + }
  117 + }
  118 + path += '?' + querystring.stringify(options);
  119 + }
  120 +
  121 + request = this.socket.request({
  122 + host: this.host,
  123 + port: this.port,
  124 + method: method.toUpperCase(),
  125 + path: path,
  126 + headers: headers
  127 + });
  128 +
  129 + if (data && data.on) { headers['Transfer-Encoding'] = 'chunked' }
  130 +
  131 + headers['Connection'] = 'keep-alive';
  132 +
  133 + request.on('response', function (res) {
  134 + promise.emit('response', res);
  135 + res.on('data', function (chunk) { promise.emit('data', chunk) });
  136 + res.on('end', function () { promise.emit('end') });
  137 + });
  138 +
  139 + if (data) {
  140 + if (data.on) {
  141 + data.on('data', function (chunk) { request.write(chunk) });
  142 + data.on('end', function () { request.end() });
  143 + } else {
  144 + request.write(data, 'utf8');
  145 + request.end();
  146 + }
  147 + } else {
  148 + request.end();
  149 + }
  150 +
  151 + return promise;
  152 +}
  153 +//
  154 +// Connection.request()
  155 +//
  156 +// This is the entry point for all requests to CouchDB, at this point,
  157 +// the database name has been embed in the url, by one of the wrappers.
  158 +//
  159 +cradle.Connection.prototype.request = function (method, path, /* [options], [data], [headers] */ callback) {
  160 + var request, that = this, args = Array.prototype.slice.call(arguments, 2);
  161 +
  162 + if (typeof(callback = args.pop()) !== 'function') {
  163 + args.push(callback);
  164 + callback = function () {};
  165 + }
  166 +
  167 + var options = args.shift() || {},
  168 + data = args.shift() || null,
  169 + headers = cradle.merge({ host: this.host }, args.shift() || {});
  170 +
  171 + //
  172 + // Handle POST/PUT data. We also convert functions to strings,
  173 + // so they can be used in _design documents.
  174 + //
  175 + if (data) {
  176 + data = JSON.stringify(data, function (k, val) {
  177 + if (typeof(val) === 'function') {
  178 + return val.toString();
  179 + } else { return val }
  180 + });
  181 + headers["Content-Length"] = Buffer.byteLength(data);
  182 + headers["Content-Type"] = "application/json";
  183 + }
  184 +
  185 + request = this.rawRequest(method, path, options, data, headers);
  186 +
  187 + //
  188 + // Initialize the request, send the body, and finally,
  189 + // dispatch the request.
  190 + //
  191 + request.on('response', function (res) {
  192 + var body = [];
  193 +
  194 + res.setEncoding('utf8');
  195 + res.on('data', function (chunk) {
  196 + chunk && body.push(chunk);
  197 + }).on('end', function () {
  198 + var json, response;
  199 +
  200 + if (method === 'HEAD') {
  201 + callback(null, res.headers, res.statusCode);
  202 + } else {
  203 + try { json = JSON.parse(body.join('')) }
  204 + catch (e) { return callback(e) }
  205 +
  206 +
  207 + if (json.error) {
  208 + cradle.extend(json, { headers: res.headers });
  209 + json.headers.status = res.statusCode;
  210 + callback(json);
  211 + } else {
  212 + // If the `raw` option was set, we return the parsed
  213 + // body as-is. If not, we wrap it in a `Response` object.
  214 + callback(null, that.options.raw ? json : new(cradle.Response)(json, res));
  215 + }
  216 + }
  217 + });
  218 + });
  219 +};
  220 +
  221 +//
  222 +// The database object
  223 +//
  224 +// We return an object with database functions,
  225 +// closing around the `name` argument.
  226 +//
  227 +cradle.Connection.prototype.database = function (name) {
  228 + var that = this, connection = this;
  229 +
  230 + return {
  231 + name: name,
  232 + //
  233 + // The database document cache.
  234 + //
  235 + cache: new(cradle.Cache)(that.options),
  236 +
  237 + // A wrapper around `Connection.request`,
  238 + // which prepends the database name.
  239 + query: function (method, path /* [options], [data], [headers], [callback] */) {
  240 + var args = Array.prototype.slice.call(arguments, 2);
  241 + that.request.apply(that, [method, [name, path].join('/')].concat(args));
  242 + },
  243 + exists: function (callback) {
  244 + this.query('HEAD', '/', function (err, res, status) {
  245 + if (err) {
  246 + callback(err);
  247 + } else {
  248 + if (status === 404) {
  249 + callback(null, false);
  250 + } else {
  251 + callback(null, true);
  252 + }
  253 + }
  254 + });
  255 + },
  256 +
  257 + // Fetch either a single document from the database, or cache,
  258 + // or multiple documents from the database.
  259 + // If it's a single doc from the db, attempt to save it to the cache.
  260 + get: function (id, rev) {
  261 + var that = this, options = null,
  262 + args = new(Args)(arguments);
  263 +
  264 + if (Array.isArray(id)) { // Bulk GET
  265 + this.query('POST', '/_all_docs', { include_docs: true }, { keys: id },
  266 + function (err, res) { args.callback(err, res) });
  267 + } else {
  268 + if (rev && args.length === 2) {
  269 + if (typeof(rev) === 'string') { options = { rev: rev } }
  270 + else if (typeof(rev) === 'object') { options = rev }
  271 + } else if (this.cache.has(id)) {
  272 + return args.callback(null, this.cache.get(id));
  273 + }
  274 + this.query('GET', id, options, function (err, res) {
  275 + if (! err) that.cache.save(res.id, res.json);
  276 + args.callback(err, res);
  277 + });
  278 + }
  279 + },
  280 +
  281 + save: function (/* [id], [rev], doc | [doc, ...] */) {
  282 + var args = new(Args)(arguments),
  283 + array = args.all.slice(0), doc, id, rev;
  284 +
  285 + if (Array.isArray(args.first)) {
  286 + doc = args.first;
  287 + } else {
  288 + doc = array.pop(),
  289 + id = array.shift(),
  290 + rev = array.shift();
  291 + }
  292 + this._save(id, rev, doc, args.callback);
  293 + },
  294 + _save: function (id, rev, doc, callback) {
  295 + var options = connection.options;
  296 + var document = {}, that = this;
  297 +
  298 + // Bulk Insert
  299 + if (Array.isArray(doc)) {
  300 + document.docs = doc;
  301 + if (options.allOrNothing) { document.all_or_nothing = true }
  302 + this.query('POST', '/_bulk_docs', {}, document, callback);
  303 + } else {
  304 + // PUT a single document, with an id (Create or Update)
  305 + if (id) {
  306 + // Design document
  307 + if (/^_design\/(\w|%)+$/.test(id) && !('views' in doc)) {
  308 + document.language = "javascript";
  309 + document.views = doc;
  310 + } else {
  311 + document = doc;
  312 + }
  313 + // Try to set the '_rev' attribute of the document.
  314 + // If it wasn't passed, attempt to retrieve it from the cache.
  315 + rev && (document._rev = rev);
  316 +
  317 + if (document._rev) {
  318 + this.put(id, document, callback);
  319 + } else if (this.cache.has(id)) {
  320 + document._rev = this.cache.get(id)._rev;
  321 + this.put(id, document, callback);
  322 + } else {
  323 + // Attempt to create a new document. If it fails,
  324 + // because an existing document with that _id exists (409),
  325 + // perform a HEAD, to get the _rev, and try to re-save.
  326 + this.put(id, document, function (e, res) {
  327 + if (e && e.headers.status === 409) { // Conflict
  328 + that.head(id, function (e, headers) {
  329 + document._rev = headers['etag'].slice(1, -1);
  330 + that.put(id, document, callback);
  331 + });
  332 + } else { callback(e, res) }
  333 + });
  334 + }
  335 + // POST a single document, without an id (Create)
  336 + } else {
  337 + this.post(doc, callback);
  338 + }
  339 + }
  340 + },
  341 +
  342 + merge: function (/* [id], doc */) {
  343 + var args = Array.prototype.slice.call(arguments),
  344 + callback = args.pop(),
  345 + doc = args.pop(),
  346 + id = args.pop() || doc._id;
  347 +
  348 + this._merge(id, doc, callback);
  349 + },
  350 + _merge: function (id, doc, callback) {
  351 + var that = this;
  352 + this.get(id, function (e, res) {
  353 + if (e) { return callback(e) }
  354 + doc = cradle.merge({}, res.json || res, doc);
  355 + that.save(id, res._rev, doc, callback);
  356 + });
  357 + },
  358 +
  359 + //
  360 + // PUT a document, and write through cache
  361 + //
  362 + put: function (id, doc, callback) {
  363 + var cache = this.cache;
  364 + if (typeof(id) !== 'string') { throw new(TypeError)("id must be a string") }
  365 + this.query('PUT', id, null, doc, function (e, res) {
  366 + if (! e) { cache.save(id, cradle.merge({}, doc, { _rev: res.rev })) }
  367 + callback && callback(e, res);
  368 + });
  369 + },
  370 +
  371 + //
  372 + // POST a document, and write through cache
  373 + //
  374 + post: function (doc, callback) {
  375 + var cache = this.cache;
  376 + this.query('POST', '/', null, doc, function (e, res) {
  377 + if (! e) { cache.save(res.id, cradle.merge({}, doc, { _rev: res.rev })) }
  378 + callback && callback(e, res);
  379 + });
  380 + },
  381 +
  382 + //
  383 + // Perform a HEAD request
  384 + //
  385 + head: function (id, callback) {
  386 + this.query('HEAD', id, null, callback);
  387 + },
  388 +
  389 + insert: function () {
  390 + throw new(Error)("`insert` is deprecated, use `save` instead");
  391 + },
  392 +
  393 + replicate: function (target, options, callback) {
  394 + if (typeof(options) === 'function') { callback = options, options = {} }
  395 + that.replicate(cradle.merge({ source: name, target: target }, options), callback);
  396 + },
  397 +
  398 + // Destroys a database with 'DELETE'
  399 + // we raise an exception if arguments were supplied,
  400 + // as we don't want users to confuse this function with `remove`.
  401 + destroy: function (callback) {
  402 + if (arguments.length > 1) {
  403 + throw new(Error)("destroy() doesn't take any additional arguments");
  404 + } else {
  405 + this.query('DELETE', '/', callback);
  406 + }
  407 + },
  408 +
  409 + // Delete a document
  410 + // if the _rev wasn't supplied, we attempt to retrieve it from the
  411 + // cache. If the deletion was successful, we purge the cache.
  412 + remove: function (id, rev) {
  413 + var that = this, doc, args = new(Args)(arguments);
  414 +
  415 + if (typeof(rev) !== 'string') {
  416 + if (doc = this.cache.get(id)) { rev = doc._rev }
  417 + else { throw new(Error)("rev needs to be supplied") }
  418 + }
  419 + this.query('DELETE', id, {rev: rev}, function (err, res) {
  420 + if (! err) { that.cache.purge(id) }
  421 + args.callback(err, res);
  422 + });
  423 + },
  424 + create: function (callback) {
  425 + this.query('PUT', '/', callback);
  426 + },
  427 + info: function (callback) {
  428 + this.query('GET', '/', callback);
  429 + },
  430 + all: function (options, callback) {
  431 + if (arguments.length === 1) { callback = options, options = {} }
  432 + this.query('GET', '/_all_docs', options, callback);
  433 + },
  434 + compact: function (design) {
  435 + var headers = {};
  436 + headers['Content-Type'] = "application/json";
  437 + this.query('POST', '/_compact' + (typeof(design) === 'string' ? '/' + design : ''),
  438 + {}, {}, headers, Args.last(arguments));
  439 + },
  440 + viewCleanup: function (callback) {
  441 + this.query('POST', '/_view_cleanup', callback);
  442 + },
  443 + allBySeq: function (options) {
  444 + options = typeof(options) === 'object' ? options : {};
  445 + this.query('GET', '/_all_docs_by_seq', options, Args.last(arguments));
  446 + },
  447 +
  448 + // Query a view, passing any options to the query string.
  449 + // Some query string parameters' values have to be JSON-encoded.
  450 + view: function (path, options) {
  451 + var args = new(Args)(arguments);
  452 +
  453 + path = path.split('/');
  454 + path = ['_design', path[0], '_view', path[1]].join('/');
  455 +
  456 + if (typeof(options) === 'object') {
  457 + ['key', 'startkey', 'endkey'].forEach(function (k) {
  458 + if (k in options) { options[k] = JSON.stringify(options[k]) }
  459 + });
  460 + }
  461 +
  462 + if (options && options.keys) {
  463 + this.query('POST', path, {}, options, args.callback);
  464 + } else {
  465 + this.query('GET', path, options, args.callback);
  466 + }
  467 + },
  468 +
  469 + // Query a list, passing any options to the query string.
  470 + // Some query string parameters' values have to be JSON-encoded.
  471 + list: function (path, options) {
  472 + var args = new(Args)(arguments);
  473 + path = path.split('/');
  474 +
  475 + if (typeof(options) === 'object') {
  476 + ['key', 'startkey', 'endkey'].forEach(function (k) {
  477 + if (k in options) { options[k] = JSON.stringify(options[k]) }
  478 + });
  479 + }
  480 + this.query('GET', ['_design', path[0], '_list', path[1], path[2]].join('/'), options, args.callback);
  481 + },
  482 +
  483 + update: function(path, id, options) {
  484 + var args = new(Args)(arguments);
  485 + path = path.split('/');
  486 +
  487 + if (id) {
  488 + this.query('PUT', ['_design', path[0], '_update', path[1], id].join('/'), options, args.callback);
  489 + } else {
  490 + this.query('POST', ['_design', path[0], '_update', path[1]].join('/'), options, args.callback);
  491 + }
  492 + },
  493 +
  494 + push: function (doc) {},
  495 +
  496 + changes: function (options, callback) {
  497 + var promise = new(events.EventEmitter);
  498 +
  499 + if (typeof(options) === 'function') { callback = options, options = {}; }
  500 +
  501 + if (callback) {
  502 + this.query('GET', '_changes', options, callback);
  503 + } else {
  504 + options = options || {};
  505 + options.feed = options.feed || 'continuous';
  506 + options.heartbeat = options.heartbeat || 1000;
  507 +
  508 + that.rawRequest('GET', [name, '_changes'].join('/'), options).on('response', function (res) {
  509 + var response = new(events.EventEmitter), buffer = [];
  510 + res.setEncoding('utf8');
  511 +
  512 + response.statusCode = res.statusCode;
  513 + response.headers = res.headers;
  514 +
  515 + promise.emit('response', response);
  516 +
  517 + res.on('data', function (chunk) {
  518 + if (chunk.trim()) {
  519 + buffer.push(chunk);
  520 +
  521 + if (chunk.indexOf('\n') !== -1) {
  522 + buffer.length && response.emit('data', JSON.parse(buffer.join('')));
  523 + buffer = [];
  524 + }
  525 + }
  526 + }).on('end', function () {
  527 + response.emit('end');
  528 + });
  529 + });
  530 + return promise;
  531 + }
  532 + },
  533 +
  534 + saveAttachment: function (/* id, [rev], attachmentName, contentType, dataOrStream */) {
  535 + var doc, pathname, headers = {}, response, body = [], resHeaders, error, db = this;
  536 +
  537 + var args = new(Args)(arguments), params = args.all;
  538 +
  539 + if (typeof(args.first) === 'object') { throw new(TypeError)("first argument must be a document id") }
  540 +
  541 + var id = params.shift(),
  542 + dataOrStream = params.pop(),
  543 + contentType = params.pop(),
  544 + attachmentName = params.pop(),
  545 + rev = params.pop();
  546 +
  547 + if (!rev && db.cache.has(id)) {
  548 + doc = { rev: db.cache.get(id)._rev };
  549 + } else if (rev) {
  550 + doc = { rev: rev };
  551 + } else {
  552 + doc = {};
  553 + }
  554 +
  555 + pathname = '/' + [name, id, attachmentName].join('/');
  556 + headers['Content-Type'] = contentType;
  557 +
  558 + that.rawRequest('PUT', pathname, doc, dataOrStream, headers)
  559 + .on('response', function (res) { resHeaders = { status: res.statusCode } })
  560 + .on('data', function (chunk) { body.push(chunk) })
  561 + .on('end', function () {
  562 + response = JSON.parse(body.join(''));
  563 + response.headers = resHeaders;
  564 +
  565 + if (response.headers.status == 201) {
  566 + if (db.cache.has(id)) {
  567 + cached = db.cache.store[id].document;
  568 + cached._rev = response.rev;
  569 + cached._attachments = cached._attachments || {};
  570 + cached._attachments[attachmentName] = { stub: true };
  571 + }
  572 + args.callback(null, response);
  573 + } else {
  574 + args.callback(response);
  575 + }
  576 + });
  577 + },
  578 +
  579 + getAttachment: function(docId, attachmentName) {
  580 + var pathname, req;
  581 + pathname = '/' + [name, docId, attachmentName].join('/');
  582 + return that.rawRequest('GET', pathname);
  583 + },
  584 +
  585 + temporaryView: function (doc, callback) {
  586 + this.query('POST', '_temp_view', null, doc, callback);
  587 + }
  588 + }
  589 +
  590 +};
  591 +
  592 +//
  593 +// Wrapper functions for the server API
  594 +//
  595 +cradle.Connection.prototype.databases = function (c) {
  596 + this.request('GET', '/_all_dbs', c);
  597 +};
  598 +cradle.Connection.prototype.config = function (c) {
  599 + this.request('GET', '/_config', c);
  600 +};
  601 +cradle.Connection.prototype.info = function (c) {
  602 + this.request('GET', '/', c);
  603 +};
  604 +cradle.Connection.prototype.stats = function (c) {
  605 + this.request('GET', '/_stats', c);
  606 +};
  607 +cradle.Connection.prototype.activeTasks = function (c) {
  608 + this.request('GET', '/_active_tasks', c);
  609 +};
  610 +cradle.Connection.prototype.uuids = function (count, callback) {
  611 + if (typeof(count) === 'function') { callback = count, count = null }
  612 + this.request('GET', '/_uuids', count ? {count: count} : {}, callback);
  613 +};
  614 +cradle.Connection.prototype.replicate = function (options, callback) {
  615 + this.request('POST', '/_replicate', null, options, callback);
  616 +};
  617 +
  618 +cradle.merge = function (target) {
  619 + var objs = Array.prototype.slice.call(arguments, 1);
  620 + objs.forEach(function(o) {
  621 + Object.keys(o).forEach(function (attr) {
  622 + if (! o.__lookupGetter__(attr)) {
  623 + target[attr] = o[attr];
  624 + }
  625 + });
  626 + });
  627 + return target;
  628 +}
102 node_modules/cradle/lib/cradle/cache.js
... ... @@ -0,0 +1,102 @@
  1 +var Response = require('./response').Response;
  2 +//
  3 +// Each database object has its own cache store.
  4 +// The cache.* methods are all wrappers around
  5 +// `cache.query`, which transparently checks if
  6 +// caching is enabled, before performing any action.
  7 +//
  8 +this.Cache = function (options) {
  9 + var that = this;
  10 +
  11 + this.store = {};
  12 + this.options = options;
  13 + this.size = options.cacheSize || 0;
  14 + this.keys = 0;
  15 +};
  16 +
  17 +this.Cache.prototype = {
  18 + // API
  19 + get: function (id) { return this.query('get', id) },
  20 + save: function (id, doc) { return this.query('save', id, doc) },
  21 + purge: function (id) { return this.query('purge', id) },
  22 + has: function (id) { return this.query('has', id) },
  23 +
  24 + _get: function (id) {
  25 + var entry;
  26 +
  27 + if (id in this.store) {
  28 + entry = this.store[id];
  29 + entry.atime = Date.now();
  30 +
  31 + if (this.options.raw) {
  32 + return entry.document;
  33 + } else {
  34 + // If the document is already wrapped in a `Response`,
  35 + // just return it. Else, wrap it first. We clone the documents
  36 + // before returning them, to protect them from modification.
  37 + if (entry.document.toJSON) {
  38 + return clone(entry.document);
  39 + } else {
  40 + return new(Response)(clone(entry.document));
  41 + }
  42 + }
  43 + }
  44 + },
  45 + _has: function (id) {
  46 + return id in this.store;
  47 + },
  48 + _save: function (id, doc) {
  49 + if (! this._has(id)) {
  50 + this.keys ++;
  51 + this.prune();
  52 + }
  53 +
  54 + return this.store[id] = {
  55 + atime: Date.now(),
  56 + document: doc
  57 + };
  58 + },
  59 + _purge: function (id) {
  60 + if (id) {
  61 + delete(this.store[id]);
  62 + this.keys --;
  63 + } else {
  64 + this.store = {};
  65 + }
  66 + },
  67 + query: function (op, id, doc) {
  68 + if (this.options.cache) {
  69 + return this['_' + op](id, doc);
  70 + } else {
  71 + return false;
  72 + }
  73 + },
  74 + prune: function () {
  75 + var that = this;
  76 + if (this.size && this.keys > this.size) {
  77 + process.nextTick(function () {
  78 + var store = that.store,
  79 + keys = Object.keys(store),
  80 + pruned = Math.ceil(that.size / 8);
  81 +
  82 + keys.sort(function (a, b) {
  83 + return store[a].atime > store[b].atime ? 1 : -1;
  84 + });
  85 +
  86 + for (var i = 0; i < pruned; i++) {
  87 + delete(store[keys[i]]);
  88 + }
  89 + that.keys -= pruned;
  90 + });
  91 + }
  92 + }
  93 +};
  94 +
  95 +function clone(obj) {
  96 + return Object.keys(obj).reduce(function (clone, k) {
  97 + if (! obj.__lookupGetter__(k)) {
  98 + clone[k] = obj[k];
  99 + }
  100 + return clone;
  101 + }, {});
  102 +}
123 node_modules/cradle/lib/cradle/response.js
... ... @@ -0,0 +1,123 @@
  1 +//
  2 +// HTTP response wrapper
  3 +//
  4 +// It allows us to call array-like methods on documents
  5 +// with a 'row' attribute.
  6 +//
  7 +this.Response = function Response(json, response) {
  8 + var obj, headers;
  9 +
  10 + // If there's rows, this is the result
  11 + // of a view function.
  12 + // We want to return this as an Array.
  13 + if (json.rows) {
  14 + obj = json.rows.slice(0);
  15 + obj.__proto__ = new(Array);
  16 + Object.keys(json).forEach(function (k) {
  17 + Object.defineProperty(obj.__proto__, k, {
  18 + value: json[k],
  19 + enumerable: false
  20 + });
  21 + });
  22 + } else if (json.results) {
  23 + obj = json.results.slice(0);
  24 + obj.__proto__ = new(Array);
  25 + obj.last_seq = json.last_seq;
  26 + } else if (json.uuids) {
  27 + obj = json.uuids;
  28 + obj.__proto__ = new(Array);
  29 + } else if (Array.isArray(json)) {
  30 + obj = json.slice(0);
  31 + obj.__proto__ = new(Array);
  32 + } else {
  33 + obj = {};
  34 + obj.__proto__ = new(Object);
  35 + Object.keys(json).forEach(function (k) {
  36 + obj[k] = json[k];
  37 + });
  38 + }
  39 +
  40 + // If the response was originally a document,
  41 + // give access to it via the 'json' getter.
  42 + if (!Array.isArray(json) && !obj.json) {
  43 + Object.defineProperty(obj, 'json', {
  44 + value: json,
  45 + enumerable: false
  46 + });
  47 + }
  48 +
  49 + if (response) {
  50 + headers = { status: response.statusCode };
  51 + Object.keys(response.headers).forEach(function (k) {
  52 + headers[k] = response.headers[k];
  53 + });
  54 +
  55 + // Set the 'headers' special field, with the response's status code.
  56 + exports.extend(obj, 'headers' in obj ? { _headers: headers }
  57 + : { headers: headers });
  58 + }
  59 +
  60 + // Alias '_rev' and '_id'
  61 + if (obj.id && obj.rev) {
  62 + exports.extend(obj, { _id: obj.id, _rev: obj.rev });
  63 + } else if (obj._id && obj._rev) {
  64 + exports.extend(obj, { id: obj._id, rev: obj._rev });
  65 + }
  66 +
  67 + if (Array.isArray(obj) && json.rows) {
  68 + exports.extend(obj, exports.collectionPrototype);
  69 + }
  70 + exports.extend(obj, exports.basePrototype);
  71 +
  72 + // Set the constructor to be this function
  73 + Object.defineProperty(obj, 'constructor', {
  74 + value: arguments.callee
  75 + });
  76 +
  77 + return obj;
  78 +};
  79 +
  80 +this.basePrototype = {
  81 + toJSON: function () {
  82 + return this;
  83 + },
  84 + toString: function () {
  85 + return JSON.stringify(this);
  86 + }
  87 +};
  88 +
  89 +this.collectionPrototype = {
  90 + forEach: function (f) {
  91 + for (var i = 0, value; i < this.length; i++) {
  92 + value = this[i].doc || this[i].json || this[i].value || this[i];
  93 + if (f.length === 1) {
  94 + f.call(this[i], value);