Skip to content

Instantly share code, notes, and snippets.

@erdesigns-eu
Created August 23, 2022 18:43
Show Gist options
  • Save erdesigns-eu/3d20c92d0ddbf7b13d7f06ae744a6252 to your computer and use it in GitHub Desktop.
Save erdesigns-eu/3d20c92d0ddbf7b13d7f06ae744a6252 to your computer and use it in GitHub Desktop.
Simple Memory Cache
/**
* Simple in-memory cache
*/
module.exports = class MemCache {
// Cache object
#cache = Object.create(null);
// Cache size
#size = 0;
// Default cache timeout duration (30sec)
#timeout = 30000;
// Class constructor
constructor(options) {
const vm = this;
// Set default cache timeout
if (options && options.timeout && !isNaN(options.timeout)) {
vm.#timeout = parseInt(`${options.timeout}`);
}
}
// Check if value is JSON
#isJSON(value) {
if (typeof value !== 'string') return false;
try {
const result = JSON.parse(value);
const type = Object.prototype.toString.call(result);
return type === '[object Object]'
|| type === '[object Array]';
} catch (err) {
return false;
}
}
// Put to cache
put(key, value, timeout, timeoutCB) {
// Validate timeout
if (typeof timeout !== 'undefined' && (typeof timeout !== 'number' || isNaN(timeout) || timeout <= 0)) {
throw new Error('Cache timeout must be a positive number');
}
// Validate timeout callback function
if (typeof timeoutCB !== 'undefined' && typeof timeoutCB !== 'function') {
throw new Error('Cache timeout callback must be a function');
}
// Reference to cache (for timeout functon)
var cache = this.#cache;
// Reference to size (for timeout function)
var size = this.#size;
// Clear "old" timeout if record already exists in the cache
var oldRecord = cache[key];
if (oldRecord) {
clearTimeout(oldRecord.timeout);
} else {
this.#size++;
}
// Create new record for the cache
const expire = timeout ? timeout : this.#timeout;
var record = {
value : value,
json : this.#isJSON(value),
expire : Date.now() + expire,
timeout : setTimeout(() => {
size--;
delete cache[key];
if (timeoutCB) {
timeoutCB(key, value);
}
}, expire)
}
// Attach record to cache
cache[key] = record;
// Return value
return value;
}
// Delete from cache
del(key) {
// Reference to cache
var cache = this.#cache;
// Reference to size
var size = this.#size;
// Can delete key?
var canDelete = true;
// Remove timeout from record
var oldRecord = cache[key];
if (oldRecord) {
clearTimeout(oldRecord.timeout);
if (!isNaN(oldRecord.expire) && oldRecord.expire < Date.now()) {
canDelete = false;
}
} else {
canDelete = false;
}
// Delete key from cache
if (canDelete) {
size--;
delete cache[key];
}
// Return success
return canDelete;
}
// Clear cache
clear() {
// Reference to cache
var cache = this.#cache;
// Reference to size
var size = this.#size;
// Loop over keys in cache, and remove all timeouts
for (var key in cache) {
clearTimeout(cache[key].timeout);
}
// Reset size
size = 0;
// Reset cache object
cache = Object.create(null);
}
// Get value of key from cache
get(key) {
// Reference to cache
var cache = this.#cache;
// Get record if not expired
var record = cache[key];
if (typeof record !== 'undefined') {
if (isNaN(record.expire) || record.expire >= Date.now()) {
return record.json ? JSON.parse(record.value) : record.value;
} else {
size--;
delete cache[key];
}
}
// Key doesnt exist or record is expired
return null;
}
// Export cache to JSON
export() {
// Reference to cache
var cache = this.#cache;
// Resulting object to be stringified
var result = Object.create(null);
// Loop over keys and add to result
for (var key in cache) {
var record = cache[key];
// Add record to result
result[key] = {
value : record.value,
json : record.json,
expire : record.expire || 'NaN'
};
}
// Return as JSON string
return JSON.stringify(result);
}
// Import JSON to cache
import(json, skipDuplicates) {
// Reference to cache
var cache = this.#cache;
// Parse JSON
const importCache = JSON.parse(json);
// Current time
const currentTime = Date.now();
// Loop over importCache keys and add to the cache
for (var key in importCache) {
if (Object.hasOwn(importCache, key)) {
// Check for duplicates and skip if skipDuplicates is set to true
if (skipDuplicates === true) {
if (cache[key]) {
continue;
}
}
var record = importCache[key];
var remain = record.expire - currentTime;
// If this record already exists and is expired, delete it.
if (remain <= 0) {
this.del(key);
continue;
}
// Put record in cache
this.put(key, record.json ? JSON.parse(record.value) : record.value, remain > 0 ? remain : undefined);
}
}
// Return the new size of the cache
return this.#size;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment