monsqlize 1.0.0 → 1.0.2

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 (61) hide show
  1. package/CHANGELOG.md +92 -2419
  2. package/README.md +630 -1070
  3. package/index.d.ts +252 -15
  4. package/lib/cache.js +8 -8
  5. package/lib/common/validation.js +64 -1
  6. package/lib/connect.js +3 -3
  7. package/lib/errors.js +10 -0
  8. package/lib/index.js +118 -9
  9. package/lib/infrastructure/ssh-tunnel-ssh2.js +211 -0
  10. package/lib/infrastructure/ssh-tunnel.js +40 -0
  11. package/lib/infrastructure/uri-parser.js +35 -0
  12. package/lib/lock/Lock.js +66 -0
  13. package/lib/lock/errors.js +27 -0
  14. package/lib/lock/index.js +12 -0
  15. package/lib/logger.js +1 -1
  16. package/lib/model/examples/test.js +4 -4
  17. package/lib/mongodb/common/accessor-helpers.js +17 -3
  18. package/lib/mongodb/connect.js +68 -13
  19. package/lib/mongodb/index.js +140 -7
  20. package/lib/mongodb/management/collection-ops.js +4 -4
  21. package/lib/mongodb/management/index-ops.js +18 -18
  22. package/lib/mongodb/management/validation-ops.js +3 -3
  23. package/lib/mongodb/queries/aggregate.js +14 -5
  24. package/lib/mongodb/queries/chain.js +52 -45
  25. package/lib/mongodb/queries/count.js +16 -6
  26. package/lib/mongodb/queries/distinct.js +15 -6
  27. package/lib/mongodb/queries/find-and-count.js +22 -13
  28. package/lib/mongodb/queries/find-by-ids.js +5 -5
  29. package/lib/mongodb/queries/find-one-by-id.js +1 -1
  30. package/lib/mongodb/queries/find-one.js +12 -3
  31. package/lib/mongodb/queries/find-page.js +12 -0
  32. package/lib/mongodb/queries/find.js +15 -6
  33. package/lib/mongodb/queries/index.js +1 -0
  34. package/lib/mongodb/queries/watch.js +537 -0
  35. package/lib/mongodb/writes/delete-many.js +20 -11
  36. package/lib/mongodb/writes/delete-one.js +18 -9
  37. package/lib/mongodb/writes/find-one-and-delete.js +19 -10
  38. package/lib/mongodb/writes/find-one-and-replace.js +36 -20
  39. package/lib/mongodb/writes/find-one-and-update.js +36 -20
  40. package/lib/mongodb/writes/increment-one.js +16 -7
  41. package/lib/mongodb/writes/index.js +13 -13
  42. package/lib/mongodb/writes/insert-batch.js +46 -37
  43. package/lib/mongodb/writes/insert-many.js +22 -13
  44. package/lib/mongodb/writes/insert-one.js +18 -9
  45. package/lib/mongodb/writes/replace-one.js +33 -17
  46. package/lib/mongodb/writes/result-handler.js +14 -14
  47. package/lib/mongodb/writes/update-many.js +34 -18
  48. package/lib/mongodb/writes/update-one.js +33 -17
  49. package/lib/mongodb/writes/upsert-one.js +25 -9
  50. package/lib/operators.js +1 -1
  51. package/lib/redis-cache-adapter.js +3 -3
  52. package/lib/slow-query-log/base-storage.js +69 -0
  53. package/lib/slow-query-log/batch-queue.js +96 -0
  54. package/lib/slow-query-log/config-manager.js +195 -0
  55. package/lib/slow-query-log/index.js +237 -0
  56. package/lib/slow-query-log/mongodb-storage.js +323 -0
  57. package/lib/slow-query-log/query-hash.js +38 -0
  58. package/lib/transaction/DistributedCacheLockManager.js +240 -5
  59. package/lib/transaction/Transaction.js +1 -1
  60. package/lib/utils/objectid-converter.js +566 -0
  61. package/package.json +11 -5
@@ -1,4 +1,6 @@
1
1
  const { MongoClient } = require('mongodb');
2
+ const { SSHTunnelManager } = require('../infrastructure/ssh-tunnel');
3
+ const { parseUri } = require('../infrastructure/uri-parser');
2
4
 
3
5
  // 懒加载 MongoDB Memory Server(仅在需要时加载)
4
6
  let MongoMemoryServer;
@@ -117,26 +119,65 @@ async function stopMemoryServer(logger) {
117
119
 
118
120
  /**
119
121
  * 建立 MongoDB 连接(适配器内部使用)
120
- * @param {{ databaseName: string, config: { uri?: string, options?: object, useMemoryServer?: boolean, readPreference?: string }, logger: any, defaults: object, type?: string }} params
121
- * @returns {Promise<{ client: import('mongodb').MongoClient, db: any }>} 返回已连接的 client 与默认 db 句柄(若可用)
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 隧道实例(若使用)
122
124
  */
123
125
  async function connectMongo({ databaseName, config, logger, defaults, type = 'mongodb' }) {
124
- let { uri, options = {}, useMemoryServer, memoryServerOptions, readPreference } = config || {};
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
+ }
125
148
 
126
- // 🔑 根据 config.useMemoryServer 决定是否使用内存数据库
127
- if (useMemoryServer === true) {
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) {
128
169
  try {
129
- uri = await startMemoryServer(logger, memoryServerOptions);
170
+ effectiveUri = await startMemoryServer(logger, memoryServerOptions);
130
171
  } catch (err) {
131
172
  // 如果启动内存服务器失败,且没有提供 uri,抛出错误
132
- if (!uri) {
173
+ if (!effectiveUri) {
133
174
  throw new Error('Failed to start Memory Server and no URI provided');
134
175
  }
135
- try { logger && logger.warn && logger.warn('Failed to start Memory Server, using provided URI', { uri }); } catch (_) { }
176
+ logger?.warn?.('Failed to start Memory Server, using provided URI', { uri: effectiveUri });
136
177
  }
137
178
  }
138
179
 
139
- if (!uri) throw new Error('MongoDB connect requires config.uri or config.useMemoryServer');
180
+ if (!effectiveUri) throw new Error('MongoDB connect requires config.uri or config.useMemoryServer');
140
181
 
141
182
  // 🔑 合并 readPreference 到 MongoClient options
142
183
  const clientOptions = { ...options };
@@ -144,17 +185,21 @@ async function connectMongo({ databaseName, config, logger, defaults, type = 'mo
144
185
  clientOptions.readPreference = readPreference;
145
186
  }
146
187
 
147
- const client = new MongoClient(uri, clientOptions);
188
+ const client = new MongoClient(effectiveUri, clientOptions);
148
189
  try {
149
190
  await client.connect();
150
191
  let db = null;
151
192
  try { db = client.db(databaseName); } catch (_) { db = null; }
152
193
  const ctx = buildLogContext({ type, databaseName, defaults, config });
153
194
  // try { logger && logger.info && logger.info('✅ MongoDB connected', ctx); } catch (_) {}
154
- return { client, db };
195
+ return { client, db, sshTunnel };
155
196
  } catch (err) {
197
+ // 连接失败,清理SSH隧道
198
+ if (sshTunnel) {
199
+ await sshTunnel.close();
200
+ }
156
201
  const ctx = buildLogContext({ type, databaseName, defaults, config });
157
- try { logger && logger.error && logger.error('❌ MongoDB connection failed', ctx, err); } catch (_) { }
202
+ logger?.error?.('❌ MongoDB connection failed', ctx, err);
158
203
  throw err;
159
204
  }
160
205
  }
@@ -164,11 +209,21 @@ async function connectMongo({ databaseName, config, logger, defaults, type = 'mo
164
209
  * @param {import('mongodb').MongoClient} client
165
210
  * @param {any} logger
166
211
  * @param {boolean} [stopMemory=false] - 是否同时停止内存服务器
212
+ * @param {any} [sshTunnel=null] - SSH隧道实例(如果使用)
167
213
  */
168
- async function closeMongo(client, logger, stopMemory = false) {
214
+ async function closeMongo(client, logger, stopMemory = false, sshTunnel = null) {
169
215
  if (!client) return;
170
216
  try { await client.close(); } catch (e) { try { logger && logger.warn && logger.warn('MongoDB close error', e && (e.stack || e)); } catch (_) { } }
171
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
+
172
227
  // 如果指定停止内存服务器
173
228
  if (stopMemory) {
174
229
  await stopMemoryServer(logger);
@@ -17,7 +17,8 @@ const {
17
17
  createCountOps,
18
18
  createAggregateOps,
19
19
  createDistinctOps,
20
- createFindPageOps // 分页查询工厂函数
20
+ createFindPageOps, // 分页查询工厂函数
21
+ createWatchOps // 🆕 watch 方法
21
22
  } = require('./queries');
22
23
 
23
24
  const {
@@ -45,7 +46,7 @@ const {
45
46
  createDeleteOneOps,
46
47
  createDeleteManyOps,
47
48
  createFindOneAndDeleteOps
48
- } = require("./writes");
49
+ } = require('./writes');
49
50
 
50
51
  const { EventEmitter } = require('events');
51
52
  module.exports = class {
@@ -95,7 +96,7 @@ module.exports = class {
95
96
 
96
97
  try {
97
98
  this._connecting = (async () => {
98
- const { client, db } = await connectMongo({
99
+ const { client, db, sshTunnel } = await connectMongo({
99
100
  databaseName: this.databaseName,
100
101
  config: this.config,
101
102
  logger: this.logger,
@@ -104,6 +105,12 @@ module.exports = class {
104
105
  });
105
106
  this.client = client;
106
107
  this.db = db;
108
+ this._sshTunnel = sshTunnel; // 🔴 保存SSH隧道实例
109
+
110
+ // 🔴 初始化慢查询日志存储
111
+ this._initializeSlowQueryLog();
112
+
113
+
107
114
  try { this.emit && this.emit('connected', { type: this.type, db: this.databaseName, scope: this.defaults?.namespace?.scope }); } catch (_) { }
108
115
  return this.client;
109
116
  })();
@@ -150,7 +157,8 @@ module.exports = class {
150
157
  { db: ns.db, coll: ns.coll, iid, type: this.type },
151
158
  options,
152
159
  fn,
153
- mongoSlowLogShaper
160
+ mongoSlowLogShaper,
161
+ this.onSlowQueryEmit // 🔴 传递慢查询回调
154
162
  );
155
163
  }
156
164
 
@@ -198,7 +206,12 @@ module.exports = class {
198
206
  }, this.logger, this.defaults, {
199
207
  keyBuilder: mongoKeyBuilder,
200
208
  slowLogShaper: mongoSlowLogShaper,
201
- onSlowQueryEmit: (meta) => { try { this.emit && this.emit('slow-query', meta); } catch (_) { } },
209
+ onSlowQueryEmit: (meta) => {
210
+ try {
211
+ // 触发slow-query事件,由事件监听器处理保存
212
+ this.emit && this.emit('slow-query', meta);
213
+ } catch (_) { }
214
+ },
202
215
  onQueryEmit: (meta) => { try { this.emit && this.emit('query', meta); } catch (_) { } }
203
216
  });
204
217
 
@@ -218,7 +231,8 @@ module.exports = class {
218
231
  mongoSlowLogShaper,
219
232
  type: this.type,
220
233
  cache: this.cache,
221
- getCache: () => this.cache // 动态获取 cache(支持测试时的临时替换)
234
+ getCache: () => this.cache, // 动态获取 cache(支持测试时的临时替换)
235
+ autoConvertConfig: this.autoConvertConfig // ✅ v1.3.0: 传递 ObjectId 自动转换配置
222
236
  };
223
237
 
224
238
  // ========================================
@@ -248,6 +262,8 @@ module.exports = class {
248
262
  // explain 功能已集成到 find() 的链式调用和 options 参数中
249
263
  // 分页查询
250
264
  ...createFindPageOps(moduleContext),
265
+ // 🆕 watch 方法 - Change Streams (v1.1.0)
266
+ ...createWatchOps(moduleContext),
251
267
  // 写操作方法 - Insert
252
268
  ...createInsertOneOps(moduleContext),
253
269
  ...createInsertManyOps(moduleContext),
@@ -302,6 +318,19 @@ module.exports = class {
302
318
  * 关闭连接并释放资源
303
319
  */
304
320
  async close() {
321
+ // 🔴 关闭慢查询日志管理器
322
+ if (this.slowQueryLogManager) {
323
+ try {
324
+ await this.slowQueryLogManager.close();
325
+ this.slowQueryLogManager = null;
326
+ this.onSlowQueryEmit = null;
327
+ } catch (err) {
328
+ if (this.logger.error) {
329
+ this.logger.error('[SlowQueryLog] Failed to close manager:', err);
330
+ }
331
+ }
332
+ }
333
+
305
334
  if (this.client) {
306
335
  await closeMongo(this.client, this.logger);
307
336
  }
@@ -455,4 +484,108 @@ module.exports = class {
455
484
  return collectionOps.runCommand(command, options);
456
485
  }
457
486
 
458
- }
487
+ /**
488
+ * 🔴 初始化慢查询日志存储
489
+ * @private
490
+ */
491
+ _initializeSlowQueryLog() {
492
+ const slowQueryLogConfig = this.defaults?.slowQueryLog;
493
+
494
+ // 支持 boolean 快捷配置
495
+ if (!slowQueryLogConfig) {
496
+ return;
497
+ }
498
+
499
+ try {
500
+ const { SlowQueryLogManager, SlowQueryLogConfigManager } = require('../slow-query-log');
501
+
502
+ // 使用配置管理器合并配置
503
+ const mergedConfig = SlowQueryLogConfigManager.mergeConfig(
504
+ slowQueryLogConfig,
505
+ this.type // 业务库类型(用于自动推断storage.type)
506
+ );
507
+
508
+ // 验证配置
509
+ SlowQueryLogConfigManager.validate(mergedConfig, this.type);
510
+
511
+ // 如果未启用,直接返回
512
+ if (!mergedConfig.enabled) {
513
+ return;
514
+ }
515
+
516
+ // 创建慢查询日志管理器
517
+ this.slowQueryLogManager = new SlowQueryLogManager(
518
+ mergedConfig,
519
+ this.client, // 传递MongoDB客户端(复用连接)
520
+ this.type, // 业务库类型
521
+ this.logger
522
+ );
523
+
524
+ // 🔴 使用事件监听器代替直接回调
525
+ // 监听 'slow-query' 事件,自动保存慢查询日志
526
+ this.on('slow-query', async (meta) => {
527
+ if (this.slowQueryLogManager) {
528
+ try {
529
+ await this.slowQueryLogManager.save(meta);
530
+ } catch (err) {
531
+ // 保存失败不影响主流程
532
+ if (this.logger.error) {
533
+ this.logger.error('[SlowQueryLog] Save failed:', err);
534
+ }
535
+ }
536
+ }
537
+ });
538
+
539
+ if (this.logger.info) {
540
+ this.logger.info('[SlowQueryLog] Manager initialized');
541
+ }
542
+ } catch (err) {
543
+ if (this.logger.error) {
544
+ this.logger.error('[SlowQueryLog] Failed to initialize:', err);
545
+ }
546
+ // 初始化失败不影响主流程
547
+ }
548
+ }
549
+
550
+ /**
551
+ * 🔴 获取慢查询日志(查询接口)
552
+ * @param {Object} filter - 查询条件
553
+ * @param {Object} options - 查询选项
554
+ * @returns {Promise<Object[]>}
555
+ */
556
+ async getSlowQueryLogs(filter, options) {
557
+ if (!this.slowQueryLogManager) {
558
+ throw new Error('Slow query log is not enabled');
559
+ }
560
+ return this.slowQueryLogManager.query(filter, options);
561
+ }
562
+
563
+ /**
564
+ * 🔴 关闭MongoDB连接和SSH隧道
565
+ * @param {boolean} [stopMemory=false] - 是否停止Memory Server
566
+ */
567
+ async close(stopMemory = false) {
568
+ if (!this.client) {
569
+ return;
570
+ }
571
+
572
+ try {
573
+ await closeMongo(
574
+ this.client,
575
+ this.logger,
576
+ stopMemory,
577
+ this._sshTunnel // 传递SSH隧道实例
578
+ );
579
+
580
+ this.client = null;
581
+ this.db = null;
582
+ this._sshTunnel = null;
583
+
584
+ this.emit?.('closed', { type: this.type, db: this.databaseName });
585
+ } catch (err) {
586
+ this.logger?.error?.('❌ Failed to close MongoDB connection', err);
587
+ throw err;
588
+ }
589
+ }
590
+
591
+ };
@@ -104,7 +104,7 @@ module.exports = function createCollectionOps(context) {
104
104
  // 使用 MongoDB 原生命令获取集合统计
105
105
  const result = await db.command({
106
106
  collStats: collection.collectionName,
107
- scale: scale
107
+ scale
108
108
  });
109
109
 
110
110
  return {
@@ -302,7 +302,7 @@ module.exports = function createCollectionOps(context) {
302
302
 
303
303
  const command = {
304
304
  convertToCapped: collection.collectionName,
305
- size: size
305
+ size
306
306
  };
307
307
 
308
308
  if (options.max) {
@@ -314,7 +314,7 @@ module.exports = function createCollectionOps(context) {
314
314
  if (logger) {
315
315
  logger.info('Collection converted to capped', {
316
316
  collection: collection.collectionName,
317
- size: size,
317
+ size,
318
318
  max: options.max
319
319
  });
320
320
  }
@@ -323,7 +323,7 @@ module.exports = function createCollectionOps(context) {
323
323
  ok: result.ok,
324
324
  collection: collection.collectionName,
325
325
  capped: true,
326
- size: size
326
+ size
327
327
  };
328
328
  } catch (error) {
329
329
  if (logger) {
@@ -85,7 +85,7 @@ function createIndexOps(context, databaseName, collectionName, nativeCollection)
85
85
  const normalizedOptions = normalizeIndexOptions(options);
86
86
 
87
87
  // 3. 记录开始日志
88
- logger.debug(`[createIndex] 开始创建索引`, {
88
+ logger.debug('[createIndex] 开始创建索引', {
89
89
  ns,
90
90
  keys,
91
91
  options: normalizedOptions
@@ -96,7 +96,7 @@ function createIndexOps(context, databaseName, collectionName, nativeCollection)
96
96
 
97
97
  // 5. 记录成功日志
98
98
  const duration = Date.now() - startTime;
99
- logger.info(`[createIndex] 索引创建成功`, {
99
+ logger.info('[createIndex] 索引创建成功', {
100
100
  ns,
101
101
  indexName: typeof result === 'string' ? result : result.name || 'unknown',
102
102
  keys,
@@ -111,7 +111,7 @@ function createIndexOps(context, databaseName, collectionName, nativeCollection)
111
111
  // 7. 错误处理
112
112
  const duration = Date.now() - startTime;
113
113
 
114
- logger.error(`[createIndex] 索引创建失败`, {
114
+ logger.error('[createIndex] 索引创建失败', {
115
115
  ns,
116
116
  keys,
117
117
  options,
@@ -207,7 +207,7 @@ function createIndexOps(context, databaseName, collectionName, nativeCollection)
207
207
  }));
208
208
 
209
209
  // 3. 记录开始日志
210
- logger.debug(`[createIndexes] 开始批量创建索引`, {
210
+ logger.debug('[createIndexes] 开始批量创建索引', {
211
211
  ns,
212
212
  count: normalizedSpecs.length,
213
213
  specs: normalizedSpecs
@@ -218,7 +218,7 @@ function createIndexOps(context, databaseName, collectionName, nativeCollection)
218
218
 
219
219
  // 5. 记录成功日志
220
220
  const duration = Date.now() - startTime;
221
- logger.info(`[createIndexes] 批量创建索引成功`, {
221
+ logger.info('[createIndexes] 批量创建索引成功', {
222
222
  ns,
223
223
  count: normalizedSpecs.length,
224
224
  indexNames: result,
@@ -232,7 +232,7 @@ function createIndexOps(context, databaseName, collectionName, nativeCollection)
232
232
  // 7. 错误处理
233
233
  const duration = Date.now() - startTime;
234
234
 
235
- logger.error(`[createIndexes] 批量创建索引失败`, {
235
+ logger.error('[createIndexes] 批量创建索引失败', {
236
236
  ns,
237
237
  count: indexSpecs?.length || 0,
238
238
  error: error.message,
@@ -269,7 +269,7 @@ function createIndexOps(context, databaseName, collectionName, nativeCollection)
269
269
 
270
270
  try {
271
271
  // 1. 记录开始日志
272
- logger.debug(`[listIndexes] 开始列出索引`, { ns });
272
+ logger.debug('[listIndexes] 开始列出索引', { ns });
273
273
 
274
274
  // 2. 执行查询
275
275
  const cursor = nativeCollection.listIndexes();
@@ -277,7 +277,7 @@ function createIndexOps(context, databaseName, collectionName, nativeCollection)
277
277
 
278
278
  // 3. 记录成功日志
279
279
  const duration = Date.now() - startTime;
280
- logger.debug(`[listIndexes] 列出索引成功`, {
280
+ logger.debug('[listIndexes] 列出索引成功', {
281
281
  ns,
282
282
  count: indexes.length,
283
283
  indexNames: indexes.map(idx => idx.name),
@@ -291,7 +291,7 @@ function createIndexOps(context, databaseName, collectionName, nativeCollection)
291
291
  // 5. 错误处理
292
292
  const duration = Date.now() - startTime;
293
293
 
294
- logger.error(`[listIndexes] 列出索引失败`, {
294
+ logger.error('[listIndexes] 列出索引失败', {
295
295
  ns,
296
296
  error: error.message,
297
297
  code: error.code,
@@ -301,7 +301,7 @@ function createIndexOps(context, databaseName, collectionName, nativeCollection)
301
301
  // MongoDB 命名空间不存在错误(集合不存在)
302
302
  if (error.code === 26) {
303
303
  // 返回空数组(集合不存在时没有索引)
304
- logger.debug(`[listIndexes] 集合不存在,返回空数组`, { ns });
304
+ logger.debug('[listIndexes] 集合不存在,返回空数组', { ns });
305
305
  return [];
306
306
  }
307
307
 
@@ -347,14 +347,14 @@ function createIndexOps(context, databaseName, collectionName, nativeCollection)
347
347
  }
348
348
 
349
349
  // 2. 记录开始日志
350
- logger.debug(`[dropIndex] 开始删除索引`, { ns, indexName });
350
+ logger.debug('[dropIndex] 开始删除索引', { ns, indexName });
351
351
 
352
352
  // 3. 执行删除
353
353
  const result = await nativeCollection.dropIndex(indexName);
354
354
 
355
355
  // 4. 记录成功日志
356
356
  const duration = Date.now() - startTime;
357
- logger.info(`[dropIndex] 删除索引成功`, {
357
+ logger.info('[dropIndex] 删除索引成功', {
358
358
  ns,
359
359
  indexName,
360
360
  result,
@@ -368,7 +368,7 @@ function createIndexOps(context, databaseName, collectionName, nativeCollection)
368
368
  // 6. 错误处理
369
369
  const duration = Date.now() - startTime;
370
370
 
371
- logger.error(`[dropIndex] 删除索引失败`, {
371
+ logger.error('[dropIndex] 删除索引失败', {
372
372
  ns,
373
373
  indexName,
374
374
  error: error.message,
@@ -381,7 +381,7 @@ function createIndexOps(context, databaseName, collectionName, nativeCollection)
381
381
  if (errorMsg.includes('_id_') || errorMsg.includes('cannot drop _id index')) {
382
382
  throw createError(
383
383
  'INVALID_ARGUMENT',
384
- `不允许删除 _id 索引`,
384
+ '不允许删除 _id 索引',
385
385
  { ns, indexName, cause: error }
386
386
  );
387
387
  }
@@ -418,14 +418,14 @@ function createIndexOps(context, databaseName, collectionName, nativeCollection)
418
418
 
419
419
  try {
420
420
  // 1. 记录开始日志
421
- logger.debug(`[dropIndexes] 开始删除所有索引`, { ns });
421
+ logger.debug('[dropIndexes] 开始删除所有索引', { ns });
422
422
 
423
423
  // 2. 执行删除(MongoDB 会自动保留 _id 索引)
424
424
  const result = await nativeCollection.dropIndexes();
425
425
 
426
426
  // 3. 记录成功日志
427
427
  const duration = Date.now() - startTime;
428
- logger.info(`[dropIndexes] 删除所有索引成功`, {
428
+ logger.info('[dropIndexes] 删除所有索引成功', {
429
429
  ns,
430
430
  result,
431
431
  duration
@@ -438,7 +438,7 @@ function createIndexOps(context, databaseName, collectionName, nativeCollection)
438
438
  // 5. 错误处理
439
439
  const duration = Date.now() - startTime;
440
440
 
441
- logger.error(`[dropIndexes] 删除所有索引失败`, {
441
+ logger.error('[dropIndexes] 删除所有索引失败', {
442
442
  ns,
443
443
  error: error.message,
444
444
  code: error.code,
@@ -447,7 +447,7 @@ function createIndexOps(context, databaseName, collectionName, nativeCollection)
447
447
 
448
448
  // 命名空间不存在(集合不存在)
449
449
  if (error.code === 26) {
450
- logger.debug(`[dropIndexes] 集合不存在,无需删除索引`, { ns });
450
+ logger.debug('[dropIndexes] 集合不存在,无需删除索引', { ns });
451
451
  return { ok: 1, msg: '集合不存在,无索引可删除', nIndexesWas: 0 };
452
452
  }
453
453
 
@@ -79,7 +79,7 @@ function createValidationOps(context) {
79
79
 
80
80
  const command = {
81
81
  collMod: collection.collectionName,
82
- validator: validator
82
+ validator
83
83
  };
84
84
 
85
85
  if (options.validationLevel) {
@@ -150,7 +150,7 @@ function createValidationOps(context) {
150
150
 
151
151
  logger.info('Validation level set', {
152
152
  collection: collection.collectionName,
153
- level: level
153
+ level
154
154
  });
155
155
 
156
156
  return {
@@ -201,7 +201,7 @@ function createValidationOps(context) {
201
201
 
202
202
  logger.info('Validation action set', {
203
203
  collection: collection.collectionName,
204
- action: action
204
+ action
205
205
  });
206
206
 
207
207
  return {
@@ -4,6 +4,7 @@
4
4
  */
5
5
 
6
6
  const { AggregateChain } = require('./chain');
7
+ const { convertAggregationPipeline } = require('../../utils/objectid-converter');
7
8
 
8
9
  /**
9
10
  * 创建 aggregate 查询操作
@@ -40,12 +41,20 @@ function createAggregateOps(context) {
40
41
  * @returns {Promise<Array>|ReadableStream|AggregateChain} 聚合结果数组或可读流(当 stream: true 时);当 explain=true 时返回执行计划;默认返回 AggregateChain 实例支持链式调用
41
42
  */
42
43
  aggregate: (pipeline = [], options = {}) => {
44
+ // ✅ v1.3.0: 自动转换聚合管道中的 ObjectId 字符串
45
+ const convertedPipeline = convertAggregationPipeline(pipeline, 0, {
46
+ logger: context.logger,
47
+ excludeFields: context.autoConvertConfig?.excludeFields,
48
+ customFieldPatterns: context.autoConvertConfig?.customFieldPatterns,
49
+ maxDepth: context.autoConvertConfig?.maxDepth || 5
50
+ });
51
+
43
52
  // 如果没有提供 options 或 options 为空对象,返回 AggregateChain 以支持完整的链式调用
44
53
  const hasOptions = options && Object.keys(options).length > 0;
45
54
 
46
55
  if (!hasOptions) {
47
56
  // 返回 AggregateChain 实例,支持 .hint().collation() 等链式调用
48
- return new AggregateChain(context, pipeline, {});
57
+ return new AggregateChain(context, convertedPipeline, {});
49
58
  }
50
59
 
51
60
  // 如果提供了 options,执行原有逻辑(向后兼容)
@@ -70,13 +79,13 @@ function createAggregateOps(context) {
70
79
  // 如果启用 explain,直接返回执行计划(不缓存)
71
80
  if (explain) {
72
81
  const verbosity = typeof explain === 'string' ? explain : 'queryPlanner';
73
- const cursor = collection.aggregate(pipeline, aggOptions);
82
+ const cursor = collection.aggregate(convertedPipeline, aggOptions);
74
83
  return cursor.explain(verbosity);
75
84
  }
76
85
 
77
86
  // 如果启用流式返回,直接返回 MongoDB 游标流
78
87
  if (stream) {
79
- const cursor = collection.aggregate(pipeline, aggOptions);
88
+ const cursor = collection.aggregate(convertedPipeline, aggOptions);
80
89
  const readableStream = cursor.stream();
81
90
 
82
91
  // 添加慢查询日志支持
@@ -116,12 +125,12 @@ function createAggregateOps(context) {
116
125
  const resultPromise = run(
117
126
  'aggregate',
118
127
  options,
119
- async () => collection.aggregate(pipeline, aggOptions).toArray()
128
+ async () => collection.aggregate(convertedPipeline, aggOptions).toArray()
120
129
  );
121
130
 
122
131
  // 添加 explain 方法支持链式调用(与原生 MongoDB 一致)
123
132
  resultPromise.explain = async (verbosity = 'queryPlanner') => {
124
- const cursor = collection.aggregate(pipeline, aggOptions);
133
+ const cursor = collection.aggregate(convertedPipeline, aggOptions);
125
134
  return cursor.explain(verbosity);
126
135
  };
127
136