monsqlize 1.1.1 → 1.1.4
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 +119 -1
- package/README.md +88 -3
- package/index.d.ts +11 -0
- package/lib/function-cache.js +509 -0
- package/lib/index.js +12 -0
- package/lib/infrastructure/ConnectionPoolManager.js +38 -7
- package/lib/multi-level-cache.js +26 -3
- package/lib/utils/objectid-converter.js +38 -4
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# 变更日志 (CHANGELOG)
|
|
2
2
|
|
|
3
3
|
> **说明**: 版本概览摘要,详细变更见 [changelogs/](./changelogs/) 目录
|
|
4
|
-
> **最后更新**: 2026-
|
|
4
|
+
> **最后更新**: 2026-02-09
|
|
5
5
|
|
|
6
6
|
---
|
|
7
7
|
|
|
@@ -9,6 +9,9 @@
|
|
|
9
9
|
|
|
10
10
|
| 版本 | 日期 | 变更摘要 | 详细 |
|
|
11
11
|
|------|------|---------|------|
|
|
12
|
+
| [v1.1.4](./changelogs/v1.1.4.md) | 2026-02-09 | 🎉 重大功能:通用函数缓存 - 52个测试 (100%通过) + 多层缓存 delPattern 修复 | [查看](./changelogs/v1.1.4.md) |
|
|
13
|
+
| [v1.1.3](./changelogs/v1.1.3.md) | 2026-02-03 | 📚 文档完善:多连接池文档优化 + 健康检查详解 + 验证体系规范 | [查看](./changelogs/v1.1.3.md) |
|
|
14
|
+
| [v1.1.2](#v112---日志优化) | 2026-01-27 | 🔧 优化:ObjectId 转换日志默认静默 + Saga 日志修复 | [查看](#v112---日志优化) |
|
|
12
15
|
| [v1.1.1](#v111---objectid-跨版本兼容) | 2026-01-27 | 🔧 Bug修复:新增跨版本 ObjectId 兼容性(支持 mongoose bson@4.x/5.x)| [查看](#v111---objectid-跨版本兼容) |
|
|
13
16
|
| [v1.1.0](./changelogs/v1.1.0.md) | 2026-01-21 | 🎉 重大更新:新增49个操作符,实现100% MongoDB操作符支持(122/122)| [查看](./changelogs/v1.1.0.md) |
|
|
14
17
|
| [v1.0.9](./changelogs/v1.0.9.md) | 2026-01-19 | 🎉 重大功能:统一表达式系统 - 67个操作符 + 107个测试 (100%通过) | [查看](./changelogs/v1.0.9.md) |
|
|
@@ -32,8 +35,123 @@
|
|
|
32
35
|
|
|
33
36
|
---
|
|
34
37
|
|
|
38
|
+
## 文档与验证改进
|
|
39
|
+
|
|
40
|
+
### v1.1.3 - 文档完善与验证体系 📚
|
|
41
|
+
|
|
42
|
+
**发布日期**: 2026-02-03
|
|
43
|
+
**版本类型**: 文档优化 + 验证体系完善
|
|
44
|
+
**重要性**: ⭐⭐⭐⭐
|
|
45
|
+
|
|
46
|
+
#### 核心改进
|
|
47
|
+
|
|
48
|
+
**多连接池文档优化**:
|
|
49
|
+
- ✅ 修复代码实现(ConnectionPoolManager 导出、selectPool 返回值完整)
|
|
50
|
+
- ✅ 验证 100% 通过(76 项功能验证)
|
|
51
|
+
- ✅ 文档精简(2082 行 → 1970 行,-5%)
|
|
52
|
+
- ✅ 目录格式统一、删除重复内容
|
|
53
|
+
|
|
54
|
+
**健康检查机制详解**:
|
|
55
|
+
- ✅ 新增文档 `docs/multi-pool-health-check.md`
|
|
56
|
+
- ✅ 详细说明工作原理、问题发现、问题处理
|
|
57
|
+
- ✅ 提供 3 种运维通知方式(日志、监控、事件)
|
|
58
|
+
- ✅ 集成方案(Prometheus、企业微信/钉钉、邮件)
|
|
59
|
+
- ✅ 完整的生产环境告警系统代码
|
|
60
|
+
|
|
61
|
+
**验证体系规范**:
|
|
62
|
+
- ✅ 文档规范说明(目录格式、内容规范、禁止内容)
|
|
63
|
+
- ✅ 文档更新流程(4 步)
|
|
64
|
+
- ✅ 引用关系规范(建立原则、禁止情况、判断流程)
|
|
65
|
+
- ✅ 验证通过标准(6 项)
|
|
66
|
+
|
|
67
|
+
#### 影响范围
|
|
68
|
+
|
|
69
|
+
- **文档文件**: 3 个(multi-pool.md、multi-pool-health-check.md、README.md)
|
|
70
|
+
- **代码文件**: 2 个(lib/index.js、ConnectionPoolManager.js)
|
|
71
|
+
- **验证文件**: 5 个(清单、脚本、报告)
|
|
72
|
+
- **规范文件**: 1 个(validation/README.md)
|
|
73
|
+
|
|
74
|
+
#### 升级说明
|
|
75
|
+
|
|
76
|
+
**无破坏性变更**,现有代码无需修改:
|
|
77
|
+
```bash
|
|
78
|
+
npm update monsqlize
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
📖 **详细变更**: [查看完整变更日志](./changelogs/v1.1.3.md)
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
35
85
|
## 里程碑版本
|
|
36
86
|
|
|
87
|
+
### v1.1.2 - 日志优化 🔧
|
|
88
|
+
|
|
89
|
+
**发布日期**: 2026-01-27
|
|
90
|
+
**重要性**: ⭐⭐⭐
|
|
91
|
+
|
|
92
|
+
**优化内容**:
|
|
93
|
+
|
|
94
|
+
1. **ObjectId 转换日志默认静默**
|
|
95
|
+
- ✅ **默认完全静默**: 不输出任何 ObjectId 转换日志
|
|
96
|
+
- ✅ **用户反馈驱动**: 根据用户反馈,转换日志无实际作用
|
|
97
|
+
- ✅ **可选启用**: 支持按需启用摘要或详细日志
|
|
98
|
+
- ✅ **生产友好**: 减少日志量,避免日志污染
|
|
99
|
+
|
|
100
|
+
2. **Saga 日志修复**
|
|
101
|
+
- ✅ **修复误导性日志**: 正确区分内存缓存和 Redis 缓存
|
|
102
|
+
- ✅ **准确识别**: 通过检测 `cache.keys` 和 `cache.publish` 方法识别 Redis
|
|
103
|
+
- ✅ **日志准确**: 内存缓存显示"使用内存缓存",Redis 显示"使用 Redis 存储"
|
|
104
|
+
|
|
105
|
+
**配置选项**:
|
|
106
|
+
|
|
107
|
+
```javascript
|
|
108
|
+
// 默认配置(完全静默)
|
|
109
|
+
const msq = new MonSQLize({
|
|
110
|
+
type: 'mongodb',
|
|
111
|
+
config: { uri: 'mongodb://localhost:27017' }
|
|
112
|
+
});
|
|
113
|
+
// ✅ 无任何 ObjectId 转换日志
|
|
114
|
+
|
|
115
|
+
// 启用摘要日志(调试用)
|
|
116
|
+
const msq = new MonSQLize({
|
|
117
|
+
type: 'mongodb',
|
|
118
|
+
config: { uri: 'mongodb://localhost:27017' },
|
|
119
|
+
autoConvertObjectId: {
|
|
120
|
+
silent: false // 关闭静默
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
// 输出:[DEBUG] Converted 15 cross-version ObjectIds
|
|
124
|
+
|
|
125
|
+
// 启用详细日志(深度调试)
|
|
126
|
+
const msq = new MonSQLize({
|
|
127
|
+
type: 'mongodb',
|
|
128
|
+
config: { uri: 'mongodb://localhost:27017' },
|
|
129
|
+
autoConvertObjectId: {
|
|
130
|
+
silent: false,
|
|
131
|
+
verbose: true
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
// 输出:每个 ObjectId 都有一条日志
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
**效果对比**:
|
|
138
|
+
|
|
139
|
+
| 模式 | silent | verbose | 日志输出 | 适用场景 |
|
|
140
|
+
|------|--------|---------|---------|---------|
|
|
141
|
+
| **静默模式**(默认)✅ | `true` | - | 无 | 生产环境、日常开发 |
|
|
142
|
+
| **摘要模式** | `false` | `false` | 1条摘要 | 需要了解转换情况时 |
|
|
143
|
+
| **详细模式** | `false` | `true` | N条详情 | 深度调试、排查问题 |
|
|
144
|
+
|
|
145
|
+
**修改的文件**:
|
|
146
|
+
- `lib/utils/objectid-converter.js`: 添加 `silent` 配置,默认 `true`
|
|
147
|
+
- `lib/saga/SagaOrchestrator.js`: 修复 Redis 识别逻辑
|
|
148
|
+
|
|
149
|
+
**详细文档**:
|
|
150
|
+
- [ObjectId 日志配置](./docs/objectid-logging-optimization.md)
|
|
151
|
+
- [FAQ - Q3: 如何关闭日志](./docs/objectid-cross-version-faq.md#q3)
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
37
155
|
### v1.1.1 - ObjectId 跨版本兼容 🔧
|
|
38
156
|
|
|
39
157
|
**发布日期**: 2026-01-27
|
package/README.md
CHANGED
|
@@ -465,7 +465,92 @@ const user = await users.findOne({ email: 'test@example.com' });
|
|
|
465
465
|
|
|
466
466
|
## 🌟 核心特性
|
|
467
467
|
|
|
468
|
-
### 0.
|
|
468
|
+
### 0. 🎨 通用函数缓存 🆕 v1.1.4 - 为任意函数添加缓存
|
|
469
|
+
|
|
470
|
+
**52个测试(100% 通过)**,为任意异步函数添加缓存能力,性能提升**50000x**!
|
|
471
|
+
|
|
472
|
+
<table>
|
|
473
|
+
<tr>
|
|
474
|
+
<td width="50%">
|
|
475
|
+
|
|
476
|
+
**🆕 装饰器模式**
|
|
477
|
+
|
|
478
|
+
```javascript
|
|
479
|
+
const { withCache } = require('monsqlize');
|
|
480
|
+
|
|
481
|
+
// 业务函数
|
|
482
|
+
async function getUserProfile(userId) {
|
|
483
|
+
const user = await msq.collection('users')
|
|
484
|
+
.findOne({ _id: userId });
|
|
485
|
+
const orders = await msq.collection('orders')
|
|
486
|
+
.find({ userId }).toArray();
|
|
487
|
+
return { user, orders };
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// 添加缓存(零侵入)
|
|
491
|
+
const cached = withCache(getUserProfile, {
|
|
492
|
+
ttl: 300000, // 5分钟
|
|
493
|
+
cache: msq.getCache()
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
// 使用
|
|
497
|
+
await cached('user123'); // 首次:查询数据库
|
|
498
|
+
await cached('user123'); // 再次:从缓存读取 ⚡
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
</td>
|
|
502
|
+
<td width="50%">
|
|
503
|
+
|
|
504
|
+
**核心优势**
|
|
505
|
+
|
|
506
|
+
- ✅ **零侵入** - 装饰器模式,不修改原函数
|
|
507
|
+
- ✅ **自动序列化** - 支持复杂参数(对象、Date等)
|
|
508
|
+
- ✅ **并发控制** - 防止缓存击穿
|
|
509
|
+
- ✅ **双层缓存** - 本地 + Redis,最佳性能
|
|
510
|
+
- ✅ **条件缓存** - 基于返回值决定是否缓存
|
|
511
|
+
- ✅ **统计监控** - 命中率、调用次数等
|
|
512
|
+
- ✅ **命名空间** - 多模块缓存隔离
|
|
513
|
+
- ✅ **TypeScript** - 完整类型支持
|
|
514
|
+
|
|
515
|
+
**性能提升**:
|
|
516
|
+
- 🚀 复杂业务函数:50000x
|
|
517
|
+
- 🚀 外部 API 调用:200000x
|
|
518
|
+
- 🚀 复杂计算:100000x
|
|
519
|
+
|
|
520
|
+
</td>
|
|
521
|
+
</tr>
|
|
522
|
+
</table>
|
|
523
|
+
|
|
524
|
+
**FunctionCache 类管理**:
|
|
525
|
+
|
|
526
|
+
```javascript
|
|
527
|
+
const { FunctionCache } = require('monsqlize');
|
|
528
|
+
|
|
529
|
+
const fnCache = new FunctionCache(msq, {
|
|
530
|
+
namespace: 'myApp',
|
|
531
|
+
defaultTTL: 60000
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
// 注册多个函数
|
|
535
|
+
fnCache.register('getUserProfile', getUserProfileFn);
|
|
536
|
+
fnCache.register('getOrderStats', getOrderStatsFn);
|
|
537
|
+
|
|
538
|
+
// 执行
|
|
539
|
+
await fnCache.execute('getUserProfile', 'user123');
|
|
540
|
+
|
|
541
|
+
// 失效缓存
|
|
542
|
+
await fnCache.invalidate('getUserProfile', 'user123');
|
|
543
|
+
|
|
544
|
+
// 查看统计
|
|
545
|
+
const stats = fnCache.getStats('getUserProfile');
|
|
546
|
+
console.log('命中率:', stats.hitRate);
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
📖 [完整文档](./docs/function-cache.md) · [键生成机制](./docs/function-cache-key-generation.md)
|
|
550
|
+
|
|
551
|
+
---
|
|
552
|
+
|
|
553
|
+
### 1. 🎯 统一表达式系统 🆕 v1.1.0 - 让聚合查询像SQL一样简单
|
|
469
554
|
|
|
470
555
|
**122个操作符(100% MongoDB支持!新增49个函数)**,让MongoDB聚合查询**像写SQL一样简单**!
|
|
471
556
|
|
|
@@ -578,7 +663,7 @@ await orders.aggregate([
|
|
|
578
663
|
|
|
579
664
|
---
|
|
580
665
|
|
|
581
|
-
###
|
|
666
|
+
### 2. ⚡ 智能缓存系统 - 性能提升 10~100 倍
|
|
582
667
|
|
|
583
668
|
<table>
|
|
584
669
|
<tr>
|
|
@@ -720,7 +805,7 @@ await msq.collection('users').insertOne({ name: 'Alice' });
|
|
|
720
805
|
|
|
721
806
|
[完整文档](./docs/sync-backup.md)
|
|
722
807
|
|
|
723
|
-
###
|
|
808
|
+
### 4. 📦 便利方法 - 减少 60~80% 代码
|
|
724
809
|
|
|
725
810
|
<table>
|
|
726
811
|
<tr>
|
package/index.d.ts
CHANGED
|
@@ -20,6 +20,7 @@ declare module 'monsqlize' {
|
|
|
20
20
|
import type * as Collection from './types/collection';
|
|
21
21
|
import type * as Model from './types/model';
|
|
22
22
|
import type * as MonSQLizeModule from './types/monsqlize';
|
|
23
|
+
import type * as FunctionCacheTypes from './types/function-cache';
|
|
23
24
|
|
|
24
25
|
// ========================================
|
|
25
26
|
// 基础类型
|
|
@@ -190,5 +191,15 @@ declare module 'monsqlize' {
|
|
|
190
191
|
// ========================================
|
|
191
192
|
export import MonSQLize = MonSQLizeModule.MonSQLize;
|
|
192
193
|
export default MonSQLizeModule.MonSQLize;
|
|
194
|
+
|
|
195
|
+
// ========================================
|
|
196
|
+
// 函数缓存(v1.1.4+)
|
|
197
|
+
// ========================================
|
|
198
|
+
export import WithCacheOptions = FunctionCacheTypes.WithCacheOptions;
|
|
199
|
+
export import CacheStats = FunctionCacheTypes.CacheStats;
|
|
200
|
+
export import FunctionCacheOptions = FunctionCacheTypes.FunctionCacheOptions;
|
|
201
|
+
export import CachedFunction = FunctionCacheTypes.CachedFunction;
|
|
202
|
+
export import withCache = FunctionCacheTypes.withCache;
|
|
203
|
+
export import FunctionCache = FunctionCacheTypes.FunctionCache;
|
|
193
204
|
}
|
|
194
205
|
|
|
@@ -0,0 +1,509 @@
|
|
|
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
|
+
|
|
16
|
+
// 并发去重映射(防止缓存击穿)
|
|
17
|
+
const __inflightFunctions = new Map();
|
|
18
|
+
|
|
19
|
+
// 缓存未命中的特殊标记(使用 Symbol 确保唯一性)
|
|
20
|
+
const CACHE_MISS = Symbol('CACHE_MISS');
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 基础装饰器:为函数添加缓存能力
|
|
24
|
+
*
|
|
25
|
+
* @param {Function} fn - 要缓存的异步函数
|
|
26
|
+
* @param {Object} options - 缓存配置
|
|
27
|
+
* @param {number} [options.ttl=60000] - 缓存时间(毫秒)
|
|
28
|
+
* @param {Function} [options.keyBuilder] - 自定义键生成函数
|
|
29
|
+
* @param {Object} [options.cache] - 缓存实例(可选)
|
|
30
|
+
* @param {string} [options.namespace='fn'] - 命名空间
|
|
31
|
+
* @param {Function} [options.condition] - 条件缓存函数
|
|
32
|
+
* @param {boolean} [options.enableStats=true] - 启用统计
|
|
33
|
+
* @returns {Function} 包装后的函数
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* // 基础用法
|
|
37
|
+
* const cachedFn = withCache(originalFn, { ttl: 60000 });
|
|
38
|
+
* const result = await cachedFn('arg1', 'arg2');
|
|
39
|
+
*
|
|
40
|
+
* // 自定义键生成
|
|
41
|
+
* const cachedFn = withCache(originalFn, {
|
|
42
|
+
* ttl: 300000,
|
|
43
|
+
* keyBuilder: (userId) => `user:${userId}`
|
|
44
|
+
* });
|
|
45
|
+
*
|
|
46
|
+
* // 条件缓存(只缓存非空结果)
|
|
47
|
+
* const cachedFn = withCache(originalFn, {
|
|
48
|
+
* ttl: 60000,
|
|
49
|
+
* condition: (result) => result && result.length > 0
|
|
50
|
+
* });
|
|
51
|
+
*/
|
|
52
|
+
function withCache(fn, options = {}) {
|
|
53
|
+
const {
|
|
54
|
+
ttl = 60000,
|
|
55
|
+
keyBuilder,
|
|
56
|
+
cache,
|
|
57
|
+
namespace = 'fn',
|
|
58
|
+
condition,
|
|
59
|
+
enableStats = true
|
|
60
|
+
} = options;
|
|
61
|
+
|
|
62
|
+
// 参数验证
|
|
63
|
+
if (typeof fn !== 'function') {
|
|
64
|
+
throw new Error('fn must be a function');
|
|
65
|
+
}
|
|
66
|
+
if (ttl !== undefined && (typeof ttl !== 'number' || ttl < 0)) {
|
|
67
|
+
throw new Error('ttl must be a non-negative number');
|
|
68
|
+
}
|
|
69
|
+
if (keyBuilder !== undefined && typeof keyBuilder !== 'function') {
|
|
70
|
+
throw new Error('keyBuilder must be a function');
|
|
71
|
+
}
|
|
72
|
+
if (condition !== undefined && typeof condition !== 'function') {
|
|
73
|
+
throw new Error('condition must be a function');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// 使用全局缓存或自定义缓存
|
|
77
|
+
const cacheInstance = cache || CacheFactory.createDefault();
|
|
78
|
+
|
|
79
|
+
// 验证缓存实例
|
|
80
|
+
if (!CacheFactory.isValidCache(cacheInstance)) {
|
|
81
|
+
throw new Error('Invalid cache instance: must implement CacheLike interface');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// 统计信息
|
|
85
|
+
const stats = {
|
|
86
|
+
hits: 0,
|
|
87
|
+
misses: 0,
|
|
88
|
+
errors: 0,
|
|
89
|
+
totalTime: 0,
|
|
90
|
+
calls: 0
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
// 返回包装后的函数
|
|
94
|
+
const wrappedFn = async function(...args) {
|
|
95
|
+
// 1. 生成缓存键
|
|
96
|
+
let cacheKey;
|
|
97
|
+
try {
|
|
98
|
+
cacheKey = keyBuilder
|
|
99
|
+
? `${namespace}:${keyBuilder(...args)}`
|
|
100
|
+
: `${namespace}:${fn.name}:${CacheFactory.stableStringify(args)}`;
|
|
101
|
+
} catch (err) {
|
|
102
|
+
// 键生成失败,直接执行原函数
|
|
103
|
+
if (enableStats) stats.errors++;
|
|
104
|
+
return await fn.apply(this, args);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// 2. 尝试从缓存读取
|
|
108
|
+
const startTime = Date.now();
|
|
109
|
+
let cached = CACHE_MISS;
|
|
110
|
+
try {
|
|
111
|
+
const value = await cacheInstance.get(cacheKey);
|
|
112
|
+
// 只有当缓存中有这个键时才认为命中(即使值是 undefined)
|
|
113
|
+
// 我们需要区分"缓存未命中"和"缓存值是 undefined"
|
|
114
|
+
// 但是 cache.get() 无法区分,所以我们使用 exists() 来判断
|
|
115
|
+
const exists = await cacheInstance.exists(cacheKey);
|
|
116
|
+
if (exists) {
|
|
117
|
+
cached = value;
|
|
118
|
+
}
|
|
119
|
+
} catch (err) {
|
|
120
|
+
if (enableStats) stats.errors++;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (cached !== CACHE_MISS) {
|
|
124
|
+
if (enableStats) {
|
|
125
|
+
stats.hits++;
|
|
126
|
+
stats.calls++;
|
|
127
|
+
stats.totalTime += Date.now() - startTime;
|
|
128
|
+
}
|
|
129
|
+
return cached;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// 3. 并发控制(防止缓存击穿)
|
|
133
|
+
if (__inflightFunctions.has(cacheKey)) {
|
|
134
|
+
try {
|
|
135
|
+
const result = await __inflightFunctions.get(cacheKey);
|
|
136
|
+
if (enableStats) {
|
|
137
|
+
stats.hits++;
|
|
138
|
+
stats.calls++;
|
|
139
|
+
}
|
|
140
|
+
return result;
|
|
141
|
+
} catch (err) {
|
|
142
|
+
// 并发请求失败,继续执行
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// 4. 执行原函数
|
|
147
|
+
const promise = (async () => {
|
|
148
|
+
try {
|
|
149
|
+
const result = await fn.apply(this, args);
|
|
150
|
+
|
|
151
|
+
// 5. 条件缓存
|
|
152
|
+
let shouldCache = true;
|
|
153
|
+
if (condition) {
|
|
154
|
+
try {
|
|
155
|
+
shouldCache = condition(result);
|
|
156
|
+
} catch (err) {
|
|
157
|
+
// 条件函数失败,默认缓存
|
|
158
|
+
if (enableStats) stats.errors++;
|
|
159
|
+
shouldCache = true;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (shouldCache) {
|
|
164
|
+
try {
|
|
165
|
+
await cacheInstance.set(cacheKey, result, ttl);
|
|
166
|
+
} catch (err) {
|
|
167
|
+
if (enableStats) stats.errors++;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return result;
|
|
172
|
+
} finally {
|
|
173
|
+
__inflightFunctions.delete(cacheKey);
|
|
174
|
+
}
|
|
175
|
+
})();
|
|
176
|
+
|
|
177
|
+
__inflightFunctions.set(cacheKey, promise);
|
|
178
|
+
|
|
179
|
+
try {
|
|
180
|
+
const result = await promise;
|
|
181
|
+
if (enableStats) {
|
|
182
|
+
stats.misses++;
|
|
183
|
+
stats.calls++;
|
|
184
|
+
stats.totalTime += Date.now() - startTime;
|
|
185
|
+
}
|
|
186
|
+
return result;
|
|
187
|
+
} catch (err) {
|
|
188
|
+
if (enableStats) {
|
|
189
|
+
stats.errors++;
|
|
190
|
+
stats.calls++;
|
|
191
|
+
}
|
|
192
|
+
throw err;
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
// 挂载统计方法
|
|
197
|
+
wrappedFn.getCacheStats = () => ({
|
|
198
|
+
...stats,
|
|
199
|
+
hitRate: stats.hits / (stats.hits + stats.misses) || 0,
|
|
200
|
+
avgTime: stats.totalTime / stats.calls || 0
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
return wrappedFn;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* 函数缓存管理类
|
|
208
|
+
*
|
|
209
|
+
* @class FunctionCache
|
|
210
|
+
*
|
|
211
|
+
* @example
|
|
212
|
+
* const fnCache = new FunctionCache(msq);
|
|
213
|
+
*
|
|
214
|
+
* // 注册函数
|
|
215
|
+
* fnCache.register('getUserProfile', getUserProfileFn, { ttl: 300000 });
|
|
216
|
+
*
|
|
217
|
+
* // 执行函数
|
|
218
|
+
* const profile = await fnCache.execute('getUserProfile', 'user123');
|
|
219
|
+
*
|
|
220
|
+
* // 失效缓存
|
|
221
|
+
* await fnCache.invalidate('getUserProfile', 'user123');
|
|
222
|
+
*
|
|
223
|
+
* // 查看统计
|
|
224
|
+
* const stats = fnCache.getStats('getUserProfile');
|
|
225
|
+
*/
|
|
226
|
+
class FunctionCache {
|
|
227
|
+
/**
|
|
228
|
+
* 构造函数
|
|
229
|
+
* @param {Object} msq - MonSQLize 实例(可选)
|
|
230
|
+
* @param {Object} options - 配置选项
|
|
231
|
+
* @param {string} [options.namespace='action'] - 命名空间
|
|
232
|
+
* @param {number} [options.defaultTTL=60000] - 默认 TTL(毫秒)
|
|
233
|
+
* @param {boolean} [options.enableStats=true] - 启用统计
|
|
234
|
+
*/
|
|
235
|
+
constructor(msq, options = {}) {
|
|
236
|
+
// 参数验证
|
|
237
|
+
if (options && typeof options !== 'object') {
|
|
238
|
+
throw new Error('options must be an object');
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
this.cache = msq ? msq.getCache() : CacheFactory.createDefault();
|
|
242
|
+
|
|
243
|
+
// 验证缓存实例
|
|
244
|
+
if (!CacheFactory.isValidCache(this.cache)) {
|
|
245
|
+
throw new Error('Invalid cache instance from MonSQLize');
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
this.functions = new Map();
|
|
249
|
+
this.stats = new Map();
|
|
250
|
+
this.options = {
|
|
251
|
+
namespace: options.namespace || 'action',
|
|
252
|
+
defaultTTL: options.defaultTTL || 60000,
|
|
253
|
+
enableStats: options.enableStats !== false
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
// 参数验证
|
|
257
|
+
if (typeof this.options.namespace !== 'string') {
|
|
258
|
+
throw new Error('namespace must be a string');
|
|
259
|
+
}
|
|
260
|
+
if (typeof this.options.defaultTTL !== 'number' || this.options.defaultTTL < 0) {
|
|
261
|
+
throw new Error('defaultTTL must be a non-negative number');
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* 注册函数
|
|
267
|
+
* @param {string} name - 函数名称
|
|
268
|
+
* @param {Function} fn - 函数实现
|
|
269
|
+
* @param {Object} options - 缓存配置
|
|
270
|
+
* @param {number} [options.ttl] - 缓存过期时间(毫秒)
|
|
271
|
+
* @param {Array<string>} [options.collections] - 依赖的 MongoDB 集合名称(自动失效)
|
|
272
|
+
*/
|
|
273
|
+
register(name, fn, options = {}) {
|
|
274
|
+
if (!name || typeof name !== 'string') {
|
|
275
|
+
throw new Error('Function name must be a non-empty string');
|
|
276
|
+
}
|
|
277
|
+
if (typeof fn !== 'function') {
|
|
278
|
+
throw new Error('fn must be a function');
|
|
279
|
+
}
|
|
280
|
+
if (options && typeof options !== 'object') {
|
|
281
|
+
throw new Error('options must be an object');
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const cachedFn = withCache(fn, {
|
|
285
|
+
...options,
|
|
286
|
+
cache: this.cache,
|
|
287
|
+
namespace: `${this.options.namespace}:${name}`,
|
|
288
|
+
ttl: options.ttl !== undefined ? options.ttl : this.options.defaultTTL
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
this.functions.set(name, cachedFn);
|
|
292
|
+
|
|
293
|
+
if (this.options.enableStats) {
|
|
294
|
+
this.stats.set(name, {
|
|
295
|
+
hits: 0,
|
|
296
|
+
misses: 0,
|
|
297
|
+
errors: 0,
|
|
298
|
+
calls: 0,
|
|
299
|
+
totalTime: 0
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// 🆕 v1.1.4: 建立集合依赖关系(用于自动失效)
|
|
304
|
+
if (options.collections && Array.isArray(options.collections)) {
|
|
305
|
+
this._registerDependencies(name, options.collections);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
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
|
+
|
|
353
|
+
/**
|
|
354
|
+
* 执行函数
|
|
355
|
+
* @param {string} name - 函数名称
|
|
356
|
+
* @param {...any} args - 函数参数
|
|
357
|
+
* @returns {Promise<any>}
|
|
358
|
+
*/
|
|
359
|
+
async execute(name, ...args) {
|
|
360
|
+
const fn = this.functions.get(name);
|
|
361
|
+
if (!fn) {
|
|
362
|
+
throw new Error(`Function '${name}' not registered`);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
const startTime = Date.now();
|
|
366
|
+
try {
|
|
367
|
+
const result = await fn(...args);
|
|
368
|
+
|
|
369
|
+
if (this.options.enableStats) {
|
|
370
|
+
const stats = this.stats.get(name);
|
|
371
|
+
if (stats) {
|
|
372
|
+
stats.calls++;
|
|
373
|
+
stats.totalTime += Date.now() - startTime;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
return result;
|
|
378
|
+
} catch (err) {
|
|
379
|
+
if (this.options.enableStats) {
|
|
380
|
+
const stats = this.stats.get(name);
|
|
381
|
+
if (stats) {
|
|
382
|
+
stats.errors++;
|
|
383
|
+
stats.calls++;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
throw err;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* 失效缓存
|
|
392
|
+
* @param {string} name - 函数名称
|
|
393
|
+
* @param {...any} args - 函数参数
|
|
394
|
+
*/
|
|
395
|
+
async invalidate(name, ...args) {
|
|
396
|
+
if (!name || typeof name !== 'string') {
|
|
397
|
+
throw new Error('Function name must be a non-empty string');
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
const fn = this.functions.get(name);
|
|
401
|
+
if (!fn) {
|
|
402
|
+
throw new Error(`Function '${name}' not registered`);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// 获取原始函数(从缓存的函数中提取)
|
|
406
|
+
const originalFn = fn;
|
|
407
|
+
|
|
408
|
+
// 缓存键格式:${namespace}:${name}:${fnName}:${args}
|
|
409
|
+
// 但是因为我们在 register 时已经将 name 加入 namespace,
|
|
410
|
+
// withCache 会再拼接 fn.name,所以这里需要使用正确的完整键
|
|
411
|
+
// 实际键格式:${this.options.namespace}:${name}:${fn.name}:${args}
|
|
412
|
+
|
|
413
|
+
// 构建通配符模式来删除所有匹配的缓存
|
|
414
|
+
const pattern = `${this.options.namespace}:${name}:*${CacheFactory.stableStringify(args)}*`;
|
|
415
|
+
await this.cache.delPattern(pattern);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* 批量失效缓存
|
|
420
|
+
* @param {string} pattern - 失效模式(支持通配符 *)
|
|
421
|
+
* @returns {Promise<number>} 删除的缓存条目数
|
|
422
|
+
*/
|
|
423
|
+
async invalidatePattern(pattern) {
|
|
424
|
+
if (!pattern || typeof pattern !== 'string') {
|
|
425
|
+
throw new Error('Pattern must be a non-empty string');
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
const fullPattern = `${this.options.namespace}:${pattern}`;
|
|
429
|
+
return await this.cache.delPattern(fullPattern);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* 获取统计信息
|
|
434
|
+
* @param {string} [name] - 函数名称(可选)
|
|
435
|
+
* @returns {Object|null}
|
|
436
|
+
*/
|
|
437
|
+
getStats(name) {
|
|
438
|
+
if (name) {
|
|
439
|
+
const stats = this.stats.get(name);
|
|
440
|
+
if (!stats) return null;
|
|
441
|
+
return {
|
|
442
|
+
...stats,
|
|
443
|
+
hitRate: stats.hits / (stats.hits + stats.misses) || 0,
|
|
444
|
+
avgTime: stats.totalTime / stats.calls || 0
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
const allStats = {};
|
|
449
|
+
for (const [fnName, stats] of this.stats.entries()) {
|
|
450
|
+
allStats[fnName] = {
|
|
451
|
+
...stats,
|
|
452
|
+
hitRate: stats.hits / (stats.hits + stats.misses) || 0,
|
|
453
|
+
avgTime: stats.totalTime / stats.calls || 0
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
return allStats;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* 列出所有已注册的函数
|
|
461
|
+
* @returns {string[]}
|
|
462
|
+
*/
|
|
463
|
+
list() {
|
|
464
|
+
return Array.from(this.functions.keys());
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* 重置统计信息
|
|
469
|
+
* @param {string} [name] - 函数名称(可选)
|
|
470
|
+
*/
|
|
471
|
+
resetStats(name) {
|
|
472
|
+
if (name) {
|
|
473
|
+
const stats = this.stats.get(name);
|
|
474
|
+
if (stats) {
|
|
475
|
+
Object.assign(stats, {
|
|
476
|
+
hits: 0,
|
|
477
|
+
misses: 0,
|
|
478
|
+
errors: 0,
|
|
479
|
+
calls: 0,
|
|
480
|
+
totalTime: 0
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
} else {
|
|
484
|
+
for (const stats of this.stats.values()) {
|
|
485
|
+
Object.assign(stats, {
|
|
486
|
+
hits: 0,
|
|
487
|
+
misses: 0,
|
|
488
|
+
errors: 0,
|
|
489
|
+
calls: 0,
|
|
490
|
+
totalTime: 0
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* 清空所有已注册的函数
|
|
498
|
+
*/
|
|
499
|
+
clear() {
|
|
500
|
+
this.functions.clear();
|
|
501
|
+
this.stats.clear();
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
module.exports = {
|
|
506
|
+
withCache,
|
|
507
|
+
FunctionCache
|
|
508
|
+
};
|
|
509
|
+
|
package/lib/index.js
CHANGED
|
@@ -918,6 +918,10 @@ module.exports = class {
|
|
|
918
918
|
// ========== 导出 Model 类 ==========
|
|
919
919
|
module.exports.Model = require('./model');
|
|
920
920
|
|
|
921
|
+
// ========== 导出 ConnectionPoolManager ==========
|
|
922
|
+
// 🆕 v1.0.8: 多连接池管理
|
|
923
|
+
module.exports.ConnectionPoolManager = require('./infrastructure/ConnectionPoolManager');
|
|
924
|
+
|
|
921
925
|
// ========== 导出表达式工厂函数 ==========
|
|
922
926
|
// 🆕 v1.0.9: 统一表达式语法
|
|
923
927
|
const createExpression = require('./expression');
|
|
@@ -925,3 +929,11 @@ const createExpression = require('./expression');
|
|
|
925
929
|
// 导出表达式创建函数
|
|
926
930
|
module.exports.expr = createExpression; // v1.0.9: 统一表达式语法 ⭐
|
|
927
931
|
module.exports.createExpression = createExpression; // 完整版别名
|
|
932
|
+
|
|
933
|
+
// ========== 导出函数缓存 ==========
|
|
934
|
+
// 🆕 v1.1.4: 通用函数缓存
|
|
935
|
+
const { withCache, FunctionCache } = require('./function-cache');
|
|
936
|
+
|
|
937
|
+
module.exports.withCache = withCache;
|
|
938
|
+
module.exports.FunctionCache = FunctionCache;
|
|
939
|
+
|
|
@@ -203,17 +203,18 @@ class ConnectionPoolManager {
|
|
|
203
203
|
* @param {Object} options - 选项
|
|
204
204
|
* @param {string} [options.pool] - 手动指定连接池名称
|
|
205
205
|
* @param {Object} [options.poolPreference] - 连接池偏好
|
|
206
|
-
|
|
206
|
+
* @returns {{name: string, client: MongoClient, db: Db, collection: Function}}
|
|
207
207
|
* @throws {Error} 如果无可用连接池
|
|
208
208
|
*/
|
|
209
209
|
selectPool(operation, options = {}) {
|
|
210
210
|
// 手动指定连接池
|
|
211
211
|
if (options.pool) {
|
|
212
|
-
const
|
|
213
|
-
if (!
|
|
212
|
+
const poolData = this._pools.get(options.pool);
|
|
213
|
+
if (!poolData) {
|
|
214
214
|
throw new Error(`Pool '${options.pool}' not found`);
|
|
215
215
|
}
|
|
216
|
-
|
|
216
|
+
const config = this._configs.get(options.pool);
|
|
217
|
+
return this._createPoolResult(options.pool, poolData.client, config);
|
|
217
218
|
}
|
|
218
219
|
|
|
219
220
|
// 获取健康的连接池列表
|
|
@@ -240,15 +241,45 @@ class ConnectionPoolManager {
|
|
|
240
241
|
...options
|
|
241
242
|
});
|
|
242
243
|
|
|
243
|
-
const
|
|
244
|
-
if (!
|
|
244
|
+
const poolData = this._pools.get(poolName);
|
|
245
|
+
if (!poolData) {
|
|
245
246
|
throw new Error(`Selected pool '${poolName}' not available`);
|
|
246
247
|
}
|
|
247
248
|
|
|
248
249
|
// 记录统计
|
|
249
250
|
this._stats.recordSelection(poolName, operation);
|
|
250
251
|
|
|
251
|
-
|
|
252
|
+
const config = this._configs.get(poolName);
|
|
253
|
+
return this._createPoolResult(poolName, poolData.client, config);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* 创建连接池结果对象(包含 db 和 collection 访问器)
|
|
258
|
+
*
|
|
259
|
+
* @private
|
|
260
|
+
* @param {string} name - 连接池名称
|
|
261
|
+
* @param {MongoClient} client - MongoDB 客户端
|
|
262
|
+
* @param {Object} config - 连接池配置
|
|
263
|
+
* @returns {{name: string, client: MongoClient, db: Db, collection: Function}}
|
|
264
|
+
*/
|
|
265
|
+
_createPoolResult(name, client, config) {
|
|
266
|
+
// 从 URI 中提取数据库名称
|
|
267
|
+
let dbName;
|
|
268
|
+
try {
|
|
269
|
+
const url = new URL(config.uri);
|
|
270
|
+
dbName = url.pathname.slice(1) || 'test';
|
|
271
|
+
} catch (err) {
|
|
272
|
+
dbName = 'test';
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const db = client.db(dbName);
|
|
276
|
+
|
|
277
|
+
return {
|
|
278
|
+
name,
|
|
279
|
+
client,
|
|
280
|
+
db,
|
|
281
|
+
collection: (collectionName) => db.collection(collectionName)
|
|
282
|
+
};
|
|
252
283
|
}
|
|
253
284
|
|
|
254
285
|
/**
|
package/lib/multi-level-cache.js
CHANGED
|
@@ -157,10 +157,33 @@ class MultiLevelCache {
|
|
|
157
157
|
}
|
|
158
158
|
|
|
159
159
|
async delPattern(pattern) {
|
|
160
|
-
|
|
160
|
+
let deleted = 0;
|
|
161
|
+
|
|
162
|
+
// 删除本地缓存
|
|
163
|
+
try {
|
|
164
|
+
deleted = await this.local.delPattern(pattern);
|
|
165
|
+
} catch(err) {
|
|
166
|
+
// 忽略本地删除错误
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// 删除远端缓存(如果存在)
|
|
170
|
+
if (this.remote && typeof this.remote.delPattern === 'function') {
|
|
171
|
+
try {
|
|
172
|
+
await this._withTimeout(this.remote.delPattern(pattern));
|
|
173
|
+
} catch(err) {
|
|
174
|
+
// 远端删除失败,降级处理
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
161
178
|
// 向集群广播(可选)
|
|
162
|
-
try {
|
|
163
|
-
|
|
179
|
+
try {
|
|
180
|
+
if (this.publish) {
|
|
181
|
+
this.publish({ type: 'invalidate', pattern, ts: Date.now() });
|
|
182
|
+
}
|
|
183
|
+
} catch(err) {
|
|
184
|
+
// 忽略广播错误
|
|
185
|
+
}
|
|
186
|
+
|
|
164
187
|
return deleted;
|
|
165
188
|
}
|
|
166
189
|
|
|
@@ -123,9 +123,16 @@ function convertObjectIdStrings(obj, fieldPath = '', depth = 0, visited = new We
|
|
|
123
123
|
logger = null,
|
|
124
124
|
excludeFields = [],
|
|
125
125
|
customFieldPatterns = [],
|
|
126
|
-
maxDepth = 10
|
|
126
|
+
maxDepth = 10,
|
|
127
|
+
verbose = false, // 详细日志模式(默认关闭)
|
|
128
|
+
silent = true, // 🆕 静默模式(默认开启,不输出任何日志)
|
|
129
|
+
_conversionStats = null // 内部统计对象(递归传递)
|
|
127
130
|
} = options;
|
|
128
131
|
|
|
132
|
+
// 初始化统计(仅在顶层调用)
|
|
133
|
+
const stats = _conversionStats || { count: 0, fields: [] };
|
|
134
|
+
const isTopLevel = depth === 0 && !_conversionStats;
|
|
135
|
+
|
|
129
136
|
try {
|
|
130
137
|
// 1. 深度保护(防止栈溢出)
|
|
131
138
|
if (depth > maxDepth) {
|
|
@@ -157,7 +164,15 @@ function convertObjectIdStrings(obj, fieldPath = '', depth = 0, visited = new We
|
|
|
157
164
|
const hexString = obj.toString();
|
|
158
165
|
if (isValidObjectIdString(hexString)) {
|
|
159
166
|
const converted = new ObjectId(hexString);
|
|
160
|
-
|
|
167
|
+
|
|
168
|
+
// 更新统计
|
|
169
|
+
stats.count++;
|
|
170
|
+
if (stats.fields.length < 5) { // 只记录前5个字段路径
|
|
171
|
+
stats.fields.push(fieldPath);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// 详细模式:每次转换都输出日志(仅在非静默模式下)
|
|
175
|
+
if (!silent && verbose && logger && logger.debug) {
|
|
161
176
|
logger.debug('[ObjectId Converter] Cross-version ObjectId converted', {
|
|
162
177
|
from: obj.constructor.name,
|
|
163
178
|
to: 'ObjectId',
|
|
@@ -165,6 +180,7 @@ function convertObjectIdStrings(obj, fieldPath = '', depth = 0, visited = new We
|
|
|
165
180
|
fieldPath
|
|
166
181
|
});
|
|
167
182
|
}
|
|
183
|
+
|
|
168
184
|
return converted;
|
|
169
185
|
}
|
|
170
186
|
} catch (error) {
|
|
@@ -212,7 +228,10 @@ function convertObjectIdStrings(obj, fieldPath = '', depth = 0, visited = new We
|
|
|
212
228
|
let hasConverted = false;
|
|
213
229
|
const converted = obj.map((item, index) => {
|
|
214
230
|
const itemPath = `${fieldPath}[${index}]`;
|
|
215
|
-
const newItem = convertObjectIdStrings(item, itemPath, depth + 1, visited,
|
|
231
|
+
const newItem = convertObjectIdStrings(item, itemPath, depth + 1, visited, {
|
|
232
|
+
...options,
|
|
233
|
+
_conversionStats: stats // 传递统计对象
|
|
234
|
+
});
|
|
216
235
|
if (newItem !== item) {
|
|
217
236
|
hasConverted = true;
|
|
218
237
|
}
|
|
@@ -276,7 +295,10 @@ function convertObjectIdStrings(obj, fieldPath = '', depth = 0, visited = new We
|
|
|
276
295
|
}
|
|
277
296
|
} else {
|
|
278
297
|
// 6.5 递归处理
|
|
279
|
-
const newValue = convertObjectIdStrings(value, currentPath, depth + 1, visited,
|
|
298
|
+
const newValue = convertObjectIdStrings(value, currentPath, depth + 1, visited, {
|
|
299
|
+
...options,
|
|
300
|
+
_conversionStats: stats // 传递统计对象
|
|
301
|
+
});
|
|
280
302
|
if (newValue !== value) {
|
|
281
303
|
hasConverted = true;
|
|
282
304
|
}
|
|
@@ -302,6 +324,18 @@ function convertObjectIdStrings(obj, fieldPath = '', depth = 0, visited = new We
|
|
|
302
324
|
}
|
|
303
325
|
// 异常时返回原值,确保不中断流程
|
|
304
326
|
return obj;
|
|
327
|
+
} finally {
|
|
328
|
+
// 顶层调用:输出转换摘要(仅在非静默模式且有转换时)
|
|
329
|
+
if (!silent && isTopLevel && stats.count > 0 && logger && logger.debug) {
|
|
330
|
+
const message = stats.count === 1
|
|
331
|
+
? 'Converted 1 cross-version ObjectId'
|
|
332
|
+
: `Converted ${stats.count} cross-version ObjectIds`;
|
|
333
|
+
|
|
334
|
+
logger.debug(`[ObjectId Converter] ${message}`, {
|
|
335
|
+
count: stats.count,
|
|
336
|
+
sampleFields: stats.fields.slice(0, 3) // 只显示前3个字段示例
|
|
337
|
+
});
|
|
338
|
+
}
|
|
305
339
|
}
|
|
306
340
|
}
|
|
307
341
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "monsqlize",
|
|
3
|
-
"version": "1.1.
|
|
4
|
-
"description": "A lightweight MongoDB ORM with multi-level caching, transaction support, distributed features, Saga distributed transactions,
|
|
3
|
+
"version": "1.1.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",
|
|
7
7
|
"type": "commonjs",
|