Created
August 23, 2022 18:43
-
-
Save erdesigns-eu/3d20c92d0ddbf7b13d7f06ae744a6252 to your computer and use it in GitHub Desktop.
Simple Memory Cache
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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