"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); var __async = (__this, __arguments, generator) => { return new Promise((resolve, reject) => { var fulfilled = (value) => { try { step(generator.next(value)); } catch (e) { reject(e); } }; var rejected = (value) => { try { step(generator.throw(value)); } catch (e) { reject(e); } }; var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected); step((generator = generator.apply(__this, __arguments)).next()); }); }; // src/index.ts var src_exports = {}; __export(src_exports, { CacheError: () => CacheError, DAY: () => DAY, HOUR: () => HOUR, InvalidStateError: () => InvalidStateError, MINUTE: () => MINUTE, MONTH: () => MONTH, SECOND: () => SECOND, ValidationError: () => ValidationError, WEEK: () => WEEK, YEAR: () => YEAR, cacheErrorCodes: () => cacheErrorCodes, default: () => src_default }); module.exports = __toCommonJS(src_exports); // src/errors.ts var cacheErrorCodes = { CACHE_ERROR: "CACHE_ERROR", VALIDATION_ERROR: "VALIDATION_ERROR", INVALID_STATE_ERROR: "INVALID_STATE_ERROR" }; var CacheError = class extends Error { constructor(message) { super(message); this.name = cacheErrorCodes.CACHE_ERROR; } }; var ValidationError = class extends CacheError { constructor(message) { super(message); this.name = cacheErrorCodes.VALIDATION_ERROR; } }; var InvalidStateError = class extends CacheError { constructor(message) { super(message); this.name = cacheErrorCodes.INVALID_STATE_ERROR; } }; // src/utils.ts function sleep(ms) { return __async(this, null, function* () { yield new Promise((resolve) => setTimeout(resolve, ms)); }); } function hasOwnProperty(obj, key) { return Object.prototype.hasOwnProperty.call(obj, key); } function withTimeout(promise, ms) { return Promise.race([ promise, sleep(ms) ]); } // src/Cache.ts var Cache = class { /** * Creates a new Cache instance. */ constructor(options) { this._evictStrategy = null; this._keys = []; this._keyLength = 0; this._cache = {}; this._pendingRefreshes = {}; this._maintenanceLoopTimeout = null; this._namespace = (options == null ? void 0 : options.namespace) || null; this._expireAfterMs = (options == null ? void 0 : options.expireAfterMs) || null; this._staleAfterMs = (options == null ? void 0 : options.staleAfterMs) || null; this._staleTimeoutMs = (options == null ? void 0 : options.staleTimeoutMs) || null; this._refreshAfterMs = (options == null ? void 0 : options.refreshAfterMs) || null; this._refreshTimeoutMs = null; this._timePrecisionMs = (options == null ? void 0 : options.timePrecisionMs) || 1e4; this._fetchMethod = (options == null ? void 0 : options.fetchMethod) || null; this._maxKeys = (options == null ? void 0 : options.maxKeys) || null; this._evictStrategy = (options == null ? void 0 : options.evictStrategy) || null; this._isStopped = true; if (((options == null ? void 0 : options.refreshAfterMs) || (options == null ? void 0 : options.staleAfterMs)) && !(options == null ? void 0 : options.fetchMethod)) { throw new ValidationError(this._getLogPrefix() + "fetchMethod is required when refreshAfterMs or staleAfterMs is set"); } this.resume(); } /** * Returns the value of a key in the cache if present, and undefined otherwise. */ get(key) { return this._get(key, this._now()); } /** * Returns the value of multiple keys in the cache if present, and undefined otherwise. */ mget(keys) { const ts = this._now(); return keys.map((key) => this._get(key, ts)); } /** * Returns a promise that resolves to the value of a key in the cache if present, * and fetches it asynchronously otherwise. */ fetch(key) { return __async(this, null, function* () { if (!this._fetchMethod) { throw new ValidationError(this._getLogPrefix() + "fetchMethod is required when calling fetch"); } this._validateKey(key); if (this.has(key) && !this._isStale(key) && !this._isExpired(key)) { return this.get(key); } this.refresh(key); if (!this.has(key)) { yield this._pendingRefreshes[key]; } else if (this._staleTimeoutMs) { yield withTimeout(this._pendingRefreshes[key], this._staleTimeoutMs); } return this.get(key); }); } /** * Sets a key-value pair in the cache. */ set(key, value) { return this._set(key, value, this._now()); } /** * Sets multiple key-value pairs at once in the cache. */ mset(pairs) { const ts = this._now(); pairs.forEach(([key, value]) => this._set(key, value, ts)); } /** * Refreshes a key */ refresh(key) { if (!this._fetchMethod) { throw new ValidationError(this._getLogPrefix() + "fetchMethod is required when calling refresh"); } if (!this._isPendingRefresh(key)) { this._pendingRefreshes[key] = new Promise((resolve) => { const result = this._fetchMethod(key); const promise = result instanceof Promise ? result : Promise.resolve(result); promise.then((value) => { if (value != void 0) { this.set(key, value); resolve(true); } else { resolve(false); } }).catch(() => { resolve(false); }).finally(() => { delete this._pendingRefreshes[key]; }); }); } return this._pendingRefreshes[key]; } /** * Returns true if the cache has the given key, and false otherwise. */ has(key) { this._validateKey(key); return hasOwnProperty(this._cache, key); } /** * Returns an array of all keys in the cache. */ keys() { return this._keys; } /** * Deletes a key from the cache. */ del(key) { return this._del(key); } /** * Deletes multiple keys from the cache. */ mdel(keys) { keys.forEach((key) => this._del(key)); return this; } /** * Deletes all keys from the cache. */ flushAll() { for (const key in this._cache) { delete this._cache[key]; } this._updateStats(); return this; } /** * Returns full cache object, which can be used later with `load`. */ dump() { return this._cache; } /** * Loads a cache object into the cache. */ load(cache) { this._cache = cache; this._updateStats(); return this; } /** * Stops all internal timers. */ stop() { if (this._isStopped) { return; } this._isStopped = true; clearTimeout(this._maintenanceLoopTimeout); } /** * Restarts all internal timers. */ resume() { if (!this._isStopped) { return; } this._isStopped = false; if (this._refreshAfterMs || this._expireAfterMs) { this._runMaintenance(); } } /** * Returns true if internal timers are stopped, and auto-refreshes are not happening. */ isStopped() { return this._isStopped; } /** * Checks if a key is valid, and throws an error if it is not. */ _validateKey(key) { if (typeof key !== "string" || key.length === 0) { throw new ValidationError(this._getLogPrefix() + "Key must be a non-empty string. Here: " + String(key)); } } /** * Checks if a key is expired. */ _isExpired(key) { return this._expireAfterMs !== null && this._cache[key].r + this._expireAfterMs < this._now(); } /** * Checks if a key is stale. */ _isStale(key) { return this._staleAfterMs !== null && this._cache[key].r + this._staleAfterMs < this._now(); } /** * Checks if a key is pending refresh. */ _isPendingRefresh(key) { return hasOwnProperty(this._pendingRefreshes, key); } /** * Checks if a key needs to be refreshed. */ _needsRefresh(key) { return this._refreshAfterMs !== null && !this._isPendingRefresh(key) && this._cache[key].r + this._refreshAfterMs < this._now(); } _set(key, value, ts) { this._validateKey(key); if (!this.has(key) && this._maxKeys && this._keyLength >= this._maxKeys) { this._onMaxKeysReached(); } const oldWrapper = this._cache[key]; this._cache[key] = { v: value, c: ts, a: (oldWrapper == null ? void 0 : oldWrapper.a) || null, r: ts }; this._updateStats(); } _get(key, ts) { this._validateKey(key); if (!this.has(key)) { return; } if (this._isExpired(key)) { this.del(key); return; } this._cache[key].a = ts; return this._cache[key].v; } _del(key) { this._validateKey(key); delete this._cache[key]; this._updateStats(); return this; } /** * Evicts keys based on the evict strategy or throws an error if no strategy is set. */ _onMaxKeysReached() { switch (this._evictStrategy) { case "LRU": this._evictLRU(); break; case "FIFO": this._evictFIFO(); break; default: throw new InvalidStateError(this._getLogPrefix() + "Cache is full. Either increase maxKeys or set an evictStrategy"); } } /** * Evicts the least recently used keys to make room for an extra key. */ _evictLRU() { const keysByAccessedAtDescNullsLast = this._sortKeysDescNullsLast((key) => this._cache[key].a || this._cache[key].r); const keysToEvict = keysByAccessedAtDescNullsLast.slice(this._maxKeys - 1); keysToEvict.forEach((key) => this.del(key)); } /** * Evicts the least recently refreshed keys to make room for an extra key. */ _evictFIFO() { const keysByCreatedAtDescNullsLast = this._sortKeysDescNullsLast((key) => this._cache[key].c); const keysToEvict = keysByCreatedAtDescNullsLast.slice(this._maxKeys - 1); keysToEvict.forEach((key) => this.del(key)); } _sortKeysDescNullsLast(propGetter) { return this._keys.sort((a, b) => { const propA = propGetter(a); const propB = propGetter(b); if (propA === propB) { return 0; } return propA - propB < 0 ? 1 : -1; }); } /** * Returns the current timestamp in milliseconds. */ _now() { return Date.now(); } /** * Runs the auto-eviction and auto-refresh loops. */ _runMaintenance() { return __async(this, null, function* () { if (this._expireAfterMs) { this._evictExpiredValues(); } if (this._refreshAfterMs) { yield this._refreshOldValues(); } if (!this.isStopped()) { this._maintenanceLoopTimeout = setTimeout( () => this._runMaintenance(), this._timePrecisionMs ); } }); } /** * Evicts all expired values. */ _evictExpiredValues() { this.keys().filter((key) => this._isExpired(key)).forEach((key) => this.del(key)); } /** * Refreshes all values that need to be refreshed. */ _refreshOldValues() { return __async(this, null, function* () { const keysToRefresh = this.keys().filter((key) => this._needsRefresh(key)); for (const key of keysToRefresh) { yield this.refresh(key); } }); } /** * Updates the internal stats of the cache. */ _updateStats() { this._keys = Object.keys(this._cache); this._keyLength = this._keys.length; } /** * Returns a log prefix with the namespace if set. */ _getLogPrefix() { return `[Cache${this._namespace ? `:${this._namespace}` : ""}] `; } }; // src/constants.ts var SECOND = 1e3; var MINUTE = 60 * SECOND; var HOUR = 60 * MINUTE; var DAY = 24 * HOUR; var WEEK = 7 * DAY; var MONTH = 30 * DAY; var YEAR = 365 * DAY; // src/index.ts var src_default = Cache; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { CacheError, DAY, HOUR, InvalidStateError, MINUTE, MONTH, SECOND, ValidationError, WEEK, YEAR, cacheErrorCodes });