0.1.1Updated a month ago
// deno-lint-ignore-file no-explicit-any

interface MemCacheEntry<T = any> {
  expires_at: number
  data: T
}

export class MemCache<T = any> {
  readonly expiration_period_seconds: number
  readonly interval_ms: number
  private interval_id!: number

  /**
   * Creates a new non-persistent cache.
   * 
   * @param expiration_period_seconds How long an entry should be kept in this cache.
   * @param clean_interval_seconds How frequently this cache should be checked for stale entries and cleaned up.
   */
  constructor(expiration_period_seconds: number = 60, clean_interval_seconds: number = 10) {
    this.expiration_period_seconds = expiration_period_seconds;
    this.interval_ms = clean_interval_seconds * 1000;
    this.queue();
  }

  private _entries: Map<string, MemCacheEntry<T>> = new Map<string, MemCacheEntry<T>>()

  /**
   * Get the value of a cached item
   */
  get(key: string): T | null {
    if(!this._entries.has(key)) return null;

    const entry = this._entries.get(key)!;

    entry.expires_at = Date.now();
    return entry.data as T;
  }

  /**
   * Checks whether a cached item exists
   */
  has(key: string) {
    return this._entries.has(key);
  }

  /**
   * Sets the cached item and returns it
   */
  set(key: string, value: T) {
    this._entries.set(key, { data: value, expires_at: Date.now() + this.expiration_period_seconds * 1000 });
    return value;
  }

  /**
   * Removes the cached item and returns it (if it exists)
   */
  delete(key: string) {
    if(!this._entries.has(key)) return null;
    const value = this.get(key);
    this._entries.delete(key);
    return value;
  }

  private queue() {
    this.interval_id = setTimeout(this.clean.bind(this), this.interval_ms);
  }

  private clean() {
    clearTimeout(this.interval_id);

    const expiration_timestamp = Date.now();

    for(const [key, { expires_at }] of Object.entries(this._entries)) {
      if(expires_at < expiration_timestamp) this._entries.delete(key);
    }

    this.queue();
  }
}