UNPKG

@nasriya/cachify

Version:

A lightweight, extensible in-memory caching library for storing anything, with built-in TTL and customizable cache types.

238 lines (237 loc) 8.39 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const atomix_1 = __importDefault(require("@nasriya/atomix")); const cron_1 = __importDefault(require("@nasriya/cron")); const TTLItemConfig_1 = __importDefault(require("../../configs/strategies/ttl/TTLItemConfig")); const helpers_1 = __importDefault(require("../helpers")); class KVCacheRecord { #_flavor = 'kvs'; #_engines = []; #_proxy; #_events; #_scope = 'global'; #_key; #_ttl; #_expireJob = {}; #_initialized = false; #_stats = { size: 0, dates: { created: 0, expireAt: undefined, lastAccess: undefined, lastUpdate: undefined }, counts: { read: 0, update: 0, touch: 0, hit: 0, miss: 0 } }; constructor(key, configs, proxy, events) { this.#_proxy = proxy; this.#_events = events; this.#_key = key; this.#_scope = configs.scope; this.#_engines = configs.storeIn; if (configs.ttl.value > 0) { this.#_ttl = new TTLItemConfig_1.default(configs.ttl, 'kvs'); } if (configs.preload) { switch (configs.initiator) { case 'warmup': { this.#_stats.dates.created = Date.now(); } break; case 'restore': { this.#_stats = configs.stats; } break; } } else { this.#_stats.dates.created = Date.now(); } this.#_events.on('remove', (event) => { if (event.item.key === this.#_key) { this.#_expireJob?.cancel?.(); } }, { once: true }); } /** * Initializes the key-value cache record by storing the given value across engines * and setting the record's internal state and TTL. * * If the record is already initialized, this method does nothing. * * @param value - The value to be stored in the cache record. * @param preload - Whether to preload the record upon creation. * @private */ async _init(value, preload) { try { if (this.#_initialized) { return; } this.#_stats.size = helpers_1.default.records.estimateSize(this.#_key, value); await this.#_proxy.set(this, value); this.#_helpers.refreshTTL(); await this.#_events.emit.create(this, { preload: preload }); } catch (error) { throw error; } finally { this.#_initialized = true; } } #_helpers = { refreshTTL: () => { const ttlConfig = this.#_ttl; if (!ttlConfig) { return; } const ttl = this.#_ttl.value; if (ttl === 0) { return; } const sliding = this.#_ttl.sliding; const baseTime = sliding ? this.#_stats.dates.lastAccess || this.#_stats.dates.created : this.#_stats.dates.created; const expireAt = baseTime + ttl; if (expireAt === this.#_stats.dates.expireAt) { return; } this.#_stats.dates.expireAt = expireAt; this.#_expireJob?.cancel?.(); const policy = ttlConfig.policy; if (policy === 'evict') { this.#_expireJob = cron_1.default.scheduleTime(expireAt, async () => { this.#_ttl?.onExpire?.(this); await this.#_events.emit.expire(this); }); } }, registerAccess: () => { this.#_stats.dates.lastUpdate = Date.now(); this.#_helpers.refreshTTL(); } }; /** * Retrieves the flavor of the cache record. * @returns {'kvs'} The flavor of the cache record. */ get flavor() { return this.#_flavor; } /** * Retrieves the list of engines associated with the cache record. * @returns {string[]} An array of engine names. */ get engines() { return this.#_engines; } /** * Retrieves the key associated with the cache record. * @returns {string} The key associated with the cache record. */ get key() { return this.#_key; } /** * Retrieves the scope associated with the cache record. * @returns {string} The scope of the cache record. */ get scope() { return this.#_scope; } /** * Retrieves the statistics for the cache record. * The statistics include the dates of creation, last access, last update, and expiration, as well as the counts of access, update, and touch events. * @returns The statistics of the cache record. */ get stats() { const cloned = atomix_1.default.dataTypes.object.smartClone(this.#_stats); return atomix_1.default.dataTypes.record.deepFreeze(cloned); } /** * Retrieves the value associated with the cache record. * If the record does not exist in the cache, it returns undefined. * The method emits a 'read' event with the reason 'hit' upon successful retrieval. * @returns {Promise<T>} A promise resolving with the value associated with the cache record, or undefined if no record exists. * @since v1.0.0 */ async read() { this.#_stats.counts.read++; this.#_helpers.registerAccess(); const response = await this.#_proxy.read(this); return response.value; } /** * Updates the value associated with the cache record. * The method emits an 'update' event after updating the record's value. * @param value - The new value to be associated with the cache record. * @since v1.0.0 */ async update(value) { await this.#_proxy.set(this, value); this.#_stats.counts.update++; await this.#_events.emit.update(this); } /** * Updates the record's last access date and emits a `touch` event. * This method is used to update the record's metadata without modifying the value. */ async touch() { this.#_stats.counts.touch++; this.#_helpers.registerAccess(); await this.#_events.emit.touch(this); } /** * Converts the cache record to a JSON-compatible object. * The JSON object will contain the flavor, scope, key, statistics, value, and TTL settings of the cache record. * @returns The JSON object representation of the cache record. */ toJSON() { const cloned = atomix_1.default.dataTypes.object.smartClone({ flavor: this.#_flavor, engines: this.#_engines, scope: this.#_scope, key: this.#_key, stats: this.#_stats, ttl: { value: this.#_ttl ? this.#_ttl.value : 0, sliding: this.#_ttl ? this.#_ttl.sliding : false } }); return atomix_1.default.dataTypes.record.deepFreeze(cloned); } /** * Exports the cache record to a JSON-compatible object. * The exported object will contain the flavor, scope, key, statistics, value, and TTL settings of the cache record. * If the record does not exist in the cache, it returns undefined. * * This is meant to be used for backups. * @returns The JSON object representation of the cache record, or undefined if no record exists. * @since v1.0.0 */ async export() { const response = await this.#_proxy.read(this); if (response.value === undefined) { return undefined; } return { flavor: this.#_flavor, engines: this.#_engines, scope: this.#_scope, key: this.#_key, value: response.value, stats: this.#_stats, ttl: { value: this.#_ttl ? this.#_ttl.value : 0, sliding: this.#_ttl ? this.#_ttl.sliding : false, }, }; } } exports.default = KVCacheRecord;