UNPKG

@catbee/utils

Version:

A modular, production-grade utility toolkit for Node.js and TypeScript, designed for robust, scalable applications (including Express-based services). All utilities are tree-shakable and can be imported independently.

291 lines (287 loc) 7.86 kB
/* * The MIT License * * Copyright (c) 2026 Catbee Technologies. https://catbee.in/license * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ import { getCatbeeGlobalConfig } from '@catbee/utils/config'; var __defProp = Object.defineProperty; var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); var TTLCache = class { static { __name(this, "TTLCache"); } cache = /* @__PURE__ */ new Map(); ttlMs; maxSize; cleanupInterval; /** * @param options - Configuration options for the cache */ constructor(options = {}) { this.ttlMs = options.ttlMs ?? getCatbeeGlobalConfig().cache.defaultTtl; this.maxSize = options.maxSize; const autoCleanupMs = options.autoCleanupMs; if (autoCleanupMs && autoCleanupMs > 0) { this.cleanupInterval = setInterval(() => { this.cleanup(); }, autoCleanupMs); } } /** * Sets a key-value pair in the cache with the default TTL. * * @param key - The key to set. * @param value - The value to associate with the key. */ set(key, value) { this.setWithTTL(key, value, this.ttlMs); } /** * Sets a key-value pair in the cache with a custom TTL. * * @param key - The key to set. * @param value - The value to associate with the key. * @param ttlMs - Time-to-live in milliseconds. */ setWithTTL(key, value, ttlMs) { const now = Date.now(); const expiresAt = now + ttlMs; if (this.cache.has(key)) { this.cache.delete(key); } this.cache.set(key, { value, expiresAt, lastAccessed: now }); if (this.maxSize && this.cache.size > this.maxSize) { this.evictLRU(); } } /** * Retrieves the value for a given key if it hasn't expired. * * @param key - The key to retrieve. * @returns The cached value, or undefined if not found or expired. */ get(key) { const entry = this.cache.get(key); if (!entry) return void 0; const now = Date.now(); if (now > entry.expiresAt) { this.cache.delete(key); return void 0; } entry.lastAccessed = now; this.cache.delete(key); this.cache.set(key, entry); return entry.value; } /** * Retrieves or computes a value if it's not in the cache or has expired. * * @param key - The key to retrieve * @param producer - Function to generate the value if not cached * @param ttlMs - Optional custom TTL for the computed value * @returns The cached or computed value */ async getOrCompute(key, producer, ttlMs) { const value = this.get(key); if (value !== void 0) return value; const newValue = await producer(); this.setWithTTL(key, newValue, ttlMs ?? this.ttlMs); return newValue; } /** * Checks if the key exists and hasn't expired. * * @param key - The key to check. * @returns `true` if key exists and is valid, otherwise `false`. */ has(key) { return this.get(key) !== void 0; } /** * Deletes a key from the cache. * * @param key - The key to delete. * @returns `true` if the key existed and was removed, `false` otherwise. */ delete(key) { return this.cache.delete(key); } /** * Clears all entries from the cache. */ clear() { this.cache.clear(); } /** * Returns the number of entries currently in the cache (includes expired entries until next access/cleanup). * * @returns Number of items in the cache (may include expired keys). */ size() { return this.cache.size; } /** * Set multiple key-value pairs at once with the default TTL. * * @param entries - Array of [key, value] tuples to set */ setMany(entries) { for (const [key, value] of entries) { this.set(key, value); } } /** * Get multiple values at once. * * @param keys - Array of keys to retrieve * @returns Array of values (undefined for keys that don't exist or expired) */ getMany(keys) { return keys.map((key) => this.get(key)); } /** * Removes all expired entries from the cache. * * @returns Number of entries removed. */ cleanup() { let removed = 0; const now = Date.now(); for (const [key, entry] of this.cache.entries()) { if (now > entry.expiresAt) { this.cache.delete(key); removed++; } } return removed; } /** * Returns an iterator of all current valid [key, value] pairs. * * @returns IterableIterator<[K, V]> */ *entries() { for (const [key, entry] of this.cache.entries()) { if (Date.now() <= entry.expiresAt) { yield [ key, entry.value ]; } } } /** * Returns an iterator of all current valid keys. * * @returns IterableIterator<K> */ *keys() { for (const [key, entry] of this.cache.entries()) { if (Date.now() <= entry.expiresAt) { yield key; } } } /** * Returns an iterator of all current valid values. * * @returns IterableIterator<V> */ *values() { for (const entry of this.cache.values()) { if (Date.now() <= entry.expiresAt) { yield entry.value; } } } /** * Extends the expiration of a key by the specified time or default TTL. * * @param key - The key to refresh * @param ttlMs - Optional new TTL in milliseconds (uses default if not specified) * @returns true if the key was found and refreshed, false otherwise */ refresh(key, ttlMs) { const entry = this.cache.get(key); if (!entry) return false; const now = Date.now(); if (now > entry.expiresAt) { this.cache.delete(key); return false; } entry.expiresAt = now + (ttlMs ?? this.ttlMs); entry.lastAccessed = now; return true; } /** * Returns a snapshot of cache stats. * * @returns Object containing cache statistics */ stats() { const now = Date.now(); let expired = 0; let valid = 0; for (const entry of this.cache.values()) { if (now > entry.expiresAt) { expired++; } else { valid++; } } return { size: this.cache.size, validEntries: valid, expiredEntries: expired, maxSize: this.maxSize }; } /** * Stop the auto-cleanup interval if it's running. */ destroy() { if (this.cleanupInterval) { clearInterval(this.cleanupInterval); this.cleanupInterval = void 0; } } /** * Evict the least recently used entry from the cache. * @private */ evictLRU() { const now = Date.now(); for (const [key, entry] of Array.from(this.cache.entries())) { if (now > entry.expiresAt) { this.cache.delete(key); } } while (this.maxSize && this.cache.size > this.maxSize) { const oldestKey = this.cache.keys().next().value; this.cache.delete(oldestKey); } } }; export { TTLCache };