Skip to content

Commit

Permalink
Add cache functionality and bind utility function
Browse files Browse the repository at this point in the history
  • Loading branch information
shtse8 committed Mar 22, 2024
1 parent 0627286 commit 3109ee0
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 0 deletions.
101 changes: 101 additions & 0 deletions src/cache.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@

import { EventEmitter } from "events";


/**
* A class that emits an event when invalidated
*/
export class Invalidator extends EventEmitter {
invalidate() {
this.emit('invalidate')
}
}

export interface CacheOptions {
ttl?: number
invalidator?: Invalidator
}

/**
* Caches the result of a function
* @param fn function to cache
* @param options cache options
* @returns a function that caches the result of the input function
* @example
* const cachedFunc = cacheFunc(async () => {
* return 'hello'
* }, { ttl: 1000 })
*
* console.log(await cachedFunc()) // 'hello'
* console.log(await cachedFunc()) // 'hello'
*
* @example
* const invalidator = new Invalidator()
* const cachedFunc = cacheFunc(async () => {
* return 'hello'
* }, { ttl: 1000, invalidator })
*
* console.log(await cachedFunc()) // 'hello'
* invalidator.invalidate()
* console.log(await cachedFunc()) // 'hello'
*
* @example
* class Example {
* const fn = cacheFunc(bind(this)._fn)
* async _fn() {
* return 'hello'
* }
* }
*/
export function cacheFunc<T, Args extends readonly unknown[]>(fn: (...args: Args) => Promise<T>, options: CacheOptions) {
const cache = new Cache<T>(options)
return (...args: Args) => cache.run(fn)
}

/**
* A class that caches a value
*/
export class Cache<T> {
private value: T | undefined
private updatedAt: Date | undefined
private ttl: number;
constructor(options: CacheOptions) {
this.ttl = options.ttl || 1000 * 60 * 60
options.invalidator?.on('invalidate', () => this.invalidate())
}

set(value: T) {
this.value = value
this.updatedAt = new Date()
}

async run(fn: () => Promise<T>) {
if (!this.value || !this.updatedAt) {
this.value = await fn()
this.updatedAt = new Date()
return this.value
}
if (Date.now() - this.updatedAt.getTime() > this.ttl) {
this.value = await fn()
this.updatedAt = new Date()
return this.value
}
return this.value
}

invalidate() {
this.value = undefined
this.updatedAt = undefined
}

get() {
if (!this.value || !this.updatedAt) {
return undefined
}
if (Date.now() - this.updatedAt.getTime() > this.ttl) {
return undefined
}
return this.value
}

}
3 changes: 3 additions & 0 deletions src/clone.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function structureClone<T>(obj: T): T {
return JSON.parse(JSON.stringify(obj));
}
18 changes: 18 additions & 0 deletions src/typed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,3 +173,21 @@ export function isEmpty(value: any): boolean {
|| (isArray(value) && value.length === 0)
|| (isObject(value) && Object.keys(value).length === 0);
}


/**
* Binds all functions in an object to the object.
* @param thisArg object to bind functions to
* @returns the object with all functions bound
*/
export function bind<T extends object>(thisArg: T): T {
return new Proxy(thisArg, {
get(target, prop, receiver) {
const value = Reflect.get(target, prop, receiver);
if (typeof value === 'function') {
return value.bind(thisArg);
}
return value;
}
}) as T;
}

0 comments on commit 3109ee0

Please sign in to comment.