UNPKG

@avanio/expire-cache

Version:

Typescript/Javascript cache with expiration

553 lines (549 loc) 19.4 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getOwnPropSymbols = Object.getOwnPropertySymbols; var __hasOwnProp = Object.prototype.hasOwnProperty; var __propIsEnum = Object.prototype.propertyIsEnumerable; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __spreadValues = (a, b) => { for (var prop in b || (b = {})) if (__hasOwnProp.call(b, prop)) __defNormalProp(a, prop, b[prop]); if (__getOwnPropSymbols) for (var prop of __getOwnPropSymbols(b)) { if (__propIsEnum.call(b, prop)) __defNormalProp(a, prop, b[prop]); } return a; }; var __objRest = (source, exclude) => { var target = {}; for (var prop in source) if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0) target[prop] = source[prop]; if (source != null && __getOwnPropSymbols) for (var prop of __getOwnPropSymbols(source)) { if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop)) target[prop] = source[prop]; } return target; }; 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); // src/index.mts var index_exports = {}; __export(index_exports, { ExpireCache: () => ExpireCache, ExpireTimeoutCache: () => ExpireTimeoutCache, TieredCache: () => TieredCache }); module.exports = __toCommonJS(index_exports); // src/ExpireCache.mts var import_events = require("events"); var import_logger_like = require("@avanio/logger-like"); var defaultLogMap = { cleanExpired: import_logger_like.LogLevel.None, clear: import_logger_like.LogLevel.None, constructor: import_logger_like.LogLevel.None, delete: import_logger_like.LogLevel.None, expires: import_logger_like.LogLevel.None, get: import_logger_like.LogLevel.None, has: import_logger_like.LogLevel.None, onExpire: import_logger_like.LogLevel.None, set: import_logger_like.LogLevel.None, size: import_logger_like.LogLevel.None }; var ExpireCache = class extends import_events.EventEmitter { /** * Creates a new instance of the ExpireCache class * @param logger - The logger to use (optional) * @param logMapping - The log mapping to use (optional). Default is all logging disabled * @param defaultExpireMs - The default expiration time in milliseconds (optional) */ constructor(logger, logMapping, defaultExpireMs) { super(); this.cache = /* @__PURE__ */ new Map(); this.cacheTtl = /* @__PURE__ */ new Map(); this.logger = new import_logger_like.MapLogger(logger, Object.assign({}, defaultLogMap, logMapping)); this.logger.logKey("constructor", `ExpireCache created, defaultExpireMs: ${String(defaultExpireMs)}`); this.defaultExpireMs = defaultExpireMs; } set(key, data, expires) { var _a; const expireTs = (_a = this.getExpireDate(expires)) == null ? void 0 : _a.getTime(); this.logger.logKey("set", `ExpireCache set key: ${String(key)}, expireTs: ${String(expireTs)}`); this.emit("set", key, data, this.getExpireDate(expires)); this.cache.set(key, data); this.cacheTtl.set(key, expireTs); } get(key) { this.logger.logKey("get", `ExpireCache get key: ${String(key)}`); this.emit("get", key); this.cleanExpired(); return this.cache.get(key); } has(key) { this.logger.logKey("has", `ExpireCache has key: ${String(key)}`); this.cleanExpired(); return this.cache.has(key); } expires(key) { this.logger.logKey("expires", `ExpireCache get expire for key: ${String(key)}`); const expires = this.cacheTtl.get(key); this.cleanExpired(); return expires ? new Date(expires) : void 0; } delete(key) { this.logger.logKey("delete", `ExpireCache delete key: ${String(key)}`); const entry = this.cache.get(key); if (entry) { this.notifyExpires(/* @__PURE__ */ new Map([[key, entry]])); this.emit("delete", key); } this.cacheTtl.delete(key); return this.cache.delete(key); } clear() { this.logger.logKey("clear", `ExpireCache clear`); const copy = new Map(this.cache); this.notifyExpires(copy); this.emit("clear", copy); this.cache.clear(); this.cacheTtl.clear(); } size() { this.logger.logKey("size", `ExpireCache size: ${this.cache.size.toString()}`); return this.cache.size; } entries() { this.cleanExpired(); return new Map(this.cache).entries(); } keys() { this.cleanExpired(); return new Map(this.cache).keys(); } values() { this.cleanExpired(); return new Map(this.cache).values(); } /** * Sets the default expiration time in milliseconds * @param expireMs - The default expiration time in milliseconds */ setExpireMs(expireMs) { this.defaultExpireMs = expireMs; } /** * Cleans expired cache entries */ cleanExpired() { const now = (/* @__PURE__ */ new Date()).getTime(); const deleteEntries = /* @__PURE__ */ new Map(); for (const [key, expire] of this.cacheTtl.entries()) { if (expire !== void 0 && expire < now) { const value = this.cache.get(key); if (value) { deleteEntries.set(key, value); this.cache.delete(key); } this.cacheTtl.delete(key); } } if (deleteEntries.size > 0) { this.notifyExpires(deleteEntries); this.logger.logKey("cleanExpired", `ExpireCache expired count: ${deleteEntries.size.toString()}`); } } notifyExpires(entries) { for (const [key, value] of entries.entries()) { this.emit("expires", key, value); } } getExpireDate(expires) { const defaultExpireDate = this.defaultExpireMs ? new Date(Date.now() + this.defaultExpireMs) : void 0; return expires != null ? expires : defaultExpireDate; } }; // src/ExpireTimeoutCache.mts var import_events2 = require("events"); var import_logger_like2 = require("@avanio/logger-like"); var defaultLogMap2 = { cleanExpired: import_logger_like2.LogLevel.None, clear: import_logger_like2.LogLevel.None, constructor: import_logger_like2.LogLevel.None, delete: import_logger_like2.LogLevel.None, expires: import_logger_like2.LogLevel.None, get: import_logger_like2.LogLevel.None, has: import_logger_like2.LogLevel.None, onExpire: import_logger_like2.LogLevel.None, set: import_logger_like2.LogLevel.None, size: import_logger_like2.LogLevel.None }; var ExpireTimeoutCache = class extends import_events2.EventEmitter { /** * Creates a new instance of the ExpireTimeoutCache class * @param logger - The logger to use (optional) * @param logMapping - The log mapping to use (optional). Default is all logging disabled * @param defaultExpireMs - The default expiration time in milliseconds (optional) */ constructor(logger, logMapping, defaultExpireMs) { super(); this.cache = /* @__PURE__ */ new Map(); this.cacheTimeout = /* @__PURE__ */ new Map(); this.logger = new import_logger_like2.MapLogger(logger, Object.assign({}, defaultLogMap2, logMapping)); this.logger.logKey("constructor", `ExpireTimeoutCache created, defaultExpireMs: ${String(defaultExpireMs)}`); this.defaultExpireMs = defaultExpireMs; } set(key, data, expires) { this.clearTimeout(key); const _a = this.handleTimeoutSetup(key, this.getExpireDate(expires)), { expiresInMs } = _a, options = __objRest(_a, ["expiresInMs"]); const expireString = expiresInMs ? `${expiresInMs.toString()} ms` : "undefined"; this.logger.logKey("set", `ExpireTimeoutCache set key: ${String(key)}, expireTs: ${expireString}`); this.cache.set(key, data); this.cacheTimeout.set(key, options); } get(key) { this.logger.logKey("get", `ExpireTimeoutCache get key: ${String(key)}`); this.emit("get", key); return this.cache.get(key); } has(key) { this.logger.logKey("has", `ExpireTimeoutCache has key: ${String(key)}`); return this.cache.has(key); } expires(key) { var _a; this.logger.logKey("expires", `ExpireTimeoutCache get expire for key: ${String(key)}`); return (_a = this.cacheTimeout.get(key)) == null ? void 0 : _a.expires; } delete(key) { this.logger.logKey("delete", `ExpireTimeoutCache delete key: ${String(key)}`); this.clearTimeout(key); const entry = this.cache.get(key); if (entry) { this.emit("delete", key); this.notifyExpires(/* @__PURE__ */ new Map([[key, entry]])); } return this.cache.delete(key); } clear() { this.logger.logKey("clear", `ExpireTimeoutCache clear`); this.cacheTimeout.forEach((_value, key) => this.clearTimeout(key)); const copy = new Map(this.cache); this.notifyExpires(copy); this.emit("clear", copy); this.cache.clear(); this.cacheTimeout.clear(); } size() { this.logger.logKey("size", `ExpireTimeoutCache size: ${this.cache.size.toString()}`); return this.cache.size; } entries() { return new Map(this.cache).entries(); } keys() { return new Map(this.cache).keys(); } values() { return new Map(this.cache).values(); } /** * Set the default expiration time in milliseconds * @param expireMs - The default expiration time in milliseconds */ setExpireMs(expireMs) { this.defaultExpireMs = expireMs; } clearTimeout(key) { const entry = this.cacheTimeout.get(key); if (entry == null ? void 0 : entry.timeout) { clearTimeout(entry.timeout); entry.timeout = void 0; } } notifyExpires(entries) { for (const [key, value] of entries.entries()) { this.emit("expires", key, value); } } getExpireDate(expires) { const defaultExpireDate = this.defaultExpireMs ? new Date(Date.now() + this.defaultExpireMs) : void 0; return expires != null ? expires : defaultExpireDate; } handleExpiredCallback(key) { this.logger.logKey("onExpire", `ExpireTimeoutCache onExpire key: ${String(key)}`); this.delete(key); } handleTimeoutSetup(key, expiresDate) { const expiresInMs = expiresDate && expiresDate.getTime() - Date.now(); const timeout = expiresInMs !== void 0 ? setTimeout(() => this.handleExpiredCallback(key), expiresInMs) : void 0; return { expiresInMs, timeout, expires: expiresDate }; } }; // src/TieredCache.mts var import_events3 = require("events"); var import_logger_like3 = require("@avanio/logger-like"); var defaultLogMap3 = { clear: import_logger_like3.LogLevel.None, clearTimeoutKey: import_logger_like3.LogLevel.None, constructor: import_logger_like3.LogLevel.None, delete: import_logger_like3.LogLevel.None, get: import_logger_like3.LogLevel.None, has: import_logger_like3.LogLevel.None, runTimeout: import_logger_like3.LogLevel.None, set: import_logger_like3.LogLevel.None, setTimeout: import_logger_like3.LogLevel.None, size: import_logger_like3.LogLevel.None }; var TieredCache = class extends import_events3.EventEmitter { constructor(logger, logMapping) { super(); this.cache = /* @__PURE__ */ new Map(); this.cacheTimeout = /* @__PURE__ */ new Map(); this.logger = new import_logger_like3.MapLogger(logger, Object.assign({}, defaultLogMap3, logMapping)); this.logCacheName(); this.handleCacheEntry = this.handleCacheEntry.bind(this); this.statusData = { size: 0, tiers: __spreadValues({}, this.getInitialStatusData()) }; } /** * Get cache entry from cache * @param key - cache key * @param tier - cache tier * @param timeout - optional update to new timeout for cache entry (if not provided, default timeout for tier will be used) * @returns - promise that resolves when cache entry is set */ async get(key, tier, timeout) { this.logger.logKey("get", `MultiTierCache ${this.cacheName} get: '${String(key)}' tier: ${tier}`); const entry = this.cache.get(key); const value = await this.handleCacheEntry(key, tier, entry); if (value) { this.setTimeout(key, timeout != null ? timeout : this.handleTierDefaultTimeout(tier)); } return value; } /** * Set cache entry * @param key - cache key * @param tier - cache tier * @param data - cache data * @param timeout - optional timeout for cache entry. Else timeout will be checked from handleTimeoutValue or default timeout for tier. */ async set(key, tier, data, timeout) { this.logger.logKey("set", `MultiTierCache ${this.cacheName} set: '${String(key)}' tier: ${tier}`); await this.handleSetValue(key, tier, data, timeout); this.emit("set", [key]); this.emit("update", this.buildStatus(true)); } /** * Set multiple cache entries * @param tier - cache tier * @param entries - iterable of key, data pairs * @param timeout - optional timeout for cache entry. Else timeout will be checked from handleTimeoutValue or default timeout for tier. */ async setEntries(tier, entries, timeout) { const entriesArray = Array.from(entries); this.logger.logKey("set", `MultiTierCache ${this.cacheName} setEntries (count: ${entriesArray.length.toString()}) tier: ${tier}`); for (const [key, data] of entriesArray) { await this.handleSetValue(key, tier, data, timeout); } this.emit( "set", entriesArray.map(([key]) => key) ); this.emit("update", this.buildStatus(true)); } /** * Get tier type for current key */ getTier(key) { const entry = this.cache.get(key); return entry == null ? void 0 : entry.tier; } /** * Iterate this.cache values and use handleCacheEntry to get data * @param tier */ tierValues(tier) { const iterator = this.cache.entries(); const currentTierResolve = this.handleCacheEntry.bind(this); return { [Symbol.asyncIterator]: () => { return { async next() { const { value, done } = iterator.next(); if (done) { return { value: void 0, done }; } return { value: await currentTierResolve(value[0], tier, value[1]), done }; } }; } }; } tierEntries(tier) { const iterator = this.cache.entries(); const currentTierResolve = this.handleCacheEntry.bind(this); return { [Symbol.asyncIterator]: () => { return { async next() { const { value, done } = iterator.next(); if (done) { return { value, done: true }; } return { value: [value[0], await currentTierResolve(value[0], tier, value[1])], done: false }; } }; } }; } keys() { return this.cache.keys(); } has(key) { this.logger.logKey("has", `MultiTierCache ${this.cacheName} has: '${String(key)}'`); return this.cache.has(key); } size() { this.logger.logKey("size", `MultiTierCache ${this.cacheName} size: ${this.cache.size.toString()}`); return this.cache.size; } clear() { const keys = new Set(this.cache.keys()); this.clearAllTimeouts(); this.cache.clear(); this.logger.logKey("clear", `MultiTierCache ${this.cacheName} clear`); this.emit("delete", keys); this.emit("clear"); this.emit("update", this.buildStatus(true)); } delete(key) { const isDeleted = this.handleDeleteValue(key); if (isDeleted) { this.logger.logKey("delete", `MultiTierCache ${this.cacheName} delete: '${String(key)}'`); this.emit("delete", [key]); this.emit("update", this.buildStatus(true)); } return isDeleted; } deleteKeys(keys) { const deleteKeys = []; for (const key of keys) { this.clearTimeoutKey(key); if (this.cache.delete(key)) { deleteKeys.push(key); } } this.logger.logKey("delete", `MultiTierCache ${this.cacheName} deleteKeys (count: ${deleteKeys.length.toString()})`); this.emit("delete", deleteKeys); this.emit("update", this.buildStatus(true)); return deleteKeys.length; } status() { return this.buildStatus(false); } buildStatus(rebuild) { if (!rebuild) { return this.statusData; } this.statusData = Object.freeze({ size: this.cache.size, tiers: Array.from(this.cache.values()).reduce( (acc, { tier: type }) => { acc[type]++; return acc; }, __spreadValues({}, this.getInitialStatusData()) ) }); return this.statusData; } /** * Internal helper to set cache entry and set timeout * @param key - cache entry key * @param tier - cache entry tier * @param data - cache entry data * @param timeout - timeout value, optional */ async handleSetValue(key, tier, data, timeout) { this.cache.set(key, { tier, data }); this.setTimeout(key, timeout != null ? timeout : await this.handleTimeoutValue(key, tier, data)); } /** * Internal helper to delete cache entry and cancel its timeout * @param key - cache entry key * @returns true if entry was deleted, false if not found */ handleDeleteValue(key) { this.clearTimeoutKey(key); return this.cache.delete(key); } logCacheName() { this.logger.logKey("constructor", `MultiTierCache ${this.cacheName} created`); } setTimeout(key, timeout) { const oldTimeout = this.cacheTimeout.get(key); if (oldTimeout) { clearTimeout(oldTimeout); } if (timeout !== void 0) { this.cacheTimeout.set( key, setTimeout(() => void this.runTimeout(key), timeout) ); this.logger.logKey("setTimeout", `MultiTierCache ${this.cacheName} setTimeout: '${String(key)}' = timeouts: ${timeout.toString()}`); } } clearTimeoutKey(key) { const oldTimeout = this.cacheTimeout.get(key); if (oldTimeout) { this.logger.logKey("clearTimeoutKey", `MultiTierCache ${this.cacheName} clearTimeoutKey: '${String(key)}'`); clearTimeout(oldTimeout); } this.cacheTimeout.delete(key); } clearAllTimeouts() { for (const timeout of this.cacheTimeout.values()) { if (timeout) { clearTimeout(timeout); } } this.cacheTimeout.clear(); } async runTimeout(key) { try { const timeoutValue = await this.handleTierTimeout(key); if (timeoutValue === void 0) { this.logger.logKey("runTimeout", `MultiTierCache ${this.cacheName} runTimeout: '${String(key)}' cleared`); this.clearTimeoutKey(key); } else { this.logger.logKey("runTimeout", `MultiTierCache ${this.cacheName} runTimeout: '${String(key)}' cleared, new timeout: ${timeoutValue.toString()}`); this.setTimeout(key, timeoutValue); } this.emit("update", this.buildStatus(true)); } catch (error) { this.logger.error(error); } } }; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { ExpireCache, ExpireTimeoutCache, TieredCache }); //# sourceMappingURL=index.js.map