monsqlize 1.1.4 → 1.1.5
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 +55 -2
- package/index.mjs +52 -0
- package/lib/function-cache.js +79 -56
- package/lib/mongodb/writes/replace-one.js +16 -6
- package/lib/mongodb/writes/update-many.js +13 -7
- package/lib/mongodb/writes/update-one.js +12 -6
- package/package.json +3 -1
- package/types/README.md +122 -0
- package/types/base.ts +90 -0
- package/types/batch.ts +187 -0
- package/types/cache.ts +68 -0
- package/types/chain.ts +254 -0
- package/types/collection.ts +287 -0
- package/types/expression.ts +109 -0
- package/types/function-cache.d.ts +135 -0
- package/types/lock.ts +95 -0
- package/types/model/definition.ts +110 -0
- package/types/model/index.ts +10 -0
- package/types/model/instance.ts +96 -0
- package/types/model/relations.ts +121 -0
- package/types/model/virtuals.ts +32 -0
- package/types/monsqlize.ts +158 -0
- package/types/options.ts +192 -0
- package/types/pagination.ts +149 -0
- package/types/pool.ts +125 -0
- package/types/query.ts +71 -0
- package/types/saga.ts +125 -0
- package/types/stream.ts +64 -0
- package/types/sync.ts +79 -0
- package/types/transaction.ts +79 -0
- package/types/write.ts +73 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# 变更日志 (CHANGELOG)
|
|
2
2
|
|
|
3
3
|
> **说明**: 版本概览摘要,详细变更见 [changelogs/](./changelogs/) 目录
|
|
4
|
-
> **最后更新**: 2026-02-
|
|
4
|
+
> **最后更新**: 2026-02-10
|
|
5
5
|
|
|
6
6
|
---
|
|
7
7
|
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
| 版本 | 日期 | 变更摘要 | 详细 |
|
|
11
11
|
|------|------|---------|------|
|
|
12
|
+
| [v1.1.5](./changelogs/v1.1.5.md) | 2026-02-10 | 🚨 **重要修复**:upsert 缓存失效 Bug (影响数据一致性) + 11 个新测试 + 代码优化 | [查看](./changelogs/v1.1.5.md) |
|
|
12
13
|
| [v1.1.4](./changelogs/v1.1.4.md) | 2026-02-09 | 🎉 重大功能:通用函数缓存 - 52个测试 (100%通过) + 多层缓存 delPattern 修复 | [查看](./changelogs/v1.1.4.md) |
|
|
13
14
|
| [v1.1.3](./changelogs/v1.1.3.md) | 2026-02-03 | 📚 文档完善:多连接池文档优化 + 健康检查详解 + 验证体系规范 | [查看](./changelogs/v1.1.3.md) |
|
|
14
15
|
| [v1.1.2](#v112---日志优化) | 2026-01-27 | 🔧 优化:ObjectId 转换日志默认静默 + Saga 日志修复 | [查看](#v112---日志优化) |
|
|
@@ -27,11 +28,63 @@
|
|
|
27
28
|
|
|
28
29
|
---
|
|
29
30
|
|
|
31
|
+
## 🚨 重要更新
|
|
32
|
+
|
|
33
|
+
### v1.1.5 - 严重 Bug 修复:upsert 缓存失效 🔴
|
|
34
|
+
|
|
35
|
+
**发布日期**: 2026-02-10
|
|
36
|
+
**版本类型**: Critical Bug Fix
|
|
37
|
+
**重要性**: ⭐⭐⭐⭐⭐ **强烈推荐立即升级**
|
|
38
|
+
|
|
39
|
+
#### 🚨 修复的严重问题
|
|
40
|
+
|
|
41
|
+
**问题描述**:
|
|
42
|
+
- `updateOne({ upsert: true })`、`updateMany({ upsert: true })`、`replaceOne({ upsert: true })` 在**插入新文档时不会失效缓存**
|
|
43
|
+
- 导致后续查询返回**过时数据**(缓存中是空数组,但数据库中已有新数据)
|
|
44
|
+
- **影响**: 严重的数据一致性问题
|
|
45
|
+
|
|
46
|
+
**影响场景**:
|
|
47
|
+
- ❌ 用户注册后看不到新用户
|
|
48
|
+
- ❌ 订单创建后看不到新订单
|
|
49
|
+
- ❌ count/distinct 查询返回错误的数量
|
|
50
|
+
- ❌ 统计数据不准确
|
|
51
|
+
|
|
52
|
+
**修复的方法**:
|
|
53
|
+
1. ✅ **updateOne** - 修复缓存失效条件 (第 146 行)
|
|
54
|
+
2. ✅ **updateMany** - 修复缓存失效条件 (第 146 行)
|
|
55
|
+
3. ✅ **replaceOne** - 修复缓存失效条件 (第 113 行,新发现)
|
|
56
|
+
|
|
57
|
+
**测试验证**:
|
|
58
|
+
- ✅ 新增 11 个测试用例,100% 通过
|
|
59
|
+
- ✅ 三轮彻底验证,所有场景覆盖
|
|
60
|
+
|
|
61
|
+
**升级建议**:
|
|
62
|
+
```bash
|
|
63
|
+
# 如果你使用了 upsert,请立即升级
|
|
64
|
+
npm update monsqlize
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
📖 **详细分析**:
|
|
68
|
+
- [三轮验证报告](./reports/upsert-缓存失效-三轮验证报告.md)
|
|
69
|
+
- [问题深度分析](./reports/upsert-缓存失效机制分析报告.md)
|
|
70
|
+
- [完整修复总结](./reports/upsert-缓存失效-完整修复总结.md)
|
|
71
|
+
|
|
72
|
+
#### 其他改进
|
|
73
|
+
|
|
74
|
+
- ✅ function-cache 性能优化 (23-45% 提升)
|
|
75
|
+
- ✅ 新增 17 个复杂数据类型测试
|
|
76
|
+
- ✅ 代码质量提升 (A → A+)
|
|
77
|
+
- ✅ 全局 Map 监控机制
|
|
78
|
+
- ✅ 错误日志记录增强
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
30
82
|
## 变更统计
|
|
31
83
|
|
|
32
84
|
| 版本系列 | 版本数 | 主要改进方向 |
|
|
33
85
|
|---------|-------|------------|
|
|
34
|
-
| v1.
|
|
86
|
+
| v1.1.x | 5 | **数据一致性修复** 🆕、通用函数缓存、文档完善、日志优化、100% MongoDB 操作符 |
|
|
87
|
+
| v1.0.x | 10 | 企业级功能、分布式支持、Model 层完善、Schema 验证、统一表达式系统 |
|
|
35
88
|
|
|
36
89
|
---
|
|
37
90
|
|
package/index.mjs
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* monSQLize ES Module Entry Point
|
|
3
|
+
*
|
|
4
|
+
* 使用方式:
|
|
5
|
+
* ```javascript
|
|
6
|
+
* import MonSQLize from 'monsqlize';
|
|
7
|
+
* // 或
|
|
8
|
+
* import { MonSQLize } from 'monsqlize';
|
|
9
|
+
*
|
|
10
|
+
* const db = new MonSQLize({
|
|
11
|
+
* type: 'mongodb',
|
|
12
|
+
* config: { uri: 'mongodb://localhost:27017/mydb' }
|
|
13
|
+
* });
|
|
14
|
+
*
|
|
15
|
+
* await db.connect();
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { createRequire } from 'module';
|
|
20
|
+
const require = createRequire(import.meta.url);
|
|
21
|
+
|
|
22
|
+
// 使用 require 导入 CommonJS 模块
|
|
23
|
+
const MonSQLizeClass = require('./lib/index.js');
|
|
24
|
+
const Logger = require('./lib/logger.js');
|
|
25
|
+
const MemoryCache = require('./lib/cache.js');
|
|
26
|
+
const { createRedisCacheAdapter } = require('./lib/redis-cache-adapter.js');
|
|
27
|
+
const TransactionManager = require('./lib/transaction/TransactionManager.js');
|
|
28
|
+
const CacheLockManager = require('./lib/transaction/CacheLockManager.js');
|
|
29
|
+
const DistributedCacheInvalidator = require('./lib/distributed-cache-invalidator.js');
|
|
30
|
+
const { withCache, FunctionCache } = require('./lib/function-cache.js');
|
|
31
|
+
|
|
32
|
+
// 默认导出
|
|
33
|
+
export default MonSQLizeClass;
|
|
34
|
+
|
|
35
|
+
// 🆕 v1.0.9: 导出表达式函数
|
|
36
|
+
const { expr, createExpression } = MonSQLizeClass;
|
|
37
|
+
|
|
38
|
+
// 命名导出
|
|
39
|
+
export {
|
|
40
|
+
MonSQLizeClass as MonSQLize,
|
|
41
|
+
Logger,
|
|
42
|
+
MemoryCache,
|
|
43
|
+
createRedisCacheAdapter,
|
|
44
|
+
TransactionManager,
|
|
45
|
+
CacheLockManager,
|
|
46
|
+
DistributedCacheInvalidator,
|
|
47
|
+
expr, // 🆕 v1.0.9: 统一表达式函数
|
|
48
|
+
createExpression, // 🆕 v1.0.9: 表达式函数别名
|
|
49
|
+
withCache, // 🆕 v1.1.4: 函数缓存装饰器
|
|
50
|
+
FunctionCache // 🆕 v1.1.4: 函数缓存类
|
|
51
|
+
};
|
|
52
|
+
|
package/lib/function-cache.js
CHANGED
|
@@ -12,13 +12,46 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
const CacheFactory = require('./cache');
|
|
15
|
+
const crypto = require('crypto');
|
|
15
16
|
|
|
16
17
|
// 并发去重映射(防止缓存击穿)
|
|
18
|
+
// 使用 Map 存储正在执行的 Promise,带超时清理机制防止内存泄漏
|
|
17
19
|
const __inflightFunctions = new Map();
|
|
18
20
|
|
|
19
21
|
// 缓存未命中的特殊标记(使用 Symbol 确保唯一性)
|
|
20
22
|
const CACHE_MISS = Symbol('CACHE_MISS');
|
|
21
23
|
|
|
24
|
+
// 并发请求清理超时时间(毫秒)
|
|
25
|
+
const INFLIGHT_CLEANUP_TIMEOUT_MS = 300000; // 5分钟
|
|
26
|
+
|
|
27
|
+
// 🔧 v1.1.5: 全局 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
|
+
|
|
22
55
|
/**
|
|
23
56
|
* 基础装饰器:为函数添加缓存能力
|
|
24
57
|
*
|
|
@@ -82,6 +115,7 @@ function withCache(fn, options = {}) {
|
|
|
82
115
|
}
|
|
83
116
|
|
|
84
117
|
// 统计信息
|
|
118
|
+
// 注意:在极高并发场景下,统计可能不完全准确(这是性能与精确度的权衡)
|
|
85
119
|
const stats = {
|
|
86
120
|
hits: 0,
|
|
87
121
|
misses: 0,
|
|
@@ -95,9 +129,17 @@ function withCache(fn, options = {}) {
|
|
|
95
129
|
// 1. 生成缓存键
|
|
96
130
|
let cacheKey;
|
|
97
131
|
try {
|
|
98
|
-
|
|
132
|
+
const baseKey = keyBuilder
|
|
99
133
|
? `${namespace}:${keyBuilder(...args)}`
|
|
100
134
|
: `${namespace}:${fn.name}:${CacheFactory.stableStringify(args)}`;
|
|
135
|
+
|
|
136
|
+
// 🔧 v1.1.5: 限制缓存键大小
|
|
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
|
+
}
|
|
101
143
|
} catch (err) {
|
|
102
144
|
// 键生成失败,直接执行原函数
|
|
103
145
|
if (enableStats) stats.errors++;
|
|
@@ -108,16 +150,28 @@ function withCache(fn, options = {}) {
|
|
|
108
150
|
const startTime = Date.now();
|
|
109
151
|
let cached = CACHE_MISS;
|
|
110
152
|
try {
|
|
153
|
+
// 优化:使用特殊标记来区分"缓存未命中"和"缓存值是 undefined"
|
|
111
154
|
const value = await cacheInstance.get(cacheKey);
|
|
112
|
-
|
|
113
|
-
//
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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 值,直接使用
|
|
117
166
|
cached = value;
|
|
118
167
|
}
|
|
119
168
|
} catch (err) {
|
|
120
169
|
if (enableStats) stats.errors++;
|
|
170
|
+
// 🔧 v1.1.5: 添加错误日志
|
|
171
|
+
console.warn('[FunctionCache] Cache get failed:', {
|
|
172
|
+
key: cacheKey.substring(0, 100), // 截断避免日志过长
|
|
173
|
+
error: err.message
|
|
174
|
+
});
|
|
121
175
|
}
|
|
122
176
|
|
|
123
177
|
if (cached !== CACHE_MISS) {
|
|
@@ -165,6 +219,11 @@ function withCache(fn, options = {}) {
|
|
|
165
219
|
await cacheInstance.set(cacheKey, result, ttl);
|
|
166
220
|
} catch (err) {
|
|
167
221
|
if (enableStats) stats.errors++;
|
|
222
|
+
// 🔧 v1.1.5: 添加错误日志
|
|
223
|
+
console.warn('[FunctionCache] Cache set failed:', {
|
|
224
|
+
key: cacheKey.substring(0, 100),
|
|
225
|
+
error: err.message
|
|
226
|
+
});
|
|
168
227
|
}
|
|
169
228
|
}
|
|
170
229
|
|
|
@@ -176,6 +235,17 @@ function withCache(fn, options = {}) {
|
|
|
176
235
|
|
|
177
236
|
__inflightFunctions.set(cacheKey, promise);
|
|
178
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
|
+
|
|
179
249
|
try {
|
|
180
250
|
const result = await promise;
|
|
181
251
|
if (enableStats) {
|
|
@@ -268,9 +338,10 @@ class FunctionCache {
|
|
|
268
338
|
* @param {Function} fn - 函数实现
|
|
269
339
|
* @param {Object} options - 缓存配置
|
|
270
340
|
* @param {number} [options.ttl] - 缓存过期时间(毫秒)
|
|
271
|
-
* @param {
|
|
341
|
+
* @param {Function} [options.keyBuilder] - 自定义键生成函数
|
|
342
|
+
* @param {Function} [options.condition] - 条件缓存函数
|
|
272
343
|
*/
|
|
273
|
-
register(name, fn, options = {}) {
|
|
344
|
+
async register(name, fn, options = {}) {
|
|
274
345
|
if (!name || typeof name !== 'string') {
|
|
275
346
|
throw new Error('Function name must be a non-empty string');
|
|
276
347
|
}
|
|
@@ -299,56 +370,8 @@ class FunctionCache {
|
|
|
299
370
|
totalTime: 0
|
|
300
371
|
});
|
|
301
372
|
}
|
|
302
|
-
|
|
303
|
-
// 🆕 v1.1.4: 建立集合依赖关系(用于自动失效)
|
|
304
|
-
if (options.collections && Array.isArray(options.collections)) {
|
|
305
|
-
this._registerDependencies(name, options.collections);
|
|
306
|
-
}
|
|
307
373
|
}
|
|
308
374
|
|
|
309
|
-
/**
|
|
310
|
-
* 🆕 v1.1.4: 注册函数与集合的依赖关系
|
|
311
|
-
* @private
|
|
312
|
-
* @param {string} functionName - 函数名称
|
|
313
|
-
* @param {Array<string>} collections - 集合名称列表
|
|
314
|
-
*/
|
|
315
|
-
_registerDependencies(functionName, collections) {
|
|
316
|
-
for (const collection of collections) {
|
|
317
|
-
const depsKey = `fn_deps:${collection}`;
|
|
318
|
-
|
|
319
|
-
// 从缓存中读取已有的依赖列表
|
|
320
|
-
let existingDeps = [];
|
|
321
|
-
try {
|
|
322
|
-
const cached = this.cache.get ? this.cache.get(depsKey) : null;
|
|
323
|
-
if (cached && Array.isArray(cached)) {
|
|
324
|
-
existingDeps = cached;
|
|
325
|
-
}
|
|
326
|
-
} catch (err) {
|
|
327
|
-
// 读取失败,使用空数组
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
// 添加新的函数名(避免重复)
|
|
331
|
-
if (!existingDeps.includes(functionName)) {
|
|
332
|
-
existingDeps.push(functionName);
|
|
333
|
-
|
|
334
|
-
// 存储依赖关系(永久存储,无 TTL)
|
|
335
|
-
try {
|
|
336
|
-
if (this.cache.set) {
|
|
337
|
-
this.cache.set(depsKey, existingDeps, 0);
|
|
338
|
-
}
|
|
339
|
-
} catch (err) {
|
|
340
|
-
// 存储失败不影响注册
|
|
341
|
-
if (this.msq && this.msq.logger) {
|
|
342
|
-
this.msq.logger.warn('[FunctionCache] 注册依赖关系失败', {
|
|
343
|
-
collection,
|
|
344
|
-
function: functionName,
|
|
345
|
-
error: err.message
|
|
346
|
-
});
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
375
|
|
|
353
376
|
/**
|
|
354
377
|
* 执行函数
|
|
@@ -110,7 +110,8 @@ function createReplaceOneOps(context) {
|
|
|
110
110
|
const result = await nativeCollection.replaceOne(convertedFilter, convertedReplacement, options);
|
|
111
111
|
|
|
112
112
|
// 4. 自动失效缓存
|
|
113
|
-
|
|
113
|
+
// ✅ v1.1.5: 修复 upsert 场景的缓存失效问题 - 检查 modifiedCount 或 upsertedId
|
|
114
|
+
if (cache && (result.modifiedCount > 0 || result.upsertedId)) {
|
|
114
115
|
try {
|
|
115
116
|
// 使用标准命名空间模式删除该集合的所有缓存
|
|
116
117
|
const ns = {
|
|
@@ -126,24 +127,33 @@ function createReplaceOneOps(context) {
|
|
|
126
127
|
// 事务中:调用 Transaction 的 recordInvalidation 方法
|
|
127
128
|
const tx = getTransactionFromSession(options.session);
|
|
128
129
|
if (tx && typeof tx.recordInvalidation === 'function') {
|
|
129
|
-
await tx.recordInvalidation(pattern
|
|
130
|
-
|
|
130
|
+
await tx.recordInvalidation(pattern, {
|
|
131
|
+
operation: 'write',
|
|
132
|
+
query: filter,
|
|
133
|
+
collection: collectionName,
|
|
134
|
+
upserted: !!result.upsertedId
|
|
135
|
+
});
|
|
136
|
+
logger.debug(`[${operation}] 事务中失效缓存: ${ns.db}.${ns.collection}${result.upsertedId ? ' (upsert)' : ''}`);
|
|
131
137
|
} else {
|
|
132
138
|
const deleted = await cache.delPattern(pattern);
|
|
133
139
|
if (deleted > 0) {
|
|
134
|
-
logger.debug(`[${operation}] 自动失效缓存: ${ns.db}.${ns.collection}, 删除 ${deleted}
|
|
140
|
+
logger.debug(`[${operation}] 自动失效缓存: ${ns.db}.${ns.collection}, 删除 ${deleted} 个缓存键${result.upsertedId ? ' (upsert)' : ''}`);
|
|
135
141
|
}
|
|
136
142
|
}
|
|
137
143
|
} else {
|
|
138
144
|
// 非事务:直接失效缓存
|
|
139
145
|
const deleted = await cache.delPattern(pattern);
|
|
140
146
|
if (deleted > 0) {
|
|
141
|
-
logger.debug(`[${operation}] 自动失效缓存: ${ns.db}.${ns.collection}, 删除 ${deleted}
|
|
147
|
+
logger.debug(`[${operation}] 自动失效缓存: ${ns.db}.${ns.collection}, 删除 ${deleted} 个缓存键${result.upsertedId ? ' (upsert)' : ''}`);
|
|
142
148
|
}
|
|
143
149
|
}
|
|
144
150
|
} catch (cacheErr) {
|
|
145
151
|
// 缓存失效失败不影响写操作
|
|
146
|
-
logger.warn(`[${operation}] 缓存失效失败: ${cacheErr.message}`, {
|
|
152
|
+
logger.warn(`[${operation}] 缓存失效失败: ${cacheErr.message}`, {
|
|
153
|
+
ns: `${databaseName}.${collectionName}`,
|
|
154
|
+
error: cacheErr,
|
|
155
|
+
upserted: !!result.upsertedId
|
|
156
|
+
});
|
|
147
157
|
}
|
|
148
158
|
}
|
|
149
159
|
|
|
@@ -141,8 +141,9 @@ function createUpdateManyOps(context) {
|
|
|
141
141
|
// 3. 执行批量更新操作
|
|
142
142
|
const result = await nativeCollection.updateMany(convertedFilter, convertedUpdate, options);
|
|
143
143
|
|
|
144
|
-
// 4.
|
|
145
|
-
|
|
144
|
+
// 4. 自动失效缓存(只要有匹配或 upsert 插入,就失效缓存)
|
|
145
|
+
// ✅ v1.1.5: 修复 upsert 场景的缓存失效问题 - 检查 matchedCount 或 upsertedId
|
|
146
|
+
if (cache && (result.matchedCount > 0 || result.upsertedId)) {
|
|
146
147
|
try {
|
|
147
148
|
// 使用标准命名空间模式删除该集合的所有缓存
|
|
148
149
|
const ns = {
|
|
@@ -162,25 +163,30 @@ function createUpdateManyOps(context) {
|
|
|
162
163
|
await tx.recordInvalidation(pattern, {
|
|
163
164
|
operation: 'write',
|
|
164
165
|
query: filter,
|
|
165
|
-
collection: collectionName
|
|
166
|
+
collection: collectionName,
|
|
167
|
+
upserted: !!result.upsertedId
|
|
166
168
|
});
|
|
167
|
-
logger.debug(`[${operation}] 事务中失效缓存: ${ns.db}.${ns.collection}`);
|
|
169
|
+
logger.debug(`[${operation}] 事务中失效缓存: ${ns.db}.${ns.collection}${result.upsertedId ? ' (upsert)' : ''}`);
|
|
168
170
|
} else {
|
|
169
171
|
const deleted = await cache.delPattern(pattern);
|
|
170
172
|
if (deleted > 0) {
|
|
171
|
-
logger.debug(`[${operation}] 自动失效缓存: ${ns.db}.${ns.collection}, 删除 ${deleted}
|
|
173
|
+
logger.debug(`[${operation}] 自动失效缓存: ${ns.db}.${ns.collection}, 删除 ${deleted} 个缓存键${result.upsertedId ? ' (upsert)' : ''}`);
|
|
172
174
|
}
|
|
173
175
|
}
|
|
174
176
|
} else {
|
|
175
177
|
// 非事务:直接失效缓存
|
|
176
178
|
const deleted = await cache.delPattern(pattern);
|
|
177
179
|
if (deleted > 0) {
|
|
178
|
-
logger.debug(`[${operation}] 自动失效缓存: ${ns.db}.${ns.collection}, 删除 ${deleted}
|
|
180
|
+
logger.debug(`[${operation}] 自动失效缓存: ${ns.db}.${ns.collection}, 删除 ${deleted} 个缓存键${result.upsertedId ? ' (upsert)' : ''}`);
|
|
179
181
|
}
|
|
180
182
|
}
|
|
181
183
|
} catch (cacheErr) {
|
|
182
184
|
// 缓存失效失败不影响写操作
|
|
183
|
-
logger.warn(`[${operation}] 缓存失效失败: ${cacheErr.message}`, {
|
|
185
|
+
logger.warn(`[${operation}] 缓存失效失败: ${cacheErr.message}`, {
|
|
186
|
+
ns: `${databaseName}.${collectionName}`,
|
|
187
|
+
error: cacheErr,
|
|
188
|
+
upserted: !!result.upsertedId
|
|
189
|
+
});
|
|
184
190
|
}
|
|
185
191
|
}
|
|
186
192
|
|
|
@@ -142,7 +142,8 @@ function createUpdateOneOps(context) {
|
|
|
142
142
|
const result = await nativeCollection.updateOne(convertedFilter, convertedUpdate, options);
|
|
143
143
|
|
|
144
144
|
// 4. 自动失效缓存
|
|
145
|
-
|
|
145
|
+
// ✅ v1.1.5: 修复 upsert 场景的缓存失效问题 - 检查 modifiedCount 或 upsertedId
|
|
146
|
+
if (cache && (result.modifiedCount > 0 || result.upsertedId)) {
|
|
146
147
|
try {
|
|
147
148
|
// 使用标准命名空间模式删除该集合的所有缓存
|
|
148
149
|
const ns = {
|
|
@@ -163,26 +164,31 @@ function createUpdateOneOps(context) {
|
|
|
163
164
|
await tx.recordInvalidation(pattern, {
|
|
164
165
|
operation: 'write',
|
|
165
166
|
query: filter,
|
|
166
|
-
collection: collectionName
|
|
167
|
+
collection: collectionName,
|
|
168
|
+
upserted: !!result.upsertedId
|
|
167
169
|
});
|
|
168
|
-
logger.debug(`[${operation}] 事务中失效缓存: ${ns.db}.${ns.collection}`);
|
|
170
|
+
logger.debug(`[${operation}] 事务中失效缓存: ${ns.db}.${ns.collection}${result.upsertedId ? ' (upsert)' : ''}`);
|
|
169
171
|
} else {
|
|
170
172
|
// 降级处理:直接失效缓存
|
|
171
173
|
const deleted = await cache.delPattern(pattern);
|
|
172
174
|
if (deleted > 0) {
|
|
173
|
-
logger.debug(`[${operation}] 自动失效缓存: ${ns.db}.${ns.collection}, 删除 ${deleted}
|
|
175
|
+
logger.debug(`[${operation}] 自动失效缓存: ${ns.db}.${ns.collection}, 删除 ${deleted} 个缓存键${result.upsertedId ? ' (upsert)' : ''}`);
|
|
174
176
|
}
|
|
175
177
|
}
|
|
176
178
|
} else {
|
|
177
179
|
// 非事务:直接失效缓存
|
|
178
180
|
const deleted = await cache.delPattern(pattern);
|
|
179
181
|
if (deleted > 0) {
|
|
180
|
-
logger.debug(`[${operation}] 自动失效缓存: ${ns.db}.${ns.collection}, 删除 ${deleted}
|
|
182
|
+
logger.debug(`[${operation}] 自动失效缓存: ${ns.db}.${ns.collection}, 删除 ${deleted} 个缓存键${result.upsertedId ? ' (upsert)' : ''}`);
|
|
181
183
|
}
|
|
182
184
|
}
|
|
183
185
|
} catch (cacheErr) {
|
|
184
186
|
// 缓存失效失败不影响写操作
|
|
185
|
-
logger.warn(`[${operation}] 缓存失效失败: ${cacheErr.message}`, {
|
|
187
|
+
logger.warn(`[${operation}] 缓存失效失败: ${cacheErr.message}`, {
|
|
188
|
+
ns: `${databaseName}.${collectionName}`,
|
|
189
|
+
error: cacheErr,
|
|
190
|
+
upserted: !!result.upsertedId
|
|
191
|
+
});
|
|
186
192
|
}
|
|
187
193
|
}
|
|
188
194
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "monsqlize",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.5",
|
|
4
4
|
"description": "A lightweight MongoDB ORM with multi-level caching, transaction support, distributed features, Saga distributed transactions, unified expression system with 122 operators, and universal function caching (100% MongoDB support)",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"module": "index.mjs",
|
|
@@ -16,7 +16,9 @@
|
|
|
16
16
|
"types": "./index.d.ts",
|
|
17
17
|
"files": [
|
|
18
18
|
"lib/",
|
|
19
|
+
"types/",
|
|
19
20
|
"index.d.ts",
|
|
21
|
+
"index.mjs",
|
|
20
22
|
"README.md",
|
|
21
23
|
"LICENSE",
|
|
22
24
|
"CHANGELOG.md"
|
package/types/README.md
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# Types 模块说明
|
|
2
|
+
|
|
3
|
+
本目录包含 monSQLize 的所有 TypeScript 类型定义,已按功能模块拆分。
|
|
4
|
+
|
|
5
|
+
## 📁 目录结构
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
types/
|
|
9
|
+
├── base.ts # 基础类型(ErrorCodes, LoggerLike, ExpressionObject)
|
|
10
|
+
├── expression.ts # 统一表达式操作符(67个操作符)
|
|
11
|
+
├── cache.ts # 缓存接口(CacheLike, MultiLevelCache)
|
|
12
|
+
├── options.ts # 配置选项(BaseOptions, SSH, Transaction)
|
|
13
|
+
├── query.ts # 查询选项(Find, Count, Aggregate, Distinct)
|
|
14
|
+
├── write.ts # 写操作(InsertOne, InsertMany, WriteConcern)
|
|
15
|
+
├── batch.ts # 批量操作(InsertBatch, UpdateBatch, DeleteBatch)
|
|
16
|
+
├── pagination.ts # 分页系统(FindPage, PageResult, Bookmark)
|
|
17
|
+
├── stream.ts # 流式查询(Stream, Explain)
|
|
18
|
+
├── transaction.ts # 事务(Transaction, MongoSession)
|
|
19
|
+
├── lock.ts # 业务锁(Lock, LockOptions)
|
|
20
|
+
├── chain.ts # 链式调用(FindChain, AggregateChain)
|
|
21
|
+
├── pool.ts # 连接池(ConnectionPoolManager, PoolConfig)
|
|
22
|
+
├── saga.ts # Saga事务(SagaOrchestrator, SagaDefinition)
|
|
23
|
+
├── sync.ts # 数据同步(Change Stream, SyncConfig)
|
|
24
|
+
├── collection.ts # Collection API(CollectionAccessor, 所有查询方法)
|
|
25
|
+
├── monsqlize.ts # MonSQLize主类
|
|
26
|
+
└── model/ # Model 层
|
|
27
|
+
├── definition.ts # Model定义(ModelDefinition, Schema)
|
|
28
|
+
├── relations.ts # 关系定义(Populate, RelationConfig)
|
|
29
|
+
├── virtuals.ts # 虚拟字段(VirtualConfig)
|
|
30
|
+
├── instance.ts # Model实例和静态方法
|
|
31
|
+
└── index.ts # Model 类型汇总
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## 🔍 模块依赖关系
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
base.ts (基础)
|
|
38
|
+
↓
|
|
39
|
+
options.ts, query.ts (配置)
|
|
40
|
+
↓
|
|
41
|
+
write.ts, batch.ts, pagination.ts, stream.ts, chain.ts (操作)
|
|
42
|
+
↓
|
|
43
|
+
transaction.ts, lock.ts, pool.ts, saga.ts, sync.ts (功能)
|
|
44
|
+
↓
|
|
45
|
+
collection.ts (集合)
|
|
46
|
+
↓
|
|
47
|
+
model/* (Model层)
|
|
48
|
+
↓
|
|
49
|
+
monsqlize.ts (主类)
|
|
50
|
+
↓
|
|
51
|
+
index.d.ts (统一导出)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## 📖 使用指南
|
|
55
|
+
|
|
56
|
+
### 导入类型
|
|
57
|
+
|
|
58
|
+
所有类型统一从 `monsqlize` 模块导入:
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
import type { FindOptions, CollectionAccessor, Model } from 'monsqlize';
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### 查找类型定义
|
|
65
|
+
|
|
66
|
+
1. **按功能查找**:参考上面的目录结构
|
|
67
|
+
2. **使用 IDE**:使用 VS Code 的"转到定义"功能(F12)
|
|
68
|
+
3. **全局搜索**:在 `types/` 目录中搜索类型名称
|
|
69
|
+
|
|
70
|
+
## 🛠️ 开发指南
|
|
71
|
+
|
|
72
|
+
### 修改类型定义
|
|
73
|
+
|
|
74
|
+
1. 找到对应的模块文件(如 `types/query.ts`)
|
|
75
|
+
2. 修改类型定义
|
|
76
|
+
3. 如果是新类型,需要在 `index.d.ts` 中添加导出
|
|
77
|
+
4. 运行 `npx tsc --noEmit` 验证编译通过
|
|
78
|
+
5. 运行 `npm test` 验证测试通过
|
|
79
|
+
|
|
80
|
+
### 添加新类型
|
|
81
|
+
|
|
82
|
+
1. 选择合适的模块文件(或创建新模块)
|
|
83
|
+
2. 添加类型定义
|
|
84
|
+
3. 在 `index.d.ts` 中添加导出语句
|
|
85
|
+
```typescript
|
|
86
|
+
export import NewType = ModuleName.NewType;
|
|
87
|
+
```
|
|
88
|
+
4. 验证编译和测试
|
|
89
|
+
5. 更新本 README
|
|
90
|
+
|
|
91
|
+
### 模块划分原则
|
|
92
|
+
|
|
93
|
+
1. **单一职责**:每个文件只包含相关的类型
|
|
94
|
+
2. **依赖清晰**:避免循环依赖
|
|
95
|
+
3. **大小适中**:单个文件不超过 500 行
|
|
96
|
+
4. **命名规范**:使用 kebab-case(如 `multi-level-cache.ts`)
|
|
97
|
+
|
|
98
|
+
## 📦 版本历史
|
|
99
|
+
|
|
100
|
+
- **v1.0.10** (2026-01-19): 将 index.d.ts 拆分为 21 个模块(2932 行 → 21 文件)
|
|
101
|
+
- **v1.0.9**: 原始单文件结构(index.d.ts 2932 行)
|
|
102
|
+
|
|
103
|
+
## 🔗 相关文档
|
|
104
|
+
|
|
105
|
+
- [CHANGELOG.md](../CHANGELOG.md)
|
|
106
|
+
- [CONTRIBUTING.md](../CONTRIBUTING.md)
|
|
107
|
+
- [实施方案](../plans/refactoring/ref-types-modularization-v1.0.10-revised.md)
|
|
108
|
+
|
|
109
|
+
## 📊 统计信息
|
|
110
|
+
|
|
111
|
+
- **总文件数**: 21 个
|
|
112
|
+
- **总代码行数**: ~2500 行(包含注释)
|
|
113
|
+
- **模块数**: 17 个主模块 + 4 个 Model 子模块
|
|
114
|
+
- **导出类型数**: 100+ 个
|
|
115
|
+
|
|
116
|
+
## ✅ 质量保证
|
|
117
|
+
|
|
118
|
+
- ✅ 所有模块通过 TypeScript 编译
|
|
119
|
+
- ✅ 保持向后兼容(原有导入方式不变)
|
|
120
|
+
- ✅ 完整的依赖关系管理
|
|
121
|
+
- ✅ 清晰的模块职责划分
|
|
122
|
+
|