monsqlize 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +2474 -0
- package/LICENSE +21 -0
- package/README.md +1368 -0
- package/index.d.ts +1052 -0
- package/lib/cache.js +491 -0
- package/lib/common/cursor.js +58 -0
- package/lib/common/docs-urls.js +72 -0
- package/lib/common/index-options.js +222 -0
- package/lib/common/log.js +60 -0
- package/lib/common/namespace.js +21 -0
- package/lib/common/normalize.js +33 -0
- package/lib/common/page-result.js +42 -0
- package/lib/common/runner.js +56 -0
- package/lib/common/server-features.js +231 -0
- package/lib/common/shape-builders.js +26 -0
- package/lib/common/validation.js +49 -0
- package/lib/connect.js +76 -0
- package/lib/constants.js +54 -0
- package/lib/count-queue.js +187 -0
- package/lib/distributed-cache-invalidator.js +259 -0
- package/lib/errors.js +157 -0
- package/lib/index.js +352 -0
- package/lib/logger.js +224 -0
- package/lib/model/examples/test.js +114 -0
- package/lib/mongodb/common/accessor-helpers.js +44 -0
- package/lib/mongodb/common/agg-pipeline.js +32 -0
- package/lib/mongodb/common/iid.js +27 -0
- package/lib/mongodb/common/lexicographic-expr.js +52 -0
- package/lib/mongodb/common/shape.js +31 -0
- package/lib/mongodb/common/sort.js +38 -0
- package/lib/mongodb/common/transaction-aware.js +24 -0
- package/lib/mongodb/connect.js +178 -0
- package/lib/mongodb/index.js +458 -0
- package/lib/mongodb/management/admin-ops.js +199 -0
- package/lib/mongodb/management/bookmark-ops.js +166 -0
- package/lib/mongodb/management/cache-ops.js +49 -0
- package/lib/mongodb/management/collection-ops.js +386 -0
- package/lib/mongodb/management/database-ops.js +201 -0
- package/lib/mongodb/management/index-ops.js +474 -0
- package/lib/mongodb/management/index.js +16 -0
- package/lib/mongodb/management/namespace.js +30 -0
- package/lib/mongodb/management/validation-ops.js +267 -0
- package/lib/mongodb/queries/aggregate.js +133 -0
- package/lib/mongodb/queries/chain.js +623 -0
- package/lib/mongodb/queries/count.js +88 -0
- package/lib/mongodb/queries/distinct.js +68 -0
- package/lib/mongodb/queries/find-and-count.js +183 -0
- package/lib/mongodb/queries/find-by-ids.js +235 -0
- package/lib/mongodb/queries/find-one-by-id.js +170 -0
- package/lib/mongodb/queries/find-one.js +61 -0
- package/lib/mongodb/queries/find-page.js +565 -0
- package/lib/mongodb/queries/find.js +161 -0
- package/lib/mongodb/queries/index.js +49 -0
- package/lib/mongodb/writes/delete-many.js +181 -0
- package/lib/mongodb/writes/delete-one.js +173 -0
- package/lib/mongodb/writes/find-one-and-delete.js +193 -0
- package/lib/mongodb/writes/find-one-and-replace.js +222 -0
- package/lib/mongodb/writes/find-one-and-update.js +223 -0
- package/lib/mongodb/writes/increment-one.js +243 -0
- package/lib/mongodb/writes/index.js +41 -0
- package/lib/mongodb/writes/insert-batch.js +498 -0
- package/lib/mongodb/writes/insert-many.js +218 -0
- package/lib/mongodb/writes/insert-one.js +171 -0
- package/lib/mongodb/writes/replace-one.js +199 -0
- package/lib/mongodb/writes/result-handler.js +236 -0
- package/lib/mongodb/writes/update-many.js +205 -0
- package/lib/mongodb/writes/update-one.js +207 -0
- package/lib/mongodb/writes/upsert-one.js +190 -0
- package/lib/multi-level-cache.js +189 -0
- package/lib/operators.js +330 -0
- package/lib/redis-cache-adapter.js +237 -0
- package/lib/transaction/CacheLockManager.js +161 -0
- package/lib/transaction/DistributedCacheLockManager.js +239 -0
- package/lib/transaction/Transaction.js +314 -0
- package/lib/transaction/TransactionManager.js +266 -0
- package/lib/transaction/index.js +10 -0
- package/package.json +111 -0
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* upsertOne 写操作模块
|
|
3
|
+
* @description 便利方法:存在则更新,不存在则插入
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { createError, ErrorCodes } = require('../../errors');
|
|
7
|
+
const { isInTransaction, getTransactionFromSession } = require("../common/transaction-aware");
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* 创建 upsertOne 操作
|
|
11
|
+
* @param {Object} context - 上下文对象
|
|
12
|
+
* @returns {Function} upsertOne 方法
|
|
13
|
+
*/
|
|
14
|
+
function createUpsertOneOps(context) {
|
|
15
|
+
const {
|
|
16
|
+
collection,
|
|
17
|
+
defaults,
|
|
18
|
+
instanceId,
|
|
19
|
+
effectiveDbName,
|
|
20
|
+
logger,
|
|
21
|
+
emit,
|
|
22
|
+
mongoSlowLogShaper,
|
|
23
|
+
cache,
|
|
24
|
+
type
|
|
25
|
+
} = context;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* upsert 单个文档(存在则更新,不存在则插入)
|
|
29
|
+
* @param {Object} filter - 查询条件
|
|
30
|
+
* @param {Object} update - 更新内容(直接设置字段,自动包装为 $set)
|
|
31
|
+
* @param {Object} [options={}] - 操作选项
|
|
32
|
+
* @param {number} [options.maxTimeMS] - 操作超时(毫秒)
|
|
33
|
+
* @param {string} [options.comment] - 查询注释
|
|
34
|
+
* @returns {Promise<Object>} 操作结果
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* // 基础用法
|
|
38
|
+
* const result = await collection('users').upsertOne(
|
|
39
|
+
* { userId: 'user123' },
|
|
40
|
+
* { name: 'Alice', email: 'alice@example.com' }
|
|
41
|
+
* );
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* // 带选项
|
|
45
|
+
* const result = await collection('config').upsertOne(
|
|
46
|
+
* { key: 'theme' },
|
|
47
|
+
* { value: 'dark', updatedAt: new Date() },
|
|
48
|
+
* { maxTimeMS: 5000, comment: 'sync-config' }
|
|
49
|
+
* );
|
|
50
|
+
*/
|
|
51
|
+
const upsertOne = async function upsertOne(filter, update, options = {}) {
|
|
52
|
+
const startTime = Date.now();
|
|
53
|
+
|
|
54
|
+
// 1. 参数验证
|
|
55
|
+
if (!filter || typeof filter !== 'object' || Array.isArray(filter)) {
|
|
56
|
+
throw createError(
|
|
57
|
+
ErrorCodes.INVALID_ARGUMENT,
|
|
58
|
+
'filter 必须是非空对象',
|
|
59
|
+
[{ field: 'filter', type: 'type', message: 'filter 必须是对象', received: typeof filter }]
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (!update || typeof update !== 'object' || Array.isArray(update)) {
|
|
64
|
+
throw createError(
|
|
65
|
+
ErrorCodes.INVALID_ARGUMENT,
|
|
66
|
+
'update 必须是非空对象',
|
|
67
|
+
[{ field: 'update', type: 'type', message: 'update 必须是对象', received: typeof update }]
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// 2. 检查 update 是否包含更新操作符
|
|
72
|
+
const hasOperator = Object.keys(update).some(key => key.startsWith('$'));
|
|
73
|
+
|
|
74
|
+
// 如果没有操作符,自动包装为 $set
|
|
75
|
+
const updateDoc = hasOperator ? update : { $set: update };
|
|
76
|
+
|
|
77
|
+
// 3. 构建选项
|
|
78
|
+
const maxTimeMS = options.maxTimeMS !== undefined ? options.maxTimeMS : defaults.maxTimeMS;
|
|
79
|
+
const comment = options.comment;
|
|
80
|
+
|
|
81
|
+
const driverOpts = { upsert: true, maxTimeMS };
|
|
82
|
+
if (comment) driverOpts.comment = comment;
|
|
83
|
+
|
|
84
|
+
// 4. 执行 updateOne 操作
|
|
85
|
+
let result;
|
|
86
|
+
try {
|
|
87
|
+
result = await collection.updateOne(filter, updateDoc, driverOpts);
|
|
88
|
+
} catch (error) {
|
|
89
|
+
// 统一错误处理
|
|
90
|
+
if (error.code === 11000) {
|
|
91
|
+
throw createError(
|
|
92
|
+
ErrorCodes.DUPLICATE_KEY,
|
|
93
|
+
'批量插入失败:违反唯一性约束',
|
|
94
|
+
[{ field: '_id', type: 'unique', message: error.message }],
|
|
95
|
+
error
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
throw error;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// 5. 自动失效缓存
|
|
102
|
+
const upsertedCount = result.matchedCount === 0 ? 1 : 0;
|
|
103
|
+
|
|
104
|
+
if (cache && (result.modifiedCount > 0 || upsertedCount > 0)) {
|
|
105
|
+
try {
|
|
106
|
+
const namespace = `${instanceId}:${type}:${effectiveDbName}:${collection.collectionName}`;
|
|
107
|
+
const pattern = `${namespace}:*`;
|
|
108
|
+
|
|
109
|
+
// 检查是否在事务中
|
|
110
|
+
if (isInTransaction(options)) {
|
|
111
|
+
// 事务中:调用 Transaction 的 recordInvalidation 方法
|
|
112
|
+
const tx = getTransactionFromSession(options.session);
|
|
113
|
+
if (tx && typeof tx.recordInvalidation === 'function') {
|
|
114
|
+
// 🚀 传递 metadata 支持文档级别锁
|
|
115
|
+
await tx.recordInvalidation(pattern, {
|
|
116
|
+
operation: 'write',
|
|
117
|
+
query: filter,
|
|
118
|
+
collection: collection.collectionName
|
|
119
|
+
});
|
|
120
|
+
logger?.debug?.(`[upsertOne] 事务中失效缓存: ${collection.collectionName}`);
|
|
121
|
+
} else {
|
|
122
|
+
const deleted = await cache.delPattern(pattern);
|
|
123
|
+
if (deleted > 0) {
|
|
124
|
+
logger?.debug?.(`[upsertOne] 自动失效缓存: ${collection.collectionName}, 删除 ${deleted} 个缓存键`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
} else {
|
|
128
|
+
// 非事务:直接失效缓存
|
|
129
|
+
const deleted = await cache.delPattern(pattern);
|
|
130
|
+
if (deleted > 0) {
|
|
131
|
+
logger?.debug?.(`[upsertOne] 自动失效缓存: ${collection.collectionName}, 删除 ${deleted} 个缓存键`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
} catch (cacheError) {
|
|
135
|
+
logger?.warn?.('[upsertOne] 缓存失效失败', { error: cacheError.message });
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// 6. 慢查询日志
|
|
140
|
+
const duration = Date.now() - startTime;
|
|
141
|
+
const slowQueryMs = defaults?.slowQueryMs || 1000;
|
|
142
|
+
|
|
143
|
+
if (duration >= slowQueryMs) {
|
|
144
|
+
try {
|
|
145
|
+
const meta = {
|
|
146
|
+
operation: 'upsertOne',
|
|
147
|
+
durationMs: duration,
|
|
148
|
+
iid: instanceId,
|
|
149
|
+
type: type,
|
|
150
|
+
db: effectiveDbName,
|
|
151
|
+
collection: collection.collectionName,
|
|
152
|
+
matchedCount: result.matchedCount,
|
|
153
|
+
modifiedCount: result.modifiedCount,
|
|
154
|
+
upsertedId: result.upsertedId,
|
|
155
|
+
filter: mongoSlowLogShaper?.sanitize ? mongoSlowLogShaper.sanitize(filter) : filter,
|
|
156
|
+
update: mongoSlowLogShaper?.sanitize ? mongoSlowLogShaper.sanitize(updateDoc) : updateDoc,
|
|
157
|
+
comment: comment
|
|
158
|
+
};
|
|
159
|
+
logger?.warn?.('🐌 Slow query: upsertOne', meta);
|
|
160
|
+
emit?.('slow-query', meta);
|
|
161
|
+
} catch (_) {
|
|
162
|
+
// 忽略日志错误
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// 7. 日志记录
|
|
167
|
+
logger?.debug?.('[upsertOne] 操作完成', {
|
|
168
|
+
ns: `${effectiveDbName}.${collection.collectionName}`,
|
|
169
|
+
duration: duration,
|
|
170
|
+
matchedCount: result.matchedCount,
|
|
171
|
+
modifiedCount: result.modifiedCount,
|
|
172
|
+
upsertedId: result.upsertedId === null ? undefined : result.upsertedId,
|
|
173
|
+
upsertedCount: upsertedCount
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
// 8. 返回结果
|
|
177
|
+
return {
|
|
178
|
+
acknowledged: result.acknowledged,
|
|
179
|
+
matchedCount: result.matchedCount,
|
|
180
|
+
modifiedCount: result.modifiedCount,
|
|
181
|
+
upsertedId: result.upsertedId === null ? undefined : result.upsertedId,
|
|
182
|
+
upsertedCount: upsertedCount
|
|
183
|
+
};
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
return { upsertOne };
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
module.exports = { createUpsertOneOps };
|
|
190
|
+
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MultiLevelCache: 组合本地与远端缓存,实现 CacheLike 接口
|
|
3
|
+
* 设计要点:
|
|
4
|
+
* - 读:本地优先;本地未命中查远端,命中则异步回填本地;远端失败需优雅降级
|
|
5
|
+
* - 写:默认本地+远端双写;可配置本地优先、远端异步
|
|
6
|
+
* - 删:本地删除;远端删除尽力而为;delPattern 默认仅本地执行并返回删除数(重型场景交由远端广播/索引)
|
|
7
|
+
* - keys/clear:仅作用于本地层(避免误伤远端)
|
|
8
|
+
* - TTL 单位毫秒,与上层保持一致
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
class MultiLevelCache {
|
|
12
|
+
/**
|
|
13
|
+
* @param {Object} options
|
|
14
|
+
* @param {Object} options.local - 本地 CacheLike
|
|
15
|
+
* @param {Object} options.remote - 远端 CacheLike(可选)
|
|
16
|
+
* @param {Object} [options.policy]
|
|
17
|
+
* @param {'both'|'local-first-async-remote'} [options.policy.writePolicy='both']
|
|
18
|
+
* @param {boolean} [options.policy.backfillLocalOnRemoteHit=true]
|
|
19
|
+
* @param {number} [options.remoteTimeoutMs=50] - 远端单次操作超时
|
|
20
|
+
* @param {(msg:object)=>void} [options.publish] - 可选:失效广播发布器
|
|
21
|
+
*/
|
|
22
|
+
constructor(options = {}) {
|
|
23
|
+
const { local, remote, policy = {}, remoteTimeoutMs = 50, publish } = options;
|
|
24
|
+
if (!local || typeof local.get !== 'function') {
|
|
25
|
+
throw new Error('MultiLevelCache requires a valid local cache');
|
|
26
|
+
}
|
|
27
|
+
this.local = local;
|
|
28
|
+
this.remote = remote;
|
|
29
|
+
this.policy = {
|
|
30
|
+
writePolicy: policy.writePolicy || 'both',
|
|
31
|
+
backfillLocalOnRemoteHit: policy.backfillLocalOnRemoteHit !== false,
|
|
32
|
+
};
|
|
33
|
+
this.remoteTimeoutMs = Number(remoteTimeoutMs) || 50;
|
|
34
|
+
this.publish = typeof publish === 'function' ? publish : null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* 动态设置 publish 回调(用于分布式缓存失效)
|
|
39
|
+
* @param {Function} publishFn - 发布函数,接收 { type, pattern, ts } 对象
|
|
40
|
+
*/
|
|
41
|
+
setPublish(publishFn) {
|
|
42
|
+
if (typeof publishFn === 'function') {
|
|
43
|
+
this.publish = publishFn;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* 设置锁管理器(透传到本地缓存)
|
|
49
|
+
* @param {Object} lockManager
|
|
50
|
+
*/
|
|
51
|
+
setLockManager(lockManager) {
|
|
52
|
+
if (this.local && typeof this.local.setLockManager === 'function') {
|
|
53
|
+
this.local.setLockManager(lockManager);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// 工具:为远端操作加超时与降级
|
|
58
|
+
async _withTimeout(promise) {
|
|
59
|
+
if (!this.remote) return Promise.reject(new Error('NO_REMOTE'));
|
|
60
|
+
let timer;
|
|
61
|
+
const t = new Promise((_, reject) => {
|
|
62
|
+
timer = setTimeout(() => reject(new Error('REMOTE_TIMEOUT')), this.remoteTimeoutMs);
|
|
63
|
+
if (timer && typeof timer.unref === 'function') try { timer.unref(); } catch(_) {}
|
|
64
|
+
});
|
|
65
|
+
try {
|
|
66
|
+
const val = await Promise.race([promise, t]);
|
|
67
|
+
return val;
|
|
68
|
+
} finally {
|
|
69
|
+
clearTimeout(timer);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async get(key) {
|
|
74
|
+
const v = await this.local.get(key);
|
|
75
|
+
if (v !== undefined) return v;
|
|
76
|
+
try {
|
|
77
|
+
const r = await this._withTimeout(this.remote.get(key));
|
|
78
|
+
if (r !== undefined && this.policy.backfillLocalOnRemoteHit) {
|
|
79
|
+
// 无剩余 TTL 信息,采用一个保守的回填策略:原 TTL 由上层 set 决定;这里回填一个短 TTL(例如 50ms)不合适
|
|
80
|
+
// 因为我们无法得知原始 TTL,这里直接不设置 TTL(等同于本地不持久)。但为了可用性,设置 50% 的常用短 TTL可配置。
|
|
81
|
+
// 为保持简单,我们不传 TTL(由本地实现解释,等价于无过期)。
|
|
82
|
+
// 若调用方强依赖 TTL 严格一致性,应在远端适配器中扩展 getWithTTL。
|
|
83
|
+
try { await this.local.set(key, r); } catch(_) {}
|
|
84
|
+
}
|
|
85
|
+
return r;
|
|
86
|
+
} catch(_) {
|
|
87
|
+
return undefined; // 远端异常降级
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async set(key, val, ttl = 0) {
|
|
92
|
+
await this.local.set(key, val, ttl);
|
|
93
|
+
if (!this.remote) return;
|
|
94
|
+
if (this.policy.writePolicy === 'both') {
|
|
95
|
+
try { await this._withTimeout(this.remote.set(key, val, ttl)); } catch(_) {}
|
|
96
|
+
} else {
|
|
97
|
+
// local-first-async-remote
|
|
98
|
+
this._withTimeout(this.remote.set(key, val, ttl)).catch(() => {});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async del(key) {
|
|
103
|
+
let a = false, b = false;
|
|
104
|
+
try { a = await this.local.del(key); } catch(_) {}
|
|
105
|
+
if (this.remote) {
|
|
106
|
+
try { b = await this._withTimeout(this.remote.del(key)).then(Boolean).catch(() => false); } catch(_) {}
|
|
107
|
+
}
|
|
108
|
+
return !!(a || b);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async exists(key) {
|
|
112
|
+
const a = await this.local.exists(key);
|
|
113
|
+
if (a) return true;
|
|
114
|
+
if (!this.remote) return false;
|
|
115
|
+
try { return !!(await this._withTimeout(this.remote.exists(key))); } catch(_) { return false; }
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async getMany(keys) {
|
|
119
|
+
const out = {};
|
|
120
|
+
// 先批量从本地取
|
|
121
|
+
const localRes = await this.local.getMany(keys);
|
|
122
|
+
const misses = [];
|
|
123
|
+
for (const k of keys) {
|
|
124
|
+
const v = localRes[k];
|
|
125
|
+
if (v !== undefined) out[k] = v; else misses.push(k);
|
|
126
|
+
}
|
|
127
|
+
if (!misses.length || !this.remote) return out;
|
|
128
|
+
try {
|
|
129
|
+
const remoteRes = await this._withTimeout(this.remote.getMany(misses));
|
|
130
|
+
if (remoteRes && typeof remoteRes === 'object') {
|
|
131
|
+
// 回填本地(异步)
|
|
132
|
+
if (this.policy.backfillLocalOnRemoteHit) this.local.setMany(remoteRes).catch(() => {});
|
|
133
|
+
for (const k of misses) { if (remoteRes[k] !== undefined) out[k] = remoteRes[k]; }
|
|
134
|
+
}
|
|
135
|
+
} catch(_) { /* 降级 */ }
|
|
136
|
+
return out;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async setMany(obj, ttl = 0) {
|
|
140
|
+
await this.local.setMany(obj, ttl);
|
|
141
|
+
if (!this.remote) return true;
|
|
142
|
+
if (this.policy.writePolicy === 'both') {
|
|
143
|
+
try { await this._withTimeout(this.remote.setMany(obj, ttl)); } catch(_) {}
|
|
144
|
+
} else {
|
|
145
|
+
this._withTimeout(this.remote.setMany(obj, ttl)).catch(() => {});
|
|
146
|
+
}
|
|
147
|
+
return true;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async delMany(keys) {
|
|
151
|
+
let n = 0;
|
|
152
|
+
try { n += await this.local.delMany(keys); } catch(_) {}
|
|
153
|
+
if (this.remote) {
|
|
154
|
+
try { await this._withTimeout(this.remote.delMany(keys)); } catch(_) {}
|
|
155
|
+
}
|
|
156
|
+
return n;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async delPattern(pattern) {
|
|
160
|
+
const deleted = await this.local.delPattern(pattern);
|
|
161
|
+
// 向集群广播(可选)
|
|
162
|
+
try { if (this.publish) this.publish({ type: 'invalidate', pattern, ts: Date.now() }); } catch(_) {}
|
|
163
|
+
// 默认不在远端做大规模扫描,交由 TTL 自然过期或使用远端的 tag 机制(未来可扩展)
|
|
164
|
+
return deleted;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
clear() {
|
|
168
|
+
// 仅清理本地
|
|
169
|
+
try { this.local.clear(); } catch(_) {}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
keys(pattern = '*') {
|
|
173
|
+
try { return this.local.keys(pattern); } catch(_) { return []; }
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
getStats() {
|
|
177
|
+
const local = this.local.getStats ? this.local.getStats() : null;
|
|
178
|
+
const remote = this.remote && this.remote.getStats ? this.remote.getStats() : null;
|
|
179
|
+
const hits = (local?.hits || 0) + (remote?.hits || 0);
|
|
180
|
+
const misses = (local?.misses || 0) + (remote?.misses || 0);
|
|
181
|
+
return {
|
|
182
|
+
local,
|
|
183
|
+
remote,
|
|
184
|
+
hitRateApprox: hits / (hits + misses) || 0,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
module.exports = MultiLevelCache;
|