Comparing version 3.1.0 to 3.2.0
@@ -27,13 +27,3 @@ // After starting this example load http://localhost:8080 and hit refresh, you will notice that it loads the response from cache for the first 5 seconds and then reloads the cache. Look at the console to see it setting and getting items from cache. | ||
var logFunc = function (item) { | ||
console.log(item); | ||
}; | ||
var generateFunc = function (next) { | ||
next(null, 'my example'); | ||
}; | ||
internals.policy.getOrGenerate('myExample', logFunc, generateFunc, callback); | ||
internals.policy.get('myExample', callback); | ||
}; | ||
@@ -49,3 +39,9 @@ | ||
var policyOptions = { | ||
expiresIn: 5000 | ||
expiresIn: 5000, | ||
generateFunc: function (id, next) { | ||
var item = 'example'; | ||
console.log(item); | ||
return next(null, item); | ||
} | ||
}; | ||
@@ -52,0 +48,0 @@ |
@@ -19,2 +19,3 @@ // Load modules | ||
this.rule = internals.Policy.compile(options, !!cache); | ||
this._pendings = {}; // id -> [callbacks] | ||
@@ -31,16 +32,21 @@ if (cache) { | ||
internals.Policy.prototype.get = function (id, callback) { | ||
internals.Policy.prototype.get = function (key, callback) { // key: string or { id: 'id' } | ||
var self = this; | ||
if (!this._cache) { | ||
return callback(null, null); | ||
// Check if request is already pending | ||
var id = (key && typeof key === 'object') ? key.id : key; | ||
if (this._pendings[id]) { | ||
this._pendings[id].push(callback); | ||
return; | ||
} | ||
this._cache.get({ segment: this._segment, id: id }, function (err, cached) { | ||
this._pendings[id] = [callback]; | ||
if (err) { | ||
return callback(err); | ||
} | ||
// Lookup in cache | ||
var timer = new Hoek.Timer(); | ||
this._get(id, function (err, cached) { | ||
if (cached) { | ||
@@ -50,64 +56,12 @@ cached.isStale = (self.rule.staleIn ? (Date.now() - cached.stored) >= self.rule.staleIn : false); | ||
return callback(null, cached); | ||
}); | ||
}; | ||
// No generate method | ||
internals.Policy.prototype.set = function (id, value, ttl, callback) { | ||
callback = callback || Hoek.ignore; | ||
if (!this._cache) { | ||
return callback(null); | ||
} | ||
ttl = ttl || internals.Policy.ttl(this.rule); | ||
this._cache.set({ segment: this._segment, id: id }, value, ttl, callback); | ||
}; | ||
internals.Policy.prototype.drop = function (id, callback) { | ||
callback = callback || Hoek.ignore; | ||
if (!this._cache) { | ||
return callback(null); | ||
} | ||
this._cache.drop({ segment: this._segment, id: id }, callback); | ||
}; | ||
internals.Policy.prototype.ttl = function (created) { | ||
return internals.Policy.ttl(this.rule, created); | ||
}; | ||
internals.Policy.prototype.getOrGenerate = function (id, generateFunc, callback) { | ||
var self = this; | ||
// Check if cache enabled | ||
if (!this._cache) { | ||
return self._validate(id, null, generateFunc, null, callback); | ||
} | ||
// Lookup in cache | ||
var timer = new Hoek.Timer(); | ||
this.get(id, function (err, cached) { | ||
// Error | ||
if (err) { | ||
report = err; | ||
return self._validate(id, null, generateFunc, err, callback); | ||
if (!self.rule.generateFunc) { | ||
return self._finalize(id, err, cached); // Pass 'cached' as 'value' and omit other arguments for backwards compatibility | ||
} | ||
// Not found | ||
// Error / Not found | ||
if (!cached) { | ||
return self._validate(id, null, generateFunc, { msec: timer.elapsed() }, callback); | ||
if (err || !cached) { | ||
return self._generate(id, key, null, { msec: timer.elapsed(), error: err }, callback); | ||
} | ||
@@ -124,3 +78,9 @@ | ||
return self._validate(id, cached, generateFunc, report, callback); | ||
// Check if found and fresh | ||
if (!cached.isStale) { | ||
return self._finalize(id, null, cached.item, cached, report); | ||
} | ||
return self._generate(id, key, cached, report, callback); | ||
}); | ||
@@ -130,18 +90,16 @@ }; | ||
internals.Policy.prototype._validate = function (id, cached, generateFunc, report, callback) { | ||
internals.Policy.prototype._get = function (id, callback) { | ||
var self = this; | ||
if (!this._cache) { | ||
return Hoek.nextTick(callback)(null, null); | ||
} | ||
// Check if found and fresh | ||
this._cache.get({ segment: this._segment, id: id }, callback); | ||
}; | ||
if (cached && | ||
!cached.isStale) { | ||
return callback(null, cached.item, cached, report); | ||
} | ||
internals.Policy.prototype._generate = function (id, key, cached, report, callback) { | ||
// Not in cache, or cache stale | ||
var self = this; | ||
callback = Hoek.once(callback); // Return only the first callback between stale timeout and generated fresh | ||
if (cached && | ||
@@ -156,3 +114,3 @@ cached.isStale) { | ||
return callback(null, cached.item, cached, report); | ||
return self._finalize(id, null, cached.item, cached, report); | ||
}, this.rule.staleTimeout); | ||
@@ -167,3 +125,3 @@ } | ||
return callback(Boom.serverTimeout(), null, null, report); | ||
return self._finalize(id, Boom.serverTimeout(), null, null, report); | ||
}, this.rule.generateTimeout); | ||
@@ -174,3 +132,3 @@ } | ||
generateFunc.call(null, function (err, value, ttl) { | ||
this.rule.generateFunc.call(null, key, function (err, value, ttl) { | ||
@@ -188,3 +146,3 @@ // Error or not cached | ||
return callback(err, value, null, report); // Ignored if stale value already returned | ||
return self._finalize(id, err, value, null, report); // Ignored if stale value already returned | ||
}); | ||
@@ -194,11 +152,74 @@ }; | ||
internals.Policy.prototype._finalize = function (id, err, value, cached, report) { | ||
var pendings = this._pendings[id]; | ||
if (!pendings) { | ||
return; | ||
} | ||
delete this._pendings[id]; // Return only the first callback between stale timeout and generated fresh | ||
for (var i = 0, il = pendings.length; i < il; ++i) { | ||
pendings[i](err, value, cached, report); | ||
} | ||
}; | ||
internals.Policy.prototype.getOrGenerate = function (id, generateFunc, callback) { // For backwards compatibility | ||
var self = this; | ||
this.rule.generateFunc = function (id, next) { | ||
self.rule.generateFunc = null; | ||
return generateFunc(next); | ||
}; | ||
return this.get(id, callback); | ||
}; | ||
internals.Policy.prototype.set = function (key, value, ttl, callback) { | ||
callback = callback || Hoek.ignore; | ||
if (!this._cache) { | ||
return callback(null); | ||
} | ||
ttl = ttl || internals.Policy.ttl(this.rule); | ||
var id = (key && typeof key === 'object') ? key.id : key; | ||
this._cache.set({ segment: this._segment, id: id }, value, ttl, callback); | ||
}; | ||
internals.Policy.prototype.drop = function (id, callback) { | ||
callback = callback || Hoek.ignore; | ||
if (!this._cache) { | ||
return callback(null); | ||
} | ||
this._cache.drop({ segment: this._segment, id: id }, callback); | ||
}; | ||
internals.Policy.prototype.ttl = function (created) { | ||
return internals.Policy.ttl(this.rule, created); | ||
}; | ||
internals.Policy.compile = function (options, serverSide) { | ||
/* | ||
* { | ||
* expiresIn: 30000, | ||
* expiresAt: '13:00', | ||
* staleIn: 20000, | ||
* staleTimeout: 500, | ||
* generateTimeout: 500 | ||
* } | ||
{ | ||
expiresIn: 30000, | ||
expiresAt: '13:00', | ||
generateFunc: function (id, next) { next(err, result, ttl); } | ||
generateTimeout: 500, | ||
staleIn: 20000, | ||
staleTimeout: 500 | ||
} | ||
*/ | ||
@@ -222,2 +243,4 @@ | ||
Hoek.assert(!options.staleTimeout || !options.expiresIn || options.staleTimeout < (options.expiresIn - options.staleIn), 'staleTimeout must be less than the delta between expiresIn and staleIn'); | ||
// Hoek.assert(options.generateFunc || !options.generateTimeout, 'Rule cannot include generateTimeout without generateFunc'); // Disabled for backwards compatibility | ||
Hoek.assert(!options.generateFunc || typeof options.generateFunc === 'function', 'generateFunc must be a function'); | ||
@@ -245,5 +268,15 @@ // Expiration | ||
// generateTimeout | ||
if (options.generateFunc) { | ||
rule.generateFunc = options.generateFunc; | ||
} | ||
if (options.generateTimeout) { // Keep outside options.generateFunc condition for backwards compatibility | ||
rule.generateTimeout = options.generateTimeout; | ||
} | ||
// Stale | ||
if (options.staleIn) { | ||
if (options.staleIn) { // Keep outside options.generateFunc condition for backwards compatibility | ||
Hoek.assert(serverSide, 'Cannot use stale options without server-side caching'); | ||
@@ -254,8 +287,2 @@ rule.staleIn = options.staleIn; | ||
// generateTimeout | ||
if (options.generateTimeout) { | ||
rule.generateTimeout = options.generateTimeout; | ||
} | ||
return rule; | ||
@@ -265,5 +292,5 @@ }; | ||
internals.Policy.ttl = function (rule, created) { | ||
internals.Policy.ttl = function (rule, created, now) { | ||
var now = Date.now(); | ||
now = now || Date.now(); | ||
created = created || now; | ||
@@ -277,24 +304,26 @@ var age = now - created; | ||
if (rule.expiresIn) { | ||
var ttl = rule.expiresIn - age; | ||
return (ttl > 0 ? ttl : 0); // Can be negative | ||
return Math.max(rule.expiresIn - age, 0); | ||
} | ||
if (rule.expiresAt) { | ||
if (created !== now && | ||
now - created > internals.day) { // If the item was created more than a 24 hours ago | ||
if (age > internals.day) { // If the item was created more than a 24 hours ago | ||
return 0; | ||
} | ||
var expiresAt = new Date(created); // Assume everything expires in relation to now | ||
var expiresAt = new Date(created); // Compare expiration time on the same day | ||
expiresAt.setHours(rule.expiresAt.hours); | ||
expiresAt.setMinutes(rule.expiresAt.minutes); | ||
expiresAt.setSeconds(0); | ||
expiresAt.setMilliseconds(0); | ||
var expires = expiresAt.getTime(); | ||
var expiresIn = expiresAt.getTime() - created; | ||
if (expiresIn <= 0) { | ||
expiresIn += internals.day; // Time passed for today, move to tomorrow | ||
if (expires <= created) { | ||
expires += internals.day; // Move to tomorrow | ||
} | ||
return expiresIn - age; | ||
if (now >= expires) { // Expired | ||
return 0; | ||
} | ||
return expires - now; | ||
} | ||
@@ -301,0 +330,0 @@ |
{ | ||
"name": "catbox", | ||
"description": "Multi-strategy object caching service", | ||
"version": "3.1.0", | ||
"version": "3.2.0", | ||
"author": "Eran Hammer <eran@hammer.io> (http://hueniverse.com)", | ||
"contributors": [ | ||
"Wyatt Preul <wpreul@gmail.com> (http://jsgeek.com)" | ||
"Wyatt Preul <wpreul@gmail.com> (http://jsgeek.com)", | ||
"Ben Acker <benacker@gmail.com>" | ||
], | ||
@@ -22,3 +23,3 @@ "repository": "git://github.com/hapijs/catbox", | ||
"devDependencies": { | ||
"lab": "3.x.x" | ||
"lab": "4.x.x" | ||
}, | ||
@@ -25,0 +26,0 @@ "scripts": { |
@@ -8,3 +8,3 @@ ![catbox Logo](https://raw.github.com/hapijs/catbox/master/images/catbox.png) | ||
Lead Maintainer: [Van Nguyen](https://github.com/thegoleffect) | ||
Lead Maintainer: [Ben Acker](https://github.com/nvcexploder) | ||
@@ -82,5 +82,13 @@ **catbox** is a multi-strategy key-value object store. It comes with extensions supporting a memory cache, | ||
expire. Uses local time. Cannot be used together with `expiresIn`. | ||
- `staleIn` - number of milliseconds to mark an item stored in cache as stale and reload it. Must be less than `expiresIn`. | ||
- `generateFunc` - a function used to generate a new cache item if one is not found in the cache when calling `get()`. The method's | ||
signature is `function(id, next)` where: | ||
- `id` - the `id` string or object provided to the `get()` method. | ||
- `next` - the method called when the new item is returned with the signature `function(err, value, ttl)` where: | ||
- `err` - an error condition. | ||
- `value` - the new value generated. | ||
- `ttl` - the cache ttl value in milliseconds. Set to `0` to skip storing in the cache. Defaults to the cache global policy. | ||
- `staleIn` - number of milliseconds to mark an item stored in cache as stale and attempt to regenerate it when `generateFunc` is | ||
provided. Must be less than `expiresIn`. | ||
- `staleTimeout` - number of milliseconds to wait before checking if an item is stale. | ||
- `generateTimeout` - number of milliseconds to wait before returning a timeout error when the `getOrGenerate()` `generateFunc` function | ||
- `generateTimeout` - number of milliseconds to wait before returning a timeout error when the `generateFunc` function | ||
takes too long to return a value. When the value is eventually returned, it is stored in the cache for future requests. | ||
@@ -95,6 +103,20 @@ - `cache` - a `Client` instance (which has already been started). | ||
- `get(id, callback)` - retrieve an item from the cache where: | ||
- `id` - the unique item identifier (within the policy segment). | ||
- `callback` - a function with the signature `function(err, cached)` where `cached` is the object returned by the `client.get()` with | ||
the additional `isStale` boolean key. | ||
- `get(id, callback)` - retrieve an item from the cache. If the item is not found and the `generateFunc` method was provided, a new value | ||
is generated, stored in the cache, and returned. Multiple concurrent requests are queued and processed once. The method arguments are: | ||
- `id` - the unique item identifier (within the policy segment). Can be a string or an object with the required 'id' key. | ||
- `callback` - the return function. The function signature is based on the `generateFunc` settings. If the `generateFunc` is not set, | ||
the signature is `function(err, cached)`. Otherwise, the signature is `function(err, value, cached, report)` where: | ||
- `err` - any errors encountered. | ||
- `value` - the fetched or generated value. | ||
- `cached` - `null` if a valid item was not found in the cache, or an object with the following keys: | ||
- `item` - the cached `value`. | ||
- `stored` - the timestamp when the item was stored in the cache. | ||
- `ttl` - the cache ttl value for the record. | ||
- `isStale` - `true` if the item is stale. | ||
- `report` - an object with logging information about the generation operation containing the following keys (as relevant): | ||
- `msec` - the cache lookup time in milliseconds. | ||
- `stored` - the timestamp when the item was stored in the cache. | ||
- `isStale` - `true` if the item is stale. | ||
- `ttl` - the cache ttl value for the record. | ||
- `error` - lookup error. | ||
- `set(id, value, ttl, callback)` - store an item in the cache where: | ||
@@ -110,14 +132,2 @@ - `id` - the unique item identifier (within the policy segment). | ||
- `ttl(created)` - given a `created` timestamp in milliseconds, returns the time-to-live left based on the configured rules. | ||
- `getOrGenerate(id, generateFunc, callback)` - get an item from the cache if found, otherwise calls the `generateFunc` to produce a new value | ||
and stores it in the cache. This method applies the staleness rules. Its arguments are: | ||
- `id` - the unique item identifier (within the policy segment). | ||
- `generateFunc` - the function used to generate a new cache item if one is not found in the cache. The method's signature is | ||
`function(err, value, ttl)` where: | ||
- `err` - an error condition. | ||
- `value` - the new value generated. | ||
- `ttl` - the cache ttl value in milliseconds. Set to `0` to skip storing in the cache. Defaults to the cache global policy. | ||
- `callback` - a function with the signature `function(err, value, cached, report)` where: | ||
- `err` - any errors encountered. | ||
- `value` - the fetched or generated value. | ||
- `cached` - the `cached` object returned by `policy.get()` is the item was found in the cache. | ||
- `report` - an object with logging information about the operation. | ||
@@ -14,7 +14,8 @@ // Load modules | ||
var lab = exports.lab = Lab.script(); | ||
var expect = Lab.expect; | ||
var before = Lab.before; | ||
var after = Lab.after; | ||
var describe = Lab.experiment; | ||
var it = Lab.test; | ||
var before = lab.before; | ||
var after = lab.after; | ||
var describe = lab.experiment; | ||
var it = lab.test; | ||
@@ -21,0 +22,0 @@ |
@@ -15,7 +15,8 @@ // Load modules | ||
var lab = exports.lab = Lab.script(); | ||
var expect = Lab.expect; | ||
var before = Lab.before; | ||
var after = Lab.after; | ||
var describe = Lab.experiment; | ||
var it = Lab.test; | ||
var before = lab.before; | ||
var after = lab.after; | ||
var describe = lab.experiment; | ||
var it = lab.test; | ||
@@ -22,0 +23,0 @@ |
@@ -15,7 +15,6 @@ // Load modules | ||
var lab = exports.lab = Lab.script(); | ||
var expect = Lab.expect; | ||
var before = Lab.before; | ||
var after = Lab.after; | ||
var describe = Lab.experiment; | ||
var it = Lab.test; | ||
var describe = lab.experiment; | ||
var it = lab.test; | ||
@@ -28,3 +27,3 @@ | ||
var client = new Catbox.Client(Import); | ||
var cache = new Catbox.Policy({ expiresIn: 1000 }, client, 'test'); | ||
var policy = new Catbox.Policy({ expiresIn: 1000 }, client, 'test'); | ||
@@ -35,7 +34,7 @@ client.start(function (err) { | ||
cache.set('x', '123', null, function (err) { | ||
policy.set('x', '123', null, function (err) { | ||
expect(err).to.not.exist; | ||
cache.get('x', function (err, result) { | ||
policy.get('x', function (err, result) { | ||
@@ -53,3 +52,3 @@ expect(err).to.not.exist; | ||
var client = new Catbox.Client(Import); | ||
var cache = new Catbox.Policy({}, client, 'test'); | ||
var policy = new Catbox.Policy({}, client, 'test'); | ||
@@ -60,6 +59,6 @@ client.start(function (err) { | ||
cache.set('x', '123', null, function (err) { | ||
policy.set('x', '123', null, function (err) { | ||
expect(err).to.not.exist; | ||
cache.get('x', function (err, result) { | ||
policy.get('x', function (err, result) { | ||
@@ -77,3 +76,3 @@ expect(err).to.not.exist; | ||
var client = new Catbox.Client(Import); | ||
var cache = new Catbox.Policy({}, client, 'test'); | ||
var policy = new Catbox.Policy({}, client, 'test'); | ||
@@ -84,6 +83,6 @@ client.start(function (err) { | ||
cache.set('x', '123', 1000, function (err) { | ||
policy.set('x', '123', 1000, function (err) { | ||
expect(err).to.not.exist; | ||
cache.get('x', function (err, result) { | ||
policy.get('x', function (err, result) { | ||
@@ -100,6 +99,5 @@ expect(err).to.not.exist; | ||
var cache = new Catbox.Policy({ expiresIn: 1 }); | ||
var policy = new Catbox.Policy({ expiresIn: 1 }); | ||
var key = { id: 'x', segment: 'test' }; | ||
cache.get(key, function (err, result) { | ||
policy.get('x', function (err, result) { | ||
@@ -114,6 +112,5 @@ expect(err).to.not.exist; | ||
var cache = new Catbox.Policy({ expiresIn: 1 }); | ||
var policy = new Catbox.Policy({ expiresIn: 1 }); | ||
var key = { id: 'x', segment: 'test' }; | ||
cache.set(key, 'y', 100, function (err) { | ||
policy.set('x', 'y', 100, function (err) { | ||
@@ -127,6 +124,5 @@ expect(err).to.not.exist; | ||
var cache = new Catbox.Policy({ expiresIn: 1 }); | ||
var policy = new Catbox.Policy({ expiresIn: 1 }); | ||
var key = { id: 'x', segment: 'test' }; | ||
cache.drop(key, function (err) { | ||
policy.drop('x', function (err) { | ||
@@ -161,2 +157,49 @@ expect(err).to.not.exist; | ||
it('returns cached item using object id', function (done) { | ||
var client = new Catbox.Client(Import); | ||
var policy = new Catbox.Policy({ expiresIn: 1000 }, client, 'test'); | ||
client.start(function (err) { | ||
expect(err).to.not.exist; | ||
policy.set({ id: 'x' }, '123', null, function (err) { | ||
expect(err).to.not.exist; | ||
policy.get({ id: 'x' }, function (err, result) { | ||
expect(err).to.not.exist; | ||
expect(result.item).to.equal('123'); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
it('returns error on null id', function (done) { | ||
var client = new Catbox.Client(Import); | ||
var policy = new Catbox.Policy({ expiresIn: 1000 }, client, 'test'); | ||
client.start(function (err) { | ||
expect(err).to.not.exist; | ||
policy.set(null, '123', null, function (err) { | ||
expect(err).to.exist; | ||
expect(err.message).to.equal('Invalid key'); | ||
policy.get(null, function (err, result) { | ||
expect(err).to.exist; | ||
expect(err.message).to.equal('Invalid key'); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
it('passes an error to the callback when an error occurs getting the item', function (done) { | ||
@@ -189,3 +232,3 @@ | ||
policy.get({ id: 'test1', segment: 'test2' }, function (err, result) { | ||
policy.get('test1', function (err, result) { | ||
@@ -228,3 +271,3 @@ expect(err).to.be.instanceOf(Error); | ||
policy.get({ id: 'test1', segment: 'test2' }, function (err, result) { | ||
policy.get('test1', function (err, result) { | ||
@@ -236,46 +279,498 @@ expect(result.item).to.equal('item'); | ||
}); | ||
}); | ||
describe('#getOrGenerate', function () { | ||
describe('generate', function () { | ||
it('returns falsey items', function (done) { | ||
it('returns falsey items', function (done) { | ||
var engine = { | ||
start: function (callback) { | ||
var engine = { | ||
start: function (callback) { | ||
callback(); | ||
}, | ||
isReady: function () { | ||
callback(); | ||
}, | ||
isReady: function () { | ||
return true; | ||
}, | ||
get: function (key, callback) { | ||
return true; | ||
}, | ||
get: function (key, callback) { | ||
callback(null, { | ||
stored: false, | ||
item: false | ||
callback(null, { | ||
stored: false, | ||
item: false | ||
}); | ||
}, | ||
validateSegmentName: function () { | ||
return null; | ||
} | ||
}; | ||
var policyConfig = { | ||
expiresIn: 50000, | ||
generateFunc: function (id, next) { | ||
return next(null, false); | ||
} | ||
}; | ||
var client = new Catbox.Client(engine); | ||
var policy = new Catbox.Policy(policyConfig, client, 'test'); | ||
policy.get('test1', function (err, item) { | ||
expect(err).to.equal(null); | ||
expect(item).to.equal(false); | ||
done(); | ||
}); | ||
}); | ||
it('bypasses cache when not configured', function (done) { | ||
var policy = new Catbox.Policy({ | ||
expiresIn: 1, | ||
generateFunc: function (id, next) { | ||
return next(null, 'new result'); | ||
} | ||
}); | ||
policy.get('test', function (err, value, cached) { | ||
expect(err).to.not.exist; | ||
expect(value).to.equal('new result'); | ||
expect(cached).to.not.exist; | ||
done(); | ||
}); | ||
}); | ||
it('returns the processed cached item', function (done) { | ||
var rule = { | ||
expiresIn: 100, | ||
staleIn: 20, | ||
staleTimeout: 5, | ||
generateFunc: function (id, next) { | ||
return next(null, { gen: ++gen }); | ||
} | ||
}; | ||
var client = new Catbox.Client(Import, { partition: 'test-partition' }); | ||
var policy = new Catbox.Policy(rule, client, 'test-segment'); | ||
var gen = 0; | ||
client.start(function () { | ||
policy.get('test', function (err, value, cached) { | ||
expect(value.gen).to.equal(1); | ||
done(); | ||
}); | ||
}, | ||
validateSegmentName: function () { | ||
}); | ||
}); | ||
return null; | ||
} | ||
}; | ||
var policyConfig = { | ||
expiresIn: 50000 | ||
}; | ||
it('returns the processed cached item after cache error', function (done) { | ||
function generateFunc(next) { | ||
next(null, false); | ||
} | ||
var rule = { | ||
expiresIn: 100, | ||
staleIn: 20, | ||
staleTimeout: 5, | ||
generateFunc: function (id, next) { | ||
var client = new Catbox.Client(engine); | ||
var policy = new Catbox.Policy(policyConfig, client, 'test'); | ||
return next(null, { gen: ++gen }); | ||
} | ||
}; | ||
policy.getOrGenerate('test1', generateFunc, function (err, item) { | ||
var client = new Catbox.Client(Import, { partition: 'test-partition' }); | ||
client.get = function (key, callback) { callback(new Error('bad client')); }; | ||
var policy = new Catbox.Policy(rule, client, 'test-segment'); | ||
expect(err).to.equal(null); | ||
expect(item).to.equal(false); | ||
done(); | ||
var gen = 0; | ||
client.start(function () { | ||
policy.get('test', function (err, value, cached) { | ||
expect(value.gen).to.equal(1); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
it('returns the processed cached item using manual ttl', function (done) { | ||
var rule = { | ||
expiresIn: 26, | ||
staleIn: 20, | ||
staleTimeout: 5, | ||
generateFunc: function (id, next) { | ||
setTimeout(function () { | ||
return next(null, { gen: ++gen }, 100); | ||
}, 6); | ||
} | ||
}; | ||
var client = new Catbox.Client(Import, { partition: 'test-partition' }); | ||
var policy = new Catbox.Policy(rule, client, 'test-segment'); | ||
var gen = 0; | ||
client.start(function () { | ||
policy.get('test', function (err, value1, cached) { | ||
expect(value1.gen).to.equal(1); // Fresh | ||
setTimeout(function () { | ||
policy.get('test', function (err, value2, cached) { | ||
expect(value2.gen).to.equal(1); // Stale | ||
done(); | ||
}); | ||
}, 27); | ||
}); | ||
}); | ||
}); | ||
it('returns stale object then fresh object based on timing', function (done) { | ||
var rule = { | ||
expiresIn: 100, | ||
staleIn: 20, | ||
staleTimeout: 5, | ||
generateFunc: function (id, next) { | ||
setTimeout(function () { | ||
return next(null, { gen: ++gen }, 100); | ||
}, 6); | ||
} | ||
}; | ||
var client = new Catbox.Client(Import, { partition: 'test-partition' }); | ||
var policy = new Catbox.Policy(rule, client, 'test-segment'); | ||
var gen = 0; | ||
client.start(function () { | ||
policy.get('test', function (err, value1, cached) { | ||
expect(value1.gen).to.equal(1); // Fresh | ||
setTimeout(function () { | ||
policy.get('test', function (err, value2, cached) { | ||
expect(value2.gen).to.equal(1); // Stale | ||
setTimeout(function () { | ||
policy.get('test', function (err, value3, cached) { | ||
expect(value3.gen).to.equal(2); // Fresh | ||
done(); | ||
}); | ||
}, 3); | ||
}); | ||
}, 21); | ||
}); | ||
}); | ||
}); | ||
it('returns stale object then invalidate cache on error', function (done) { | ||
var rule = { | ||
expiresIn: 100, | ||
staleIn: 20, | ||
staleTimeout: 5, | ||
generateFunc: function (id, next) { | ||
++gen; | ||
setTimeout(function () { | ||
if (gen !== 2) { | ||
return next(null, { gen: gen }); | ||
} | ||
return next(new Error()); | ||
}, 6); | ||
} | ||
}; | ||
var client = new Catbox.Client(Import, { partition: 'test-partition' }); | ||
var policy = new Catbox.Policy(rule, client, 'test-segment'); | ||
var gen = 0; | ||
client.start(function () { | ||
policy.get('test', function (err, value1, cached) { | ||
expect(value1.gen).to.equal(1); // Fresh | ||
setTimeout(function () { | ||
policy.get('test', function (err, value2, cached) { | ||
// Generates a new one in background which will produce Error and clear the cache | ||
expect(value2.gen).to.equal(1); // Stale | ||
setTimeout(function () { | ||
policy.get('test', function (err, value3, cached) { | ||
expect(value3.gen).to.equal(3); // Fresh | ||
done(); | ||
}); | ||
}, 3); | ||
}); | ||
}, 21); | ||
}); | ||
}); | ||
}); | ||
it('returns fresh objects', function (done) { | ||
var rule = { | ||
expiresIn: 100, | ||
staleIn: 20, | ||
staleTimeout: 10, | ||
generateFunc: function (id, next) { | ||
return next(null, { gen: ++gen }); | ||
} | ||
}; | ||
var client = new Catbox.Client(Import, { partition: 'test-partition' }); | ||
var policy = new Catbox.Policy(rule, client, 'test-segment'); | ||
var gen = 0; | ||
client.start(function () { | ||
policy.get('test', function (err, value1, cached) { | ||
expect(value1.gen).to.equal(1); // Fresh | ||
setTimeout(function () { | ||
policy.get('test', function (err, value2, cached) { | ||
expect(value2.gen).to.equal(2); // Fresh | ||
setTimeout(function () { | ||
policy.get('test', function (err, value3, cached) { | ||
expect(value3.gen).to.equal(2); // Fresh | ||
done(); | ||
}); | ||
}, 1); | ||
}); | ||
}, 21); | ||
}); | ||
}); | ||
}); | ||
it('returns error when generated within stale timeout', function (done) { | ||
var rule = { | ||
expiresIn: 30, | ||
staleIn: 20, | ||
staleTimeout: 5, | ||
generateFunc: function (id, next) { | ||
++gen; | ||
if (gen !== 2) { | ||
return next(null, { gen: gen }); | ||
} | ||
return next(new Error()); | ||
} | ||
}; | ||
var client = new Catbox.Client(Import, { partition: 'test-partition' }); | ||
var policy = new Catbox.Policy(rule, client, 'test-segment'); | ||
var gen = 0; | ||
client.start(function () { | ||
policy.get('test', function (err, value1, cached) { | ||
expect(value1.gen).to.equal(1); // Fresh | ||
setTimeout(function () { | ||
policy.get('test', function (err, value2, cached) { | ||
// Generates a new one which will produce Error | ||
expect(err).to.be.instanceof(Error); // Stale | ||
done(); | ||
}); | ||
}, 21); | ||
}); | ||
}); | ||
}); | ||
it('returns new object when stale has less than staleTimeout time left', function (done) { | ||
var rule = { | ||
expiresIn: 25, | ||
staleIn: 15, | ||
staleTimeout: 5, | ||
generateFunc: function (id, next) { | ||
return next(null, { gen: ++gen }); | ||
} | ||
}; | ||
var client = new Catbox.Client(Import, { partition: 'test-partition' }); | ||
var policy = new Catbox.Policy(rule, client, 'test-segment'); | ||
var gen = 0; | ||
client.start(function () { | ||
policy.get('test', function (err, value1, cached) { | ||
expect(value1.gen).to.equal(1); // Fresh | ||
setTimeout(function () { | ||
policy.get('test', function (err, value2, cached) { | ||
expect(value2.gen).to.equal(1); // Fresh | ||
setTimeout(function () { | ||
policy.get('test', function (err, value3, cached) { | ||
expect(value3.gen).to.equal(2); // Fresh | ||
done(); | ||
}); | ||
}, 11); | ||
}); | ||
}, 10); | ||
}); | ||
}); | ||
}); | ||
it('invalidates cache on error without stale', function (done) { | ||
var rule = { | ||
expiresIn: 20, | ||
staleIn: 5, | ||
staleTimeout: 5, | ||
generateFunc: function (id, next) { | ||
++gen; | ||
if (gen === 2) { | ||
return next(new Error()); | ||
} | ||
return next(null, { gen: gen }); | ||
} | ||
}; | ||
var client = new Catbox.Client(Import, { partition: 'test-partition' }); | ||
var policy = new Catbox.Policy(rule, client, 'test-segment'); | ||
var gen = 0; | ||
client.start(function () { | ||
policy.get('test', function (err, value1, cached) { | ||
expect(value1.gen).to.equal(1); // Fresh | ||
setTimeout(function () { | ||
policy.get('test', function (err, value2, cached) { | ||
expect(err).to.exist; | ||
policy._get('test', function (err, value3) { | ||
expect(value3).to.equal(null); | ||
done(); | ||
}); | ||
}); | ||
}, 8); | ||
}); | ||
}); | ||
}); | ||
it('returns timeout error when generate takes too long', function (done) { | ||
var rule = { | ||
expiresIn: 10, | ||
generateTimeout: 5, | ||
generateFunc: function (id, next) { | ||
setTimeout(function () { | ||
return next(null, { gen: ++gen }); | ||
}, 6); | ||
} | ||
}; | ||
var client = new Catbox.Client(Import, { partition: 'test-partition' }); | ||
var policy = new Catbox.Policy(rule, client, 'test-segment'); | ||
var gen = 0; | ||
client.start(function () { | ||
policy.get('test', function (err, value1, cached) { | ||
expect(err.output.statusCode).to.equal(503); | ||
setTimeout(function () { | ||
policy.get('test', function (err, value2, cached) { | ||
expect(value2.gen).to.equal(1); | ||
setTimeout(function () { | ||
policy.get('test', function (err, value3, cached) { | ||
expect(err.output.statusCode).to.equal(503); | ||
done(); | ||
}); | ||
}, 10); | ||
}); | ||
}, 2); | ||
}); | ||
}); | ||
}); | ||
it('queues requests while pending', function (done) { | ||
var gen = 0; | ||
var rule = { | ||
expiresIn: 100, | ||
generateFunc: function (id, next) { | ||
return next(null, { gen: ++gen }); | ||
} | ||
}; | ||
var client = new Catbox.Client(Import, { partition: 'test-partition' }); | ||
var policy = new Catbox.Policy(rule, client, 'test-segment'); | ||
client.start(function () { | ||
var result = null; | ||
var compare = function (err, value, cached) { | ||
if (!result) { | ||
result = value; | ||
return; | ||
} | ||
expect(result).to.equal(value); | ||
done(); | ||
}; | ||
policy.get('test', compare); | ||
policy.get('test', compare); | ||
}); | ||
}); | ||
}); | ||
@@ -349,5 +844,85 @@ }); | ||
var result = policy.ttl(Date.now() - 10000); | ||
expect(result).to.be.within(39999, 40001); // There can occassionally be a 1ms difference | ||
expect(result).to.be.within(39999, 40001); // There can occasionally be a 1ms difference | ||
done(); | ||
}); | ||
it('returns expired when created in the future', function (done) { | ||
var config = { | ||
expiresAt: '10:00' | ||
}; | ||
var rules = new Catbox.Policy.compile(config); | ||
var created = new Date('Sat Sep 06 2014 13:00:00').getTime(); | ||
var now = new Date('Sat Sep 06 2014 12:00:00').getTime(); | ||
var ttl = Catbox.Policy.ttl(rules, created, now); | ||
expect(ttl).to.equal(0); | ||
done(); | ||
}); | ||
it('returns expired on c-e-n same day', function (done) { | ||
var config = { | ||
expiresAt: '10:00' | ||
}; | ||
var rules = new Catbox.Policy.compile(config); | ||
var created = new Date('Sat Sep 06 2014 9:00:00').getTime(); | ||
var now = new Date('Sat Sep 06 2014 11:00:00').getTime(); | ||
var ttl = Catbox.Policy.ttl(rules, created, now); | ||
expect(ttl).to.equal(0); | ||
done(); | ||
}); | ||
it('returns expired on c-(midnight)-e-n', function (done) { | ||
var config = { | ||
expiresAt: '10:00' | ||
}; | ||
var rules = new Catbox.Policy.compile(config); | ||
var created = new Date('Sat Sep 06 2014 11:00:00').getTime(); | ||
var now = new Date('Sat Sep 07 2014 10:00:01').getTime(); | ||
var ttl = Catbox.Policy.ttl(rules, created, now); | ||
expect(ttl).to.equal(0); | ||
done(); | ||
}); | ||
it('returns ttl on c-n-e same day', function (done) { | ||
var config = { | ||
expiresAt: '10:00' | ||
}; | ||
var rules = new Catbox.Policy.compile(config); | ||
var created = new Date('Sat Sep 06 2014 9:00:00').getTime(); | ||
var now = new Date('Sat Sep 06 2014 9:30:00').getTime(); | ||
var ttl = Catbox.Policy.ttl(rules, created, now); | ||
expect(ttl).to.equal(30 * 60 * 1000); | ||
done(); | ||
}); | ||
it('returns ttl on c-(midnight)-n-e', function (done) { | ||
var config = { | ||
expiresAt: '10:00' | ||
}; | ||
var rules = new Catbox.Policy.compile(config); | ||
var created = new Date('Sat Sep 06 2014 11:00:00').getTime(); | ||
var now = new Date('Sat Sep 07 2014 9:00:00').getTime(); | ||
var ttl = Catbox.Policy.ttl(rules, created, now); | ||
expect(ttl).to.equal(60 * 60 * 1000); | ||
done(); | ||
}); | ||
}); | ||
@@ -386,3 +961,3 @@ | ||
var client = new Catbox.Client(Import); | ||
var cache = new Catbox.Policy(config, client); | ||
var policy = new Catbox.Policy(config, client); | ||
}; | ||
@@ -559,3 +1134,3 @@ | ||
var cache = new Catbox.Policy(config); | ||
var policy = new Catbox.Policy(config); | ||
}; | ||
@@ -851,3 +1426,3 @@ | ||
describe('getOrGenerate', function () { | ||
describe('#getOrGenerate', function () { | ||
@@ -1274,1 +1849,2 @@ it('bypasses cache when not configured', function (done) { | ||
149370
17
2210
130