Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,48 @@ which should print
* note that `require('cache')` would return the default instance of Cache
* while `require('cache').Cache` is the actual class

# HitCache
This is a wrapper on top of the cache which provides time & hits based cache expiry, simply put content which are fetched frequently stay in the cache for longer vs content which do not have same frequency get pushed out of it.

## Example:
```javascript
const cache = require('memory-cache');
const hitCacheType = require('memory-cache').HitCache;
const hitCache = new hitCacheType(cache);

hitCache.set("Laukik", "Disappears in 5 seconds, cause not hits.", 5000);

setTimeout(() => {
console.log("Laukik: " + hitCache.get("Laukik")); //Expected: Null
}, 6000);


hitCache.set("Popular-Laukik", "Will not disapper in 5 seconds, cause it got hit.", 5000);

//IMP:Every call to get for a given key will increase the life by lifespan param provided at the time of set.

setTimeout(() => {
console.log("Popular-Laukik: " + hitCache.get("Popular-Laukik")); //Expected: "Will not disapper in 5 seconds, cause it got hit."
}, 2000);

setTimeout(() => {
console.log("Popular-Laukik: " + hitCache.get("Popular-Laukik")); //Expected: "Will not disapper in 5 seconds, cause it got hit."
}, 6000);
```

## API
### Constructor `constructor(cache)`
* Parameter `cache`: Instance of the cache which should hold values. Eg:`require('cache').Cache` or `require('cache')`

### set `set(key, value, lifeSpan)`
* Parameter `key` : Key for the cached value.
* Parameter `value` : Content to be cached.
* Parameter `lifeSpan` : Default expiry time for the content, after which it will be evicted from cache unless it has hits.

### get `get(key)`
* Parameter `key` : Key for the cached value.
* Returns `null` if the key is not found, else returns content and increases its lifespan by lifespan parameter defined at the time of set.

## Note on Patches/Pull Requests

* Fork the project.
Expand Down
6 changes: 4 additions & 2 deletions gulpfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,13 @@ var istanbul = require('gulp-istanbul');
/****************/
var paths = {
js: [
'index.js'
'index.js',
'./hit-cache/hit-cache.js'
],

tests: [
'test.js'
'test.js',
'./hit-cache/hit-cache.specs.js'
]
};

Expand Down
23 changes: 23 additions & 0 deletions hit-cache/hit-cache.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
module.exports = function HitCache(cache) {

this.set = function (key, value, lifeSpan) {
var valueWrapper = { "remainingLife": 0, "value": value, "lifespan": lifeSpan };
cache.put(key, valueWrapper, lifeSpan, this._reIncarnation);
};

this.get = function (key) {
var value = cache.get(key);
if (value != undefined) {
value.remainingLife++;//This is by ref updated in value.
value = value.value;
}
return value;
};

this._reIncarnation = function (key, value) {
if (value.remainingLife > 0) {
value.remainingLife--;
cache.put(key, value, value.lifespan, this._reIncarnation);
}
};
}
109 changes: 109 additions & 0 deletions hit-cache/hit-cache.specs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
var expect = require('chai').expect;
var hitsBasedCache = require('./hit-cache');

function generateMock() {
var mockCache = {};
mockCache.put = function (key, value, lifespan, timeoutCallback) {
mockCache.key = key;
mockCache.value = value;
mockCache.lifespan = lifespan;
mockCache.timeoutCallback = timeoutCallback
};
mockCache.get = function (key) { return mockCache.value };
return mockCache;
}

describe('Hits based cache', function () {
it('should set correct values when set is called', function () {
//Mock
var mock = generateMock();
//Setup
const UUT = new hitsBasedCache(mock);
const key = 'Hello';
const value = 'World';
const lifespanInMillis = 5000;

UUT.set(key, value, lifespanInMillis);
//Validate
expect(mock.value.remainingLife).to.equals(0);
expect(mock.lifespan).to.equals(lifespanInMillis);
expect(mock.value.value).to.equals(value);
});

it('should get correct value after set is called', function () {
//Mock
var mock = generateMock();
//Setup
const UUT = new hitsBasedCache(mock);
const key = 'Hello';
const value = 'World';
const lifespanInMillis = 5000;

UUT.set(key, value, lifespanInMillis);
var cachedvalue = UUT.get(key);
//Validate
expect(cachedvalue).to.equals(value);
});

it('should get Undefined after lifespan is over.', function () {
//Mock
var mock = generateMock();
//Setup
const UUT = new hitsBasedCache(mock);
const key = 'Hello';
const value = 'World';
const lifespanInMillis = 5000;

UUT.set(key, value, lifespanInMillis);
var tempKey = mock.key;
var tempValue = mock.value;
delete mock.key;
delete mock.value;
delete mock.lifespan;
mock.timeoutCallback(tempKey, tempValue);//Simulate timeout

//Validate
var actual = UUT.get(key);
expect(undefined).to.equals(actual);
});

it('should increment the lifecount by one when get is called.', function () {
//Mock
var mock = generateMock();
//Setup
const UUT = new hitsBasedCache(mock);
const key = 'Hello';
const value = 'World';
const lifespanInMillis = 5000;

UUT.set(key, value, lifespanInMillis);
var actual = UUT.get(key);

//Validate
expect(value).to.equals(actual);
expect(mock.value.remainingLife).to.equals(1);
expect(mock.lifespan).to.equals(lifespanInMillis);
});

it('should decrement the lifecount by one when lifespan is ellapsed.', function () {
//Mock
var mock = generateMock();
//Setup
const UUT = new hitsBasedCache(mock);
const key = 'Hello';
const value = 'World';
const lifespanInMillis = 5000;

UUT.set(key, value, lifespanInMillis);
var actual = UUT.get(key);

//Validate
expect(value).to.equals(actual);
expect(mock.value.remainingLife).to.equals(1);
expect(mock.lifespan).to.equals(lifespanInMillis);
mock.timeoutCallback(mock.key, mock.value);
expect(value).to.equals(actual);
expect(mock.value.remainingLife).to.equals(0);
expect(mock.lifespan).to.equals(lifespanInMillis);
});
})
31 changes: 16 additions & 15 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
'use strict';

function Cache () {
function Cache() {
var _cache = Object.create(null);
var _hitCount = 0;
var _missCount = 0;
var _size = 0;
var _debug = false;

this.put = function(key, value, time, timeoutCallback) {
this.put = function (key, value, time, timeoutCallback) {
if (_debug) {
console.log('caching: %s = %j (@%s)', key, value, time);
}
Expand All @@ -31,7 +31,7 @@ function Cache () {
};

if (!isNaN(record.expire)) {
record.timeout = setTimeout(function() {
record.timeout = setTimeout(function () {
_del(key);
if (timeoutCallback) {
timeoutCallback(key, value);
Expand All @@ -44,7 +44,7 @@ function Cache () {
return value;
};

this.del = function(key) {
this.del = function (key) {
var canDelete = true;

var oldRecord = _cache[key];
Expand All @@ -64,12 +64,12 @@ function Cache () {
return canDelete;
};

function _del(key){
function _del(key) {
_size--;
delete _cache[key];
}

this.clear = function() {
this.clear = function () {
for (var key in _cache) {
clearTimeout(_cache[key].timeout);
}
Expand All @@ -81,7 +81,7 @@ function Cache () {
}
};

this.get = function(key) {
this.get = function (key) {
var data = _cache[key];
if (typeof data != "undefined") {
if (isNaN(data.expire) || data.expire >= Date.now()) {
Expand All @@ -99,11 +99,11 @@ function Cache () {
return null;
};

this.size = function() {
this.size = function () {
return _size;
};

this.memsize = function() {
this.memsize = function () {
var size = 0,
key;
for (key in _cache) {
Expand All @@ -112,23 +112,23 @@ function Cache () {
return size;
};

this.debug = function(bool) {
this.debug = function (bool) {
_debug = bool;
};

this.hits = function() {
this.hits = function () {
return _hitCount;
};

this.misses = function() {
this.misses = function () {
return _missCount;
};

this.keys = function() {
this.keys = function () {
return Object.keys(_cache);
};

this.exportJson = function() {
this.exportJson = function () {
var plainJsCache = {};

// Discard the `timeout` property.
Expand All @@ -144,7 +144,7 @@ function Cache () {
return JSON.stringify(plainJsCache);
};

this.importJson = function(jsonToImport, options) {
this.importJson = function (jsonToImport, options) {
var cacheToImport = JSON.parse(jsonToImport);
var currTime = Date.now();

Expand Down Expand Up @@ -188,3 +188,4 @@ function Cache () {

module.exports = new Cache();
module.exports.Cache = Cache;
module.exports.HitCache = require('./hit-cache/hit-cache');
Loading