@avanio/expire-cache
Version:
Typescript/Javascript cache with expiration
535 lines (532 loc) • 18.7 kB
JavaScript
var __defProp = Object.defineProperty;
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;
};
// src/ExpireCache.mts
import { EventEmitter } from "events";
import { LogLevel, MapLogger } from "@avanio/logger-like";
var defaultLogMap = {
cleanExpired: LogLevel.None,
clear: LogLevel.None,
constructor: LogLevel.None,
delete: LogLevel.None,
expires: LogLevel.None,
get: LogLevel.None,
has: LogLevel.None,
onExpire: LogLevel.None,
set: LogLevel.None,
size: LogLevel.None
};
var ExpireCache = class extends EventEmitter {
/**
* Creates a new instance of the ExpireCache class
* @param {ILoggerLike} logger - The logger to use (optional)
* @param {Partial<ExpireCacheLogMapType>} logMapping - The log mapping to use (optional). Default is all logging disabled
* @param {number} 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 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 {number} 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
import { EventEmitter as EventEmitter2 } from "events";
import { LogLevel as LogLevel2, MapLogger as MapLogger2 } from "@avanio/logger-like";
var defaultLogMap2 = {
cleanExpired: LogLevel2.None,
clear: LogLevel2.None,
constructor: LogLevel2.None,
delete: LogLevel2.None,
expires: LogLevel2.None,
get: LogLevel2.None,
has: LogLevel2.None,
onExpire: LogLevel2.None,
set: LogLevel2.None,
size: LogLevel2.None
};
var ExpireTimeoutCache = class extends EventEmitter2 {
/**
* Creates a new instance of the ExpireTimeoutCache class
* @param {ILoggerLike} logger - The logger to use (optional)
* @param {Partial<ExpireTimeoutCacheLogMapType>} logMapping - The log mapping to use (optional). Default is all logging disabled
* @param {number} 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 MapLogger2(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 {number} 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
import { EventEmitter as EventEmitter3 } from "events";
import { LogLevel as LogLevel3, MapLogger as MapLogger3 } from "@avanio/logger-like";
var defaultLogMap3 = {
clear: LogLevel3.None,
clearTimeoutKey: LogLevel3.None,
constructor: LogLevel3.None,
delete: LogLevel3.None,
get: LogLevel3.None,
has: LogLevel3.None,
runTimeout: LogLevel3.None,
set: LogLevel3.None,
setTimeout: LogLevel3.None,
size: LogLevel3.None
};
var TieredCache = class extends EventEmitter3 {
constructor(logger, logMapping) {
super();
this.cache = /* @__PURE__ */ new Map();
this.cacheTimeout = /* @__PURE__ */ new Map();
this.logger = new MapLogger3(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} key - cache key
* @param {T['tier']} tier - cache tier
* @param {TimeoutEnum} [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} key - cache key
* @param {T['tier']} tier - cache tier
* @param {T['data']} data - cache data
* @param {TimeoutEnum} [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 {T['tier']} tier - cache tier
* @param {Iterable<[Key, T['data']]>} entries - iterable of key, data pairs
* @param {TimeoutEnum} [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
* @param {Key} key - cache key
* @returns {Tiers[number]['tier'] | undefined} The tier type or undefined if not found
*/
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 {Tiers[number]['tier']} tier - cache tier to get values for
* @returns {AsyncIterable<Tiers[number]['data']>} Async iterable of cache values
*/
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 };
}
};
}
};
}
/**
* Iterate this.cache entries and use handleCacheEntry to get data
* @param {Tiers[number]['tier']} tier - cache tier to get entries for
* @returns {AsyncIterable<[Key, Tiers[number]['data']]>} Async iterable of key-value pairs
*/
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} key - cache entry key
* @param {T['tier']} tier - cache entry tier
* @param {T['data']} data - cache entry data
* @param {TimeoutEnum} [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} 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);
}
}
};
export {
ExpireCache,
ExpireTimeoutCache,
TieredCache
};
//# sourceMappingURL=index.mjs.map