Skip to content

Instantly share code, notes, and snippets.

@dmmulroy
Created April 7, 2023 18:01
Show Gist options
  • Save dmmulroy/33c31bef81554b099726b3a680dad974 to your computer and use it in GitHub Desktop.
Save dmmulroy/33c31bef81554b099726b3a680dad974 to your computer and use it in GitHub Desktop.
SwrLruCache.ts
import { Cache, Key, State } from 'swr'
/**
* SwrLruCache is a Least Recently Used (LRU) cache implementing the SWR {@link Cache}
* interface. It manages fetched data and errors while limiting the number of
* stored items. This optimizes memory usage by evicting the least accessed
* items when capacity is reached.
*/
export class SwrLruCache<Data = unknown, Error = unknown>
implements Cache<Data>
{
private readonly cache = new Map<Key, State<Data, Error>>()
constructor(private readonly capacity: number) {}
/**
* Returns an iterable iterator over the keys in the cache.
*
* @returns {IterableIterator<string>} - An iterator of the cache keys.
*/
public keys(): IterableIterator<string> {
// We need to cast here because the SWR Cache is incorrectly typed and should
// return an IterableIterator<Key> instead of an IterableIterator<string>.
// This is safe to do as the only place .keys() is called internally in SWR
// is here: https://github.com/vercel/swr/blob/main/_internal/utils/mutate.ts#L75
return this.cache.keys() as IterableIterator<string>
}
/**
* Retrieves the data and error associated with the given key from the cache.
*
* @param {Key} key - The key of the cache entry to retrieve.
* @returns {State<Data, Error> | undefined} - The cache entry, or undefined if not found.
*/
public get(key: Key): State<Data, Error> | undefined {
const value = this.cache.get(key)
if (value === undefined) {
return undefined
}
this.setMostRecentlyUsed(key, value)
return value
}
/**
* Sets the data and error associated with the given key in the cache.
* If the cache size exceeds its capacity after the insertion, the least recently used item is removed.
*
* @param {Key} key - The key of the cache entry to set.
* @param {State<Data, Error>} value - The value to be stored in the cache.
*/
public set(key: Key, value: State<Data, Error>): void {
this.setMostRecentlyUsed(key, value)
if (this.cache.size > this.capacity) {
const oldestKey = this.cache.keys().next().value
this.cache.delete(oldestKey)
}
}
/**
* Removes the cache entry associated with the given key.
* @param {Key} key - The key of the cache entry to delete.
*/
public delete(key: Key): void {
this.cache.delete(key)
}
/**
* Set the given key-value pair as the most recently used in the cache.
*
* The `delete` method is called before the `set` method to ensure that the
* key-value pair is moved to the end of the Map's key insertion order.
* Since the LRU cache evicts the least recently used item based on the
* insertion order of the keys, calling `delete` first makes sure that the
* most recently accessed key is placed at the end, thus preserving the LRU
* eviction strategy.
*
* @param {Key} key - The key to be set as the most recently used.
* @param {State<Data, Error>} value - The value associated with the key.
*/
private setMostRecentlyUsed(key: Key, value: State<Data, Error>): void {
this.cache.delete(key)
this.cache.set(key, value)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment