@nasriya/cachify
Version:
A lightweight, extensible in-memory caching library for storing anything, with built-in TTL and customizable cache types.
245 lines (244 loc) • 11.6 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.KVsEventsManager = void 0;
const atomix_1 = __importDefault(require("@nasriya/atomix"));
const cachify_1 = __importDefault(require("../../../../cachify"));
const tools_1 = require("@nasriya/atomix/tools");
class KVsEventsManager {
#_eventEmitter;
constructor() {
const eventsEmitter = this.#_eventEmitter = new tools_1.EventEmitter();
eventsEmitter.maxHandlers = Infinity;
eventsEmitter.on('*', (event) => {
if (cachify_1.default.debug) {
console.group(event.type || 'Unknown', 'Event');
console.debug(event, { colors: true, depth: Infinity });
console.groupEnd();
}
});
}
/**
* Emits an event with the specified payload, and also emits the event on the wildcard event handler.
* @param {E} event - The name of the event to emit.
* @param {KVCacheEvents[E]['payload']} payload - The payload to be emitted with the event.
* @since v1.0.0
*/
async #_emit(event, payload) {
await this.#_eventEmitter.emit(event, payload);
}
/**
* Registers an event handler for a specified key-value cache event.
*
* @template E - The type of the key-value cache event.
* @param {E} event - The name of the event to listen for. Must be a non-empty string.
* @param {(payload: KVCacheEvents[E]['payload']) => void} handler - The function to be executed when the event is emitted.
* @param {AddHandlerOptions} [options] - Optional configurations for the event handler.
* @throws {TypeError} Throws if the event is not a string or the handler is not a function.
* @throws {RangeError} Throws if the event is an empty string.
* @since v1.0.0
*/
on(event, handler, options) {
const configs = {
once: false,
type: 'normal',
};
if (!atomix_1.default.valueIs.string(event)) {
throw new TypeError(`The provided event (${event}) is not a string.`);
}
if (atomix_1.default.valueIs.emptyString(event)) {
throw new RangeError(`The provided event (${event}) is empty.`);
}
if (typeof handler !== 'function') {
throw new TypeError(`The provided handler (${handler}) is not a function.`);
}
const isRecord = atomix_1.default.valueIs.record(options);
if (options !== undefined && !isRecord) {
throw new TypeError(`The "options" parameter (when provided) must be a record, but instead got ${typeof options}`);
}
if (isRecord) {
if (atomix_1.default.dataTypes.record.hasOwnProperty(options, 'once')) {
if (typeof options.once !== 'boolean') {
throw new TypeError(`The "once" option must be a boolean, but instead got ${typeof options.once}`);
}
configs.once = options.once;
}
if (atomix_1.default.dataTypes.record.hasOwnProperty(options, 'type')) {
if (!atomix_1.default.valueIs.string(options.type)) {
throw new TypeError(`The "type" option must be a string, but instead got ${typeof options.type}`);
}
if (!['normal', 'beforeAll', 'afterAll'].includes(options.type)) {
throw new RangeError(`The "type" option must be one of "normal", "beforeAll", or "afterAll", but instead got ${options.type}`);
}
configs.type = options.type;
}
}
this.#_eventEmitter.on(event, handler, configs);
}
/**
* Registers a handler that will be executed for all key-value cache events.
*
* @param {(event: KVCachePayload) => void} handler - The function to be executed when any event is emitted.
* @returns {void}
* @since v1.0.0
*/
onAny(handler) {
this.#_eventEmitter.on('*', handler);
}
/**
* Removes a previously registered handler for a specific key-value cache event or all events.
*
* @template K - The type of the key-value cache event or 'Any' to indicate all events.
* @param {K} event - The event to remove the handler for. If set to 'Any', removes the handler for all events.
* @param {(payload: K extends KVCacheEvent ? KVCacheEvents[K]['payload'] : KVCachePayload) => void} handler - The function to be removed.
* @returns {void}
* @since v1.0.0
*/
removeHandler(event, handler) {
this.#_eventEmitter.remove.handler(event === 'Any' ? '*' : event, handler);
}
emit = {
/**
* Emits the 'evict' event when a record is evicted from the cache due to various reasons.
* @param {KVCacheRecord} record - The record that was evicted from the cache.
* @param {{ reason: EvictReason }} options - An object containing the reason for the eviction.
* @since v1.0.0
*/
evict: async (record, options) => {
await this.emit.remove(record, { reason: options.reason });
},
/**
* Emits the 'expire' event when a record expires from the key-value cache.
* A record is considered expired if it has a TTL greater than 0 and the expiration date is in the past.
* If the record has a TTL of 0, it is never considered expired.
* @param {KVCacheRecord} record - The record that expired from the cache.
* @since v1.0.0
*/
expire: async (record) => {
await this.emit.remove(record, { reason: 'expire' });
},
/**
* Emits the 'remove' event for a record being removed from the key-value cache.
*
* This method creates and emits a payload for the 'remove' event, including the record's data and the reason for removal.
*
* @param {KVCacheRecord} record - The key-value cache record that is being removed.
* @param {Object} [options] - The options for the removal event.
* @param {KVRemovalReason} [options.reason='manual'] - The reason for the removal. Defaults to 'manual'.
* @since v1.0.0
*/
remove: async (record, options = { reason: 'manual' }) => {
const removalReason = options?.reason || 'manual';
const payload = { item: record.toJSON(), flavor: record.flavor, type: 'remove', reason: removalReason };
await this.#_emit('remove', payload);
},
/**
* Emits the 'bulkRemove' event for multiple records being removed from the key-value cache.
*
* This method creates and emits a payload for the 'bulkRemove' event, including the records' data and the reason for removal.
*
* @param {KVCacheRecord[]} records - An array of key-value cache records that are being removed.
* @param {Object} [options] - The options for the removal event.
* @param {KVRemovalReason} [options.reason='manual'] - The reason for the removal. Defaults to 'manual'.
* @since v1.0.0
*/
bulkRemove: async (records, options = { reason: 'manual' }) => {
const removalReason = options?.reason || 'manual';
const payload = {
items: records.map(record => record.toJSON()),
flavor: records[0].flavor,
type: 'bulkRemove',
reason: removalReason
};
await this.#_emit('bulkRemove', payload);
},
/**
* Emits the 'create' event when a record is added to the key-value cache.
*
* This method creates and emits a payload for the 'create' event, including the record's data and whether it was preloaded or not.
*
* @param {KVCacheRecord} record - The key-value cache record that was added to the cache.
* @param {Object} [options] - The options for the creation event.
* @param {boolean} [options.preload=false] - Whether the record was preloaded, or not. Defaults to false.
* @since v1.0.0
*/
create: async (record, options = { preload: false }) => {
const payload = { item: record.toJSON(), flavor: record.flavor, type: 'create', preload: options.preload === true };
await this.#_emit('create', payload);
},
/**
* Emits the 'read' event when a record is accessed in the key-value cache.
*
* This method creates and emits a payload for the 'read' event, including the record's data.
*
* @param {KVCacheRecord} record - The key-value cache record that was accessed.
* @since v1.0.0
*/
read: async (record) => {
const payload = { item: record.toJSON(), flavor: record.flavor, type: 'read' };
await this.#_emit('read', payload);
},
/**
* Emits the 'update' event when a record is updated in the key-value cache.
* This method creates and emits a payload for the 'update' event, including the record's data.
* @param {KVCacheRecord} record - The key-value cache record that was updated.
* @since v1.0.0
*/
update: async (record) => {
const payload = { item: record.toJSON(), flavor: record.flavor, type: 'update' };
await this.#_emit('update', payload);
},
/**
* Emits the 'clear' event when the entire key-value cache is being cleared.
* This results in the removal of all entries from the cache.
*
* **Note:** This event is only emitted by the cache manager.
* @param {KVCacheRecord} record - The record associated with the clear operation.
* @since v1.0.0
*/
clear: async (record) => {
await this.emit.remove(record, { reason: 'clear' });
},
/**
* Emits the 'touch' event for a record accessed in the key-value cache.
*
* This method creates and emits a payload for the 'touch' event, including the record's data.
* It is used to update the record's metadata, such as LRU, without modifying the value.
*
* @param {KVCacheRecord} record - The key-value cache record that was accessed.
* @since v1.0.0
*/
touch: async (record) => {
const payload = { item: record.toJSON(), flavor: record.flavor, type: 'touch' };
await this.#_emit('touch', payload);
}
};
/**
* Cleans up internal state and resources used by the events manager.
*
* This includes:
* - Canceling any pending internal timers (e.g. debounced warnings)
* - Releasing memory references to ensure the process can exit cleanly
* - Resetting internal state in preparation for teardown or testing
*
* This method is especially useful in unit tests to prevent open handles
* (such as `setTimeout`) from blocking Jest's process exit.
*
* > Note: This does **not** remove any user-registered event handlers. If needed,
* handlers should be removed explicitly via `removeHandler()` before calling this.
*
* @example
* afterEach(() => {
* cachify.events.dispose();
* });
*
* @since v1.0.0
*/
dispose() {
this.#_eventEmitter.dispose();
}
}
exports.KVsEventsManager = KVsEventsManager;
exports.default = KVsEventsManager;