Skip to content

Instantly share code, notes, and snippets.

@trezy
Last active February 4, 2025 22:38
Show Gist options
  • Save trezy/5f2d5738c551765544dcd8b8500ab1b8 to your computer and use it in GitHub Desktop.
Save trezy/5f2d5738c551765544dcd8b8500ab1b8 to your computer and use it in GitHub Desktop.
type UnknownArgs = Array<unknown>
type UnknownCallback = (...args: Array<unknown>) => unknown
export class ExecutionCache {
/** The string used to separate keys in a cache combined key. */
#cacheKeySeparator = '::'
/** Stores all cache items. */
#cache: Map<string, unknown> = new Map
/** Stores arg keys. */
#keys: WeakMap<WeakKey, string> = new Map
/** These value types will be used as their own keys. */
#selfKeys = [
'bigint',
'number',
'string',
]
/** These value types will be stringified and used as their own keys. */
#stringifiedKeys = [
'boolean',
'undefined',
]
/**
* Retrieves keys for an array of args.
*
* @param args The arguments to retrieve keys for.
* @param createIfMissing Whether to create keys for arguments if they don't currently exist in the cache.
*/
#getKeys(args: UnknownArgs, createIfMissing: boolean = false) {
return args.map(arg => {
if (this.#selfKeys.includes(typeof arg)) {
return arg
}
if (this.#stringifiedKeys.includes(typeof arg)) {
return String(arg)
}
if (arg === null) {
return 'null'
}
if (this.#keys.has(arg as WeakKey)) {
return this.#keys.get(arg as WeakKey)
}
if (createIfMissing) {
this.#keys.set(arg as WeakKey, crypto.randomUUID())
return this.#keys.get(arg as WeakKey)
}
return null
})
}
/**
* Returns a cached result if it receives the same function and arguments. If there is no cached result, execute the callback and cache the result.
*
* @param callback The callback to be executed.
* @param args The arguments that will be provided to the callback.
*/
execute(callback: UnknownCallback, ...args: UnknownArgs) {
const cachedResult = this.get(callback, ...args)
if (cachedResult) {
return cachedResult
}
const result = callback(...args)
this.set(result, callback, ...args)
return result
}
/**
* Retrieves a cached execution result if it exists.
*
* @param args The arguments from which the cache key will be generated.
* @returns The cached result if it exists, otherwise null.
*/
get(...args: UnknownArgs) {
const enums = this.#getKeys(args)
const isMissingEnums = enums.filter(Boolean).length === 0
if (isMissingEnums) {
return null
}
return this.#cache.get(enums.join(this.#cacheKeySeparator))
}
/**
* Stores an execution result in the cache.
*
* @param value The execution result.
* @param args The arguments from which the cache key will be generated.
* @returns The cached result if it exists, otherwise null.
*/
set(value: unknown, ...args: UnknownArgs) {
const enums = this.#getKeys(args, true)
this.#cache.set(enums.join(this.#cacheKeySeparator), value)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment