@joaquimserafim/ttl-lru-cache
Version:
A TypeScript cache implementation focused on time-based expiration with automatic cleanup and TTL support
1 lines • 18.6 kB
Source Map (JSON)
{"version":3,"sources":["../src/ttl-cache.ts"],"names":["now","isPosInt","value","isPosIntOrInf","n","TTLCache","_TTLCache","options","keysToRemove","reason","entries","key","val","max","ttl","updateAgeOnGet","checkAgeOnGet","noUpdateTTL","onRemove","skipRemoveOnSet","expiration","t","exp","expNum","current","k","oldValue","keys","excessCount","removedKeys","currentTime"],"mappings":"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","file":"index.mjs","sourcesContent":["//\n// TTL Cache implementation in TypeScript\n// A time-based cache that automatically removes entries after their TTL expires\n//\n\n// Helper function to get current timestamp in milliseconds\nconst now = (): number => performance.now();\n\n// Validation helpers\nconst isPosInt = (value: any): boolean => Number.isInteger(value) && value > 0;\nconst isPosIntOrInf = (n: any): boolean => n === Infinity || isPosInt(n);\n\n// Reasons why an item might be removed from the cache\ntype DisposeReason = \"delete\" | \"set\" | \"evict\" | \"stale\";\n\n// Configuration options for creating a new TTLCache\ninterface TTLCacheOptions<K, V> {\n\tmax?: number; // Maximum number of items to store\n\tttl?: number; // Default Time-To-Live in milliseconds\n\tupdateAgeOnGet?: boolean; // Whether to reset TTL when item is accessed\n\tcheckAgeOnGet?: boolean; // Whether to check expiration on access\n\tnoUpdateTTL?: boolean; // Whether to preserve TTL on value updates\n\tonRemove?: (value: V, key: K, reason: DisposeReason) => void; // Callback when items are removed\n\tskipRemoveOnSet?: boolean; // Whether to skip removal callback on value updates\n}\n\n// Options for the set() method\ninterface TTLCacheSetOptions {\n\tttl?: number; // Override default TTL for this item\n\tnoUpdateTTL?: boolean; // Override default TTL update behavior\n\tskipRemoveOnSet?: boolean; // Override default removal callback behavior\n}\n\nexport class TTLCache<K, V> {\n\t// Maps expiration timestamps to arrays of keys that expire at that time\n\tprivate expirations: Record<number, K[]> = Object.create(null);\n\t// Main storage for key-value pairs\n\tprivate data: Map<K, V> = new Map();\n\t// Maps keys to their expiration timestamps\n\tprivate expirationMap: Map<K, number> = new Map();\n\n\t// Cache configuration\n\tprivate ttl: number | undefined;\n\tprivate max: number;\n\tprivate updateAgeOnGet: boolean;\n\tprivate checkAgeOnGet: boolean;\n\tprivate noUpdateTTL: boolean;\n\tprivate skipRemoveOnSet: boolean;\n\n\t// Timer for automatic purging of expired items\n\tprivate timer?: ReturnType<typeof setTimeout>;\n\tprivate timerExpiration?: number;\n\n\t// Default no-op removal handler\n\tpublic handleRemoval(value: V, key: K, reason: DisposeReason): void {\n\t\t// Can be overridden with custom removal logic\n\t}\n\n\tconstructor(options: TTLCacheOptions<K, V> = {}) {\n\t\tconst {\n\t\t\tmax = Infinity,\n\t\t\tttl,\n\t\t\tupdateAgeOnGet = false,\n\t\t\tcheckAgeOnGet = false,\n\t\t\tnoUpdateTTL = false,\n\t\t\tonRemove,\n\t\t\tskipRemoveOnSet = false\n\t\t} = options;\n\n\t\tif (ttl !== undefined && !isPosIntOrInf(ttl)) {\n\t\t\tthrow new TypeError(\"ttl must be positive integer or Infinity if set\");\n\t\t}\n\t\tif (!isPosIntOrInf(max)) {\n\t\t\tthrow new TypeError(\"max must be positive integer or Infinity\");\n\t\t}\n\n\t\tthis.ttl = ttl;\n\t\tthis.max = max;\n\t\tthis.updateAgeOnGet = Boolean(updateAgeOnGet);\n\t\tthis.checkAgeOnGet = Boolean(checkAgeOnGet);\n\t\tthis.noUpdateTTL = Boolean(noUpdateTTL);\n\t\tthis.skipRemoveOnSet = Boolean(skipRemoveOnSet);\n\n\t\tif (onRemove !== undefined) {\n\t\t\tif (typeof onRemove !== \"function\") {\n\t\t\t\tthrow new TypeError(\"onRemove must be a function, received \" + typeof onRemove);\n\t\t\t}\n\t\t\tthis.handleRemoval = onRemove;\n\t\t}\n\t}\n\n\t// Sets up a timer to automatically purge expired items\n\tprivate setTimer(expiration: number, ttl: number): void {\n\t\t// Skip if we already have a timer for an earlier expiration\n\t\tif (this.timerExpiration !== undefined && this.timerExpiration < expiration) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Clear existing timer if any\n\t\tif (this.timer) {\n\t\t\tclearTimeout(this.timer);\n\t\t}\n\n\t\t// Create new timer\n\t\tconst t = setTimeout(() => {\n\t\t\tthis.timer = undefined;\n\t\t\tthis.timerExpiration = undefined;\n\t\t\tthis.purgeStale();\n\n\t\t\t// Set up next timer for remaining items\n\t\t\tfor (const exp in this.expirations) {\n\t\t\t\tconst expNum = Number(exp);\n\t\t\t\tthis.setTimer(expNum, expNum - now());\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}, ttl);\n\n\t\t// Prevent timer from keeping Node.js process alive\n\t\tif (typeof (t as any).unref === \"function\") {\n\t\t\t(t as any).unref();\n\t\t}\n\n\t\tthis.timerExpiration = expiration;\n\t\tthis.timer = t;\n\t}\n\n\t// Cancels the automatic purge timer\n\tpublic cancelTimer(): void {\n\t\tif (this.timer) {\n\t\t\tclearTimeout(this.timer);\n\t\t\tthis.timerExpiration = undefined;\n\t\t\tthis.timer = undefined;\n\t\t}\n\t}\n\n\t// Removes all items from the cache\n\tpublic clear(): void {\n\t\t// Only collect entries if we have a custom removal handler\n\t\tconst entries = this.handleRemoval !== TTLCache.prototype.handleRemoval ? [...this] : [];\n\t\tthis.data.clear();\n\t\tthis.expirationMap.clear();\n\t\tthis.cancelTimer();\n\t\tthis.expirations = Object.create(null);\n\n\t\t// Notify removal handler for each item\n\t\tfor (const [key, val] of entries) {\n\t\t\tthis.handleRemoval(val, key, \"delete\");\n\t\t}\n\t}\n\n\t// Updates the TTL for a specific key\n\tprivate setTTL(key: K, ttl: number = this.ttl as number): void {\n\t\t// Remove from current expiration list\n\t\tconst current = this.expirationMap.get(key);\n\t\tif (current !== undefined) {\n\t\t\tconst exp = this.expirations[current];\n\t\t\tif (!exp || exp.length <= 1) {\n\t\t\t\tdelete this.expirations[current];\n\t\t\t} else {\n\t\t\t\tthis.expirations[current] = exp.filter((k) => k !== key);\n\t\t\t}\n\t\t}\n\n\t\t// Add to new expiration list\n\t\tif (ttl !== Infinity) {\n\t\t\tconst expiration = Math.floor(now() + ttl);\n\t\t\tthis.expirationMap.set(key, expiration);\n\n\t\t\tif (!this.expirations[expiration]) {\n\t\t\t\tthis.expirations[expiration] = [];\n\t\t\t\tthis.setTimer(expiration, ttl);\n\t\t\t}\n\n\t\t\tthis.expirations[expiration].push(key);\n\t\t} else {\n\t\t\tthis.expirationMap.set(key, Infinity);\n\t\t}\n\t}\n\n\t// Sets or updates a cache entry\n\tpublic set(key: K, val: V, options: TTLCacheSetOptions = {}): this {\n\t\tconst {\n\t\t\tttl = this.ttl as number,\n\t\t\tnoUpdateTTL = this.noUpdateTTL,\n\t\t\tskipRemoveOnSet = this.skipRemoveOnSet\n\t\t} = options;\n\n\t\tif (!isPosIntOrInf(ttl)) {\n\t\t\tthrow new TypeError(\"ttl must be positive integer or Infinity\");\n\t\t}\n\n\t\tif (this.expirationMap.has(key)) {\n\t\t\tif (!noUpdateTTL) {\n\t\t\t\tthis.setTTL(key, ttl);\n\t\t\t}\n\n\t\t\tconst oldValue = this.data.get(key);\n\t\t\tif (oldValue !== val) {\n\t\t\t\tthis.data.set(key, val);\n\t\t\t\tif (!skipRemoveOnSet) {\n\t\t\t\t\tthis.handleRemoval(oldValue as V, key, \"set\");\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tthis.setTTL(key, ttl);\n\t\t\tthis.data.set(key, val);\n\t\t}\n\n\t\twhile (this.size > this.max) {\n\t\t\tthis.purgeToCapacity();\n\t\t}\n\n\t\treturn this;\n\t}\n\n\t// Standard Map-like methods\n\tpublic has(key: K): boolean {\n\t\treturn this.data.has(key);\n\t}\n\n\t// Gets remaining TTL for a key in milliseconds\n\tpublic getRemainingTTL(key: K): number {\n\t\tconst expiration = this.expirationMap.get(key);\n\t\treturn expiration === Infinity\n\t\t\t? Infinity\n\t\t\t: expiration !== undefined\n\t\t\t\t? Math.max(0, Math.ceil(expiration - now()))\n\t\t\t\t: 0;\n\t}\n\n\t// Retrieves a value from the cache\n\tpublic get(\n\t\tkey: K,\n\t\toptions: { updateAgeOnGet?: boolean; ttl?: number; checkAgeOnGet?: boolean } = {}\n\t): V | undefined {\n\t\tconst {\n\t\t\tupdateAgeOnGet = this.updateAgeOnGet,\n\t\t\tttl = this.ttl as number,\n\t\t\tcheckAgeOnGet = this.checkAgeOnGet\n\t\t} = options;\n\t\tconst val = this.data.get(key);\n\n\t\tif (checkAgeOnGet && this.getRemainingTTL(key) === 0) {\n\t\t\tthis.delete(key);\n\t\t\treturn undefined;\n\t\t}\n\n\t\tif (updateAgeOnGet) {\n\t\t\tthis.setTTL(key, ttl);\n\t\t}\n\t\treturn val;\n\t}\n\n\t// Removes an item from the cache\n\tpublic delete(key: K): boolean {\n\t\tconst current = this.expirationMap.get(key);\n\n\t\tif (current !== undefined) {\n\t\t\tconst value = this.data.get(key) as V;\n\t\t\tthis.data.delete(key);\n\t\t\tthis.expirationMap.delete(key);\n\t\t\tconst exp = this.expirations[current];\n\n\t\t\tif (exp) {\n\t\t\t\tif (exp.length <= 1) {\n\t\t\t\t\tdelete this.expirations[current];\n\t\t\t\t} else {\n\t\t\t\t\tthis.expirations[current] = exp.filter((k) => k !== key);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tthis.handleRemoval(value, key, \"delete\");\n\n\t\t\tif (this.size === 0) {\n\t\t\t\tthis.cancelTimer();\n\t\t\t}\n\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t}\n\n\t// Returns current number of items in cache\n\tpublic get size(): number {\n\t\treturn this.data.size;\n\t}\n\n\t// Helper method to remove a list of keys from the cache and trigger the\n\t// removal callback with the given reason.\n\tprivate removeKeys = (keysToRemove: K[], reason: \"stale\" | \"evict\" = \"evict\"): void => {\n\t\tconst entries: [K, V][] = [];\n\t\tfor (const key of keysToRemove) {\n\t\t\tentries.push([key, this.data.get(key) as V]);\n\t\t\tthis.data.delete(key);\n\t\t\tthis.expirationMap.delete(key);\n\t\t}\n\t\tfor (const [key, val] of entries) {\n\t\t\tthis.handleRemoval(val, key, reason);\n\t\t}\n\t};\n\n\t// Removes items to maintain maximum size limit\n\tprivate purgeToCapacity(): void {\n\t\t// Iterate over expiration buckets (assumes numeric keys are iterated in sorted order)\n\t\tfor (const exp in this.expirations) {\n\t\t\tconst keys = this.expirations[exp];\n\t\t\t// If removing the entire bucket still leaves the cache over capacity, remove the entire bucket.\n\t\t\tif (this.size - keys.length >= this.max) {\n\t\t\t\tdelete this.expirations[exp];\n\t\t\t\tthis.removeKeys(keys, \"evict\");\n\t\t\t} else {\n\t\t\t\t// Otherwise, remove just enough keys from this bucket to meet capacity.\n\t\t\t\tconst excessCount = this.size - this.max;\n\t\t\t\tconst removedKeys = keys.splice(0, excessCount);\n\t\t\t\tthis.removeKeys(removedKeys, \"evict\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t}\n\n\t// Removes all expired items\n\tpublic purgeStale(): void {\n\t\tconst currentTime = Math.ceil(now());\n\t\tfor (const exp in this.expirations) {\n\t\t\tif (exp === \"Infinity\" || Number(exp) > currentTime) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst keys = [...(this.expirations[exp] || [])];\n\t\t\tdelete this.expirations[exp];\n\t\t\tthis.removeKeys(keys, \"stale\"); // Now using \"stale\" as the removal reason.\n\t\t}\n\t\tif (this.size === 0) {\n\t\t\tthis.cancelTimer();\n\t\t}\n\t}\n\n\t// Iterator methods to make the cache iterable\n\tpublic *entries(): IterableIterator<[K, V]> {\n\t\tfor (const exp in this.expirations) {\n\t\t\tfor (const key of this.expirations[exp]) {\n\t\t\t\tyield [key, this.data.get(key) as V];\n\t\t\t}\n\t\t}\n\t}\n\n\tpublic *keys(): IterableIterator<K> {\n\t\tfor (const exp in this.expirations) {\n\t\t\tfor (const key of this.expirations[exp]) {\n\t\t\t\tyield key;\n\t\t\t}\n\t\t}\n\t}\n\n\tpublic *values(): IterableIterator<V> {\n\t\tfor (const exp in this.expirations) {\n\t\t\tfor (const key of this.expirations[exp]) {\n\t\t\t\tyield this.data.get(key) as V;\n\t\t\t}\n\t\t}\n\t}\n\n\tpublic [Symbol.iterator](): IterableIterator<[K, V]> {\n\t\treturn this.entries();\n\t}\n}\n"]}