diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..d3c9752 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,6 @@ +language: node_js +node_js: + - 0.4 + - 0.6 + - 0.7 +install: npm install -d diff --git a/lib/cradle.js b/lib/cradle.js index 6b28281..e5c6432 100644 --- a/lib/cradle.js +++ b/lib/cradle.js @@ -175,8 +175,16 @@ cradle.Connection.prototype.request = function (options, callback) { options.headers["Content-Length"] = Buffer.byteLength(options.body); options.headers["Content-Type"] = "application/json"; } + + if (options.cache) { + options.headers["If-None-Match"] = options.cache.etag; + } return this.rawRequest(options, function (err, res, body) { + if (options.cache && res.statusCode === 304) { + return callback(null, options.cache.store, true, res.headers.etag); + } + if (err) { return callback(err); } @@ -188,7 +196,7 @@ cradle.Connection.prototype.request = function (options, callback) { body.headers.status = res.statusCode; return callback(body); } - + try { body = JSON.parse(body) } catch (err) { } @@ -198,7 +206,7 @@ cradle.Connection.prototype.request = function (options, callback) { return callback(body); } - callback(null, self.options.raw ? body : new cradle.Response(body, res)); + callback(null, (self.options.raw || options.raw) ? body : new cradle.Response(body, res), false, res.headers.etag); }); }; diff --git a/lib/cradle/cache.js b/lib/cradle/cache.js index 5b60877..8af7276 100644 --- a/lib/cradle/cache.js +++ b/lib/cradle/cache.js @@ -16,10 +16,11 @@ this.Cache = function (options) { this.Cache.prototype = { // API - get: function (id) { return this.query('get', id) }, - save: function (id, doc) { return this.query('save', id, doc) }, - purge: function (id) { return this.query('purge', id) }, - has: function (id) { return this.query('has', id) }, + get: function (id) { return this.query('get', id) }, + headers: function (id) { return this.query('headers', id) }, + save: function (id, doc) { return this.query('save', id, doc) }, + purge: function (id) { return this.query('purge', id) }, + has: function (id) { return this.query('has', id) }, _get: function (id) { var entry; @@ -42,6 +43,13 @@ this.Cache.prototype = { } } }, + _headers: function (id) { + if (id in this.store) { + return this.store[id].document.headers; + } else { + return null; + } + }, _has: function (id) { return id in this.store; }, @@ -93,10 +101,14 @@ this.Cache.prototype = { }; function clone(obj) { - return Object.keys(obj).reduce(function (clone, k) { - if (! obj.__lookupGetter__(k)) { - clone[k] = obj[k]; - } - return clone; - }, {}); + if (Array.isArray(obj)) { + return obj.slice(0); + } else { + return Object.keys(obj).reduce(function (clone, k) { + if (! obj.__lookupGetter__(k)) { + clone[k] = obj[k]; + } + return clone; + }, {}); + } } diff --git a/lib/cradle/database/documents.js b/lib/cradle/database/documents.js index 04e5922..df4aa25 100644 --- a/lib/cradle/database/documents.js +++ b/lib/cradle/database/documents.js @@ -19,6 +19,7 @@ Database.prototype.head = function (id, callback) { Database.prototype.get = function (id, rev) { var args = new (Args)(arguments), options = null, + cache = null, that = this; if (Array.isArray(id)) { // Bulk GET @@ -35,13 +36,15 @@ Database.prototype.get = function (id, rev) { if (typeof(rev) === 'string') { options = { rev: rev } } else if (typeof(rev) === 'object') { options = rev } } else if (this.cache.has(id)) { - return args.callback(null, this.cache.get(id)); + cache = { store: this.cache.get(id) }; + cache.etag = '"' + cache.store._rev + '"'; } this.query({ path: cradle.escape(id), - query: options - }, function (err, res) { - if (! err) that.cache.save(res.id, res.json); + query: options, + cache: cache + }, function (err, res, cached) { + if (! err && ! cached) that.cache.save(res.id, res.json); args.callback(err, res); }); } @@ -212,4 +215,35 @@ Database.prototype.remove = function (id, rev) { if (! err) { that.cache.purge(id) } args.callback(err, res); }); -}; \ No newline at end of file +}; + +// Query a show, passing any options to the query string. +// Some query string parameters' values have to be JSON-encoded. +Database.prototype.show = function (path, options) { + var args = new(Args)(arguments), + that = this, + cachepath = path, + cache = null; + + path = path.split('/'); + path = ['_design', path[0], '_show', path[1], path[2]].map(querystring.escape).join('/'); + + if (typeof(options) === 'object') { + cachepath += '?' + querystring.stringify(options); + } + + if (this.cache.has(cachepath)) { + cache = { store: this.cache.get(cachepath).value, etag: this.cache.headers(cachepath).etag }; + } + + return this.query({ + method: 'GET', + path: path, + query: options, + raw: true, + cache: cache + }, function(err, res, cached, etag) { + if (! err && ! cached) that.cache.save(cachepath, { value: res, headers: { etag: etag } }); + args.callback(err, (! cached && Array.isArray(res)) ? res.slice(0) : res, etag); + }); +}; diff --git a/lib/cradle/database/views.js b/lib/cradle/database/views.js index 5790210..9c7bd67 100644 --- a/lib/cradle/database/views.js +++ b/lib/cradle/database/views.js @@ -9,19 +9,36 @@ Database.prototype.all = function (options, callback) { options = {}; } - return this._getOrPostView('/_all_docs', options, callback); + return this._getOrPostView('/_all_docs', { query: options }, callback); }; // Query a view, passing any options to the query string. // Some query string parameters' values have to be JSON-encoded. Database.prototype.view = function (path, options) { var callback = new(Args)(arguments).callback, - body; + body, + cachepath, + cache = null, + that = this; path = path.split('/'); path = ['_design', path[0], '_view', path[1]].map(querystring.escape).join('/'); - return this._getOrPostView(path, options, callback); + cachepath = path; + + if (!options.body) { + if (options && typeof options === 'object') { + cachepath += '?' + querystring.stringify(options); + } + if(this.cache.has(cachepath)) { + cache = { store: this.cache.get(cachepath), etag: this.cache.headers(cachepath).etag }; + } + } + + return this._getOrPostView(path, { query: options, cache: cache }, function(err, res, cached, etag) { + if (! err && ! cached) that.cache.save(cachepath, res); + callback(err, (!cached && Array.isArray(res)) ? res.slice(0) : res, etag); + }); }; Database.prototype.temporaryView = function (doc, options, callback) { @@ -67,41 +84,54 @@ Database.prototype.compact = function (design) { // Query a list, passing any options to the query string. // Some query string parameters' values have to be JSON-encoded. Database.prototype.list = function (path, options) { - var callback = new(Args)(arguments).callback; - path = path.split('/'); + var callback = new(Args)(arguments).callback, + cachepath = path, + cache = null, + that = this; + + path = path.split('/'), + path = ['_design', path[0], '_list', path[1], path[2]].map(querystring.escape).join('/'); + + if (!options.body) { + if (options && typeof options === 'object') { + cachepath += '?' + querystring.stringify(options); + } + if(this.cache.has(cachepath)) { + cache = { store: this.cache.get(cachepath).value, etag: this.cache.headers(cachepath).etag }; + } + } this._getOrPostView( - ['_design', path[0], '_list', path[1], path[2]].map(querystring.escape).join('/'), - options, - callback + path, + { query: options, cache: cache, raw: true }, + function(err, res, cached, etag) { + if (! err && ! cached) that.cache.save(cachepath, { value: res, headers: { etag: etag } }); + callback(err, (! cached && Array.isArray(res)) ? res.slice(0) : res, etag); + } ); }; // // Helper function which parses options and makes either a `GET` -// or `POST` request to `path` depending on if `options.keys` or -// `options.body` is present. +// or `POST` request to `path` depending on if `options.query.keys` or +// `options.query.body` is present. // Database.prototype._getOrPostView = function (path, options, callback) { - options = parseOptions(options); + var query = parseOptions(options.query); - if (options && options.body) { - body = options.body; - delete options.body; + if (query && query.body) { + options.body = query.body; + delete query.body; - return this.query({ - method: 'POST', - path: path, - query: options, - body: body - }, callback); - } + options.method = 'POST'; + } else { + options.method = 'GET'; + } - return this.query({ - method: 'GET', - path: path, - query: options - }, callback); + options.path = path; + options.query = query; + + return this.query(options, callback); } //