UNPKG

mm_mongodb

Version:

MongoDB数据库操作模块,提供简洁易用的API、缓存功能以及完整的Redis兼容接口,支持事件系统、事务和连接池管理

2,244 lines (1,998 loc) 95.2 kB
/** * @fileOverview MongoDB帮助类函数 * @author <a href="http://qww.elins.cn">邱文武</a> * @version 1.2.1 */ const { BaseService } = require('mm_base_service'); const MongoClient = require('mongodb').MongoClient; /** * @description MongoDB帮助类 */ class MongoDB extends BaseService { /** * @description 创建MongoDB帮助类函数 (构造函数) * @param {Object} config 配置参数 * @constructor */ constructor(config) { super(); // 合并配置,确保使用对象扩展运算符正确合并 this.config = { // 服务器地址 host: "localhost", // 端口号 port: 27017, // 数据库名 database: "mm", // 用户名 user: "admin", // 密码 password: "", // 连接选项 options: { minPoolSize: 10, maxPoolSize: 100, connectTimeoutMS: 10000, serverSelectionTimeoutMS: 5000, retryWrites: true }, // 重试配置 retry: { maxAttempts: 3, delayMS: 1000 } }; // 连接地址 this.url = null; // 数据库连接器 this.conn = null; // 操作的数据库 this.db = null; // 表名 this.table = "cache"; /** * 页码 */ this.page = 1; /** * 每页显示条数 */ this.size = 0; // 连接状态 this.connected = false; // 连接Promise缓存 this.connectPromise = null; // 连接重试次数 this.connectAttempts = 0; this.maxConnectAttempts = 3; this.setConfig(config); // 验证配置 this._validateConfig(); } } /** * @private * @description 验证配置有效性 * @throws {Error} 配置无效时抛出错误 */ MongoDB.prototype._validateConfig = function () { if (!this.config.host || typeof this.config.host !== 'string') { throw new Error('MongoDB配置无效:host必须是有效的字符串'); } if (!Number.isInteger(this.config.port) || this.config.port < 1 || this.config.port > 65535) { throw new Error('MongoDB配置无效:port必须是1-65535之间的整数'); } if (!this.config.database || typeof this.config.database !== 'string') { throw new Error('MongoDB配置无效:database必须是有效的字符串'); } }; /** * @private * @description 执行带重试的操作 * @param {Function} operation 要执行的操作 * @param {String} operationName 操作名称 * @return {Promise} 操作结果 */ MongoDB.prototype._withRetry = async function (operation, operationName) { const maxAttempts = this.config.retry?.maxAttempts || 3; const delayMS = this.config.retry?.delayMS || 1000; let lastError; for (let attempt = 1; attempt <= maxAttempts; attempt++) { try { return await operation(); } catch (error) { lastError = error; $.log.error(`[MongoDB] ${operationName} 第${attempt}次尝试失败:`, error.message); // 如果不是最后一次尝试,则等待后重试 if (attempt < maxAttempts) { await new Promise(resolve => setTimeout(resolve, delayMS)); } } } // 所有重试都失败,抛出最后一个错误 throw lastError; }; /** * @description 确保数据库连接已建立 * @private * @return {Promise<void>} */ MongoDB.prototype._ensureConnected = async function () { // 如果已有连接Promise,等待其完成 if (this.connectPromise) { return this.connectPromise; } // 创建新的连接Promise this.connectPromise = (async () => { try { // 检查连接状态 if (!this.db || !this.connected) { // 记录连接尝试 this.connectAttempts++; // 尝试连接 await this.open(); // 检查连接是否成功 if (this.db && this.db.collection) { $.log.debug(`[MongoDB] [_ensureConnected] 连接成功,db对象存在,开始初始化collection,table=${this.table}`); this.connected = true; this.connectAttempts = 0; // 保存collection引用到实例变量 try { this.collection = this.db.collection(this.table); $.log.debug(`[MongoDB] [_ensureConnected] 成功初始化collection: ${this.table}`); } catch (err) { $.log.error(`[MongoDB] [_ensureConnected] 初始化collection失败:`, err.message); this.collection = null; } // 确保索引已创建 if (this.collection) { await this._ensureIndexes(); } $.log.info(`[MongoDB] 成功连接到数据库,表名: ${this.table}`); } else { $.log.error(`[MongoDB] [_ensureConnected] 连接失败,db对象为空或不具有collection方法`); throw new Error('数据库连接失败,db对象为空'); } } } catch (error) { $.log.error(`[MongoDB] 连接失败 (尝试 ${this.connectAttempts}/${this.maxConnectAttempts}):`, error.message); // 重置连接Promise,允许下次重试 this.connectPromise = null; // 如果尝试次数未超过限制,抛出错误让上层处理 if (this.connectAttempts < this.maxConnectAttempts) { throw error; } else { // 超过重试次数,记录严重错误 $.log.error('[MongoDB] 达到最大连接重试次数,连接失败'); throw new Error('数据库连接失败,已达到最大重试次数'); } } })(); return this.connectPromise; }; /** * @description 确保索引已创建 * @private * @return {Promise<void>} */ MongoDB.prototype._ensureIndexes = async function () { try { // 确保db对象有效 if (!this.db || !this.db.collection) { throw new Error('数据库连接无效,无法创建索引'); } // 创建TTL索引用于自动过期 await this.db.collection(this.table).createIndex({ expire_at: 1 }, { expireAfterSeconds: 0 }); // 创建_id索引(通常自动创建,但显式指定更清晰) await this.db.collection(this.table).createIndex({ _id: 1 }); } catch (error) { // 忽略索引已存在的错误 if (error.code !== 85) { $.log.error('[MongoDB] 创建索引失败:', error); } } }; /** * @description Redis兼容API - 获取缓存值 * @param {String} key 键名 * @return {Promise<any>} 返回键值,不存在返回undefined */ MongoDB.prototype.get = async function (key) { try { await this._ensureConnected(); const result = await this.findOne({ key: key }); return result ? result.value : undefined; } catch (error) { $.log.error('[MongoDB] 获取缓存失败:', error); throw error; } }; /** * @description Redis兼容API - 清空所有缓存或匹配模式的缓存 * @param {string} pattern - 可选的键匹配模式 * @return {Promise<Boolean>} 是否成功 */ MongoDB.prototype.clear = async function (pattern) { try { // 触发前置事件 this.emit('clear_before', { pattern }); await this._ensureConnected(); if (pattern) { // 支持通配符匹配删除 const regex = new RegExp(pattern.replace(/\*/g, '.*')); const result = await this.delete({ key: { $regex: regex } }); this.emit('clear_after', { pattern, result }); return result.acknowledged; } else { // 清空所有数据,直接使用deleteMany操作 const result = await this.db.collection(this.table).deleteMany({}); this.emit('clear_after', { result }); return result.acknowledged; } } catch (error) { this.emit('clear_error', { error, pattern }); $.log.error('[MongoDB] 清空缓存失败:', error); return false; } }; /** * @description Redis兼容API - 获取所有键 * @param {String} pattern 匹配模式 * @return {Promise<Array>} 键名数组 */ MongoDB.prototype.keys = async function (pattern = '*') { try { await this._ensureConnected(); let query = {}; if (pattern !== '*') { // 简单的通配符支持 const regexPattern = pattern.replace(/\*/g, '.*').replace(/\?/g, '.'); query.key = { $regex: new RegExp(`^${regexPattern}$`) }; } const results = await this.find(query, { projection: { key: 1 } }); return results.map(item => item.key); } catch (error) { $.log.error('[MongoDB] 获取键列表失败:', error); return []; } }; /** * @description Redis兼容API - 获取或设置过期时间 * @param {String} key 键名 * @param {Number} [seconds] 过期时间(秒),不传此参数则获取剩余过期时间 * @return {Promise<Number|Boolean>} 不传seconds时返回剩余过期时间(-1表示无过期设置,-2表示已过期或不存在),传seconds时返回是否设置成功 */ MongoDB.prototype.ttl = async function (key, seconds) { try { // 参数验证 if (!key || typeof key !== 'string') { return arguments.length === 1 ? -2 : false; } // 设置模式才需要触发事件 if (seconds !== undefined && seconds !== null) { // 触发前置事件 const beforeEventData = { key, seconds, cancel: false }; this.emit('ttl_before', beforeEventData); if (beforeEventData.cancel) { return false; } let { key: finalKey, seconds: finalSeconds } = beforeEventData; await this._ensureConnected(); // 确保连接和collection存在 if (!this.db) { await this.open(); } // 直接尝试获取collection,使用实例的table属性 if (!this.collection && this.db && this.db.collection) { $.log.debug(`[MongoDB] [ttl] 设置模式:直接获取collection, table=${this.table}`); try { this.collection = this.db.collection(this.table); $.log.debug(`[MongoDB] [ttl] 设置模式:成功获取collection`); } catch (err) { $.log.error(`[MongoDB] [ttl] 设置模式:获取collection失败:`, err.message); this.collection = null; } } // 检查collection是否存在 if (!this.collection) { this.emit('ttl_after', { key: finalKey, seconds: finalSeconds, result: false }); return false; } // 检查键是否存在 const count = await this.collection.countDocuments({ key: finalKey }); if (count === 0) { this.emit('ttl_after', { key: finalKey, seconds: finalSeconds, result: false }); return false; } // 设置过期时间 const updateData = {}; if (finalSeconds === 0 || finalSeconds === -1) { // 移除过期时间 updateData.$unset = { expire_at: 1 }; } else if (finalSeconds > 0) { // 设置新的过期时间 updateData.$set = { expire_at: new Date(Date.now() + finalSeconds * 1000) }; } else { // 设置为已过期 updateData.$set = { expire_at: new Date(0) }; } await this.collection.updateOne( { key: finalKey }, updateData, { upsert: false } ); this.emit('ttl_after', { key: finalKey, seconds: finalSeconds, result: true }); return true; } // 获取模式:检查键的过期时间(不需要事件) else { await this._ensureConnected(); // 确保连接和collection存在 if (!this.db) { await this.open(); } // 直接尝试获取collection,使用实例的table属性 if (!this.collection && this.db && this.db.collection) { $.log.debug(`[MongoDB] [ttl] 直接获取collection, table=${this.table}`); try { this.collection = this.db.collection(this.table); $.log.debug(`[MongoDB] [ttl] 成功获取collection`); } catch (err) { $.log.error(`[MongoDB] [ttl] 获取collection失败:`, err.message); this.collection = null; } } // 再次检查collection if (!this.collection) { return -2; } // 查询键是否存在及过期时间 const item = await this.collection.findOne({ key }); $.log.debug(`[MongoDB] [ttl] 查询键结果: item=${JSON.stringify(item)}`); if (!item) { // 键不存在返回-2 $.log.debug(`[MongoDB] [ttl] 键 ${key} 不存在,返回-2`); return -2; } if (!item.expire_at) { // 没有过期时间返回-1 $.log.debug(`[MongoDB] [ttl] 键 ${key} 没有过期时间,返回-1`); return -1; } // 计算剩余过期时间 const expireTime = new Date(item.expire_at).getTime(); const currentTime = Date.now(); const remainingSeconds = Math.ceil((expireTime - currentTime) / 1000); if (remainingSeconds <= 0) { // 已过期返回-2 return -2; } // 返回剩余过期时间 return remainingSeconds; } } catch (error) { this.emit('ttl_error', { key, seconds, error }); $.log.error(`[MongoDB] [ttl] 操作失败:`, error.message); return arguments.length === 1 ? -2 : false; } }; /** * @description Redis兼容API - 移除键的过期时间 * @param {String} key 键名 * @return {Promise<boolean>} 移除成功返回true,键不存在或没有过期时间返回false */ MongoDB.prototype.persist = async function (key) { try { // 参数验证 if (!key || typeof key !== 'string') { return false; } // 触发前置事件 const beforeEventData = { key, cancel: false }; this.emit('persist_before', beforeEventData); if (beforeEventData.cancel) { return false; } const { key: finalKey } = beforeEventData; // 确保连接和collection存在 if (!this.db) { await this.open(); } // 直接尝试获取collection,不依赖_ensureConnected的复杂逻辑 if (!this.collection && this.db && this.db.collection) { $.log.debug(`[MongoDB] [persist] 直接获取collection, table=${this.table}`); try { this.collection = this.db.collection(this.table); $.log.debug(`[MongoDB] [persist] 成功获取collection`); } catch (err) { $.log.error(`[MongoDB] [persist] 获取collection失败:`, err.message); this.collection = null; } } // 再次检查collection if (!this.collection) { $.log.error('[MongoDB] persist: collection is undefined'); this.emit('persist_error', { key: finalKey, error: new Error('collection is undefined') }); return false; } // 检查键是否存在 const item = await this.collection.findOne({ key: finalKey }); if (!item) { // 键不存在返回false this.emit('persist_after', { key: finalKey, success: false }); return false; } // 如果没有过期时间,直接返回true if (!item.expire_at) { this.emit('persist_after', { key: finalKey, success: true }); return true; } // 从文档中移除过期时间 const result = await this.collection.updateOne( { key: finalKey }, { $unset: { expire_at: 1 } }, { upsert: false } ); // 检查更新是否成功 const success = result.modifiedCount > 0; // 触发后置事件 this.emit('persist_after', { key: finalKey, success: success }); return success; } catch (error) { $.log.error('[MongoDB] persist error:', error.message); this.emit('persist_error', { key, error }); return false; } }; /** * @param {String} key 键名 * @param {*} value 值 * @param {Number} expire 过期时间(秒) * @return {Promise<Boolean>} 是否成功 */ MongoDB.prototype._add = async function (key, value, expire) { try { await this._ensureConnected(); const data = { key: key, value: value }; if (expire) { data.expire_at = new Date(Date.now() + expire * 1000); } // 检查是否已存在 const existing = await this.findOne({ key: key }); if (existing) { // 调用_set方法时传递过期时间参数 await this._set(key, value, expire); } else { await this.addObj(data); } return true; } catch (error) { $.log.error('[MongoDB] 添加缓存失败:', error); return false; } }; /** * @description 仅当键不存在时设置缓存 * @param {String} key 键 * @param {*} value 值 * @param {Number} seconds 过期时间(秒) * @return {Promise<Boolean>} 操作结果,键不存在时设置成功返回true,键已存在返回false */ MongoDB.prototype.add = async function (key, value, seconds) { try { // 触发前置事件,允许修改或阻止操作 const beforeEventData = { key, value, ttl: seconds, cancel: false }; this.emit('add_before', beforeEventData); // 如果事件处理器标记为取消,则返回false if (beforeEventData.cancel) { $.log.debug(`[MongoDB] add操作被事件处理器取消 [${key}]`); return false; } // 使用可能被修改的值 let { key: finalKey, value: finalValue, ttl: finalSeconds } = beforeEventData; await this._ensureConnected(); // 检查键是否已存在 const existing = await this.findOne({ key: finalKey }); if (existing) { // 键已存在,返回false this.emit('add_after', { key: finalKey, value: finalValue, ttl: finalSeconds, success: false }); return false; } // 键不存在,设置值 const data = { key: finalKey, value: finalValue }; if (finalSeconds) { data.expire_at = new Date(Date.now() + finalSeconds * 1000); } await this.addObj(data); // 触发后置事件 this.emit('add_after', { key: finalKey, value: finalValue, ttl: finalSeconds, success: true }); return true; } catch (error) { $.log.error(`[MongoDB] 添加缓存失败 [${key}]:`, error); // 触发错误事件 this.emit('add_error', { key, value, ttl: seconds, error }); return false; } }; /** * @description Redis兼容API - 增加整数值 * @param {String} key 键名 * @param {Number} value 增加值 * @return {Promise<Number>} 增加后的值 */ MongoDB.prototype._addNum = async function (key, value = 1) { try { await this._ensureConnected(); const item = await this.findOne({ key: key }); let currentValue = 0; if (item && typeof item.value === 'number') { currentValue = item.value; } const newValue = currentValue + value; await this._add(key, newValue); return newValue; } catch (error) { $.log.error('[MongoDB] 增加整数值失败:', error); return undefined; } }; /** * @description Redis兼容API - 增加整数值 * @param {String} key 键名 * @param {Number} value 增加值 * @return {Promise<Number>} 增加后的值 */ /** * @description Redis兼容API - 增加整数值 * @param {String} key 键名 * @param {Number} value 增加值 * @return {Promise<Number>} 增加后的值 */ MongoDB.prototype.addInt = async function (key, value = 1, seconds = 0) { try { // 触发前置事件,允许修改或阻止操作 const beforeEventData = { key, value, seconds, cancel: false }; this.emit('addInt_before', beforeEventData); // 如果事件处理器标记为取消,则返回默认值 if (beforeEventData.cancel) { $.log.debug('[MongoDB] addInt操作被事件处理器取消'); return 0; } // 使用可能被修改的值 let { key: finalKey, value: finalValue, seconds: finalSeconds } = beforeEventData; // 参数验证 if (!finalKey || typeof finalKey !== 'string') { $.log.warn('[MongoDB] addInt: key必须是非空字符串'); return 0; } finalValue = Number(finalValue) || 0; await this._ensureConnected(); // 获取当前值 const item = await this.findOne({ key: finalKey }); let currentValue = 0; if (item) { // 确保转换为数字 currentValue = Number(item.value) || 0; } const newValue = Math.floor(currentValue + finalValue); // 确保返回整数 await this._add(finalKey, newValue, finalSeconds); // 触发后置事件 this.emit('addInt_after', { key: finalKey, value: finalValue, seconds: finalSeconds, result: newValue, success: true }); return newValue; } catch (error) { // 触发错误事件 this.emit('addInt_error', { key, value, seconds, error }); $.log.error('[MongoDB] 增加整数值失败:', error); // 与CacheBase保持一致,错误时返回默认值而不是抛出异常 return 0; } }; /** * @description Redis兼容API - 增加浮点数值 * @param {String} key 键名 * @param {Number} value 增加值 * @return {Promise<Number>} 增加后的值 */ MongoDB.prototype.addFloat = async function (key, value = 1.0, seconds = 0) { try { // 触发前置事件,允许修改或阻止操作 const beforeEventData = { key, value, seconds, cancel: false }; this.emit('addFloat_before', beforeEventData); // 如果事件处理器标记为取消,则返回默认值 if (beforeEventData.cancel) { $.log.debug('[MongoDB] addFloat操作被事件处理器取消'); return 0.0; } // 使用可能被修改的值 let { key: finalKey, value: finalValue, seconds: finalSeconds } = beforeEventData; // 参数验证 if (!finalKey || typeof finalKey !== 'string') { $.log.warn('[MongoDB] addFloat: key必须是非空字符串'); return 0.0; } finalValue = Number(finalValue) || 0.0; await this._ensureConnected(); // 获取当前值 const item = await this.findOne({ key: finalKey }); let currentValue = 0.0; if (item) { // 确保转换为数字 currentValue = Number(item.value) || 0.0; } const newValue = currentValue + finalValue; await this._add(finalKey, newValue, finalSeconds); // 触发后置事件 this.emit('addFloat_after', { key: finalKey, value: finalValue, seconds: finalSeconds, result: newValue, success: true }); return newValue; } catch (error) { // 触发错误事件 this.emit('addFloat_error', { key, value, seconds, error }); $.log.error('[MongoDB] 增加浮点数值失败:', error); // 与CacheBase保持一致,错误时返回默认值而不是抛出异常 return 0.0; } }; /** * @description Redis兼容API - 添加字符串 * @param {String} key 键名 * @param {String} value 字符串值 * @return {Promise<Number>} 添加后的字符串长度 */ MongoDB.prototype.addStr = async function (key, value) { try { // 触发前置事件 this.emit('addStr_before', { key, value }); // 参数验证 if (!key || typeof key !== 'string') { throw new Error('key必须是非空字符串'); } if (value === undefined || value === null) { value = ''; } await this._ensureConnected(); // 获取当前值 const item = await this.findOne({ key: key }); const currentValue = item ? String(item.value) : ''; const newValue = currentValue + String(value); // 设置新值 await this._add(key, newValue); // 触发后置事件 this.emit('addStr_after', { key, value, result: newValue.length }); // 返回添加后的字符串长度 return newValue.length; } catch (error) { // 触发错误事件 this.emit('addStr_error', { key, value, error }); $.log.error('[MongoDB] 添加字符串失败:', error); return 0; } }; /** * @description Redis兼容API - 排序 * @param {String} key 键名 * @param {Object} options 排序选项 * @return {Promise<Array>} 排序结果 */ MongoDB.prototype.sort = async function (key, options = {}) { try { await this._ensureConnected(); // 假设value是数组 const item = await this.findOne({ key: key }); if (!item || !Array.isArray(item.value)) return []; const sorted = [...item.value]; // 处理排序选项 if (options.desc) { sorted.sort((a, b) => b - a); } else { sorted.sort((a, b) => a - b); } // 处理限制 if (options.limit) { const offset = options.offset || 0; return sorted.slice(offset, offset + options.limit); } return sorted; } catch (error) { $.log.error('[MongoDB] 排序失败:', error); return []; } }; /** * @description Redis兼容API - 哈希表设置字段 * @param {String} key 键名 * @param {String} field 字段名 * @param {*} value 值 * @return {Promise<number>} 返回1表示新建字段,0表示更新字段 */ MongoDB.prototype.hset = async function (key, field, value) { try { await this._ensureConnected(); const item = await this.findOne({ key: key }); let hash = item && typeof item.value === 'object' && item.value !== null ? item.value : {}; // 检查字段是否存在,以确定是新建还是更新 const isNewField = !hash.hasOwnProperty(field); hash[field] = value; await this._add(key, hash); return isNewField ? 1 : 0; } catch (error) { $.log.error('[MongoDB] 哈希表设置失败:', error); throw error; } }; /** * @description Redis兼容API - 哈希表获取字段 * @param {String} key 键名 * @param {String} field 字段名 * @return {Promise<any>} 返回字段值,不存在返回undefined */ MongoDB.prototype.hget = async function (key, field) { try { await this._ensureConnected(); const item = await this.findOne({ key: key }); if (!item || typeof item.value !== 'object' || item.value === null) { return undefined; } return item.value[field] !== undefined ? item.value[field] : undefined; } catch (error) { $.log.error('[MongoDB] 哈希表获取失败:', error); throw error; } }; /** * @description Redis兼容API - 哈希表获取所有字段和值 * @param {String} key 键名 * @return {Promise<Object>} 哈希表对象 */ MongoDB.prototype.hgetall = async function (key) { try { const item = await this.findOne({ key: key }); if (!item || typeof item.value !== 'object' || item.value === null) { return {}; } return item.value; } catch (error) { $.log.error('[MongoDB] 哈希表获取全部失败:', error); return {}; } }; /** * @description Redis兼容API - 哈希表删除字段 * @param {String} key 键名 * @param {String|Array<String>} field 字段名或字段名数组 * @return {Promise<boolean>} 返回true表示成功删除,false表示未删除 */ MongoDB.prototype.hdel = async function (key, fields) { try { // 参数验证 if (!key || typeof key !== 'string') { return 0; } // 统一将fields转换为数组格式 const fieldArray = Array.isArray(fields) ? fields : [fields]; if (fieldArray.length === 0) { return 0; } await this._ensureConnected(); // 查找是否存在该键 const item = await this.findOne({ key: key }); // 如果键不存在或不是哈希表格式,返回0 if (!item || typeof item.value !== 'object' || item.value === null || Array.isArray(item.value)) { return 0; } // 计数删除的字段数量 let deletedCount = 0; const updatedValue = { ...item.value }; // 遍历要删除的字段 fieldArray.forEach(field => { if (field && Object.prototype.hasOwnProperty.call(updatedValue, field)) { delete updatedValue[field]; deletedCount++; } }); // 如果删除了字段,更新数据库 if (deletedCount > 0) { // 使用正确的方式更新数据库 await this.db.collection(this.table).updateOne( { key: key }, { $set: { value: updatedValue, updated_at: new Date() } } ); } return deletedCount; } catch (error) { $.log.error('[MongoDB] hdel操作失败:', error); return 0; } }; /** * @description Redis兼容API - 检查键是否存在 * @param {String} key 键名 * @return {Promise<Boolean>} 是否存在 */ MongoDB.prototype.has = async function (key) { try { const result = await this.findOne({ key: key }); return result !== null; } catch (error) { $.log.error('[MongoDB] [has] 检查键是否存在失败:', error); throw error; } }; /** * @description Redis兼容API - 设置键过期时间(映射到ttl) * @param {String} key 键名 * @param {Number} seconds 过期时间(秒) * @return {Promise<Boolean>} 是否成功 */ MongoDB.prototype.expire = async function (key, seconds) { try { // 参数验证 seconds = parseInt(seconds) || 0; // 触发前置事件,允许修改或阻止操作 const beforeEventData = { key, seconds, cancel: false }; this.emit('expire_before', beforeEventData); // 如果事件处理器标记为取消,则返回默认值 if (beforeEventData.cancel) { $.log.debug('[MongoDB] expire操作被事件处理器取消'); return false; } // 使用可能被修改的值 const { key: finalKey, seconds: finalSeconds } = beforeEventData; // 调用ttl方法设置过期时间 const result = await this.ttl(finalKey, finalSeconds); // 触发后置事件 this.emit('expire_after', { key: finalKey, seconds: finalSeconds, result }); // 返回布尔值表示成功或失败 return result === true || (typeof result === 'number' && result >= -2); } catch (error) { // 记录错误日志 $.log.error('[MongoDB] expire方法执行错误', { error: error.message, key, seconds }); // 触发错误事件 this.emit('expire_error', { key, seconds, error }); return false; } }; /** * @description Redis兼容API - 递增(通过addInt模拟) * @param {String} key 键名 * @return {Promise<Number>} 递增后的值 */ MongoDB.prototype.incr = async function (key, seconds = 0) { try { // 触发前置事件,允许修改或阻止操作 const beforeEventData = { key, seconds, cancel: false }; this.emit('incr_before', beforeEventData); // 如果事件处理器标记为取消,则返回默认值 if (beforeEventData.cancel) { $.log.debug('[MongoDB] incr操作被事件处理器取消'); return 0; } // 使用可能被修改的值 let { key: finalKey, seconds: finalSeconds } = beforeEventData; // 参数验证 if (!finalKey || typeof finalKey !== 'string') { $.log.warn('[MongoDB] incr: key必须是非空字符串'); return 0; } await this._ensureConnected(); const result = await this.addInt(finalKey, 1, finalSeconds); const returnResult = result !== undefined ? result : 0; // 触发后置事件 this.emit('incr_after', { key: finalKey, seconds: finalSeconds, result: returnResult, success: true }); return returnResult; } catch (error) { // 触发错误事件 this.emit('incr_error', { key, seconds, error }); $.log.error('[MongoDB] 递增操作失败:', error); return 0; } }; /** * @description Redis兼容API - 递减(通过addInt模拟) * @param {String} key 键名 * @return {Promise<Number>} 递减后的值 */ MongoDB.prototype.decr = async function (key, seconds = 0) { try { // 触发前置事件,允许修改或阻止操作 const beforeEventData = { key, seconds, cancel: false }; this.emit('decr_before', beforeEventData); // 如果事件处理器标记为取消,则返回默认值 if (beforeEventData.cancel) { $.log.debug('[MongoDB] decr操作被事件处理器取消'); return 0; } // 使用可能被修改的值 let { key: finalKey, seconds: finalSeconds } = beforeEventData; // 参数验证 if (!finalKey || typeof finalKey !== 'string') { $.log.warn('[MongoDB] decr: key必须是非空字符串'); return 0; } await this._ensureConnected(); const result = await this.addInt(finalKey, -1, finalSeconds); const returnResult = result !== undefined ? result : 0; // 触发后置事件 this.emit('decr_after', { key: finalKey, seconds: finalSeconds, result: returnResult, success: true }); return returnResult; } catch (error) { // 触发错误事件 this.emit('decr_error', { key, seconds, error }); $.log.error('[MongoDB] 递减操作失败:', error); return 0; } }; /** * @description Redis兼容API - 按指定值递增 * @param {String} key 键名 * @param {Number} value 增加值 * @return {Promise<Number>} 递增后的值 */ MongoDB.prototype.incrby = async function (key, value = 1, seconds = 0) { try { // 触发前置事件,允许修改或阻止操作 const beforeEventData = { key, value, seconds, cancel: false }; this.emit('incrby_before', beforeEventData); // 如果事件处理器标记为取消,则返回默认值 if (beforeEventData.cancel) { $.log.debug('[MongoDB] incrby操作被事件处理器取消'); return 0; } // 使用可能被修改的值 let { key: finalKey, value: finalValue, seconds: finalSeconds } = beforeEventData; // 参数验证 if (!finalKey || typeof finalKey !== 'string') { $.log.warn('[MongoDB] incrby: key必须是非空字符串'); return 0; } // 确保value是数字 finalValue = Number(finalValue) || 0; await this._ensureConnected(); const result = await this.addInt(finalKey, finalValue, finalSeconds); const returnResult = result !== undefined ? result : 0; // 触发后置事件 this.emit('incrby_after', { key: finalKey, value: finalValue, seconds: finalSeconds, result: returnResult, success: true }); return returnResult; } catch (error) { // 触发错误事件 this.emit('incrby_error', { key, value, seconds, error }); $.log.error('[MongoDB] 递增操作失败:', error); return 0; } }; /** * @description Redis兼容API - 按指定值递减 * @param {String} key 键名 * @param {Number} value 减少值 * @return {Promise<Number>} 递减后的值 */ MongoDB.prototype.decrby = async function (key, value = 1, seconds = 0) { try { // 触发前置事件,允许修改或阻止操作 const beforeEventData = { key, value, seconds, cancel: false }; this.emit('decrby_before', beforeEventData); // 如果事件处理器标记为取消,则返回默认值 if (beforeEventData.cancel) { $.log.debug('[MongoDB] decrby操作被事件处理器取消'); return 0; } // 使用可能被修改的值 let { key: finalKey, value: finalValue, seconds: finalSeconds } = beforeEventData; // 参数验证 if (!finalKey || typeof finalKey !== 'string') { $.log.warn('[MongoDB] decrby: key必须是非空字符串'); return 0; } // 确保value是数字 finalValue = Number(finalValue) || 0; await this._ensureConnected(); const result = await this.addInt(finalKey, -finalValue, finalSeconds); const returnResult = result !== undefined ? result : 0; // 触发后置事件 this.emit('decrby_after', { key: finalKey, value: finalValue, seconds: finalSeconds, result: returnResult, success: true }); return returnResult; } catch (error) { // 触发错误事件 this.emit('decrby_error', { key, value, seconds, error }); $.log.error('[MongoDB] 递减操作失败:', error); return 0; } }; /** * @description Redis兼容API - 按指定浮点值递增 * @param {String} key 键名 * @param {Number} value 浮点增加值 * @return {Promise<Number>} 递增后的值 */ MongoDB.prototype.incrbyfloat = async function (key, value = 1.0, seconds = 0) { try { // 触发前置事件,允许修改或阻止操作 const beforeEventData = { key, value, seconds, cancel: false }; this.emit('incrbyfloat_before', beforeEventData); // 如果事件处理器标记为取消,则返回默认值 if (beforeEventData.cancel) { $.log.debug('[MongoDB] incrbyfloat操作被事件处理器取消'); return 0.0; } // 使用可能被修改的值 let { key: finalKey, value: finalValue, seconds: finalSeconds } = beforeEventData; // 参数验证 if (!finalKey || typeof finalKey !== 'string') { $.log.warn('[MongoDB] incrbyfloat: key必须是非空字符串'); return 0.0; } // 确保value是数字 finalValue = Number(finalValue) || 0.0; await this._ensureConnected(); const result = await this.addFloat(finalKey, finalValue, finalSeconds); const returnResult = result !== undefined ? result : 0.0; // 触发后置事件 this.emit('incrbyfloat_after', { key: finalKey, value: finalValue, seconds: finalSeconds, result: returnResult, success: true }); return returnResult; } catch (error) { // 触发错误事件 this.emit('incrbyfloat_error', { key, value, seconds, error }); $.log.error('[MongoDB] 浮点递增操作失败:', error); return 0.0; } }; /** * @description Redis兼容API - 检查键是否存在 * @param {String|Array<String>} key 键名或键名数组 * @return {Promise<number>} 返回存在的键数量 */ MongoDB.prototype.exists = async function (key) { try { await this._ensureConnected(); // 验证数组中的每个元素都是非空字符串 if (Array.isArray(key)) { for (const k of key) { if (typeof k !== 'string' || !k) { throw new Error('all keys must be non-empty strings'); } } } let existsCount = 0; // 检查输入类型并处理 if (Array.isArray(key)) { // 处理多键检查 const results = await this.find({ key: { $in: key } }); existsCount = results.length; } else { // 处理单键检查 const result = await this.findOne({ key: key }); existsCount = result !== null ? 1 : 0; } return existsCount; } catch (error) { $.log.error('[MongoDB] 检查键是否存在失败:', error); throw error; } }; /** * @description Redis兼容API - 设置键值 * @param {String} key 键名 * @param {*} value 值 * @return {Promise<Boolean>} 是否成功 */ MongoDB.prototype._set = async function (key, value, expire) { try { await this._ensureConnected(); // 准备更新操作 const updateData = { $set: { value: value } }; // 如果提供了过期时间,添加expire_at字段 if (expire && expire > 0) { updateData.$set.expire_at = new Date(Date.now() + expire * 1000); } // 直接更新数据库,避免调用_add方法形成循环引用 const result = await this.db.collection(this.table).updateOne( { key: key }, updateData, { upsert: true } ); return true; } catch (error) { $.log.error('[MongoDB] 设置键值失败:', error); return false; // 与CacheBase保持一致,返回false而不是抛出异常 } }; /** * @description Redis兼容API - 设置键值 * @param {String} key 键名 * @param {*} value 值 * @param {Number} seconds 过期时间(秒) * @return {Promise<Boolean>} 是否成功 */ MongoDB.prototype.set = async function (key, value, seconds = 0) { return this._set(key, value, seconds); }; /** * @description Redis兼容API - 哈希表批量设置 * @param {String} key 键名 * @param {Array} fieldValues 字段值对数组,格式: [field1, value1, field2, value2, ...] 或对象格式 {field1: value1, field2: value2} * @return {Promise<Number>} 设置的字段数量 */ MongoDB.prototype.hmset = async function (key, ...fieldValues) { try { // 参数验证 if (!key || typeof key !== 'string') { $.log.warn('[MongoDB] hmset: 键名无效'); return 0; } // 处理可变参数或对象参数 let hashData = {}; if (fieldValues.length === 1 && typeof fieldValues[0] === 'object' && fieldValues[0] !== null && !Array .isArray(fieldValues[0])) { // 单个对象参数格式 {field1: value1, field2: value2} hashData = fieldValues[0]; } else { // 可变参数格式 field1, value1, field2, value2... for (let i = 0; i < fieldValues.length; i += 2) { if (i + 1 < fieldValues.length) { hashData[fieldValues[i]] = fieldValues[i + 1]; } } } const fieldsCount = Object.keys(hashData).length; // 确保连接 await this._ensureConnected(); // 直接创建或更新哈希数据,不使用事件系统 const data = { key: key, value: hashData }; const existing = await this.findOne({ key: key }); if (existing) { // 如果存在,合并现有数据 if (typeof existing.value === 'object' && existing.value !== null && !Array.isArray(existing .value)) { data.value = { ...existing.value, ...hashData }; } await this._set({ key: key }, data); } else { await this.addObj(data); } return fieldsCount; } catch (error) { $.log.error('[MongoDB] [hmset] 哈希表批量设置失败:', error); return 0; } }; /** * @description Redis兼容API - 哈希表批量获取 * @param {String} key 键名 * @param {Array} fields 字段名数组 * @return {Promise<Array>} 与字段名顺序对应的数组值,不存在返回undefined */ MongoDB.prototype.hmget = async function (key, fields) { try { // 参数验证 if (!key || typeof key !== 'string') { return Array.isArray(fields) ? fields.map(() => null) : [null]; } await this._ensureConnected(); const item = await this.findOne({ key: key }); // 确保fields是数组 const fieldArray = Array.isArray(fields) ? fields : [fields]; // 如果键不存在或不是哈希表格式,返回与字段数量相同的null数组 if (!item || typeof item.value !== 'object' || item.value === null || Array.isArray(item.value)) { return fieldArray.map(() => null); } // 返回与字段名顺序对应的数组值,不存在的字段返回null const values = fieldArray.map(field => { // 严格检查字段是否存在于对象中 return Object.prototype.hasOwnProperty.call(item.value, field) ? item.value[field] : null; }); return values; } catch (error) { $.log.error('[MongoDB] hmget操作失败:', error); // 错误时返回与fields长度相同的null数组 const fieldArray = Array.isArray(fields) ? fields : [fields]; return fieldArray.map(() => null); } }; /** * @description Redis兼容API - 哈希表批量删除字段 * @param {String} key 键名 * @param {Array} fields 字段名数组 * @return {Promise<Boolean>} 是否成功 */ MongoDB.prototype.hdelMulti = async function (key, fields) { try { await this._ensureConnected(); const item = await this.findOne({ key: key }); if (!item || typeof item.value !== 'object' || item.value === null) { return false; } let deleted = false; fields.forEach(field => { if (item.value[field] !== undefined) { delete item.value[field]; deleted = true; } }); if (deleted) { await this._set({ key: key }, { value: item.value }); } return deleted; } catch (error) { $.log.error('[MongoDB] 哈希表批量删除失败:', error); return false; } }; /** * @description Redis兼容API - 哈希表获取所有值 * @param {String} key 键名 * @return {Promise<Array>} 值数组 */ MongoDB.prototype.hvals = async function (key) { try { await this._ensureConnected(); const item = await this.findOne({ key: key }); if (!item || typeof item.value !== 'object' || item.value === null) { return []; } return Object.values(item.value); } catch (error) { $.log.error('[MongoDB] 哈希表获取所有值失败:', error); return []; } }; /** * @description Redis兼容API - 哈希表获取所有字段名 * @param {String} key 键名 * @return {Promise<Array>} 字段名数组 */ MongoDB.prototype.hkeys = async function (key) { try { await this._ensureConnected(); const item = await this.findOne({ key: key }); if (!item || typeof item.value !== 'object' || item.value === null) { return []; } return Object.keys(item.value); } catch (error) { $.log.error('[MongoDB] 哈希表获取所有字段名失败:', error); return []; } }; /** * @description Redis兼容API - 哈希表字段数量 * @param {String} key 键名 * @return {Promise<Number>} 字段数量 */ MongoDB.prototype.hlen = async function (key) { try { // 参数验证 if (!key || typeof key !== 'string') { return 0; } await this._ensureConnected(); const item = await this.findOne({ key: key }); // 如果键不存在或不是哈希表格式,返回0 if (!item || typeof item.value !== 'object' || item.value === null || Array.isArray(item.value)) { return 0; } // 使用Object.keys获取自有属性数量,这比for-in循环更高效 return Object.keys(item.value).length; } catch (error) { $.log.error('[MongoDB] hlen操作失败:', error); return 0; } }; /** * @description Redis兼容API - 哈希表字段是否存在 * @param {String} key 键名 * @param {String} field 字段名 * @return {Promise<Boolean>} 是否存在 */ MongoDB.prototype.hexists = async function (key, field) { try { await this._ensureConnected(); const item = await this.findOne({ key: key }); if (!item || typeof item.value !== 'object' || item.value === null) { return false; } return field in item.value; } catch (error) { $.log.error('[MongoDB] 哈希表字段是否存在失败:', error); return false; } }; /** * @description Redis兼容API - 列表尾部添加元素 * @param {String} key 键名 * @param {*} value 值 * @return {Promise<Number>} 添加后列表长度 */ MongoDB.prototype.rpush = async function (key, value) { try { await this._ensureConnected(); const item = await this.findOne({ key: key }); let list = item && Array.isArray(item.value) ? item.value : []; // 将值转换为字符串,与Redis行为一致 list.push(value.toString()); await this._add(key, list); return list.length; } catch (error) { $.log.error('[MongoDB] 列表尾部添加失败:', error); return 0; } }; /** * @description Redis兼容API - 列表头部添加元素 * @param {String} key 键名 * @param {*} value 值 * @return {Promise<Number>} 添加后列表长度 */ MongoDB.prototype.lpush = async function (key, value) { try { await this._ensureConnected(); const item = await this.findOne({ key: key }); let list = item && Array.isArray(item.value) ? item.value : []; // 将值转换为字符串,与Redis行为一致 list.unshift(value.toString()); await this._add(key, list); return list.length; } catch (error) { $.log.error('[MongoDB] 列表头部添加失败:', error); return 0; } }; /** * @description Redis兼容API - 列表范围查询 * @param {String} key 键名 * @param {Number} start 起始索引 * @param {Number} stop 结束索引 * @return {Promise<Array>} 元素数组 */ MongoDB.prototype.lrange = async function (key, start, stop) { try { await this._ensureConnected(); const item = await this.findOne({ key: key }); if (!item || !Array.isArray(item.value)) { return []; } // 处理负数索引 let startIndex = start < 0 ? item.value.length + start : start; let endIndex = stop < 0 ? item.value.length + stop : stop; // 确保索引有效 startIndex = Math.max(0, Math.min(startIndex, item.value.length - 1)); endIndex = Math.max(startIndex, Math.min(endIndex, item.value.length - 1)); return item.value.slice(startIndex, endIndex + 1); } catch (error) { $.log.error('[MongoDB] 列表范围查询失败:', error); return []; } }; /** * @description Redis兼容API - 获取列表指定索引元素 * @param {String} key 键名 * @param {Number} index 索引 * @return {Promise<any>} 元素值 */ MongoDB.prototype.lindex = async function (key, index) { try { await this._ensureConnected(); const item = await this.findOne({ key: key }); if (!item || !Array.isArray(item.value)) { return null; } // 处理负数索引 const realIndex = index < 0 ? item.value.length + index : index; if (realIndex < 0 || realIndex >= item.value.length) { return null; } return item.value[realIndex]; } catch (error) { $.log.error('[MongoDB] 获取列表索引元素失败:', error); return null; } }; /** * @description Redis兼容API - 设置列表指定索引元素 * @param {String} key 键名 * @param {Number} index 索引 * @param {*} value 值 * @return {Promise<Boolean>} 是否成功 */ MongoDB.prototype.lset = async function (key, index, value) { try { await this._ensureConnected(); const item = await this.findOne({ key: key }); if (!item || !Array.isArray(item.value)) { return false; } // 处理负数索引 const realIndex = index < 0 ? item.value.length + index : index; if (realIndex < 0 || realIndex >= item.value.length) { return false; } item.value[realIndex] = value; await this._set({ key: key }, { value: item.value }); return true; } catch (error) { $.log.error('[MongoDB] 设置列表索引元素失败:', error); return false; } }; /** * @description Redis兼容API - 删除列表元素 * @param {String} key 键名 * @param {Number} count 删除数量 * @param {*} value 要删除的值 * @return {Promise<Number>} 删除的元素数量 */ MongoDB.prototype.lrem = async function (key, count, value) { try { await this._ensureConnected(); const item = await this.findOne({ key: key }); if (!item || !Array.isArray(item.value)) { return 0; } let list = [...item.value]; let removedCount = 0; if (count > 0) { // 从头部删除count个等于value的元素 for (let i = 0; i < list.length && removedCount < count; i++) { if (JSON.stringify(list[i]) === JSON.stringify(value)) { list.splice(i, 1); removedCount++; i--; // 调整索引 } } } else if (count < 0) { // 从尾部删除|count|个等于value的元素 count = Math.abs(count); for (let i = list.length - 1; i >= 0 && removedCount < count; i--) { if (JSON.stringify(list[i]) === JSON.stringify(value)) { list.splice(i, 1); removedCount++; } } } else { // 删除所有等于value的元素 list = list.filter(item => JSON.stringify(item) !== JSON.stringify(value)); removedCount = item.value.length - list.length; } if (removedCount > 0) { await this._set({ key: key }, { value: list }); } return removedCount; } catch (error) { $.log.error('[MongoDB] 删除列表元素失败:', error); return 0; } }; /** * @description Redis兼容API - 裁剪列表 * @param {String} key 键名 * @param {Number} start 起始索引 * @param {Number} stop 结束索引 * @return {Promise<Boolean>} 是否成功 */ MongoDB.prototype.ltrim = async function (key, start, stop) { try { await this._ensureConnected(); const item = await this.findOne({ key: key }); if (!item || !Array.isArray(item.value)) { return false; } // 使用lrange的逻辑获取裁剪后的列表 const trimmed = await this.lrange(key, start, stop); await this._set({ key: key }, { value: trimmed }); return true; } catch (error) { $.log.error('[MongoDB] 裁剪列表失败:', error); return false; } }; /** * @description Redis兼容API - 列表长度 * @param {String} key 键名 * @return {Promise<Number>} 列表长度 */ MongoDB.prototype.llen = async function (key) { try { await this._ensureConnected(); const item = await this.findOne({ key: key }); if (!item || !Array.isArray(item.value)) { return 0; } return item.value.length; } catch (error) { $.log.error('[MongoDB] 获取列表长度失败:', error); return 0; } }; /** * @description Redis兼容API - 集合批量添加 * @param {String} key 键名 * @param {Array} members 成员数组 * @return {Promise<Number>} 添加的成员数量 */ MongoDB.prototype.saddMulti = async function (key, members) { try { await this._ensureConnected(); const item = await this.findOne({ key: key }); let set = item && Array.isArray(item.value) ? [...new Set(item.value)] : []; let addedCount = 0; members.forEach(member => { if (!set.some(existing => JSON.stringify(existing) === JSON.stringify(member))) { set.push(member); addedCount++; } }); if (addedCount > 0) { await this._add(key, set); } return addedCount; } catch (error) { $.log.error('[MongoDB] 集合并发添加失败:', error); return 0; } }; /** * @description Redis兼容API - 集合批量删除 * @param {String} key 键名 * @param {Array} members 成员数组 * @return {Promise<Number>} 删除的成员数量 */ MongoDB.prototype.sremMulti = async function (key, members) { try { await this._ensureConnected(); const item = await this.findOne({ key: key }); if (!item || !Array.isArray(item.value)) { return 0; } let set = [...new Set(item.value)]; const originalLength = set.length; // 创建要删除的值的字符串表示集合用于快速查找 const membersToRemove = new Set(members.map(m => JSON.stringify(m))); set = set.filter(member => !membersToRemove.has(JSON.stringify(member))); const removedCount = originalLength - set.length; if (removedCount > 0) { await this._add(key, set); } return removedCount; } catch (error) { $.log.error('[MongoDB] 集合并发删除失败:', error); return 0; } }; /** * @description Redis兼容API - 集合差集 * @param {String} key1 第一个集合键 * @param {String} key2 第二个集合键 * @return {Promise<Array>} 差集数组 */ MongoDB.prototype.sdiff = async function (key1, key2) { try { await this._ensureConnected(); const item1 = await this.findOne({ key: key1 }); const item2 = await this.findOne({ key: key2 }); const set1 = item1 && Array.isArray(item1.value) ? [...new Set(item1.value)] : []; const set2 = item2 && Array.isArray(item2.value) ? [...new Set(item2.value)] : []; // 创建第二个集合值的字符串表示用于快速查找 const set2Str = new Set(set2.map(m => JSON.stringify(m))); // 找出在set1中但不在set2中的元素 return set1.filter(member => !set2Str.has(JSON.stringify(member))); } catch (error) { $.log.error('[MongoDB] 集合差集失败:', error); return []; } }; /** * @description Redis兼容API - 集合交集 * @param {String} key1 第一个集合键 * @param {String} key2 第二个集合键 * @return {Promise<Array>} 交集数组 */ MongoDB.prototype.sinter = async function (key1, key2) { try { await this._ensureConnected(); const item1 = await this.findOne({ key: key1 }); const item2 = await this.findOne({ key: key2 }); const set1 = item1 && Array.isArray(item1.value) ? [...new Set(item1.value)] : []; const set2 = item2 && Array.isArray(item2.value) ? [...new Set(item2.value)] : []; // 创建较小集合的字符串表示用于快速查找,优化性能 let smallerSet, largerSet; if (set1.length <= set2.length) { smallerSet = set1; largerSet = set2; } else { smallerSet = set2; largerSet = set1; } const largerSetStr = new Set(largerSet.map(m => JSON.stringify(m))); // 找出同时存在于两个集合中的元素 return smallerSet.filter(member => largerSetStr.has(JSON.stringify(member))); } catch (error) { $.log.error('[MongoDB] 集合并集失败:', error); return []; } }; /** * @description Redis兼容API - 集合并集 * @param {String} key1 第一个集合键 * @param {String} key2 第二个集合键 * @return {Promise<Array>} 并集数组 */ MongoDB.prototype.sunion = async function (key1, key2) { try { await this._ensureConnected(); const item1 = await this.findOne({ key: key1 }); const item2 = await this.findOne({ key: key2 }); const set1 = item1 && Array.isArray(item1.value) ? item1.value : []; const set2 = item2 && Array.isArray(item2.value) ? item2.value : []; // 合并并去重 const combined = [...set1, ...set2]; // 使用Map进行对象去重,基于JSON字符串表示 const uniqueMap = new Map(); combined.forEach(item => { const key = JSON.stringify(item); if (!uniqueMap.has(key)) { uniqueMap.set(key, item); } }); return Array.from(uniqueMap.values()); } catch (error) { $.log.error('[MongoDB] 集合并集失败:', error); return []; } }; /** * @description Redis兼容API - 集合成员数量 * @param {String} key 键名 * @return {Promise<Number>} 成员数量 */ MongoDB.prototype.scard = async function (key) { try { await this._ensureConnected(); const item = await this.findOne({ key: key }); if (!item || !Array.isArray(item.value)) {