monsqlize 1.3.1 → 2.0.1

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.
Files changed (114) hide show
  1. package/CHANGELOG.md +506 -235
  2. package/LICENSE +201 -21
  3. package/README.md +542 -928
  4. package/changelogs/README.md +163 -0
  5. package/changelogs/v2.0.0.md +222 -0
  6. package/changelogs/v2.0.1.md +39 -0
  7. package/dist/cjs/index.cjs +10788 -0
  8. package/dist/cjs/mongodb/common/transaction-aware.cjs +10 -0
  9. package/dist/cjs/transaction/CacheLockManager.cjs +100 -0
  10. package/dist/cjs/transaction/Transaction.cjs +158 -0
  11. package/dist/cjs/transaction/TransactionManager.cjs +298 -0
  12. package/dist/esm/index.mjs +10838 -0
  13. package/dist/types/base.d.ts +81 -0
  14. package/dist/types/collection.d.ts +1031 -0
  15. package/dist/types/expression.d.ts +115 -0
  16. package/dist/types/index.d.ts +23 -0
  17. package/dist/types/lock.d.ts +74 -0
  18. package/dist/types/model.d.ts +530 -0
  19. package/dist/types/mongodb.d.ts +49 -0
  20. package/dist/types/monsqlize.d.ts +491 -0
  21. package/dist/types/pool.d.ts +84 -0
  22. package/dist/types/runtime.d.ts +362 -0
  23. package/dist/types/saga.d.ts +143 -0
  24. package/dist/types/slow-query-log.d.ts +126 -0
  25. package/dist/types/sync.d.ts +103 -0
  26. package/dist/types/transaction.d.ts +132 -0
  27. package/package.json +120 -117
  28. package/index.d.ts +0 -1289
  29. package/lib/cache.js +0 -491
  30. package/lib/common/cursor.js +0 -58
  31. package/lib/common/docs-urls.js +0 -72
  32. package/lib/common/index-options.js +0 -222
  33. package/lib/common/log.js +0 -60
  34. package/lib/common/namespace.js +0 -21
  35. package/lib/common/normalize.js +0 -33
  36. package/lib/common/page-result.js +0 -42
  37. package/lib/common/runner.js +0 -56
  38. package/lib/common/server-features.js +0 -231
  39. package/lib/common/shape-builders.js +0 -26
  40. package/lib/common/validation.js +0 -112
  41. package/lib/connect.js +0 -76
  42. package/lib/constants.js +0 -54
  43. package/lib/count-queue.js +0 -187
  44. package/lib/distributed-cache-invalidator.js +0 -259
  45. package/lib/errors.js +0 -167
  46. package/lib/index.js +0 -461
  47. package/lib/infrastructure/ssh-tunnel-ssh2.js +0 -211
  48. package/lib/infrastructure/ssh-tunnel.js +0 -40
  49. package/lib/infrastructure/uri-parser.js +0 -35
  50. package/lib/lock/Lock.js +0 -66
  51. package/lib/lock/errors.js +0 -27
  52. package/lib/lock/index.js +0 -12
  53. package/lib/logger.js +0 -224
  54. package/lib/model/examples/test.js +0 -114
  55. package/lib/mongodb/common/accessor-helpers.js +0 -58
  56. package/lib/mongodb/common/agg-pipeline.js +0 -32
  57. package/lib/mongodb/common/iid.js +0 -27
  58. package/lib/mongodb/common/lexicographic-expr.js +0 -52
  59. package/lib/mongodb/common/shape.js +0 -31
  60. package/lib/mongodb/common/sort.js +0 -38
  61. package/lib/mongodb/common/transaction-aware.js +0 -24
  62. package/lib/mongodb/connect.js +0 -233
  63. package/lib/mongodb/index.js +0 -591
  64. package/lib/mongodb/management/admin-ops.js +0 -199
  65. package/lib/mongodb/management/bookmark-ops.js +0 -166
  66. package/lib/mongodb/management/cache-ops.js +0 -49
  67. package/lib/mongodb/management/collection-ops.js +0 -386
  68. package/lib/mongodb/management/database-ops.js +0 -201
  69. package/lib/mongodb/management/index-ops.js +0 -474
  70. package/lib/mongodb/management/index.js +0 -16
  71. package/lib/mongodb/management/namespace.js +0 -30
  72. package/lib/mongodb/management/validation-ops.js +0 -267
  73. package/lib/mongodb/queries/aggregate.js +0 -142
  74. package/lib/mongodb/queries/chain.js +0 -630
  75. package/lib/mongodb/queries/count.js +0 -98
  76. package/lib/mongodb/queries/distinct.js +0 -77
  77. package/lib/mongodb/queries/find-and-count.js +0 -192
  78. package/lib/mongodb/queries/find-by-ids.js +0 -235
  79. package/lib/mongodb/queries/find-one-by-id.js +0 -170
  80. package/lib/mongodb/queries/find-one.js +0 -70
  81. package/lib/mongodb/queries/find-page.js +0 -577
  82. package/lib/mongodb/queries/find.js +0 -170
  83. package/lib/mongodb/queries/index.js +0 -50
  84. package/lib/mongodb/queries/watch.js +0 -537
  85. package/lib/mongodb/writes/delete-many.js +0 -190
  86. package/lib/mongodb/writes/delete-one.js +0 -182
  87. package/lib/mongodb/writes/find-one-and-delete.js +0 -202
  88. package/lib/mongodb/writes/find-one-and-replace.js +0 -238
  89. package/lib/mongodb/writes/find-one-and-update.js +0 -239
  90. package/lib/mongodb/writes/increment-one.js +0 -252
  91. package/lib/mongodb/writes/index.js +0 -41
  92. package/lib/mongodb/writes/insert-batch.js +0 -507
  93. package/lib/mongodb/writes/insert-many.js +0 -227
  94. package/lib/mongodb/writes/insert-one.js +0 -180
  95. package/lib/mongodb/writes/replace-one.js +0 -215
  96. package/lib/mongodb/writes/result-handler.js +0 -236
  97. package/lib/mongodb/writes/update-many.js +0 -221
  98. package/lib/mongodb/writes/update-one.js +0 -223
  99. package/lib/mongodb/writes/upsert-one.js +0 -206
  100. package/lib/multi-level-cache.js +0 -189
  101. package/lib/operators.js +0 -330
  102. package/lib/redis-cache-adapter.js +0 -237
  103. package/lib/slow-query-log/base-storage.js +0 -69
  104. package/lib/slow-query-log/batch-queue.js +0 -96
  105. package/lib/slow-query-log/config-manager.js +0 -195
  106. package/lib/slow-query-log/index.js +0 -237
  107. package/lib/slow-query-log/mongodb-storage.js +0 -323
  108. package/lib/slow-query-log/query-hash.js +0 -38
  109. package/lib/transaction/CacheLockManager.js +0 -161
  110. package/lib/transaction/DistributedCacheLockManager.js +0 -474
  111. package/lib/transaction/Transaction.js +0 -314
  112. package/lib/transaction/TransactionManager.js +0 -266
  113. package/lib/transaction/index.js +0 -10
  114. package/lib/utils/objectid-converter.js +0 -566
@@ -1,114 +0,0 @@
1
-
2
-
3
- module.exports = {
4
-
5
- // 枚举配置
6
- enums: {
7
- role: ['admin', 'user'],
8
- status: ['active', 'inactive']
9
- },
10
-
11
- // 定义model
12
- schema:(sc)=>{
13
- return {
14
- username: sc.string().min(3).max(30).required(),
15
- password: sc.string().pattern(/^[a-zA-Z0-9]{6,30}$/).required(),
16
- age: sc.number().integer().min(0).default(18), // 默认值
17
- role: sc.string().valid('admin', 'user').default('user'),
18
- };
19
- },
20
-
21
- // 自定义方法
22
- methods: (model)=>{
23
- return {
24
- // 实例方法
25
- instance: {
26
- checkPassword(password) {
27
- return this.password === password;
28
- },
29
- async getPosts() {
30
- // return await model.find({ userId: this._id });
31
- }
32
- },
33
- // 静态方法
34
- static: {
35
- findByName(name) {
36
- return this.find({ username: name });
37
- }
38
- }
39
- };
40
- },
41
-
42
- // 支持操作前、后处理
43
- hooks:(model)=>{
44
- return {
45
- find: {
46
- before:(ctx,options)=>{},
47
- after:(ctx,docs,result)=>{},
48
- },
49
- insert:{
50
- before:async (ctx,docs)=>{
51
- // ctx.session = await model.startTransaction(); // ctx 里传递事务对象
52
- // return ctx.data;
53
- },
54
- after:async (ctx,docs,result)=>{
55
- // await ctx.session.commitTransaction();
56
- },
57
- },
58
- update:{
59
- before:(ctx,options)=>{},
60
- after:(ctx,result)=>{},
61
- },
62
- delete:{
63
- before:(ctx,options)=>{},
64
- after:(ctx,result)=>{},
65
- }
66
- };
67
- },
68
-
69
- // 创建索引
70
- indexes: [
71
- { key: { username: 1 }, unique: true }, // 唯一索引
72
- { key: { age: -1 } }, // 普通索引,降序
73
- ],
74
-
75
- // 关系
76
- relations: {
77
- posts: {
78
- type: 'hasMany', // 一对多
79
- target: 'Post', // 目标模型
80
- foreignKey: 'userId', // 外键字段(存在哪张表里)
81
- localKey: '_id', // 本表对应字段
82
- as: 'posts', // 实例访问属性 user.posts
83
- cascade: false // 是否级联删除/更新
84
- },
85
- profile: {
86
- type: 'hasOne', // 一对一
87
- target: 'Profile',
88
- foreignKey: 'userId',
89
- localKey: '_id',
90
- as: 'profile', // 实例访问属性 user.profile
91
- cascade: true, // 删除用户时级联删除 profile
92
- required: false // 是否必须关联
93
- },
94
- roles: { // 多对多
95
- type: 'manyToMany',
96
- target: 'Role',
97
- through: 'UserRole', // 中间表
98
- foreignKey: 'userId',
99
- otherKey: 'roleId',
100
- as: 'roles'
101
- }
102
- },
103
-
104
- // 选项
105
- options: {
106
- timestamps: true, // 自动维护 createdAt / updatedAt
107
- softDelete: true, // 启用 deletedAt 替代物理删除,deletedAt = null → 正常数据,deletedAt = Date → 已删除
108
- sync: true, // 启用 index 索引自动同步
109
- version: true, // 自动维护 __v 版本号
110
- }
111
-
112
- // 自动索引 / 慢查询统计 → 全局 ORM 层统一管理
113
- // 恢复软删除 → 全局方法注入到每个启用 softDelete 的模型
114
- };
@@ -1,58 +0,0 @@
1
- // Mongo 适配器辅助工具:慢查询日志整形器(脱敏)和缓存键构建器
2
- // - mongoSlowLogShaper: 通过组合通用的元信息与 Mongo 的结构,生成安全的慢查询日志附加字段
3
- // - mongoKeyBuilder: 当 op==='findPage' 时,注入 pipelineHash(对 pipeline 进行 stableStringify 后再做 sha256)
4
- //并在键值材料中省略原始 pipeline,以保持键的简短和稳定
5
-
6
-
7
- const crypto = require('crypto');
8
- const CacheFactory = require('../../cache');
9
- const { buildCommonLogExtra } = require('../../common/shape-builders');
10
- const { shapeQuery, shapeProjection, shapeSort } = require('./shape');
11
- const { normalizeForCache } = require('../../utils/objectid-converter');
12
-
13
- /**
14
- * Mongo 专属:慢日志去敏形状构造器
15
- * @param {object} options - 原始调用选项(仅读取形状/标记,不含具体值)
16
- * @returns {object} 去敏后的形状对象(仅字段集合/标记位)
17
- */
18
- function mongoSlowLogShaper(options) {
19
- const extra = buildCommonLogExtra(options);
20
- if (options?.query) extra.queryShape = shapeQuery(options.query);
21
- if (options?.projection) extra.projectionShape = shapeProjection(options.projection);
22
- if (options?.sort) extra.sortShape = shapeSort(options.sort);
23
- return extra;
24
- }
25
-
26
- /**
27
- * Mongo 专属:缓存键构造器
28
- * 仅在 findPage 时:对 pipeline 做稳定串行化并 sha256 → pipelineHash;
29
- * 同时从参与键的 options 中去除原始 pipeline,避免键过长与不稳定。
30
- *
31
- * ✅ v1.3.0 新增:标准化 ObjectId
32
- * - 将所有 ObjectId 实例转换为字符串
33
- * - 确保 ObjectId('xxx') 和 'xxx' 生成相同的缓存键
34
- * - 避免缓存失效和重复查询
35
- *
36
- * @param {string} op
37
- * @param {object} options
38
- * @returns {object} 用于参与缓存键构造的 options 视图
39
- */
40
- function mongoKeyBuilder(op, options) {
41
- const opts = options || {};
42
-
43
- // ✅ 标准化 options 中的 ObjectId(转为字符串)
44
- // 确保查询缓存键一致性
45
- const normalizedOpts = normalizeForCache(opts);
46
-
47
- if (op !== 'findPage') return normalizedOpts;
48
-
49
- // findPage 特殊处理:pipeline 转 hash
50
- const pipelineHash = crypto
51
- .createHash('sha256')
52
- .update(CacheFactory.stableStringify(normalizedOpts.pipeline || []))
53
- .digest('hex');
54
- const { pipeline, ...rest } = normalizedOpts;
55
- return { ...rest, pipelineHash };
56
- }
57
-
58
- module.exports = { mongoSlowLogShaper, mongoKeyBuilder };
@@ -1,32 +0,0 @@
1
- /**
2
- * Mongo 聚合分页管道构造器(方案 A:先分页后联表)
3
- * 流程:$match(query) → $match($expr-游标比较) → $sort(sort) → $limit(limit+1) → 页内 $lookup →
4
- * 若为 before 方向,再 $sort(反转) 恢复视觉顺序。
5
- */
6
-
7
- const { buildLexiExpr } = require('./lexicographic-expr');
8
- const { reverseSort } = require('./sort');
9
-
10
- /**
11
- * 构造方案 A 的聚合管道
12
- * @param {object} params
13
- * @param {object} [params.query]
14
- * @param {Record<string,1|-1>} params.sort - 查询阶段使用的排序(before 方向已反转)
15
- * @param {number} params.limit - 页大小
16
- * @param {{a:object,s:object}|null} [params.cursor] - 解析后的游标对象
17
- * @param {'after'|'before'|null} [params.direction]
18
- * @param {object[]} [params.lookupPipeline] - 页内联表等追加管道
19
- * @returns {object[]} 聚合管道数组
20
- */
21
- function buildPagePipelineA({ query = {}, sort, limit, cursor, direction, lookupPipeline = [] }) {
22
- const pipeline = [];
23
- if (query && Object.keys(query).length) pipeline.push({ $match: query });
24
- if (cursor) pipeline.push({ $match: { $expr: buildLexiExpr(sort, cursor.a) } });
25
- pipeline.push({ $sort: sort });
26
- pipeline.push({ $limit: limit + 1 });
27
- if (lookupPipeline && lookupPipeline.length) pipeline.push(...lookupPipeline);
28
- if (direction === 'before') pipeline.push({ $sort: reverseSort(sort) });
29
- return pipeline;
30
- }
31
-
32
- module.exports = { buildPagePipelineA };
@@ -1,27 +0,0 @@
1
- /**
2
- * Mongo 实例指纹(iid)生成
3
- * 基于 uri/db 生成稳定短标识,用于缓存命名空间等
4
- */
5
- const crypto = require('crypto');
6
-
7
- function genInstanceId(databaseName, uri, explicitId) {
8
- if (explicitId) return String(explicitId);
9
- const safeDb = String(databaseName || '');
10
- try {
11
- const u = new URL(String(uri || ''));
12
- const proto = (u.protocol || '').replace(':', '');
13
- const host = u.hostname || '';
14
- const port = u.port || (proto === 'mongodb+srv' ? 'srv' : '27017');
15
- const rs = u.searchParams.get('replicaSet') || '';
16
- const authSource = u.searchParams.get('authSource') || '';
17
- const tls = u.searchParams.get('tls') || u.searchParams.get('ssl') || '';
18
- const safe = `${proto}://${host}:${port}/${safeDb}?rs=${rs}&auth=${authSource}&tls=${tls}`;
19
- const h = crypto.createHash('sha1').update(safe).digest('base64url').slice(0, 12);
20
- return `mdb:${h}`;
21
- } catch (_) {
22
- const h = crypto.createHash('sha1').update(String(uri || '') + '|' + safeDb).digest('base64url').slice(0, 12);
23
- return `mdb:${h}`;
24
- }
25
- }
26
-
27
- module.exports = { genInstanceId };
@@ -1,52 +0,0 @@
1
- /**
2
- * Mongo 专属:构造 $expr 的词典序比较表达式(lexicographic compare)
3
- * 场景:按复合排序键(含 `_id` 兜底)进行游标分页的"锚点之后/之前"过滤。
4
- * 例:sort={k1:1,k2:-1,_id:1}, anchor={k1:...,k2:...,_id:...}
5
- * 生成:{$or:[ {$gt:['$k1',a.k1]}, {$and:[{$eq:['$k1',a.k1]},{$lt:['$k2',a.k2]}]}, ...]}
6
- */
7
-
8
- const { ObjectId } = require('mongodb');
9
-
10
- /**
11
- * 转换锚点值,处理特殊类型
12
- * @param {any} value - 锚点值
13
- * @param {string} fieldName - 字段名
14
- * @returns {any} 转换后的值
15
- */
16
- function convertAnchorValue(value, fieldName) {
17
- // 如果是 _id 字段且值是字符串,转换为 ObjectId
18
- if (fieldName === '_id' && typeof value === 'string' && /^[0-9a-fA-F]{24}$/.test(value)) {
19
- return new ObjectId(value);
20
- }
21
-
22
- // 如果值是 ISO 日期字符串,转换为 Date 对象
23
- if (typeof value === 'string' && /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{3})?Z?$/.test(value)) {
24
- return new Date(value);
25
- }
26
-
27
- return value;
28
- }
29
-
30
- /**
31
- * 构造 $expr 词典序比较
32
- * @param {Record<string,1|-1>} sort - 稳定排序(含 `_id`)
33
- * @param {Record<string,any>} anchor - 锚点值对象
34
- * @returns {object} Mongo $expr 表达式对象
35
- */
36
- function buildLexiExpr(sort, anchor) {
37
- const keys = Object.keys(sort || {});
38
- const or = [];
39
- for (let i = 0; i < keys.length; i++) {
40
- const and = [];
41
- for (let j = 0; j < i; j++) {
42
- and.push({ $eq: [ `$${keys[j]}`, convertAnchorValue(anchor[keys[j]], keys[j]) ] });
43
- }
44
- const dir = sort[keys[i]];
45
- const op = dir === 1 ? '$gt' : '$lt';
46
- and.push({ [op]: [ `$${keys[i]}`, convertAnchorValue(anchor[keys[i]], keys[i]) ] });
47
- or.push(and.length === 1 ? and[0] : { $and: and });
48
- }
49
- return { $or: or };
50
- }
51
-
52
- module.exports = { buildLexiExpr };
@@ -1,31 +0,0 @@
1
- /**
2
- * Mongo 去敏形状构造
3
- * - shapeQuery: 仅输出字段名与运算符(以 '$' 代表),不含具体值
4
- * - shapeProjection: 将数组投影转为字段名数组,或对象投影的键名数组
5
- * - shapeSort: 仅输出排序键与 1/-1
6
- */
7
-
8
- function shapeQuery(input, maxKeys = 30, maxDepth = 3) {
9
- const walk = (v, depth) => {
10
- if (depth > maxDepth || v == null || typeof v !== 'object') return true; // 值用 true 表示
11
- if (Array.isArray(v)) return v.length ? [walk(v[0], depth + 1)] : [];
12
- const out = {};
13
- let count = 0;
14
- for (const k of Object.keys(v)) {
15
- out[k] = k.startsWith('$') ? '$' : walk(v[k], depth + 1);
16
- if (++count >= maxKeys) { out.__truncated__ = true; break; }
17
- }
18
- return out;
19
- };
20
- return walk(input, 0);
21
- }
22
-
23
- function shapeProjection(p) {
24
- return Array.isArray(p) ? p.slice(0, 30) : Object.keys(p || {}).slice(0, 30);
25
- }
26
-
27
- function shapeSort(s) {
28
- return Object.fromEntries(Object.entries(s || {}).slice(0, 30).map(([k, v]) => [k, v === -1 ? -1 : 1]));
29
- }
30
-
31
- module.exports = { shapeQuery, shapeProjection, shapeSort };
@@ -1,38 +0,0 @@
1
- /**
2
- * Mongo 专属:稳定排序与锚点选择
3
- * - ensureStableSort:确保排序键(sort)末尾包含 `_id`(稳定排序兜底)。
4
- * - reverseSort:将排序方向整体反转(1 ↔ -1),用于 before 方向的查询阶段。
5
- * - pickAnchor:按排序键顺序从文档中提取锚点字段值对象(含 `_id`)。
6
- */
7
-
8
- /**
9
- * 确保排序对象包含稳定键 `_id`
10
- * @param {Record<string, 1|-1>|undefined} sort - 排序对象
11
- * @returns {Record<string, 1|-1>} 新的排序对象(若缺 `_id` 自动追加 `_id:1`)
12
- */
13
- function ensureStableSort(sort) {
14
- const s = { ...(sort || {}) };
15
- if (!('_id' in s)) s._id = 1;
16
- return s;
17
- }
18
-
19
- /**
20
- * 反转排序方向(1 ↔ -1)
21
- * @param {Record<string, 1|-1>} sort
22
- * @returns {Record<string, 1|-1>}
23
- */
24
- function reverseSort(sort) {
25
- const r = {}; for (const k of Object.keys(sort || {})) r[k] = sort[k] === 1 ? -1 : 1; return r;
26
- }
27
-
28
- /**
29
- * 提取锚点:按排序键顺序从文档取值(含 `_id`)
30
- * @param {any} doc - 文档
31
- * @param {Record<string, 1|-1>} sort - 稳定排序(含 `_id`)
32
- * @returns {Record<string, any>} 锚点值对象 { k1: v1, k2: v2, _id: id }
33
- */
34
- function pickAnchor(doc, sort) {
35
- const a = {}; for (const k of Object.keys(sort || {})) a[k] = doc ? doc[k] : undefined; return a;
36
- }
37
-
38
- module.exports = { ensureStableSort, reverseSort, pickAnchor };
@@ -1,24 +0,0 @@
1
- /**
2
- * 检查操作是否在事务中执行
3
- * @param {Object} options - 操作选项
4
- * @returns {boolean}
5
- */
6
- function isInTransaction(options) {
7
- return !!(options && options.session && options.session.inTransaction && options.session.inTransaction());
8
- }
9
-
10
- /**
11
- * 从 session 中获取 Transaction 实例
12
- * @param {Object} session - MongoDB ClientSession
13
- * @returns {Transaction|null}
14
- */
15
- function getTransactionFromSession(session) {
16
- if (!session) return null;
17
- return session.__monSQLizeTransaction || null;
18
- }
19
-
20
- module.exports = {
21
- isInTransaction,
22
- getTransactionFromSession
23
- };
24
-
@@ -1,233 +0,0 @@
1
- const { MongoClient } = require('mongodb');
2
- const { SSHTunnelManager } = require('../infrastructure/ssh-tunnel');
3
- const { parseUri } = require('../infrastructure/uri-parser');
4
-
5
- // 懒加载 MongoDB Memory Server(仅在需要时加载)
6
- let MongoMemoryServer;
7
- let MongoMemoryReplSet;
8
- let memoryServerInstance; // 单例实例
9
-
10
- function buildLogContext({ type, databaseName, defaults, config }) {
11
- const scope = defaults?.namespace?.scope;
12
- let uriHost;
13
- try { uriHost = new URL(config?.uri || '').hostname; } catch (_) { uriHost = undefined; }
14
- return { type, db: databaseName, scope, uriHost };
15
- }
16
-
17
- /**
18
- * 启动 MongoDB Memory Server(单例模式)
19
- * @param {Object} logger - 日志记录器
20
- * @param {Object} [memoryServerOptions] - Memory Server 配置选项
21
- * @returns {Promise<string>} 返回内存数据库的连接 URI
22
- */
23
- async function startMemoryServer(logger, memoryServerOptions = {}) {
24
- if (memoryServerInstance) {
25
- const uri = memoryServerInstance.getUri();
26
- try { logger && logger.debug && logger.debug('📌 Using existing MongoDB Memory Server', { uri }); } catch (_) { }
27
- return uri;
28
- }
29
-
30
- try {
31
- // 检查是否需要副本集
32
- const needsReplSet = memoryServerOptions?.instance?.replSet;
33
-
34
- if (needsReplSet) {
35
- // 使用副本集模式
36
- if (!MongoMemoryReplSet) {
37
- MongoMemoryReplSet = require('mongodb-memory-server').MongoMemoryReplSet;
38
- }
39
-
40
- try { logger && logger.info && logger.info('🚀 Starting MongoDB Memory Server (Replica Set)...', { replSet: needsReplSet }); } catch (_) { }
41
-
42
- const replSetConfig = {
43
- replSet: {
44
- name: needsReplSet,
45
- count: 1, // 单节点副本集(足以支持事务)
46
- storageEngine: 'wiredTiger'
47
- },
48
- binary: {
49
- version: '6.0.12'
50
- }
51
- };
52
-
53
- memoryServerInstance = await MongoMemoryReplSet.create(replSetConfig);
54
- const uri = memoryServerInstance.getUri();
55
- try { logger && logger.info && logger.info('✅ MongoDB Memory Server (Replica Set) started', { uri, replSet: needsReplSet }); } catch (_) { }
56
- return uri;
57
- } else {
58
- // 使用单实例模式
59
- if (!MongoMemoryServer) {
60
- MongoMemoryServer = require('mongodb-memory-server').MongoMemoryServer;
61
- }
62
-
63
- try { logger && logger.info && logger.info('🚀 Starting MongoDB Memory Server...'); } catch (_) { }
64
-
65
- const defaultConfig = {
66
- instance: {
67
- port: undefined,
68
- dbName: 'test_db',
69
- storageEngine: 'ephemeralForTest',
70
- },
71
- binary: {
72
- version: '6.0.12',
73
- },
74
- };
75
-
76
- const config = {
77
- ...defaultConfig,
78
- ...memoryServerOptions,
79
- instance: {
80
- ...defaultConfig.instance,
81
- ...(memoryServerOptions.instance || {})
82
- },
83
- binary: {
84
- ...defaultConfig.binary,
85
- ...(memoryServerOptions.binary || {})
86
- }
87
- };
88
-
89
- memoryServerInstance = await MongoMemoryServer.create(config);
90
- const uri = memoryServerInstance.getUri();
91
- try { logger && logger.info && logger.info('✅ MongoDB Memory Server started', { uri }); } catch (_) { }
92
- return uri;
93
- }
94
- } catch (err) {
95
- try { logger && logger.error && logger.error('❌ Failed to start MongoDB Memory Server', err); } catch (_) { }
96
- throw new Error(`Failed to start MongoDB Memory Server: ${err.message}`);
97
- }
98
- }
99
-
100
- /**
101
- * 停止 MongoDB Memory Server
102
- * @param {Object} logger - 日志记录器
103
- */
104
- async function stopMemoryServer(logger) {
105
- if (!memoryServerInstance) {
106
- return;
107
- }
108
-
109
- try {
110
- try { logger && logger.info && logger.info('🛑 Stopping MongoDB Memory Server...'); } catch (_) { }
111
- await memoryServerInstance.stop();
112
- memoryServerInstance = null;
113
- try { logger && logger.info && logger.info('✅ MongoDB Memory Server stopped'); } catch (_) { }
114
- } catch (err) {
115
- try { logger && logger.warn && logger.warn('⚠️ Error stopping MongoDB Memory Server', err); } catch (_) { }
116
- memoryServerInstance = null;
117
- }
118
- }
119
-
120
- /**
121
- * 建立 MongoDB 连接(适配器内部使用)
122
- * @param {{ databaseName: string, config: { uri?: string, options?: object, useMemoryServer?: boolean, readPreference?: string, ssh?: object, remoteHost?: string, remotePort?: number, mongoHost?: string, mongoPort?: number }, logger: any, defaults: object, type?: string }} params
123
- * @returns {Promise<{ client: import('mongodb').MongoClient, db: any, sshTunnel?: any }>} 返回已连接的 client、默认 db 句柄(若可用)和 SSH 隧道实例(若使用)
124
- */
125
- async function connectMongo({ databaseName, config, logger, defaults, type = 'mongodb' }) {
126
- let { uri, options = {}, useMemoryServer, memoryServerOptions, readPreference, ssh } = config || {};
127
-
128
- let sshTunnel = null;
129
- let effectiveUri = uri;
130
-
131
- // ===== SSH 隧道逻辑 =====
132
- if (ssh) {
133
- logger?.info?.('🔐 Establishing SSH tunnel for MongoDB...');
134
-
135
- // 解析MongoDB目标地址(优先级:显式配置 > URI解析)
136
- let remoteHost = config.remoteHost || config.mongoHost;
137
- let remotePort = config.remotePort || config.mongoPort;
138
-
139
- if (!remoteHost || !remotePort) {
140
- try {
141
- const parsed = parseUri(uri);
142
- remoteHost = parsed.host;
143
- remotePort = parsed.port;
144
- } catch (err) {
145
- throw new Error('SSH tunnel requires remoteHost and remotePort, or a valid MongoDB URI');
146
- }
147
- }
148
-
149
- // 使用SSH隧道管理器工厂
150
- sshTunnel = SSHTunnelManager.create(ssh, remoteHost, remotePort, {
151
- logger,
152
- name: 'MongoDB'
153
- });
154
-
155
- try {
156
- await sshTunnel.connect();
157
-
158
- // 使用隧道URI
159
- effectiveUri = sshTunnel.getTunnelUri('mongodb', uri);
160
-
161
- logger?.info?.(`✅ MongoDB will connect via SSH tunnel: ${sshTunnel.getLocalAddress()}`);
162
- } catch (err) {
163
- logger?.error?.('❌ SSH tunnel connection failed', err);
164
- throw err;
165
- }
166
- }
167
- // ===== Memory Server 逻辑 =====
168
- else if (useMemoryServer === true) {
169
- try {
170
- effectiveUri = await startMemoryServer(logger, memoryServerOptions);
171
- } catch (err) {
172
- // 如果启动内存服务器失败,且没有提供 uri,抛出错误
173
- if (!effectiveUri) {
174
- throw new Error('Failed to start Memory Server and no URI provided');
175
- }
176
- logger?.warn?.('Failed to start Memory Server, using provided URI', { uri: effectiveUri });
177
- }
178
- }
179
-
180
- if (!effectiveUri) throw new Error('MongoDB connect requires config.uri or config.useMemoryServer');
181
-
182
- // 🔑 合并 readPreference 到 MongoClient options
183
- const clientOptions = { ...options };
184
- if (readPreference) {
185
- clientOptions.readPreference = readPreference;
186
- }
187
-
188
- const client = new MongoClient(effectiveUri, clientOptions);
189
- try {
190
- await client.connect();
191
- let db = null;
192
- try { db = client.db(databaseName); } catch (_) { db = null; }
193
- const ctx = buildLogContext({ type, databaseName, defaults, config });
194
- // try { logger && logger.info && logger.info('✅ MongoDB connected', ctx); } catch (_) {}
195
- return { client, db, sshTunnel };
196
- } catch (err) {
197
- // 连接失败,清理SSH隧道
198
- if (sshTunnel) {
199
- await sshTunnel.close();
200
- }
201
- const ctx = buildLogContext({ type, databaseName, defaults, config });
202
- logger?.error?.('❌ MongoDB connection failed', ctx, err);
203
- throw err;
204
- }
205
- }
206
-
207
- /**
208
- * 关闭 MongoDB 连接
209
- * @param {import('mongodb').MongoClient} client
210
- * @param {any} logger
211
- * @param {boolean} [stopMemory=false] - 是否同时停止内存服务器
212
- * @param {any} [sshTunnel=null] - SSH隧道实例(如果使用)
213
- */
214
- async function closeMongo(client, logger, stopMemory = false, sshTunnel = null) {
215
- if (!client) return;
216
- try { await client.close(); } catch (e) { try { logger && logger.warn && logger.warn('MongoDB close error', e && (e.stack || e)); } catch (_) { } }
217
-
218
- // 关闭SSH隧道
219
- if (sshTunnel) {
220
- try {
221
- await sshTunnel.close();
222
- } catch (e) {
223
- logger?.warn?.('SSH tunnel close error', e);
224
- }
225
- }
226
-
227
- // 如果指定停止内存服务器
228
- if (stopMemory) {
229
- await stopMemoryServer(logger);
230
- }
231
- }
232
-
233
- module.exports = { connectMongo, closeMongo, stopMemoryServer };