clean-jsdoc-theme API
cache.jsjavascript
/**
 * Storage primitives — the {@link Cache} class and its helpers.
 *
 * @module forge/cache
 */

/**
 * Options accepted by the {@link Cache} constructor.
 *
 * @typedef {Object} CacheOptions
 * @property {number} [maxSize=100] - Maximum number of entries to keep before
 *   the least-recently-used entry is evicted.
 * @property {number} [ttl=0] - Time-to-live for an entry, in milliseconds. `0`
 *   disables expiry.
 * @property {boolean} [freeze=false] - Freeze values on insert so callers can't
 *   mutate cached objects.
 */

/**
 * A tiny in-memory, least-recently-used (LRU) cache.
 *
 * This class is the showcase's centerpiece: its members exercise nearly every
 * member-level tag the theme renders — parameters, returns, examples, events,
 * deprecation, modifiers, and cross-references.
 *
 * > [!IMPORTANT]
 * > A `Cache` is **not** thread-safe across workers. Wrap shared access in a
 * > {@link Queue} if you need ordered writes.
 *
 * Eviction follows a strict recency order:
 *
 * | Operation | Effect on recency |
 * | --- | --- |
 * | `set(k, v)` | marks `k` most-recently-used |
 * | `get(k)` | marks `k` most-recently-used |
 * | `has(k)` | no change |
 *
 * @category Core/Storage order=1
 * @implements {Store}
 * @implements {Serializable}
 * @example <caption>Basic usage</caption>
 * const cache = new Cache({ maxSize: 2 });
 * cache.set('a', 1).set('b', 2);
 * cache.get('a'); // => 1, and 'a' is now most-recently-used
 * @playground
 *
 * @see {@link CacheOptions}
 * @see {@link createCache} for a one-shot factory
 * @since 1.0.0
 * @version 1.2.0
 * @author The clean-jsdoc-theme team
 */
export class Cache {
  /**
   * Create a new cache.
   *
   * @param {CacheOptions} [options={}] - Configuration for the cache.
   * @param {number} [options.maxSize=100] - Max entries before eviction.
   * @param {number} [options.ttl=0] - Per-entry time-to-live in ms.
   * @throws {RangeError} If `maxSize` is not a positive integer.
   */
  constructor(options = {}) {
    if (options.maxSize !== undefined && options.maxSize <= 0) {
      throw new RangeError('maxSize must be a positive integer');
    }

    /**
     * The maximum number of entries this cache will hold.
     *
     * @type {number}
     */
    this.maxSize = options.maxSize ?? 100;

    /**
     * Time-to-live per entry, in milliseconds. `0` means entries never expire.
     *
     * @type {number}
     */
    this.ttl = options.ttl ?? 0;

    /**
     * Backing store. Not part of the public API.
     *
     * @private
     * @type {Map<string, *>}
     */
    this._store = new Map();
  }

  /**
   * The number of entries currently stored.
   *
   * @readonly
   * @type {number}
   */
  get size() {
    return this._store.size;
  }

  /**
   * Store a value under a key. If the cache is full, the least-recently-used
   * entry is evicted first and a {@link Cache#event:evict|evict event} fires.
   *
   * @param {string} key - The key to store the value under.
   * @param {*} value - The value to cache.
   * @returns {Cache} The cache instance, for chaining.
   * @fires Cache#event:evict
   *
   * @example <caption>Calls chain</caption>
   * cache.set('user:1', { name: 'Ada' }).set('user:2', { name: 'Linus' });
   * @playground codepen jsfiddle filename=set.js highlight=1
   */
  set(key, value) {
    if (this._store.size >= this.maxSize && !this._store.has(key)) {
      const oldest = this._store.keys().next().value;
      this._store.delete(oldest);
    }
    this._store.set(key, value);
    return this;
  }

  /**
   * Look up a value by key, marking it as recently used.
   *
   * @param {string} key - The key to look up.
   * @returns {*} The cached value, or `undefined` if the key is not present.
   */
  get(key) {
    if (!this._store.has(key)) return undefined;
    const value = this._store.get(key);
    this._store.delete(key);
    this._store.set(key, value);
    return value;
  }

  /**
   * Check whether a key is present **without** affecting its recency.
   *
   * @param {string} key - The key to test.
   * @returns {boolean} `true` if the key is cached.
   */
  has(key) {
    return this._store.has(key);
  }

  /**
   * Remove a single entry.
   *
   * @deprecated Since 1.2.0 — prefer {@link Cache#clear} or let entries expire
   *   via `ttl`. This method will be removed in 2.0.
   * @param {string} key - The key to remove.
   * @returns {boolean} `true` if an entry was removed.
   */
  remove(key) {
    return this._store.delete(key);
  }

  /**
   * Remove every entry from the cache.
   *
   * @returns {void}
   */
  clear() {
    this._store.clear();
  }

  /**
   * Serialize the cache to a plain object — satisfies {@link Serializable}.
   *
   * @returns {Object<string, *>} A snapshot of every entry.
   */
  toJSON() {
    return Object.fromEntries(this._store);
  }
}

/**
 * Fired when an entry is evicted to make room for a new one.
 *
 * @event Cache#event:evict
 * @type {Object}
 * @property {string} key - The key that was evicted.
 * @property {*} value - The value that was evicted.
 */

/**
 * Create a {@link Cache} pre-populated from an object of key/value pairs.
 *
 * This is a **global** function (no `@memberof`), so it appears on the
 * aggregated **Globals** page rather than owning its own page.
 *
 * @param {Object<string, *>} entries - Initial entries to seed the cache with.
 * @param {CacheOptions} [options] - Options forwarded to the {@link Cache}
 *   constructor.
 * @returns {Cache} A new, seeded cache.
 *
 * @example
 * const cache = createCache({ a: 1, b: 2 }, { maxSize: 10 });
 * cache.get('b'); // => 2
 *
 * @iframe https://ankdev.me/clean-jsdoc-theme/api-docs/ title="createCache demo" height=360 clickToLoad=true
 * @since 1.0.0
 */
export function createCache(entries, options) {
  const cache = new Cache(options);
  for (const [key, value] of Object.entries(entries)) {
    cache.set(key, value);
  }
  return cache;
}

/**
 * Relative priority for cached writes — a numeric {@link https://jsdoc.app/tags-enum|enum}
 * rendered as a member table on the Globals page.
 *
 * @enum {number}
 * @readonly
 */
export const Priority = {
  /** Lowest priority; processed last. */
  LOW: 0,
  /** Default priority. */
  NORMAL: 1,
  /** Highest priority; jumps the queue. */
  HIGH: 2,
};