-
-
Save Aidurber/f358ec18f4e3f176e8b31c5d928493d9 to your computer and use it in GitHub Desktop.
A simple in-memory TTL cache implementation
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
function addSeconds(date: Date, seconds: number): Date { | |
const dateCopy = new Date(date) | |
dateCopy.setSeconds(dateCopy.getSeconds() + seconds) | |
return dateCopy | |
} |
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
/** | |
* Options for cache | |
*/ | |
type TTLCacheOptions = { | |
/** | |
* How long to cache the key in seconds | |
* | |
* @default 180 seconds | |
* @type {number} | |
*/ | |
ttl: number | |
} | |
/** | |
* Default values | |
*/ | |
const defaults: TTLCacheOptions = { | |
ttl: 180, | |
} | |
type WithTTL<V = any> = { | |
/** | |
* The cached value | |
* | |
* @type {V} | |
*/ | |
value: V | |
/** | |
* Expiry meta data | |
* | |
* @type {string} | |
*/ | |
expiresAt: string | |
} | |
/** | |
* A simple TTL cache with lazy expiry | |
* | |
* @class TTLCache | |
* @template V | |
*/ | |
class TTLCache<V = any> { | |
/** | |
* User options and defaults merged | |
* | |
* @private | |
* @type {TTLCacheOptions} | |
* @memberof TTLCache | |
*/ | |
private options: TTLCacheOptions | |
/** | |
* Base cache instance | |
* | |
* @private | |
* @type {Map<string, WithTTL<V>>} | |
* @memberof TTLCache | |
*/ | |
private cache: Map<string, WithTTL<V>> = new Map<string, WithTTL<V>>() | |
constructor(options?: TTLCacheOptions) { | |
// Merge user options with the defaults | |
this.options = { ...defaults, ...(options || {}) } | |
} | |
/** | |
* Clear the cache | |
* | |
* @memberof TTLCache | |
*/ | |
clear = () => { | |
this.cache.clear() | |
} | |
/** | |
* Check whether the cache contains an item | |
* | |
* @param {string} key | |
* @returns {boolean} | |
* @memberof TTLCache | |
*/ | |
has = (key: string): boolean => { | |
if (this.hasExpired(key)) return false | |
return this.cache.has(key) | |
} | |
/** | |
* Get the item from the cache by key if not expired | |
* | |
* @param {string} key | |
* @returns {(Maybe<V>)} | |
* @memberof TTLCache | |
*/ | |
get = (key: string): Maybe<V> => { | |
if (this.hasExpired(key)) return null | |
return this.cache.get(key)?.value || null | |
} | |
/** | |
* Add a new cache item | |
* | |
* @param {string} key | |
* @param {V} value | |
* @memberof TTLCache | |
*/ | |
set = (key: string, value: V): void => { | |
const expiry = addSeconds(new Date(), this.options.ttl).toISOString() | |
this.cache.set(key, { value, expiresAt: expiry }) | |
} | |
/** | |
* Remove an item from the cache | |
* | |
* @param {string} key | |
* @memberof TTLCache | |
*/ | |
remove = (key: string): void => { | |
this.cache.delete(key) | |
} | |
/** | |
* Extract the expiry date from a given key | |
* | |
* @private | |
* @param {string} key | |
* @returns {(Maybe<Date>)} | |
* @memberof TTLCache | |
*/ | |
private getExpiryFromKey = (key: string): Maybe<Date> => { | |
const result = this.cache.get(key) | |
if (!result || !result.expiresAt) return null | |
return new Date(result.expiresAt) | |
} | |
/** | |
* Check whether the given cache item has expired | |
* | |
* @private | |
* @param {string} key | |
* @returns {boolean} | |
* @memberof TTLCache | |
*/ | |
private hasExpired = (key: string): boolean => { | |
const date = this.getExpiryFromKey(key) | |
const hasExpired = date ? new Date() > date : true | |
if (hasExpired) { | |
this.remove(key) | |
} | |
return hasExpired | |
} | |
} | |
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
type MyData = { id: string; name: string } | |
const dataCache = new TTLCache<MyData>({ ttl: 60 }) | |
async function fetchMyData(userId: string): MyData { | |
const cacheKey = 'mydata:${userId}' | |
if (dataCache.has(cacheKey)) { | |
return dataCache.get(cacheKey) | |
} | |
// do API call | |
const result = await Api.get('/my-data', { params: { userId } }) | |
if (result.data) { | |
dataCache.set(cacheKey, result.data) | |
} | |
return result.data | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment