@toreda/cache
Version:
Time-based object cache in TypeScript.
186 lines (184 loc) • 6.89 kB
JavaScript
"use strict";
/**
* MIT License
*
* Copyright (c) 2019 - 2022 Toreda, Inc.
*
* 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.
*
*/
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Cache = void 0;
const strong_types_1 = require("@toreda/strong-types");
const item_1 = require("./cache/item");
const defaults_1 = require("./defaults");
const log_1 = require("@toreda/log");
const id_1 = require("./cache/item/id");
const time_1 = require("@toreda/time");
/**
* Time based object cache for TypeScript generics.
*
* @category Cache
*/
class Cache {
constructor(cfg) {
this.items = new Map();
this.itemValidator = (cfg === null || cfg === void 0 ? void 0 : cfg.itemValidator) ? cfg.itemValidator : this.defaultItemValidator.bind(this);
this.log = this.makeLog(cfg === null || cfg === void 0 ? void 0 : cfg.log);
this.capacityMax = (0, strong_types_1.uIntMake)(defaults_1.Defaults.Cache.CapacityMax, cfg === null || cfg === void 0 ? void 0 : cfg.capacityMax);
this.pruneDelay = (0, time_1.timeMake)('s', (0, strong_types_1.numberValue)(cfg === null || cfg === void 0 ? void 0 : cfg.pruneDelay, defaults_1.Defaults.Cache.PruneDelay));
this.lastPrune = (0, time_1.timeMake)('s', 0);
}
/**
* Helper that guarantees a Log instance is set during init. Check optional `log` arg and return it if
* it's a valid `Log` instance. Otherwise creates & returns a new `Log` instance.
* @param baseLog
* @returns
*/
makeLog(log) {
if (!(0, strong_types_1.typeMatch)(log, log_1.Log)) {
return new log_1.Log();
}
return log.makeLog('Cache');
}
/**
* Get current cached item count.
* @returns
*/
size() {
return this.items.size;
}
/**
* Check unexpired item with target id exists in cache. Returns false when target item expires
* but still exists in cache.
* @param id Unique ID of item in cache.
* @returns
*/
has(id) {
if (!this.items.has(id)) {
return false;
}
const item = this.items.get(id);
if (!item || !(0, strong_types_1.typeMatch)(item, item_1.CacheItem)) {
return false;
}
return (item === null || item === void 0 ? void 0 : item.expired()) === false;
}
/**
* Get item from cache matching `id` if one exists.
* @param id Globally unique item identifier.
* @returns Item of type `ItemT` if it exists, otherwise `null`.
*/
get(id) {
if (!this.has(id)) {
return null;
}
const wrapper = this.items.get(id);
if (wrapper === null || wrapper === void 0 ? void 0 : wrapper.expired()) {
return null;
}
if (!(wrapper === null || wrapper === void 0 ? void 0 : wrapper.data)) {
return null;
}
return wrapper.data;
}
/**
* Add item to cache if it does not exist. When ite
* @param item Item to cache.
* @param overwrite `true` - Overwrite existing item with same ID.
* `false` - (default) Do not overwrite existing item. add call fails.
* @returns
*/
add(item, overwrite) {
const id = (0, id_1.cacheItemId)(item);
if (!id) {
return false;
}
const has = this.has(id);
if (has === true && overwrite !== true) {
return false;
}
const wrappedItem = new item_1.CacheItem(item);
this.items.set(id, wrappedItem);
return true;
}
/**
* The default validator used to check items before they're cached. Only used when no cache
* cfg option is provided for `itemValidator`.
* @param item
* @returns
*/
defaultItemValidator(item) {
if (!item) {
return false;
}
return true;
}
/**
* Clear cached & volatile data that can be easily recreated. The system received a memory
* warning, indicating performance issues.
* @returns `true` when handler executes, `false` when handler does not execute.
*/
onMemoryWarning() {
return __awaiter(this, void 0, void 0, function* () {
this.reset();
return true;
});
}
/**
* Iterate over all items and check for expiration.
* @returns
*/
prune() {
return __awaiter(this, void 0, void 0, function* () {
// Bail out if called before enough time has elapsed.
if (!this.lastPrune.elapsed(this.pruneDelay)) {
return 0;
}
let count = 0;
for (const [key, value] of this.items) {
if (value.expired()) {
this.items.delete(key);
count++;
}
}
this.lastPrune.setNow();
return count;
});
}
/**
* Reset cache to inital state.
* @returns void
*/
reset() {
this.pruneDelay.reset();
this.lastPrune.reset();
this.capacityMax.reset();
this.items.clear();
}
}
exports.Cache = Cache;