@toreda/cache
Version:
Time-based object cache in TypeScript.
1 lines • 7.84 kB
Source Map (JSON)
{"version":3,"sources":["../../src/cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAIH,OAAO,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AACvC,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,iBAAiB,CAAC;AACjD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,aAAa,CAAC;AAC3C,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,YAAY,CAAC;AAExC,OAAO,EAAC,GAAG,EAAC,MAAM,aAAa,CAAC;AAChC,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,cAAc,CAAC;AACvC,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,sBAAsB,CAAC;AAI/C;;;;GAIG;AACH,qBAAa,KAAK,CAAC,KAAK,SAAS,SAAS;IACzC,2BAA2B;IAC3B,SAAgB,GAAG,EAAE,GAAG,CAAC;IACzB,qBAAqB;IACrB,SAAgB,KAAK,EAAE,GAAG,CAAC,WAAW,EAAE,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC;IAC1D,2DAA2D;IAC3D,SAAgB,aAAa,EAAE,CAAC,IAAI,CAAC,EAAE,KAAK,GAAG,IAAI,KAAK,OAAO,CAAC;IAChE,8DAA8D;IAC9D,SAAgB,WAAW,EAAE,IAAI,CAAC;IAClC;6CACyC;IACzC,SAAgB,UAAU,EAAE,IAAI,CAAC;IACjC,wDAAwD;IACxD,SAAgB,SAAS,EAAE,IAAI,CAAC;gBAEpB,GAAG,CAAC,EAAE,OAAO,CAAC,KAAK,CAAC;IAUhC;;;;;OAKG;IACH,OAAO,CAAC,OAAO;IAQf;;;OAGG;IACI,IAAI,IAAI,MAAM;IAIrB;;;;;OAKG;IACI,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAa/B;;;;OAIG;IACI,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,KAAK,GAAG,IAAI;IAiBpC;;;;;;OAMG;IACI,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE,SAAS,CAAC,EAAE,OAAO,GAAG,OAAO;IAmBrD;;;;;OAKG;IACI,oBAAoB,CAAC,IAAI,CAAC,EAAE,KAAK,GAAG,IAAI,GAAG,OAAO;IAQzD;;;;OAIG;IACU,eAAe,IAAI,OAAO,CAAC,OAAO,CAAC;IAMhD;;;OAGG;IACU,KAAK,IAAI,OAAO,CAAC,MAAM,CAAC;IAmBrC;;;OAGG;IACI,KAAK,IAAI,IAAI;CAMpB","file":"cache.d.ts","sourcesContent":["/**\n *\tMIT License\n *\n *\tCopyright (c) 2019 - 2022 Toreda, Inc.\n *\n *\tPermission is hereby granted, free of charge, to any person obtaining a copy\n *\tof this software and associated documentation files (the \"Software\"), to deal\n *\tin the Software without restriction, including without limitation the rights\n *\tto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n *\tcopies of the Software, and to permit persons to whom the Software is\n *\tfurnished to do so, subject to the following conditions:\n\n * \tThe above copyright notice and this permission notice shall be included in all\n * \tcopies or substantial portions of the Software.\n *\n * \tTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n *\tIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n *\tFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * \tAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n *\tLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n *\tOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * \tSOFTWARE.\n *\n */\n\nimport {numberValue, typeMatch, uIntMake} from '@toreda/strong-types';\n\nimport {CacheItem} from './cache/item';\nimport type {CacheItemId} from './cache/item/id';\nimport type {Cacheable} from './cacheable';\nimport type {CfgData} from './cfg/data';\nimport {Defaults} from './defaults';\nimport {Log} from '@toreda/log';\nimport type {Time} from '@toreda/time';\nimport type {UInt} from '@toreda/strong-types';\nimport {cacheItemId} from './cache/item/id';\nimport {timeMake} from '@toreda/time';\n\n/**\n * Time based object cache for TypeScript generics.\n *\n * @category Cache\n */\nexport class Cache<ItemT extends Cacheable> {\n\t/** Global log instance. */\n\tpublic readonly log: Log;\n\t/** Map of ItemId */\n\tpublic readonly items: Map<CacheItemId, CacheItem<ItemT>>;\n\t/** Validates objects before they're added to the cache. */\n\tpublic readonly itemValidator: (item?: ItemT | null) => boolean;\n\t/** Max number of items that can be cached at any one time. */\n\tpublic readonly capacityMax: UInt;\n\t/** Minimum number of seconds between prune calls. `prune()` execution aborts automatically when called\n\t * more frequently than delay allows. */\n\tpublic readonly pruneDelay: Time;\n\t/** Timestamp of the last successful prune operation. */\n\tpublic readonly lastPrune: Time;\n\n\tconstructor(cfg?: CfgData<ItemT>) {\n\t\tthis.items = new Map<string, CacheItem<ItemT>>();\n\n\t\tthis.itemValidator = cfg?.itemValidator ? cfg.itemValidator : this.defaultItemValidator.bind(this);\n\t\tthis.log = this.makeLog(cfg?.log);\n\t\tthis.capacityMax = uIntMake(Defaults.Cache.CapacityMax, cfg?.capacityMax);\n\t\tthis.pruneDelay = timeMake('s', numberValue(cfg?.pruneDelay, Defaults.Cache.PruneDelay));\n\t\tthis.lastPrune = timeMake('s', 0);\n\t}\n\n\t/**\n\t * Helper that guarantees a Log instance is set during init. Check optional `log` arg and return it if\n\t * it's a valid `Log` instance. Otherwise creates & returns a new `Log` instance.\n\t * @param baseLog\n\t * @returns\n\t */\n\tprivate makeLog(log?: Log): Log {\n\t\tif (!typeMatch(log, Log)) {\n\t\t\treturn new Log();\n\t\t}\n\n\t\treturn log.makeLog('Cache');\n\t}\n\n\t/**\n\t * Get current cached item count.\n\t * @returns\n\t */\n\tpublic size(): number {\n\t\treturn this.items.size;\n\t}\n\n\t/**\n\t * Check unexpired item with target id exists in cache. Returns false when target item expires\n\t * but still exists in cache.\n\t * @param id\t\tUnique ID of item in cache.\n\t * @returns\n\t */\n\tpublic has(id: string): boolean {\n\t\tif (!this.items.has(id)) {\n\t\t\treturn false;\n\t\t}\n\n\t\tconst item = this.items.get(id);\n\t\tif (!item || !typeMatch(item, CacheItem)) {\n\t\t\treturn false;\n\t\t}\n\n\t\treturn item?.expired() === false;\n\t}\n\n\t/**\n\t * Get item from cache matching `id` if one exists.\n\t * @param id\t\tGlobally unique item identifier.\n\t * @returns\t\t\tItem of type `ItemT` if it exists, otherwise `null`.\n\t */\n\tpublic get(id: string): ItemT | null {\n\t\tif (!this.has(id)) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst wrapper = this.items.get(id);\n\t\tif (wrapper?.expired()) {\n\t\t\treturn null;\n\t\t}\n\n\t\tif (!wrapper?.data) {\n\t\t\treturn null;\n\t\t}\n\n\t\treturn wrapper.data;\n\t}\n\n\t/**\n\t * Add item to cache if it does not exist. When ite\n\t * @param item\t\t\tItem to cache.\n\t * @param overwrite\t\t`true`\t-\tOverwrite existing item with same ID.\n\t *\t\t\t\t\t\t`false`\t-\t(default) Do not overwrite existing item. add call fails.\n\t * @returns\n\t */\n\tpublic add(item: ItemT, overwrite?: boolean): boolean {\n\t\tconst id = cacheItemId(item);\n\n\t\tif (!id) {\n\t\t\treturn false;\n\t\t}\n\n\t\tconst has = this.has(id);\n\n\t\tif (has === true && overwrite !== true) {\n\t\t\treturn false;\n\t\t}\n\n\t\tconst wrappedItem = new CacheItem<ItemT>(item);\n\t\tthis.items.set(id, wrappedItem);\n\n\t\treturn true;\n\t}\n\n\t/**\n\t * The default validator used to check items before they're cached. Only used when no cache\n\t * cfg option is provided for `itemValidator`.\n\t * @param item\n\t * @returns\n\t */\n\tpublic defaultItemValidator(item?: ItemT | null): boolean {\n\t\tif (!item) {\n\t\t\treturn false;\n\t\t}\n\n\t\treturn true;\n\t}\n\n\t/**\n\t * Clear cached & volatile data that can be easily recreated. The system received a memory\n\t * warning, indicating performance issues.\n\t * @returns\t\t`true` when handler executes, `false` when handler does not execute.\n\t */\n\tpublic async onMemoryWarning(): Promise<boolean> {\n\t\tthis.reset();\n\n\t\treturn true;\n\t}\n\n\t/**\n\t * Iterate over all items and check for expiration.\n\t * @returns\n\t */\n\tpublic async prune(): Promise<number> {\n\t\t// Bail out if called before enough time has elapsed.\n\t\tif (!this.lastPrune.elapsed(this.pruneDelay)) {\n\t\t\treturn 0;\n\t\t}\n\n\t\tlet count = 0;\n\n\t\tfor (const [key, value] of this.items) {\n\t\t\tif (value.expired()) {\n\t\t\t\tthis.items.delete(key);\n\t\t\t\tcount++;\n\t\t\t}\n\t\t}\n\n\t\tthis.lastPrune.setNow();\n\t\treturn count;\n\t}\n\n\t/**\n\t * Reset cache to inital state.\n\t * @returns\t\tvoid\n\t */\n\tpublic reset(): void {\n\t\tthis.pruneDelay.reset();\n\t\tthis.lastPrune.reset();\n\t\tthis.capacityMax.reset();\n\t\tthis.items.clear();\n\t}\n}\n"]}