UNPKG

@toreda/cache

Version:

Time-based object cache in TypeScript.

186 lines (184 loc) 6.89 kB
"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;