// src/index.ts
import EventEmitter from "events";
import Redis from "iovalkey";

// ../serialize/dist/index.js
import { Buffer } from "buffer";
var defaultSerialize = (data) => {
  if (data === void 0 || data === null) {
    return "null";
  }
  if (typeof data === "string") {
    return JSON.stringify(data.startsWith(":") ? ":" + data : data);
  }
  if (Buffer.isBuffer(data)) {
    return JSON.stringify(":base64:" + data.toString("base64"));
  }
  if (data?.toJSON) {
    data = data.toJSON();
  }
  if (typeof data === "object") {
    let s = "";
    const array = Array.isArray(data);
    s = array ? "[" : "{";
    let first = true;
    for (const k in data) {
      const ignore = typeof data[k] === "function" || !array && data[k] === void 0;
      if (!Object.hasOwn(data, k) || ignore) {
        continue;
      }
      if (!first) {
        s += ",";
      }
      first = false;
      if (array) {
        s += defaultSerialize(data[k]);
      } else if (data[k] !== void 0) {
        s += defaultSerialize(k) + ":" + defaultSerialize(data[k]);
      }
    }
    s += array ? "]" : "}";
    return s;
  }
  return JSON.stringify(data);
};
var defaultDeserialize = (data) => JSON.parse(data, (_, value) => {
  if (typeof value === "string") {
    if (value.startsWith(":base64:")) {
      return Buffer.from(value.slice(8), "base64");
    }
    return value.startsWith(":") ? value.slice(1) : value;
  }
  return value;
});

// ../keyv/dist/index.js
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;
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;
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;
var iterableAdapters = [
  "sqlite",
  "postgres",
  "mysql",
  "mongo",
  "redis",
  "valkey",
  "etcd"
];
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 = defaultSerialize;
  _deserialize = defaultDeserialize;
  _compression;
  _useKeyPrefix = true;
  /**
   * Keyv Constructor
   * @param {KeyvStoreAdapter | KeyvOptions} store
   * @param {Omit<KeyvOptions, 'store'>} [options] if you provide the store you can then provide the Keyv Options
   */
  constructor(store, options) {
    super();
    options ??= {};
    store ??= {};
    this.opts = {
      namespace: "keyv",
      serialize: defaultSerialize,
      deserialize: 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._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, any> | 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") {
        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", { 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", 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", result);
      if (result.length > 0) {
        this.stats.hit();
      }
      return result;
    }
    this.hooks.trigger("preGet", { 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", { 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", { key, value, ttl });
    const keyPrefixed = this._getKeyPrefix(key);
    if (ttl === void 0) {
      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);
    let result = true;
    try {
      const value2 = await store.set(keyPrefixed, serializedValue, ttl);
      if (typeof value2 === "boolean") {
        result = value2;
      }
    } catch (error) {
      result = false;
      this.emit("error", error);
    }
    this.hooks.trigger("postSet", { key: keyPrefixed, value: serializedValue, ttl });
    this.stats.set();
    return result;
  }
  /**
   * 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", { 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", { key: keyPrefixed2, value: returnResult });
      return returnResult;
    }
    const keyPrefixed = this._getKeyPrefix(key);
    this.hooks.trigger("preDelete", { key: keyPrefixed });
    let result = true;
    try {
      const value = await store.delete(keyPrefixed);
      if (typeof value === "boolean") {
        result = value;
      }
    } catch (error) {
      result = false;
      this.emit("error", error);
    }
    this.hooks.trigger("postDelete", { key: keyPrefixed, value: result });
    this.stats.delete();
    return result;
  }
  /**
   * Clear the store
   * @returns {void}
   */
  async clear() {
    this.emit("clear");
    const { store } = this.opts;
    try {
      await store.clear();
    } catch (error) {
      this.emit("error", error);
    }
  }
  /**
   * 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);
    }
    let rawData;
    try {
      rawData = await store.get(keyPrefixed);
    } catch (error) {
      this.emit("error", error);
    }
    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<void>}
   */
  async disconnect() {
    const { store } = this.opts;
    this.emit("disconnect");
    if (typeof store.disconnect === "function") {
      return store.disconnect();
    }
  }
  emit(event, ...arguments_) {
    if (event === "error" && !this.opts.emitErrors) {
      return;
    }
    super.emit(event, ...arguments_);
  }
  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;
  }
};

// src/index.ts
var createKeyv = (uri, options) => new Keyv({ store: new KeyvValkey(uri, options) });
var KeyvValkey = class extends EventEmitter {
  ttlSupport = true;
  namespace;
  opts;
  redis;
  constructor(uri, options) {
    super();
    this.opts = {};
    this.opts.useRedisSets = true;
    this.opts.dialect = "redis";
    if (typeof uri !== "string" && uri.options && ("family" in uri.options || uri.isCluster)) {
      this.redis = uri;
    } else {
      options = { ...typeof uri === "string" ? { uri } : uri, ...options };
      this.redis = new Redis(options.uri, options);
    }
    if (options !== void 0 && options.useRedisSets === false) {
      this.opts.useRedisSets = false;
    }
    this.redis.on("error", (error) => this.emit("error", error));
  }
  _getNamespace() {
    return `namespace:${this.namespace}`;
  }
  _getKeyName = (key) => {
    if (!this.opts.useRedisSets) {
      return `sets:${this._getNamespace()}:${key}`;
    }
    return key;
  };
  async get(key) {
    key = this._getKeyName(key);
    const value = await this.redis.get(key);
    if (value === null) {
      return void 0;
    }
    return value;
  }
  async getMany(keys) {
    keys = keys.map(this._getKeyName);
    return this.redis.mget(keys);
  }
  async set(key, value, ttl) {
    if (value === void 0) {
      return void 0;
    }
    key = this._getKeyName(key);
    const set = async (redis) => {
      if (typeof ttl === "number") {
        await redis.set(key, value, "PX", ttl);
      } else {
        await redis.set(key, value);
      }
    };
    if (this.opts.useRedisSets) {
      const trx = await this.redis.multi();
      await set(trx);
      await trx.sadd(this._getNamespace(), key);
      await trx.exec();
    } else {
      await set(this.redis);
    }
  }
  async delete(key) {
    key = this._getKeyName(key);
    let items = 0;
    const unlink = async (redis) => redis.unlink(key);
    if (this.opts.useRedisSets) {
      const trx = this.redis.multi();
      await unlink(trx);
      await trx.srem(this._getNamespace(), key);
      const r = await trx.exec();
      items = r[0][1];
    } else {
      items = await unlink(this.redis);
    }
    return items > 0;
  }
  async deleteMany(keys) {
    const deletePromises = keys.map(async (key) => this.delete(key));
    const results = await Promise.allSettled(deletePromises);
    return results.every((result) => result.value);
  }
  async clear() {
    if (this.opts.useRedisSets) {
      const keys = await this.redis.smembers(this._getNamespace());
      if (keys.length > 0) {
        await Promise.all([
          this.redis.unlink([...keys]),
          this.redis.srem(this._getNamespace(), [...keys])
        ]);
      }
    } else {
      const pattern = `sets:${this._getNamespace()}:*`;
      const keys = await this.redis.keys(pattern);
      if (keys.length > 0) {
        await this.redis.unlink(keys);
      }
    }
  }
  async *iterator(namespace) {
    const scan = this.redis.scan.bind(this.redis);
    const get = this.redis.mget.bind(this.redis);
    let cursor = "0";
    do {
      const [curs, keys] = await scan(cursor, "MATCH", `${namespace}:*`);
      cursor = curs;
      if (keys.length > 0) {
        const values = await get(keys);
        for (const [i] of keys.entries()) {
          const key = keys[i];
          const value = values[i];
          yield [key, value];
        }
      }
    } while (cursor !== "0");
  }
  async has(key) {
    const value = await this.redis.exists(key);
    return value !== 0;
  }
  async disconnect() {
    return this.redis.disconnect();
  }
};
var index_default = KeyvValkey;
export {
  createKeyv,
  index_default as default
};
