Skip to content

Commit

Permalink
Added tests for cache and memory objects
Browse files Browse the repository at this point in the history
  • Loading branch information
mallocator committed Mar 20, 2017
1 parent 36733d9 commit 234c2dc
Show file tree
Hide file tree
Showing 8 changed files with 384 additions and 41 deletions.
156 changes: 156 additions & 0 deletions cache.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
/**
* A payload object, that can be anything that can be stored by the cache implementation.
* @typedef {*} Payload
*/

/**
* A parent class that implements some of the common features available to all cache instances
* @abstract
*/
class Cache {
/**
*
* @param {Object} features
* @param {boolean} features.ttl Whether the cache implementation enable ttl support
* @param {CacheConfig} config The cache configuration
* @param {string} name The name of this cache instance
*/
constructor(features, config, name) {
this._features = features;
this._config = config;
this._name = name;
this._type = config.type;
this._ttl = features.ttl && config.ttl;
this._ttl && (this._ttl *= 1000);
this._timeouts = {};
}

/**
* Returns the name of this instance
* @returns {string}
*/
get name() {
return this._name;
}

/**
* Returns the type of cache this implementation is using.
* @returns {string}
*/
get type() {
return this._type;
}

/**
* Stores data in cache. Supports both callback or promise interface. Storing a value in cache
* will reset the ttl (if enabled).
* @param {string} type
* @param {string|number} id
* @param {Payload} payload
* @param {Callback} [cb]
* @returns {Promise.<Payload>}
*/
store(type, id, payload, cb) {
if (this._ttl) {
this._timeouts[type] = this._timeouts[type] || {};
this._timeouts[type][id] && clearTimeout(this._timeouts[type][id]);
this._timeouts[type][id] = setTimeout(this.remove.bind(this), this._ttl, type, id);
}
if (cb) {
return this._store(type, id, payload).then(result => cb(null, result)).catch(cb)
} else {
return this._store(type, id, payload);
}
}

/**
* Retrieve data from cache. Supports both callback or promise interface. Fetching from cache
* will reset the ttl (if enabled).
* @param {string} type
* @param {string|number} id
* @param {Callback} cb
* @returns {Promise.<Payload>}
*/
fetch(type, id, cb) {
if (this._ttl) {
this._timeouts[type][id] && clearTimeout(this._timeouts[type][id]);
this._timeouts[type][id] = setTimeout(this.remove.bind(this, type, id), this._ttl);
}
if (cb) {
return this._fetch(type, id).then(result => cb(null, result)).catch(cb);
} else {
return this._fetch(type, id);
}
}

/**
* Returns a list of payloads for the given type. An order is not guaranteed. Supports both
* callback or promise interface. Will not reset ttl if accessed (if enabled).
* @param {string} type
* @param {Callback} [cb]
* @returns {Promise.<Payload[]>}
*/
list(type, cb) {
if (cb) {
this._list(type).then(result => cb(null, result)).catch(cb);
} else {
return this._list(type);
}
}

/**
* Returns a map of id -> payload. Supports both callback or promise interface. Will not reset
* ttl if accessed (if enabled).
* @param {string} type
* @param {Callback} [cb]
* @returns {Promise.<Object<string, Payload>>}
*/
map(type, cb) {
if (cb) {
this._map(type).then(result => cb(null, result)).catch(cb);
} else {
return this._map(type);
}
}

/**
* Removes a payload from cache. Supports both callback or promise interface. Will remove the ttl
* of the entry affected.
* @param {string} type
* @param {string|number} id
* @param {Callback} [cb]
* @returns {Promise.<Payload>}
*/
remove(type, id, cb) {
if (this._timeouts[type]) {
this._timeouts[type][id] && clearTimeout(this._timeouts[type][id]);
delete this._timeouts[type][id];
}
if (cb) {
this._remove(type, id).then(result => cb(null, result)).catch(cb);
} else {
return this._remove(type, id);
}
}

/**
* Removes all payload of a given type from cache. Supports both callback and promise interface. Will
* remove the ttl of all entries affected.
* @param type
* @param cb
* @returns {*}
*/
clear(type, cb) {
if (this._timeouts[type]) {
this._timeouts[type].forEach(clearTimeout);
delete this._timeouts[type];
}
if (cb) {
this._clear(type).then(result => cb(null, result)).catch(cb);
} else {
return this._clear(type);
}
}
}

module.exports = Cache;
21 changes: 12 additions & 9 deletions file.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
const Cache = require('./cache');
const fs = require('fs');
const path = require('path');

Expand All @@ -15,13 +16,15 @@ const cwd = process.cwd();
* directories and message are stored as files. All data is also cached in memory, so that file read access only
* happens after a reboot through lazy initialization.
*/
class FileCache {
class FileCache extends Cache {
/**
* @param {FileCacheConfig} config
* @param {string} name
*/
constructor(config) {
constructor(config, name) {
super({ ttl: true }, config, name);
this._path = FileCache._mkDir(config && config.path || 'queues');
this._queues = {};
this._types = {};
}

/**
Expand All @@ -47,7 +50,7 @@ class FileCache {
* @param {Callback} [cb] Callback to be notified on async store operations
*/
store(type, id, payload, cb) {
this._queues[type][id] = payload;
this._types[type][id] = payload;
let fullPath = path.join(this._path, type);
fs.existsSync(fullPath) || fs.mkdirSync(fullPath);
fs.writeFile(path.join(fullPath, id), JSON.stringify(payload), 'utf8', err => cb && cb(err));
Expand All @@ -60,9 +63,9 @@ class FileCache {
* @returns {Object<string, Object>} A map with message id's mapping to payloads
*/
get(type, cb) {
if (this._queues[type]) {
cb(null, this._queues[type]);
return this._queues[type];
if (this._types[type]) {
cb(null, this._types[type]);
return this._types[type];
}
let fullPath = path.join(this._path, type);
let files = fs.readdirSync(fullPath);
Expand All @@ -83,7 +86,7 @@ class FileCache {
clear(type, cb) {
let fullPath = path.join(this._path, type);
fs.existsSync(fullPath) && fs.unlinkSync(fullPath);
delete this._queues[type];
delete this._types[type];
cb && cb();
}

Expand All @@ -96,7 +99,7 @@ class FileCache {
remove(type, id, cb) {
let fullPath = path.join(this._path, type, id);
fs.existsSync(fullPath) && fs.unlinkSync(fullPath);
delete this._queues[type][id];
delete this._types[type][id];
cb && cb();
}

Expand Down
45 changes: 38 additions & 7 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
let config = require('cheevr-config');
let _ = require('lodash');
let config = require('cheevr-config').addDefaultConfig(__dirname, 'config');


/**
Expand All @@ -10,17 +11,47 @@ let config = require('cheevr-config');
/**
* @typedef {Object} CacheConfig
* @property {string} type The cache type that should be used to store data. Maps directly to the file names of the caches
* @property {number} ttl A time to live in seconds, that will be enacted by the Cache class if enabled, or by the implementation itself.
*/

class Cache {
class Manager {
constructor() {
this.reset();
}
/**
* Returns a cache implementation of the given type.
* @param {string} [name=_default_]
* @param {string} [name=_default_] The name of the cache instance (will be used to look up configs in files)
* @param {CacheConfig} [instanceConfig] The cache config that will be merged with the default options for this implementation
* @returns Cache
*/
instance(name = '_default_', instanceConfig) {
if (typeof name !== 'string') {
instanceConfig = name;
name = '_default_';
}
instanceConfig = instanceConfig || this._config[name] || { type: this._defaults.defaultType };
_.defaultsDeep(instanceConfig, this._defaults[config.type]);
return new (require('./' + instanceConfig.type))(instanceConfig, name);
}

/**
* Allows to add configuration to the current configuration. Any new configs will be merged with existing ones.
* @param {Object<string, CacheConfig>} config A map of cache instance names to cache configurations
* @param {Object<string, CacheConfig>} [defaults] A map of cache types to cache configurations
*/
configure(config, defaults) {
this._config = _.merge(this._config, config);
defaults && (this._defaults = _.merge(this._defaults, defaults));
}

/**
* Will reset the cache manager to the configuration found on the file system.
*/
get(name = '_default_') {
let instanceConfig = config.cache[name] || { type: config.defaults.cache.defaultType };
return new (require('./' + instanceConfig.type))(instanceConfig);
reset() {
this._config = {};
this._defaults = {};
this.configure(config.cache, config.defaults.cache);
}
}

module.exports = new Cache();
module.exports = new Manager();
82 changes: 57 additions & 25 deletions memory.js
Original file line number Diff line number Diff line change
@@ -1,52 +1,84 @@
class MemoryCache {
constructor() {
this._queues = {};
const Cache = require('./cache');


class MemoryCache extends Cache {
constructor(config, name) {
super({ ttl: true }, config, name);
this._types = {};
}

/**
* Stores the payload in cache.
* @param {string} type The name of the type to cache for
* @param {string} id The id of the document to store
* @param {object} payload The data to cache
* @param {string} payload.id The id that is being used to reference the message later on
* @param {Callback} [cb] Callback to be notified on async store operations
* @param {string|number} id The id of the document to store
* @param {*} payload The data to cache
* @returns {Promise.<Payload>}
* @private
*/
async _store(type, id, payload) {
this._types[type] = this._types[type] || {};
this._types[type][id] = payload;
return payload;
}

/**
* Fetches payload from cache.
* @param {string} type
* @param {string|number} id
* @returns {Promise.<Payload>}
* @private
*/
store(type, id, payload, cb) {
this._queues[type] = this._queues[type] || {};
this._queues[type][id] = payload;
cb && cb();
async _fetch(type, id) {
return this._types[type] && this._types[type][id];
}

/**
* Returns all cached messages and listeners
* @param {string} type The id/name of the type for which to fetch data
* @param {Callback} [cb] Callback function for async fetching
* @returns {Object<string, Object>} A map with message id's mapping to payloads
* @returns {Promise.<Payload[]>}
* @private
*/
async _list(type) {
let list = [];
for (let id in this._types[type]) {
list.push(this._types[type][id]);
}
return list;
}

/**
* Returns a map of all stored entries for a type
* @param {string} type
* @returns {Promise.<Object<String, Payload>>}
* @private
*/
get(type, cb) {
cb && cb(null, this._queues[type]);
return this._queues[type];
async _map(type) {
return this._types[type];
}

/**
* Removes all cached data from a type
* Removes all cached data from a type.
* @param {string} type The id/name of the type to clear
* @param {Callback} [cb] Callback to be notified on async clear operations
* @returns {Promise.<Object<String, Payload>>}
* @private
*/
clear(type, cb) {
delete this._queues[type];
cb && cb();
async _clear(type) {
let entries = this._types[type];
delete this._types[type];
return entries;
}

/**
* Remove an entry from cache.
* @param {string} type The id/name of the type from which to remove the message
* @param {string} id The id of the message to remove
* @param {Callback} [cb] Callback to be notified on async remove operations
* @returns {Promise.<Payload>}
* @private
*/
remove(type, id, cb) {
delete this._queues[type][id];
cb && cb();
async _remove(type, id) {
let payload = this._types[type][id];
delete this._types[type][id];
return payload;
}
}

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"chai": "^3",
"coveralls": "^2",
"istanbul": "^0.4",
"lodash": "^4",
"mocha": "^3"
},
"dependencies": {
Expand Down
Loading

0 comments on commit 234c2dc

Please sign in to comment.