From 428284dae2286b668fe8167a0f10912ee679f32b Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Thu, 23 Feb 2012 23:32:54 -0800 Subject: [PATCH 1/5] Made caching based off etags. --- lib/cradle.js | 8 ++++++++ lib/cradle/database/documents.js | 9 ++++++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/lib/cradle.js b/lib/cradle.js index 9a38a85..08d45d3 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); + } + if (err) { return callback(err); } diff --git a/lib/cradle/database/documents.js b/lib/cradle/database/documents.js index 34e54f0..f8c88a8 100644 --- a/lib/cradle/database/documents.js +++ b/lib/cradle/database/documents.js @@ -53,6 +53,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 @@ -69,11 +70,13 @@ 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 + query: options, + cache: cache }, function (err, res) { if (! err) that.cache.save(res.id, res.json); args.callback(err, res); @@ -246,4 +249,4 @@ Database.prototype.remove = function (id, rev) { if (! err) { that.cache.purge(id) } args.callback(err, res); }); -}; \ No newline at end of file +}; From 7bf25bdf1e82def50b34a311e6def4c7408510c8 Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Sun, 26 Feb 2012 00:02:13 -0800 Subject: [PATCH 2/5] Added caching for views. --- lib/cradle.js | 4 ++-- lib/cradle/cache.js | 32 ++++++++++++++++++++++---------- lib/cradle/database/documents.js | 6 +++--- lib/cradle/database/views.js | 18 +++++++++++++++--- 4 files changed, 42 insertions(+), 18 deletions(-) diff --git a/lib/cradle.js b/lib/cradle.js index 08d45d3..8af2f70 100644 --- a/lib/cradle.js +++ b/lib/cradle.js @@ -177,12 +177,12 @@ cradle.Connection.prototype.request = function (options, callback) { } if (options.cache) { - options.headers["If-None-Match"] = '"' + options.cache.etag + '"'; + 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); + return callback(null, options.cache.store, true); } if (err) { 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 f8c88a8..8397c62 100644 --- a/lib/cradle/database/documents.js +++ b/lib/cradle/database/documents.js @@ -71,14 +71,14 @@ Database.prototype.get = function (id, rev) { else if (typeof(rev) === 'object') { options = rev } } else if (this.cache.has(id)) { cache = { store: this.cache.get(id) }; - cache.etag = cache.store._rev; + cache.etag = '"' + cache.store._rev + '"'; } this.query({ path: cradle.escape(id), query: options, cache: cache - }, function (err, res) { - if (! err) that.cache.save(res.id, res.json); + }, function (err, res, cached) { + if (! err && ! cached) that.cache.save(res.id, res.json); args.callback(err, res); }); } diff --git a/lib/cradle/database/views.js b/lib/cradle/database/views.js index 2f1f26b..02189f5 100644 --- a/lib/cradle/database/views.js +++ b/lib/cradle/database/views.js @@ -29,11 +29,23 @@ Database.prototype.view = function (path, options) { body: body }, args.callback); } else { + var that = this, + cachepath = path + ((options) ? '?' + querystring.stringify(options) : ''); + cache = null; + + if (this.cache.has(cachepath)) { + cache = { store: this.cache.get(cachepath), etag: this.cache.headers(cachepath).etag }; + } + return this.query({ method: 'GET', path: path, - query: options - }, args.callback); + query: options, + cache: cache + }, function(err, res, cached) { + if (! err && ! cached) that.cache.save(cachepath, res); + args.callback(err, (cached) ? res : res.slice(0), that.cache.headers(cachepath).etag); + }); } }; @@ -94,4 +106,4 @@ Database.prototype.list = function (path, options) { path: ['_design', path[0], '_list', path[1], path[2]].map(querystring.escape).join('/'), query: options, }, args.callback); -}; \ No newline at end of file +}; From dd26acba42563840763927e511f7aed2b9b33282 Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Mon, 27 Feb 2012 22:34:45 -0800 Subject: [PATCH 3/5] Added in caching for lists; fixed up views a bit; changed the way lists work so the raw data is actually returned, rather than an empty object if it's not JSON - basically, it bypasses the Response object entirely. --- lib/cradle.js | 8 +++---- lib/cradle/database/views.js | 41 ++++++++++++++++++++++++------------ 2 files changed, 32 insertions(+), 17 deletions(-) diff --git a/lib/cradle.js b/lib/cradle.js index 8af2f70..bb4569b 100644 --- a/lib/cradle.js +++ b/lib/cradle.js @@ -182,7 +182,7 @@ cradle.Connection.prototype.request = function (options, callback) { return this.rawRequest(options, function (err, res, body) { if (options.cache && res.statusCode === 304) { - return callback(null, options.cache.store, true); + return callback(null, options.cache.store, true, res.headers.etag); } if (err) { @@ -196,17 +196,17 @@ cradle.Connection.prototype.request = function (options, callback) { body.headers.status = res.statusCode; return callback(body); } - + try { body = JSON.parse(body) } catch (err) { } - + if (body.error) { cradle.extend(body, { headers: res.headers }); body.headers.status = res.statusCode; 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/database/views.js b/lib/cradle/database/views.js index 02189f5..2866d70 100644 --- a/lib/cradle/database/views.js +++ b/lib/cradle/database/views.js @@ -7,7 +7,10 @@ var querystring = require('querystring'), // Some query string parameters' values have to be JSON-encoded. Database.prototype.view = function (path, options) { var args = new(Args)(arguments), - body; + body, + that = this, + cachepath = path, + cache = null; path = path.split('/'); path = ['_design', path[0], '_view', path[1]].map(querystring.escape).join('/'); @@ -16,6 +19,7 @@ Database.prototype.view = function (path, options) { ['key', 'keys', 'startkey', 'endkey'].forEach(function (k) { if (k in options) { options[k] = JSON.stringify(options[k]) } }); + cachepath += ((options) ? '?' + querystring.stringify(options) : ''); } if (options && options.body) { @@ -29,10 +33,6 @@ Database.prototype.view = function (path, options) { body: body }, args.callback); } else { - var that = this, - cachepath = path + ((options) ? '?' + querystring.stringify(options) : ''); - cache = null; - if (this.cache.has(cachepath)) { cache = { store: this.cache.get(cachepath), etag: this.cache.headers(cachepath).etag }; } @@ -42,9 +42,9 @@ Database.prototype.view = function (path, options) { path: path, query: options, cache: cache - }, function(err, res, cached) { + }, function(err, res, cached, etag) { if (! err && ! cached) that.cache.save(cachepath, res); - args.callback(err, (cached) ? res : res.slice(0), that.cache.headers(cachepath).etag); + args.callback(err, (cached) ? res : res.slice(0), etag); }); } }; @@ -92,18 +92,33 @@ 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 args = new(Args)(arguments); + var args = new(Args)(arguments), + that = this, + cachepath = path, + cache = null; + path = path.split('/'); + path = ['_design', path[0], '_list', path[1], path[2]].map(querystring.escape).join('/'); if (typeof(options) === 'object') { ['key', 'keys', 'startkey', 'endkey'].forEach(function (k) { if (k in options) { options[k] = JSON.stringify(options[k]) } }); + cachepath += '?' + querystring.stringify(options); } - this.query({ - method: 'GET', - path: ['_design', path[0], '_list', path[1], path[2]].map(querystring.escape).join('/'), - query: options, - }, args.callback); + 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); + }); }; From 78439665ce7f03bcfd31d17a808ef22bc32fbd3f Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Mon, 27 Feb 2012 23:01:33 -0800 Subject: [PATCH 4/5] Added shows support in documents.js. Yes, they're cached, too. --- lib/cradle/database/documents.js | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/lib/cradle/database/documents.js b/lib/cradle/database/documents.js index 8397c62..6095c61 100644 --- a/lib/cradle/database/documents.js +++ b/lib/cradle/database/documents.js @@ -250,3 +250,34 @@ Database.prototype.remove = function (id, rev) { args.callback(err, res); }); }; + +// 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); + }); +}; From bf3c3697e07a6773de4a2ccbf4beda565c103462 Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Tue, 20 Mar 2012 22:03:00 -0700 Subject: [PATCH 5/5] Added Travis CI support. --- .travis.yml | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .travis.yml 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