monsqlize 1.3.0 → 2.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 +56 -60
- package/LICENSE +201 -21
- package/README.md +537 -1828
- package/changelogs/README.md +160 -0
- package/changelogs/v2.0.0.md +222 -0
- package/dist/cjs/index.cjs +10600 -0
- package/dist/cjs/mongodb/common/transaction-aware.cjs +10 -0
- package/dist/cjs/transaction/CacheLockManager.cjs +100 -0
- package/dist/cjs/transaction/Transaction.cjs +158 -0
- package/dist/cjs/transaction/TransactionManager.cjs +298 -0
- package/dist/esm/index.mjs +10650 -0
- package/dist/types/base.d.ts +81 -0
- package/dist/types/collection.d.ts +1031 -0
- package/dist/types/expression.d.ts +115 -0
- package/dist/types/index.d.ts +23 -0
- package/dist/types/lock.d.ts +74 -0
- package/dist/types/model.d.ts +526 -0
- package/dist/types/mongodb.d.ts +49 -0
- package/dist/types/monsqlize.d.ts +491 -0
- package/dist/types/pool.d.ts +84 -0
- package/dist/types/runtime.d.ts +362 -0
- package/dist/types/saga.d.ts +143 -0
- package/dist/types/slow-query-log.d.ts +126 -0
- package/dist/types/sync.d.ts +103 -0
- package/dist/types/transaction.d.ts +132 -0
- package/package.json +67 -69
- package/index.d.ts +0 -206
- package/index.mjs +0 -52
- package/lib/cache-invalidation.js +0 -279
- package/lib/cache.js +0 -530
- package/lib/common/cursor.js +0 -59
- package/lib/common/docs-urls.js +0 -73
- package/lib/common/index-options.js +0 -223
- package/lib/common/log.js +0 -61
- package/lib/common/namespace.js +0 -22
- package/lib/common/normalize.js +0 -34
- package/lib/common/page-result.js +0 -43
- package/lib/common/runner.js +0 -57
- package/lib/common/server-features.js +0 -232
- package/lib/common/shape-builders.js +0 -27
- package/lib/common/validation.js +0 -113
- package/lib/connect.js +0 -99
- package/lib/constants.js +0 -55
- package/lib/count-queue.js +0 -188
- package/lib/distributed-cache-invalidator.js +0 -260
- package/lib/errors.js +0 -168
- package/lib/expression/cache/ExpressionCache.js +0 -114
- package/lib/expression/compiler/ExpressionCompiler.js +0 -1090
- package/lib/expression/compiler/ExpressionCompilerExtensions.js +0 -531
- package/lib/expression/detector.js +0 -84
- package/lib/expression/factory.js +0 -29
- package/lib/expression/index.js +0 -19
- package/lib/function-cache.js +0 -533
- package/lib/index.js +0 -1251
- package/lib/infrastructure/ConnectionPoolManager.js +0 -464
- package/lib/infrastructure/HealthChecker.js +0 -281
- package/lib/infrastructure/PoolConfig.js +0 -199
- package/lib/infrastructure/PoolSelector.js +0 -225
- package/lib/infrastructure/PoolStats.js +0 -244
- package/lib/infrastructure/ssh-tunnel-ssh2.js +0 -212
- package/lib/infrastructure/ssh-tunnel.js +0 -41
- package/lib/infrastructure/uri-parser.js +0 -36
- package/lib/lock/Lock.js +0 -67
- package/lib/lock/errors.js +0 -28
- package/lib/lock/index.js +0 -13
- package/lib/logger.js +0 -225
- package/lib/model/examples/test.js +0 -311
- package/lib/model/features/defaults.js +0 -161
- package/lib/model/features/populate.js +0 -568
- package/lib/model/features/relations.js +0 -120
- package/lib/model/features/soft-delete.js +0 -349
- package/lib/model/features/version.js +0 -157
- package/lib/model/features/virtuals.js +0 -219
- package/lib/model/index.js +0 -1265
- package/lib/mongodb/common/accessor-helpers.js +0 -59
- package/lib/mongodb/common/agg-pipeline.js +0 -36
- package/lib/mongodb/common/aggregation-validator.js +0 -127
- package/lib/mongodb/common/iid.js +0 -28
- package/lib/mongodb/common/lexicographic-expr.js +0 -53
- package/lib/mongodb/common/shape.js +0 -32
- package/lib/mongodb/common/sort.js +0 -39
- package/lib/mongodb/common/transaction-aware.js +0 -25
- package/lib/mongodb/connect.js +0 -234
- package/lib/mongodb/index.js +0 -639
- package/lib/mongodb/management/admin-ops.js +0 -200
- package/lib/mongodb/management/bookmark-ops.js +0 -167
- package/lib/mongodb/management/cache-ops.js +0 -50
- package/lib/mongodb/management/collection-ops.js +0 -387
- package/lib/mongodb/management/database-ops.js +0 -202
- package/lib/mongodb/management/index-ops.js +0 -475
- package/lib/mongodb/management/index.js +0 -17
- package/lib/mongodb/management/namespace.js +0 -31
- package/lib/mongodb/management/validation-ops.js +0 -268
- package/lib/mongodb/queries/aggregate.js +0 -172
- package/lib/mongodb/queries/chain.js +0 -631
- package/lib/mongodb/queries/count.js +0 -99
- package/lib/mongodb/queries/distinct.js +0 -78
- package/lib/mongodb/queries/find-and-count.js +0 -193
- package/lib/mongodb/queries/find-by-ids.js +0 -236
- package/lib/mongodb/queries/find-one-by-id.js +0 -171
- package/lib/mongodb/queries/find-one.js +0 -71
- package/lib/mongodb/queries/find-page.js +0 -618
- package/lib/mongodb/queries/find.js +0 -171
- package/lib/mongodb/queries/index.js +0 -51
- package/lib/mongodb/queries/watch.js +0 -538
- package/lib/mongodb/writes/common/batch-retry.js +0 -65
- package/lib/mongodb/writes/delete-batch.js +0 -323
- package/lib/mongodb/writes/delete-many.js +0 -181
- package/lib/mongodb/writes/delete-one.js +0 -173
- package/lib/mongodb/writes/find-one-and-delete.js +0 -203
- package/lib/mongodb/writes/find-one-and-replace.js +0 -239
- package/lib/mongodb/writes/find-one-and-update.js +0 -240
- package/lib/mongodb/writes/increment-one.js +0 -259
- package/lib/mongodb/writes/index.js +0 -46
- package/lib/mongodb/writes/insert-batch.js +0 -508
- package/lib/mongodb/writes/insert-many.js +0 -223
- package/lib/mongodb/writes/insert-one.js +0 -169
- package/lib/mongodb/writes/replace-one.js +0 -226
- package/lib/mongodb/writes/result-handler.js +0 -237
- package/lib/mongodb/writes/update-batch.js +0 -416
- package/lib/mongodb/writes/update-many.js +0 -275
- package/lib/mongodb/writes/update-one.js +0 -273
- package/lib/mongodb/writes/upsert-one.js +0 -203
- package/lib/multi-level-cache.js +0 -244
- package/lib/operators.js +0 -330
- package/lib/redis-cache-adapter.js +0 -267
- package/lib/saga/SagaContext.js +0 -67
- package/lib/saga/SagaDefinition.js +0 -32
- package/lib/saga/SagaExecutor.js +0 -201
- package/lib/saga/SagaOrchestrator.js +0 -186
- package/lib/saga/index.js +0 -11
- package/lib/slow-query-log/base-storage.js +0 -70
- package/lib/slow-query-log/batch-queue.js +0 -97
- package/lib/slow-query-log/config-manager.js +0 -196
- package/lib/slow-query-log/index.js +0 -238
- package/lib/slow-query-log/mongodb-storage.js +0 -324
- package/lib/slow-query-log/query-hash.js +0 -39
- package/lib/sync/ChangeStreamSyncManager.js +0 -405
- package/lib/sync/ResumeTokenStore.js +0 -192
- package/lib/sync/SyncConfig.js +0 -127
- package/lib/sync/SyncTarget.js +0 -240
- package/lib/sync/index.js +0 -20
- package/lib/transaction/CacheLockManager.js +0 -162
- package/lib/transaction/DistributedCacheLockManager.js +0 -475
- package/lib/transaction/Transaction.js +0 -315
- package/lib/transaction/TransactionManager.js +0 -267
- package/lib/transaction/index.js +0 -11
- package/lib/utils/objectid-converter.js +0 -632
- package/types/README.md +0 -122
- package/types/base.ts +0 -94
- package/types/batch.ts +0 -187
- package/types/cache.ts +0 -71
- package/types/chain.ts +0 -254
- package/types/collection.ts +0 -357
- package/types/expression.ts +0 -109
- package/types/function-cache.d.ts +0 -135
- package/types/lock.ts +0 -95
- package/types/model/definition.ts +0 -152
- package/types/model/index.ts +0 -10
- package/types/model/instance.ts +0 -121
- package/types/model/relations.ts +0 -121
- package/types/model/virtuals.ts +0 -32
- package/types/monsqlize.ts +0 -245
- package/types/options.ts +0 -192
- package/types/pagination.ts +0 -154
- package/types/pool.ts +0 -125
- package/types/query.ts +0 -71
- package/types/saga.ts +0 -125
- package/types/stream.ts +0 -64
- package/types/sync.ts +0 -79
- package/types/transaction.ts +0 -79
- package/types/write.ts +0 -77
package/lib/function-cache.js
DELETED
|
@@ -1,533 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 通用函数缓存装饰器
|
|
3
|
-
*
|
|
4
|
-
* 🆕 v1.1.4: 新增函数缓存功能
|
|
5
|
-
* - 缓存任意异步函数的返回结果
|
|
6
|
-
* - 支持 TTL 过期
|
|
7
|
-
* - 支持自定义键生成
|
|
8
|
-
* - 支持命名空间隔离
|
|
9
|
-
* - 复用 monSQLize 缓存基础设施
|
|
10
|
-
*
|
|
11
|
-
* @module lib/function-cache
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
const CacheFactory = require('./cache');
|
|
15
|
-
const crypto = require('crypto');
|
|
16
|
-
|
|
17
|
-
// 并发去重映射(防止缓存击穿)
|
|
18
|
-
// 使用 Map 存储正在执行的 Promise,带超时清理机制防止内存泄漏
|
|
19
|
-
const __inflightFunctions = new Map();
|
|
20
|
-
|
|
21
|
-
// 缓存未命中的特殊标记(使用 Symbol 确保唯一性)
|
|
22
|
-
const CACHE_MISS = Symbol('CACHE_MISS');
|
|
23
|
-
|
|
24
|
-
// 并发请求清理超时时间(毫秒)
|
|
25
|
-
const INFLIGHT_CLEANUP_TIMEOUT_MS = 300000; // 5分钟
|
|
26
|
-
|
|
27
|
-
// 🔧 v1.1.6: 全局 Map 大小限制和监控
|
|
28
|
-
const MAX_INFLIGHT_SIZE = 10000;
|
|
29
|
-
|
|
30
|
-
// 定期监控和清理机制
|
|
31
|
-
const monitorInterval = setInterval(() => {
|
|
32
|
-
const currentSize = __inflightFunctions.size;
|
|
33
|
-
|
|
34
|
-
if (currentSize > MAX_INFLIGHT_SIZE) {
|
|
35
|
-
console.warn(`[FunctionCache] InflightMap size exceeded: ${currentSize}/${MAX_INFLIGHT_SIZE}`);
|
|
36
|
-
|
|
37
|
-
// 清理最旧的 10% 条目
|
|
38
|
-
const toDelete = Math.floor(currentSize * 0.1);
|
|
39
|
-
let count = 0;
|
|
40
|
-
for (const key of __inflightFunctions.keys()) {
|
|
41
|
-
if (count++ >= toDelete) break;
|
|
42
|
-
__inflightFunctions.delete(key);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
console.warn(`[FunctionCache] Cleaned ${toDelete} entries, new size: ${__inflightFunctions.size}`);
|
|
46
|
-
}
|
|
47
|
-
}, 60000); // 每分钟检查一次
|
|
48
|
-
|
|
49
|
-
// 防止阻止进程退出
|
|
50
|
-
monitorInterval.unref();
|
|
51
|
-
|
|
52
|
-
// 缓存键最大长度(字节)
|
|
53
|
-
const MAX_CACHE_KEY_LENGTH = 1024;
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* 基础装饰器:为函数添加缓存能力
|
|
57
|
-
*
|
|
58
|
-
* @param {Function} fn - 要缓存的异步函数
|
|
59
|
-
* @param {Object} options - 缓存配置
|
|
60
|
-
* @param {number} [options.ttl=60000] - 缓存时间(毫秒)
|
|
61
|
-
* @param {Function} [options.keyBuilder] - 自定义键生成函数
|
|
62
|
-
* @param {Object} [options.cache] - 缓存实例(可选)
|
|
63
|
-
* @param {string} [options.namespace='fn'] - 命名空间
|
|
64
|
-
* @param {Function} [options.condition] - 条件缓存函数
|
|
65
|
-
* @param {boolean} [options.enableStats=true] - 启用统计
|
|
66
|
-
* @returns {Function} 包装后的函数
|
|
67
|
-
*
|
|
68
|
-
* @example
|
|
69
|
-
* // 基础用法
|
|
70
|
-
* const cachedFn = withCache(originalFn, { ttl: 60000 });
|
|
71
|
-
* const result = await cachedFn('arg1', 'arg2');
|
|
72
|
-
*
|
|
73
|
-
* // 自定义键生成
|
|
74
|
-
* const cachedFn = withCache(originalFn, {
|
|
75
|
-
* ttl: 300000,
|
|
76
|
-
* keyBuilder: (userId) => `user:${userId}`
|
|
77
|
-
* });
|
|
78
|
-
*
|
|
79
|
-
* // 条件缓存(只缓存非空结果)
|
|
80
|
-
* const cachedFn = withCache(originalFn, {
|
|
81
|
-
* ttl: 60000,
|
|
82
|
-
* condition: (result) => result && result.length > 0
|
|
83
|
-
* });
|
|
84
|
-
*/
|
|
85
|
-
function withCache(fn, options = {}) {
|
|
86
|
-
const {
|
|
87
|
-
ttl = 60000,
|
|
88
|
-
keyBuilder,
|
|
89
|
-
cache,
|
|
90
|
-
namespace = 'fn',
|
|
91
|
-
condition,
|
|
92
|
-
enableStats = true
|
|
93
|
-
} = options;
|
|
94
|
-
|
|
95
|
-
// 参数验证
|
|
96
|
-
if (typeof fn !== 'function') {
|
|
97
|
-
throw new Error('fn must be a function');
|
|
98
|
-
}
|
|
99
|
-
if (ttl !== undefined && (typeof ttl !== 'number' || ttl < 0)) {
|
|
100
|
-
throw new Error('ttl must be a non-negative number');
|
|
101
|
-
}
|
|
102
|
-
if (keyBuilder !== undefined && typeof keyBuilder !== 'function') {
|
|
103
|
-
throw new Error('keyBuilder must be a function');
|
|
104
|
-
}
|
|
105
|
-
if (condition !== undefined && typeof condition !== 'function') {
|
|
106
|
-
throw new Error('condition must be a function');
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// 使用全局缓存或自定义缓存
|
|
110
|
-
const cacheInstance = cache || CacheFactory.createDefault();
|
|
111
|
-
|
|
112
|
-
// 验证缓存实例
|
|
113
|
-
if (!CacheFactory.isValidCache(cacheInstance)) {
|
|
114
|
-
throw new Error('Invalid cache instance: must implement CacheLike interface');
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// 统计信息
|
|
118
|
-
// 注意:在极高并发场景下,统计可能不完全准确(这是性能与精确度的权衡)
|
|
119
|
-
const stats = {
|
|
120
|
-
hits: 0,
|
|
121
|
-
misses: 0,
|
|
122
|
-
errors: 0,
|
|
123
|
-
totalTime: 0,
|
|
124
|
-
calls: 0
|
|
125
|
-
};
|
|
126
|
-
|
|
127
|
-
// 返回包装后的函数
|
|
128
|
-
const wrappedFn = async function(...args) {
|
|
129
|
-
// 1. 生成缓存键
|
|
130
|
-
let cacheKey;
|
|
131
|
-
try {
|
|
132
|
-
const baseKey = keyBuilder
|
|
133
|
-
? `${namespace}:${keyBuilder(...args)}`
|
|
134
|
-
: `${namespace}:${fn.name}:${CacheFactory.stableStringify(args)}`;
|
|
135
|
-
|
|
136
|
-
// 🔧 v1.1.6: 限制缓存键大小
|
|
137
|
-
if (baseKey.length > MAX_CACHE_KEY_LENGTH) {
|
|
138
|
-
const hash = crypto.createHash('sha256').update(baseKey).digest('hex');
|
|
139
|
-
cacheKey = `${namespace}:${fn.name}:hash:${hash}`;
|
|
140
|
-
} else {
|
|
141
|
-
cacheKey = baseKey;
|
|
142
|
-
}
|
|
143
|
-
} catch (err) {
|
|
144
|
-
// 键生成失败,直接执行原函数
|
|
145
|
-
if (enableStats) stats.errors++;
|
|
146
|
-
return await fn.apply(this, args);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// 2. 尝试从缓存读取
|
|
150
|
-
const startTime = Date.now();
|
|
151
|
-
let cached = CACHE_MISS;
|
|
152
|
-
try {
|
|
153
|
-
// 优化:使用特殊标记来区分"缓存未命中"和"缓存值是 undefined"
|
|
154
|
-
const value = await cacheInstance.get(cacheKey);
|
|
155
|
-
|
|
156
|
-
// 如果缓存返回 undefined,需要确认是否真的不存在
|
|
157
|
-
if (value === undefined) {
|
|
158
|
-
// 只在返回 undefined 时才调用 exists 检查
|
|
159
|
-
const exists = await cacheInstance.exists(cacheKey);
|
|
160
|
-
if (exists) {
|
|
161
|
-
cached = undefined; // 缓存的值就是 undefined
|
|
162
|
-
}
|
|
163
|
-
// 如果 exists 返回 false,cached 保持 CACHE_MISS
|
|
164
|
-
} else {
|
|
165
|
-
// 非 undefined 值,直接使用
|
|
166
|
-
cached = value;
|
|
167
|
-
}
|
|
168
|
-
} catch (err) {
|
|
169
|
-
if (enableStats) stats.errors++;
|
|
170
|
-
// 🔧 v1.1.6: 添加错误日志
|
|
171
|
-
console.warn('[FunctionCache] Cache get failed:', {
|
|
172
|
-
key: cacheKey.substring(0, 100), // 截断避免日志过长
|
|
173
|
-
error: err.message
|
|
174
|
-
});
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
if (cached !== CACHE_MISS) {
|
|
178
|
-
if (enableStats) {
|
|
179
|
-
stats.hits++;
|
|
180
|
-
stats.calls++;
|
|
181
|
-
stats.totalTime += Date.now() - startTime;
|
|
182
|
-
}
|
|
183
|
-
return cached;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// 3. 并发控制(防止缓存击穿)
|
|
187
|
-
if (__inflightFunctions.has(cacheKey)) {
|
|
188
|
-
try {
|
|
189
|
-
const result = await __inflightFunctions.get(cacheKey);
|
|
190
|
-
if (enableStats) {
|
|
191
|
-
stats.hits++;
|
|
192
|
-
stats.calls++;
|
|
193
|
-
}
|
|
194
|
-
return result;
|
|
195
|
-
} catch (err) {
|
|
196
|
-
// 并发请求失败,继续执行
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
// 4. 执行原函数
|
|
201
|
-
const promise = (async () => {
|
|
202
|
-
try {
|
|
203
|
-
const result = await fn.apply(this, args);
|
|
204
|
-
|
|
205
|
-
// 5. 条件缓存
|
|
206
|
-
let shouldCache = true;
|
|
207
|
-
if (condition) {
|
|
208
|
-
try {
|
|
209
|
-
shouldCache = condition(result);
|
|
210
|
-
} catch (err) {
|
|
211
|
-
// 条件函数失败,默认缓存
|
|
212
|
-
if (enableStats) stats.errors++;
|
|
213
|
-
shouldCache = true;
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
if (shouldCache) {
|
|
218
|
-
try {
|
|
219
|
-
await cacheInstance.set(cacheKey, result, ttl);
|
|
220
|
-
} catch (err) {
|
|
221
|
-
if (enableStats) stats.errors++;
|
|
222
|
-
// 🔧 v1.1.6: 添加错误日志
|
|
223
|
-
console.warn('[FunctionCache] Cache set failed:', {
|
|
224
|
-
key: cacheKey.substring(0, 100),
|
|
225
|
-
error: err.message
|
|
226
|
-
});
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
return result;
|
|
231
|
-
} finally {
|
|
232
|
-
__inflightFunctions.delete(cacheKey);
|
|
233
|
-
}
|
|
234
|
-
})();
|
|
235
|
-
|
|
236
|
-
__inflightFunctions.set(cacheKey, promise);
|
|
237
|
-
|
|
238
|
-
// 🔧 v1.1.4-hotfix: 超时清理机制防止内存泄漏
|
|
239
|
-
// 如果 Promise 长时间未完成,自动清理
|
|
240
|
-
const cleanupTimeout = setTimeout(() => {
|
|
241
|
-
__inflightFunctions.delete(cacheKey);
|
|
242
|
-
}, INFLIGHT_CLEANUP_TIMEOUT_MS);
|
|
243
|
-
|
|
244
|
-
// Promise 完成后清除定时器
|
|
245
|
-
promise.finally(() => {
|
|
246
|
-
clearTimeout(cleanupTimeout);
|
|
247
|
-
});
|
|
248
|
-
|
|
249
|
-
try {
|
|
250
|
-
const result = await promise;
|
|
251
|
-
if (enableStats) {
|
|
252
|
-
stats.misses++;
|
|
253
|
-
stats.calls++;
|
|
254
|
-
stats.totalTime += Date.now() - startTime;
|
|
255
|
-
}
|
|
256
|
-
return result;
|
|
257
|
-
} catch (err) {
|
|
258
|
-
if (enableStats) {
|
|
259
|
-
stats.errors++;
|
|
260
|
-
stats.calls++;
|
|
261
|
-
}
|
|
262
|
-
throw err;
|
|
263
|
-
}
|
|
264
|
-
};
|
|
265
|
-
|
|
266
|
-
// 挂载统计方法
|
|
267
|
-
wrappedFn.getCacheStats = () => ({
|
|
268
|
-
...stats,
|
|
269
|
-
hitRate: stats.hits / (stats.hits + stats.misses) || 0,
|
|
270
|
-
avgTime: stats.totalTime / stats.calls || 0
|
|
271
|
-
});
|
|
272
|
-
|
|
273
|
-
return wrappedFn;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
/**
|
|
277
|
-
* 函数缓存管理类
|
|
278
|
-
*
|
|
279
|
-
* @class FunctionCache
|
|
280
|
-
*
|
|
281
|
-
* @example
|
|
282
|
-
* const fnCache = new FunctionCache(msq);
|
|
283
|
-
*
|
|
284
|
-
* // 注册函数
|
|
285
|
-
* fnCache.register('getUserProfile', getUserProfileFn, { ttl: 300000 });
|
|
286
|
-
*
|
|
287
|
-
* // 执行函数
|
|
288
|
-
* const profile = await fnCache.execute('getUserProfile', 'user123');
|
|
289
|
-
*
|
|
290
|
-
* // 失效缓存
|
|
291
|
-
* await fnCache.invalidate('getUserProfile', 'user123');
|
|
292
|
-
*
|
|
293
|
-
* // 查看统计
|
|
294
|
-
* const stats = fnCache.getStats('getUserProfile');
|
|
295
|
-
*/
|
|
296
|
-
class FunctionCache {
|
|
297
|
-
/**
|
|
298
|
-
* 构造函数
|
|
299
|
-
* @param {Object} msq - MonSQLize 实例(可选)
|
|
300
|
-
* @param {Object} options - 配置选项
|
|
301
|
-
* @param {string} [options.namespace='action'] - 命名空间
|
|
302
|
-
* @param {number} [options.defaultTTL=60000] - 默认 TTL(毫秒)
|
|
303
|
-
* @param {boolean} [options.enableStats=true] - 启用统计
|
|
304
|
-
*/
|
|
305
|
-
constructor(msq, options = {}) {
|
|
306
|
-
// 参数验证
|
|
307
|
-
if (options && typeof options !== 'object') {
|
|
308
|
-
throw new Error('options must be an object');
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
this.cache = msq ? msq.getCache() : CacheFactory.createDefault();
|
|
312
|
-
|
|
313
|
-
// 验证缓存实例
|
|
314
|
-
if (!CacheFactory.isValidCache(this.cache)) {
|
|
315
|
-
throw new Error('Invalid cache instance from MonSQLize');
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
this.functions = new Map();
|
|
319
|
-
this.stats = new Map();
|
|
320
|
-
this.options = {
|
|
321
|
-
namespace: options.namespace || 'action',
|
|
322
|
-
defaultTTL: options.defaultTTL || 60000,
|
|
323
|
-
enableStats: options.enableStats !== false
|
|
324
|
-
};
|
|
325
|
-
|
|
326
|
-
// 参数验证
|
|
327
|
-
if (typeof this.options.namespace !== 'string') {
|
|
328
|
-
throw new Error('namespace must be a string');
|
|
329
|
-
}
|
|
330
|
-
if (typeof this.options.defaultTTL !== 'number' || this.options.defaultTTL < 0) {
|
|
331
|
-
throw new Error('defaultTTL must be a non-negative number');
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
/**
|
|
336
|
-
* 注册函数
|
|
337
|
-
* @param {string} name - 函数名称
|
|
338
|
-
* @param {Function} fn - 函数实现
|
|
339
|
-
* @param {Object} options - 缓存配置
|
|
340
|
-
* @param {number} [options.ttl] - 缓存过期时间(毫秒)
|
|
341
|
-
* @param {Function} [options.keyBuilder] - 自定义键生成函数
|
|
342
|
-
* @param {Function} [options.condition] - 条件缓存函数
|
|
343
|
-
*/
|
|
344
|
-
async register(name, fn, options = {}) {
|
|
345
|
-
if (!name || typeof name !== 'string') {
|
|
346
|
-
throw new Error('Function name must be a non-empty string');
|
|
347
|
-
}
|
|
348
|
-
if (typeof fn !== 'function') {
|
|
349
|
-
throw new Error('fn must be a function');
|
|
350
|
-
}
|
|
351
|
-
if (options && typeof options !== 'object') {
|
|
352
|
-
throw new Error('options must be an object');
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
const cachedFn = withCache(fn, {
|
|
356
|
-
...options,
|
|
357
|
-
cache: this.cache,
|
|
358
|
-
namespace: `${this.options.namespace}:${name}`,
|
|
359
|
-
ttl: options.ttl !== undefined ? options.ttl : this.options.defaultTTL
|
|
360
|
-
});
|
|
361
|
-
|
|
362
|
-
this.functions.set(name, cachedFn);
|
|
363
|
-
|
|
364
|
-
if (this.options.enableStats) {
|
|
365
|
-
this.stats.set(name, {
|
|
366
|
-
hits: 0,
|
|
367
|
-
misses: 0,
|
|
368
|
-
errors: 0,
|
|
369
|
-
calls: 0,
|
|
370
|
-
totalTime: 0
|
|
371
|
-
});
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
/**
|
|
377
|
-
* 执行函数
|
|
378
|
-
* @param {string} name - 函数名称
|
|
379
|
-
* @param {...any} args - 函数参数
|
|
380
|
-
* @returns {Promise<any>}
|
|
381
|
-
*/
|
|
382
|
-
async execute(name, ...args) {
|
|
383
|
-
const fn = this.functions.get(name);
|
|
384
|
-
if (!fn) {
|
|
385
|
-
throw new Error(`Function '${name}' not registered`);
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
const startTime = Date.now();
|
|
389
|
-
try {
|
|
390
|
-
const result = await fn(...args);
|
|
391
|
-
|
|
392
|
-
if (this.options.enableStats) {
|
|
393
|
-
const stats = this.stats.get(name);
|
|
394
|
-
if (stats) {
|
|
395
|
-
stats.calls++;
|
|
396
|
-
stats.totalTime += Date.now() - startTime;
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
return result;
|
|
401
|
-
} catch (err) {
|
|
402
|
-
if (this.options.enableStats) {
|
|
403
|
-
const stats = this.stats.get(name);
|
|
404
|
-
if (stats) {
|
|
405
|
-
stats.errors++;
|
|
406
|
-
stats.calls++;
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
throw err;
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
/**
|
|
414
|
-
* 失效缓存
|
|
415
|
-
* @param {string} name - 函数名称
|
|
416
|
-
* @param {...any} args - 函数参数
|
|
417
|
-
*/
|
|
418
|
-
async invalidate(name, ...args) {
|
|
419
|
-
if (!name || typeof name !== 'string') {
|
|
420
|
-
throw new Error('Function name must be a non-empty string');
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
const fn = this.functions.get(name);
|
|
424
|
-
if (!fn) {
|
|
425
|
-
throw new Error(`Function '${name}' not registered`);
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
// 获取原始函数(从缓存的函数中提取)
|
|
429
|
-
const originalFn = fn;
|
|
430
|
-
|
|
431
|
-
// 缓存键格式:${namespace}:${name}:${fnName}:${args}
|
|
432
|
-
// 但是因为我们在 register 时已经将 name 加入 namespace,
|
|
433
|
-
// withCache 会再拼接 fn.name,所以这里需要使用正确的完整键
|
|
434
|
-
// 实际键格式:${this.options.namespace}:${name}:${fn.name}:${args}
|
|
435
|
-
|
|
436
|
-
// 构建通配符模式来删除所有匹配的缓存
|
|
437
|
-
const pattern = `${this.options.namespace}:${name}:*${CacheFactory.stableStringify(args)}*`;
|
|
438
|
-
await this.cache.delPattern(pattern);
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
/**
|
|
442
|
-
* 批量失效缓存
|
|
443
|
-
* @param {string} pattern - 失效模式(支持通配符 *)
|
|
444
|
-
* @returns {Promise<number>} 删除的缓存条目数
|
|
445
|
-
*/
|
|
446
|
-
async invalidatePattern(pattern) {
|
|
447
|
-
if (!pattern || typeof pattern !== 'string') {
|
|
448
|
-
throw new Error('Pattern must be a non-empty string');
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
const fullPattern = `${this.options.namespace}:${pattern}`;
|
|
452
|
-
return await this.cache.delPattern(fullPattern);
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
/**
|
|
456
|
-
* 获取统计信息
|
|
457
|
-
* @param {string} [name] - 函数名称(可选)
|
|
458
|
-
* @returns {Object|null}
|
|
459
|
-
*/
|
|
460
|
-
getStats(name) {
|
|
461
|
-
if (name) {
|
|
462
|
-
const stats = this.stats.get(name);
|
|
463
|
-
if (!stats) return null;
|
|
464
|
-
return {
|
|
465
|
-
...stats,
|
|
466
|
-
hitRate: stats.hits / (stats.hits + stats.misses) || 0,
|
|
467
|
-
avgTime: stats.totalTime / stats.calls || 0
|
|
468
|
-
};
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
const allStats = {};
|
|
472
|
-
for (const [fnName, stats] of this.stats.entries()) {
|
|
473
|
-
allStats[fnName] = {
|
|
474
|
-
...stats,
|
|
475
|
-
hitRate: stats.hits / (stats.hits + stats.misses) || 0,
|
|
476
|
-
avgTime: stats.totalTime / stats.calls || 0
|
|
477
|
-
};
|
|
478
|
-
}
|
|
479
|
-
return allStats;
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
/**
|
|
483
|
-
* 列出所有已注册的函数
|
|
484
|
-
* @returns {string[]}
|
|
485
|
-
*/
|
|
486
|
-
list() {
|
|
487
|
-
return Array.from(this.functions.keys());
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
/**
|
|
491
|
-
* 重置统计信息
|
|
492
|
-
* @param {string} [name] - 函数名称(可选)
|
|
493
|
-
*/
|
|
494
|
-
resetStats(name) {
|
|
495
|
-
if (name) {
|
|
496
|
-
const stats = this.stats.get(name);
|
|
497
|
-
if (stats) {
|
|
498
|
-
Object.assign(stats, {
|
|
499
|
-
hits: 0,
|
|
500
|
-
misses: 0,
|
|
501
|
-
errors: 0,
|
|
502
|
-
calls: 0,
|
|
503
|
-
totalTime: 0
|
|
504
|
-
});
|
|
505
|
-
}
|
|
506
|
-
} else {
|
|
507
|
-
for (const stats of this.stats.values()) {
|
|
508
|
-
Object.assign(stats, {
|
|
509
|
-
hits: 0,
|
|
510
|
-
misses: 0,
|
|
511
|
-
errors: 0,
|
|
512
|
-
calls: 0,
|
|
513
|
-
totalTime: 0
|
|
514
|
-
});
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
/**
|
|
520
|
-
* 清空所有已注册的函数
|
|
521
|
-
*/
|
|
522
|
-
clear() {
|
|
523
|
-
this.functions.clear();
|
|
524
|
-
this.stats.clear();
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
module.exports = {
|
|
529
|
-
withCache,
|
|
530
|
-
FunctionCache
|
|
531
|
-
};
|
|
532
|
-
|
|
533
|
-
|