"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; 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 __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var src_exports = {}; __export(src_exports, { Keyv: () => import_keyv2.Keyv, createClient: () => import_redis2.createClient, createCluster: () => import_redis2.createCluster, createKeyv: () => createKeyv, default: () => KeyvRedis }); module.exports = __toCommonJS(src_exports); var import_node_events = __toESM(require("events"), 1); var import_redis = require("redis"); var import_keyv = require("keyv"); var import_redis2 = require("redis"); var import_keyv2 = require("keyv"); var KeyvRedis = class extends import_node_events.default { _client = (0, import_redis.createClient)(); _namespace; _keyPrefixSeparator = "::"; _clearBatchSize = 1e3; _useUnlink = true; /** * KeyvRedis constructor. * @param {string | RedisClientOptions | RedisClientType} [connect] How to connect to the Redis server. If string pass in the url, if object pass in the options, if RedisClient pass in the client. * @param {KeyvRedisOptions} [options] Options for the adapter such as namespace, keyPrefixSeparator, and clearBatchSize. */ constructor(connect, options) { super(); if (connect) { if (typeof connect === "string") { this._client = (0, import_redis.createClient)({ url: connect }); } else if (connect.connect !== void 0) { this._client = this.isClientCluster(connect) ? connect : connect; } else if (connect instanceof Object) { this._client = connect.rootNodes === void 0 ? (0, import_redis.createClient)(connect) : (0, import_redis.createCluster)(connect); } } this.setOptions(options); this.initClient(); } /** * Get the Redis client. */ get client() { return this._client; } /** * Set the Redis client. */ set client(value) { this._client = value; this.initClient(); } /** * Get the options for the adapter. */ get opts() { let url = ""; if (this._client.options) { url = this._client.options?.url ?? "redis://localhost:6379"; } const results = { namespace: this._namespace, keyPrefixSeparator: this._keyPrefixSeparator, clearBatchSize: this._clearBatchSize, dialect: "redis", url }; return results; } /** * Set the options for the adapter. */ set opts(options) { this.setOptions(options); } /** * Get the namespace for the adapter. If undefined, it will not use a namespace including keyPrefixing. * @default undefined */ get namespace() { return this._namespace; } /** * Set the namespace for the adapter. If undefined, it will not use a namespace including keyPrefixing. */ set namespace(value) { this._namespace = value; } /** * Get the separator between the namespace and key. * @default '::' */ get keyPrefixSeparator() { return this._keyPrefixSeparator; } /** * Set the separator between the namespace and key. */ set keyPrefixSeparator(value) { this._keyPrefixSeparator = value; } /** * Get the number of keys to delete in a single batch. * @default 1000 */ get clearBatchSize() { return this._clearBatchSize; } /** * Set the number of keys to delete in a single batch. */ set clearBatchSize(value) { this._clearBatchSize = value; } /** * Get if Unlink is used instead of Del for clearing keys. This is more performant but may not be supported by all Redis versions. * @default true */ get useUnlink() { return this._useUnlink; } /** * Set if Unlink is used instead of Del for clearing keys. This is more performant but may not be supported by all Redis versions. */ set useUnlink(value) { this._useUnlink = value; } /** * Get the Redis URL used to connect to the server. This is used to get a connected client. */ async getClient() { if (!this._client.isOpen) { await this._client.connect(); } return this._client; } /** * Set a key value pair in the store. TTL is in milliseconds. * @param {string} key - the key to set * @param {string} value - the value to set * @param {number} [ttl] - the time to live in milliseconds */ async set(key, value, ttl) { const client = await this.getClient(); key = this.createKeyPrefix(key, this._namespace); if (ttl) { await client.set(key, value, { PX: ttl }); } else { await client.set(key, value); } } /** * Will set many key value pairs in the store. TTL is in milliseconds. This will be done as a single transaction. * @param {Array>} entries - the key value pairs to set with optional ttl */ async setMany(entries) { const client = await this.getClient(); const multi = client.multi(); for (const { key, value, ttl } of entries) { const prefixedKey = this.createKeyPrefix(key, this._namespace); if (ttl) { multi.set(prefixedKey, value, { PX: ttl }); } else { multi.set(prefixedKey, value); } } await multi.exec(); } /** * Check if a key exists in the store. * @param {string} key - the key to check * @returns {Promise} - true if the key exists, false if not */ async has(key) { const client = await this.getClient(); key = this.createKeyPrefix(key, this._namespace); const exists = await client.exists(key); return exists === 1; } /** * Check if many keys exist in the store. This will be done as a single transaction. * @param {Array} keys - the keys to check * @returns {Promise>} - array of booleans for each key if it exists */ async hasMany(keys) { const client = await this.getClient(); const multi = client.multi(); for (const key of keys) { const prefixedKey = this.createKeyPrefix(key, this._namespace); multi.exists(prefixedKey); } const results = await multi.exec(); return results.map((result) => result === 1); } /** * Get a value from the store. If the key does not exist, it will return undefined. * @param {string} key - the key to get * @returns {Promise} - the value or undefined if the key does not exist */ async get(key) { const client = await this.getClient(); key = this.createKeyPrefix(key, this._namespace); const value = await client.get(key); if (value === null) { return void 0; } return value; } /** * Get many values from the store. If a key does not exist, it will return undefined. * @param {Array} keys - the keys to get * @returns {Promise>} - array of values or undefined if the key does not exist */ async getMany(keys) { const client = await this.getClient(); const multi = client.multi(); for (const key of keys) { const prefixedKey = this.createKeyPrefix(key, this._namespace); multi.get(prefixedKey); } const values = await multi.exec(); return values.map((value) => value === null ? void 0 : value); } /** * Delete a key from the store. * @param {string} key - the key to delete * @returns {Promise} - true if the key was deleted, false if not */ async delete(key) { const client = await this.getClient(); key = this.createKeyPrefix(key, this._namespace); let deleted = 0; deleted = await (this._useUnlink ? client.unlink(key) : client.del(key)); return deleted > 0; } /** * Delete many keys from the store. This will be done as a single transaction. * @param {Array} keys - the keys to delete * @returns {Promise} - true if any key was deleted, false if not */ async deleteMany(keys) { let result = false; const client = await this.getClient(); const multi = client.multi(); for (const key of keys) { const prefixedKey = this.createKeyPrefix(key, this._namespace); if (this._useUnlink) { multi.unlink(prefixedKey); } else { multi.del(prefixedKey); } } const results = await multi.exec(); for (const deleted of results) { if (typeof deleted === "number" && deleted > 0) { result = true; } } return result; } /** * Disconnect from the Redis server. * @returns {Promise} */ async disconnect() { if (this._client.isOpen) { await this._client.disconnect(); } } /** * Helper function to create a key with a namespace. * @param {string} key - the key to prefix * @param {string} namespace - the namespace to prefix the key with * @returns {string} - the key with the namespace such as 'namespace::key' */ createKeyPrefix(key, namespace) { if (namespace) { return `${namespace}${this._keyPrefixSeparator}${key}`; } return key; } /** * Helper function to get a key without the namespace. * @param {string} key - the key to remove the namespace from * @param {string} namespace - the namespace to remove from the key * @returns {string} - the key without the namespace such as 'key' */ getKeyWithoutPrefix(key, namespace) { if (namespace) { return key.replace(`${namespace}${this._keyPrefixSeparator}`, ""); } return key; } /** * Is the client a cluster. * @returns {boolean} - true if the client is a cluster, false if not */ isCluster() { return this.isClientCluster(this._client); } /** * Get an async iterator for the keys and values in the store. If a namespace is provided, it will only iterate over keys with that namespace. * @param {string} [namespace] - the namespace to iterate over * @returns {AsyncGenerator<[string, T | undefined], void, unknown>} - async iterator with key value pairs */ async *iterator(namespace) { if (this.isCluster()) { throw new Error("Iterating over keys in a cluster is not supported."); } else { yield* this.iteratorClient(namespace); } } /** * Get an async iterator for the keys and values in the store. If a namespace is provided, it will only iterate over keys with that namespace. * @param {string} [namespace] - the namespace to iterate over * @returns {AsyncGenerator<[string, T | undefined], void, unknown>} - async iterator with key value pairs */ async *iteratorClient(namespace) { const client = await this.getClient(); const match = namespace ? `${namespace}${this._keyPrefixSeparator}*` : "*"; let cursor = "0"; do { const result = await client.scan(Number.parseInt(cursor, 10), { MATCH: match, TYPE: "string" }); cursor = result.cursor.toString(); let { keys } = result; if (!namespace) { keys = keys.filter((key) => !key.includes(this._keyPrefixSeparator)); } if (keys.length > 0) { const values = await client.mGet(keys); for (const [i] of keys.entries()) { const key = this.getKeyWithoutPrefix(keys[i], namespace); const value = values ? values[i] : void 0; yield [key, value]; } } } while (cursor !== "0"); } /** * Clear all keys in the store. * IMPORTANT: this can cause performance issues if there are a large number of keys in the store and worse with clusters. Use with caution as not recommended for production. * If a namespace is not set it will clear all keys with no prefix. * If a namespace is set it will clear all keys with that namespace. * @returns {Promise} */ async clear() { await (this.isCluster() ? this.clearNamespaceCluster(this._namespace) : this.clearNamespace(this._namespace)); } async clearNamespace(namespace) { try { let cursor = "0"; const batchSize = this._clearBatchSize; const match = namespace ? `${namespace}${this._keyPrefixSeparator}*` : "*"; const client = await this.getClient(); do { const result = await client.scan(Number.parseInt(cursor, 10), { MATCH: match, COUNT: batchSize, TYPE: "string" }); cursor = result.cursor.toString(); let { keys } = result; if (keys.length === 0) { continue; } if (!namespace) { keys = keys.filter((key) => !key.includes(this._keyPrefixSeparator)); } if (keys.length > 0) { if (this._useUnlink) { await client.unlink(keys); } else { await client.del(keys); } } } while (cursor !== "0"); } catch (error) { this.emit("error", error); } } async clearNamespaceCluster(namespace) { throw new Error("Clearing all keys in a cluster is not supported."); } isClientCluster(client) { if (client.options === void 0 && client.scan === void 0) { return true; } return false; } setOptions(options) { if (!options) { return; } if (options.namespace) { this._namespace = options.namespace; } if (options.keyPrefixSeparator) { this._keyPrefixSeparator = options.keyPrefixSeparator; } if (options.clearBatchSize) { this._clearBatchSize = options.clearBatchSize; } if (options.useUnlink !== void 0) { this._useUnlink = options.useUnlink; } } initClient() { this._client.on("error", (error) => { this.emit("error", error); }); } }; function createKeyv(connect, options) { const adapter = new KeyvRedis(connect, options); const keyv = new import_keyv.Keyv({ store: adapter, namespace: options?.namespace, useKeyPrefix: false }); return keyv; } // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { Keyv, createClient, createCluster, createKeyv });