@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
JavaScript
"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;