"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var src_exports = {}; __export(src_exports, { Keyv: () => Keyv, KeyvHooks: () => KeyvHooks, default: () => src_default }); module.exports = __toCommonJS(src_exports); var import_serialize = require("@keyv/serialize"); // src/event-manager.ts var EventManager = class { _eventListeners; _maxListeners; constructor() { this._eventListeners = /* @__PURE__ */ new Map(); this._maxListeners = 100; } maxListeners() { return this._maxListeners; } // Add an event listener addListener(event, listener) { this.on(event, listener); } on(event, listener) { if (!this._eventListeners.has(event)) { this._eventListeners.set(event, []); } const listeners = this._eventListeners.get(event); if (listeners) { if (listeners.length >= this._maxListeners) { console.warn(`MaxListenersExceededWarning: Possible event memory leak detected. ${listeners.length + 1} ${event} listeners added. Use setMaxListeners() to increase limit.`); } listeners.push(listener); } return this; } // Remove an event listener removeListener(event, listener) { this.off(event, listener); } off(event, listener) { const listeners = this._eventListeners.get(event) ?? []; const index = listeners.indexOf(listener); if (index > -1) { listeners.splice(index, 1); } if (listeners.length === 0) { this._eventListeners.delete(event); } } once(event, listener) { const onceListener = (...arguments_) => { listener(...arguments_); this.off(event, onceListener); }; this.on(event, onceListener); } // Emit an event emit(event, ...arguments_) { const listeners = this._eventListeners.get(event); if (listeners && listeners.length > 0) { for (const listener of listeners) { listener(...arguments_); } } else if (event === "error") { if (arguments_[0] instanceof Error) { throw arguments_[0]; } else { const error = new CustomError(arguments_[0]); error.context = arguments_[0]; throw error; } } } // Get all listeners for a specific event listeners(event) { return this._eventListeners.get(event) ?? []; } // Remove all listeners for a specific event removeAllListeners(event) { if (event) { this._eventListeners.delete(event); } else { this._eventListeners.clear(); } } // Set the maximum number of listeners for a single event setMaxListeners(n) { this._maxListeners = n; } }; var CustomError = class _CustomError extends Error { context; constructor(message, context) { super(message); this.context = context; if (Error.captureStackTrace) { Error.captureStackTrace(this, _CustomError); } this.name = this.constructor.name; } }; var event_manager_default = EventManager; // src/hooks-manager.ts var HooksManager = class extends event_manager_default { _hookHandlers; constructor() { super(); this._hookHandlers = /* @__PURE__ */ new Map(); } // Adds a handler function for a specific event addHandler(event, handler) { const eventHandlers = this._hookHandlers.get(event); if (eventHandlers) { eventHandlers.push(handler); } else { this._hookHandlers.set(event, [handler]); } } // Removes a specific handler function for a specific event removeHandler(event, handler) { const eventHandlers = this._hookHandlers.get(event); if (eventHandlers) { const index = eventHandlers.indexOf(handler); if (index !== -1) { eventHandlers.splice(index, 1); } } } // Triggers all handlers for a specific event with provided data trigger(event, data) { const eventHandlers = this._hookHandlers.get(event); if (eventHandlers) { for (const handler of eventHandlers) { try { handler(data); } catch (error) { this.emit("error", new Error(`Error in hook handler for event "${event}": ${error.message}`)); } } } } // Provides read-only access to the current handlers get handlers() { return new Map(this._hookHandlers); } }; var hooks_manager_default = HooksManager; // src/stats-manager.ts var StatsManager = class extends event_manager_default { enabled = true; hits = 0; misses = 0; sets = 0; deletes = 0; errors = 0; constructor(enabled) { super(); if (enabled !== void 0) { this.enabled = enabled; } this.reset(); } hit() { if (this.enabled) { this.hits++; } } miss() { if (this.enabled) { this.misses++; } } set() { if (this.enabled) { this.sets++; } } delete() { if (this.enabled) { this.deletes++; } } reset() { this.hits = 0; this.misses = 0; this.sets = 0; this.deletes = 0; this.errors = 0; } }; var stats_manager_default = StatsManager; // src/index.ts var KeyvHooks = /* @__PURE__ */ ((KeyvHooks2) => { KeyvHooks2["PRE_SET"] = "preSet"; KeyvHooks2["POST_SET"] = "postSet"; KeyvHooks2["PRE_GET"] = "preGet"; KeyvHooks2["POST_GET"] = "postGet"; KeyvHooks2["PRE_GET_MANY"] = "preGetMany"; KeyvHooks2["POST_GET_MANY"] = "postGetMany"; KeyvHooks2["PRE_DELETE"] = "preDelete"; KeyvHooks2["POST_DELETE"] = "postDelete"; return KeyvHooks2; })(KeyvHooks || {}); var iterableAdapters = [ "sqlite", "postgres", "mysql", "mongo", "redis" ]; var Keyv = class extends event_manager_default { opts; iterator; hooks = new hooks_manager_default(); stats = new stats_manager_default(false); /** * Time to live in milliseconds */ _ttl; /** * Namespace */ _namespace; /** * Store */ _store = /* @__PURE__ */ new Map(); _serialize = import_serialize.defaultSerialize; _deserialize = import_serialize.defaultDeserialize; _compression; _useKeyPrefix = true; /** * Keyv Constructor * @param {KeyvStoreAdapter | KeyvOptions} store * @param {Omit} [options] if you provide the store you can then provide the Keyv Options */ constructor(store, options) { super(); options ??= {}; store ??= {}; this.opts = { namespace: "keyv", serialize: import_serialize.defaultSerialize, deserialize: import_serialize.defaultDeserialize, emitErrors: true, // @ts-expect-error - Map is not a KeyvStoreAdapter store: /* @__PURE__ */ new Map(), ...options }; if (store && store.get) { this.opts.store = store; } else { this.opts = { ...this.opts, ...store }; } this._store = this.opts.store; this._compression = this.opts.compression; this._serialize = this.opts.serialize; this._deserialize = this.opts.deserialize; if (this.opts.namespace) { this._namespace = this.opts.namespace; } if (this._store) { if (!this._isValidStorageAdapter(this._store)) { throw new Error("Invalid storage adapter"); } if (typeof this._store.on === "function" && this.opts.emitErrors) { this._store.on("error", (error) => this.emit("error", error)); } this._store.namespace = this._namespace; if (typeof this._store[Symbol.iterator] === "function" && this._store instanceof Map) { this.iterator = this.generateIterator(this._store); } else if ("iterator" in this._store && this._store.opts && this._checkIterableAdapter()) { this.iterator = this.generateIterator(this._store.iterator.bind(this._store)); } } if (this.opts.stats) { this.stats.enabled = this.opts.stats; } if (this.opts.ttl) { this._ttl = this.opts.ttl; } if (this.opts.useKeyPrefix !== void 0) { this._useKeyPrefix = this.opts.useKeyPrefix; } } /** * Get the current store */ get store() { return this._store; } /** * Set the current store. This will also set the namespace, event error handler, and generate the iterator. If the store is not valid it will throw an error. * @param {KeyvStoreAdapter | Map | any} store the store to set */ set store(store) { if (this._isValidStorageAdapter(store)) { this._store = store; this.opts.store = store; if (typeof store.on === "function" && this.opts.emitErrors) { store.on("error", (error) => this.emit("error", error)); } if (this._namespace) { this._store.namespace = this._namespace; } if (typeof store[Symbol.iterator] === "function" && store instanceof Map) { this.iterator = this.generateIterator(store); } else if ("iterator" in store && store.opts && this._checkIterableAdapter()) { this.iterator = this.generateIterator(store.iterator.bind(store)); } } else { throw new Error("Invalid storage adapter"); } } /** * Get the current compression function * @returns {CompressionAdapter} The current compression function */ get compression() { return this._compression; } /** * Set the current compression function * @param {CompressionAdapter} compress The compression function to set */ set compression(compress) { this._compression = compress; } /** * Get the current namespace. * @returns {string | undefined} The current namespace. */ get namespace() { return this._namespace; } /** * Set the current namespace. * @param {string | undefined} namespace The namespace to set. */ set namespace(namespace) { this._namespace = namespace; this.opts.namespace = namespace; this._store.namespace = namespace; if (this.opts.store) { this.opts.store.namespace = namespace; } } /** * Get the current TTL. * @returns {number} The current TTL. */ get ttl() { return this._ttl; } /** * Set the current TTL. * @param {number} ttl The TTL to set. */ set ttl(ttl) { this.opts.ttl = ttl; this._ttl = ttl; } /** * Get the current serialize function. * @returns {Serialize} The current serialize function. */ get serialize() { return this._serialize; } /** * Set the current serialize function. * @param {Serialize} serialize The serialize function to set. */ set serialize(serialize) { this.opts.serialize = serialize; this._serialize = serialize; } /** * Get the current deserialize function. * @returns {Deserialize} The current deserialize function. */ get deserialize() { return this._deserialize; } /** * Set the current deserialize function. * @param {Deserialize} deserialize The deserialize function to set. */ set deserialize(deserialize) { this.opts.deserialize = deserialize; this._deserialize = deserialize; } /** * Get the current useKeyPrefix value. This will enable or disable key prefixing. * @returns {boolean} The current useKeyPrefix value. * @default true */ get useKeyPrefix() { return this._useKeyPrefix; } /** * Set the current useKeyPrefix value. This will enable or disable key prefixing. * @param {boolean} value The useKeyPrefix value to set. */ set useKeyPrefix(value) { this._useKeyPrefix = value; this.opts.useKeyPrefix = value; } generateIterator(iterator) { const function_ = async function* () { for await (const [key, raw] of typeof iterator === "function" ? iterator(this._store.namespace) : iterator) { const data = await this.deserializeData(raw); if (this._useKeyPrefix && this._store.namespace && !key.includes(this._store.namespace)) { continue; } if (typeof data.expires === "number" && Date.now() > data.expires) { this.delete(key); continue; } yield [this._getKeyUnprefix(key), data.value]; } }; return function_.bind(this); } _checkIterableAdapter() { return iterableAdapters.includes(this._store.opts.dialect) || iterableAdapters.some((element) => this._store.opts.url.includes(element)); } _getKeyPrefix(key) { if (!this._useKeyPrefix) { return key; } if (!this._namespace) { return key; } return `${this._namespace}:${key}`; } _getKeyPrefixArray(keys) { if (!this._useKeyPrefix) { return keys; } if (!this._namespace) { return keys; } return keys.map((key) => `${this._namespace}:${key}`); } _getKeyUnprefix(key) { if (!this._useKeyPrefix) { return key; } return key.split(":").splice(1).join(":"); } _isValidStorageAdapter(store) { return store instanceof Map || typeof store.get === "function" && typeof store.set === "function" && typeof store.delete === "function" && typeof store.clear === "function"; } async get(key, options) { const { store } = this.opts; const isArray = Array.isArray(key); const keyPrefixed = isArray ? this._getKeyPrefixArray(key) : this._getKeyPrefix(key); const isDataExpired = (data) => typeof data.expires === "number" && Date.now() > data.expires; if (isArray) { this.hooks.trigger("preGetMany" /* PRE_GET_MANY */, { keys: keyPrefixed }); if (store.getMany === void 0) { const promises = keyPrefixed.map(async (key2) => { const rawData3 = await store.get(key2); const deserializedRow = typeof rawData3 === "string" || this.opts.compression ? await this.deserializeData(rawData3) : rawData3; if (deserializedRow === void 0 || deserializedRow === null) { return void 0; } if (isDataExpired(deserializedRow)) { await this.delete(key2); return void 0; } return options?.raw ? deserializedRow : deserializedRow.value; }); const deserializedRows = await Promise.allSettled(promises); const result2 = deserializedRows.map((row) => row.value); this.hooks.trigger("postGetMany" /* POST_GET_MANY */, result2); if (result2.length > 0) { this.stats.hit(); } return result2; } const rawData2 = await store.getMany(keyPrefixed); const result = []; for (const index in rawData2) { let row = rawData2[index]; if (typeof row === "string") { row = await this.deserializeData(row); } if (row === void 0 || row === null) { result.push(void 0); continue; } if (isDataExpired(row)) { await this.delete(key[index]); result.push(void 0); continue; } const value = options?.raw ? row : row.value; result.push(value); } this.hooks.trigger("postGetMany" /* POST_GET_MANY */, result); if (result.length > 0) { this.stats.hit(); } return result; } this.hooks.trigger("preGet" /* PRE_GET */, { key: keyPrefixed }); const rawData = await store.get(keyPrefixed); const deserializedData = typeof rawData === "string" || this.opts.compression ? await this.deserializeData(rawData) : rawData; if (deserializedData === void 0 || deserializedData === null) { this.stats.miss(); return void 0; } if (isDataExpired(deserializedData)) { await this.delete(key); this.stats.miss(); return void 0; } this.hooks.trigger("postGet" /* POST_GET */, { key: keyPrefixed, value: deserializedData }); this.stats.hit(); return options?.raw ? deserializedData : deserializedData.value; } /** * Set an item to the store * @param {string} key the key to use * @param {Value} value the value of the key * @param {number} [ttl] time to live in milliseconds * @returns {boolean} if it sets then it will return a true. On failure will return false. */ async set(key, value, ttl) { this.hooks.trigger("preSet" /* PRE_SET */, { key, value, ttl }); const keyPrefixed = this._getKeyPrefix(key); if (typeof ttl === "undefined") { ttl = this._ttl; } if (ttl === 0) { ttl = void 0; } const { store } = this.opts; const expires = typeof ttl === "number" ? Date.now() + ttl : null; if (typeof value === "symbol") { this.emit("error", "symbol cannot be serialized"); } const formattedValue = { value, expires }; const serializedValue = await this.serializeData(formattedValue); await store.set(keyPrefixed, serializedValue, ttl); this.hooks.trigger("postSet" /* POST_SET */, { key: keyPrefixed, value: serializedValue, ttl }); this.stats.set(); return true; } /** * Delete an Entry * @param {string | string[]} key the key to be deleted. if an array it will delete many items * @returns {boolean} will return true if item or items are deleted. false if there is an error */ async delete(key) { const { store } = this.opts; if (Array.isArray(key)) { const keyPrefixed2 = this._getKeyPrefixArray(key); this.hooks.trigger("preDelete" /* PRE_DELETE */, { key: keyPrefixed2 }); if (store.deleteMany !== void 0) { return store.deleteMany(keyPrefixed2); } const promises = keyPrefixed2.map(async (key2) => store.delete(key2)); const results = await Promise.allSettled(promises); const returnResult = results.every((x) => x.value === true); this.hooks.trigger("postDelete" /* POST_DELETE */, { key: keyPrefixed2, value: returnResult }); return returnResult; } const keyPrefixed = this._getKeyPrefix(key); this.hooks.trigger("preDelete" /* PRE_DELETE */, { key: keyPrefixed }); const result = await store.delete(keyPrefixed); this.hooks.trigger("postDelete" /* POST_DELETE */, { key: keyPrefixed, value: result }); this.stats.delete(); return result; } /** * Clear the store * @returns {void} */ async clear() { this.emit("clear"); const { store } = this.opts; await store.clear(); } /** * Has a key * @param {string} key the key to check * @returns {boolean} will return true if the key exists */ async has(key) { const keyPrefixed = this._getKeyPrefix(key); const { store } = this.opts; if (store.has !== void 0 && !(store instanceof Map)) { return store.has(keyPrefixed); } const rawData = await store.get(keyPrefixed); if (rawData) { const data = await this.deserializeData(rawData); if (data) { if (data.expires === void 0 || data.expires === null) { return true; } return data.expires > Date.now(); } } return false; } /** * Will disconnect the store. This is only available if the store has a disconnect method * @returns {Promise} */ async disconnect() { const { store } = this.opts; this.emit("disconnect"); if (typeof store.disconnect === "function") { return store.disconnect(); } } async serializeData(data) { if (!this._serialize) { return data; } if (this._compression?.compress) { return this._serialize({ value: await this._compression.compress(data.value), expires: data.expires }); } return this._serialize(data); } async deserializeData(data) { if (!this._deserialize) { return data; } if (this._compression?.decompress && typeof data === "string") { const result = await this._deserialize(data); return { value: await this._compression.decompress(result?.value), expires: result?.expires }; } if (typeof data === "string") { return this._deserialize(data); } return void 0; } }; var src_default = Keyv; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { Keyv, KeyvHooks });