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
@@ -4,6 +4,7 @@
4
4
  */
5
5
 
6
6
  const { createErrorMessage } = require('../../common/docs-urls');
7
+ const { convertObjectIdStrings, convertAggregationPipeline } = require('../../utils/objectid-converter');
7
8
 
8
9
  /**
9
10
  * FindChain 类 - find 查询的链式调用构建器
@@ -25,7 +26,13 @@ class FindChain {
25
26
  */
26
27
  constructor(context, query = {}, initialOptions = {}) {
27
28
  this._context = context;
28
- this._query = query;
29
+ // ✅ v1.3.0: 自动转换 ObjectId 字符串
30
+ this._query = convertObjectIdStrings(query, 'filter', 0, new WeakSet(), {
31
+ logger: context.logger,
32
+ excludeFields: context.autoConvertConfig?.excludeFields,
33
+ customFieldPatterns: context.autoConvertConfig?.customFieldPatterns,
34
+ maxDepth: context.autoConvertConfig?.maxDepth
35
+ });
29
36
  this._options = { ...initialOptions };
30
37
  this._executed = false;
31
38
  }
@@ -38,8 +45,8 @@ class FindChain {
38
45
  limit(value) {
39
46
  if (this._executed) {
40
47
  throw new Error(createErrorMessage(
41
- "Cannot call .limit() after query execution.\n" +
42
- "Tip: Create a new chain for another query:\n" +
48
+ 'Cannot call .limit() after query execution.\n' +
49
+ 'Tip: Create a new chain for another query:\n' +
43
50
  " const results = await collection('products').find({}).limit(10);",
44
51
  'chaining.limit'
45
52
  ));
@@ -47,7 +54,7 @@ class FindChain {
47
54
  if (typeof value !== 'number' || value < 0) {
48
55
  throw new Error(createErrorMessage(
49
56
  `limit() requires a non-negative number, got: ${typeof value} (${value})\n` +
50
- `Usage: .limit(10)`,
57
+ 'Usage: .limit(10)',
51
58
  'chaining.limit'
52
59
  ));
53
60
  }
@@ -63,15 +70,15 @@ class FindChain {
63
70
  skip(value) {
64
71
  if (this._executed) {
65
72
  throw new Error(createErrorMessage(
66
- "Cannot call .skip() after query execution.\n" +
67
- "Tip: Create a new chain for another query.",
73
+ 'Cannot call .skip() after query execution.\n' +
74
+ 'Tip: Create a new chain for another query.',
68
75
  'chaining.skip'
69
76
  ));
70
77
  }
71
78
  if (typeof value !== 'number' || value < 0) {
72
79
  throw new Error(createErrorMessage(
73
80
  `skip() requires a non-negative number, got: ${typeof value} (${value})\n` +
74
- `Usage: .skip(10)`,
81
+ 'Usage: .skip(10)',
75
82
  'chaining.skip'
76
83
  ));
77
84
  }
@@ -87,15 +94,15 @@ class FindChain {
87
94
  sort(value) {
88
95
  if (this._executed) {
89
96
  throw new Error(createErrorMessage(
90
- "Cannot call .sort() after query execution.",
97
+ 'Cannot call .sort() after query execution.',
91
98
  'chaining.sort'
92
99
  ));
93
100
  }
94
101
  if (!value || (typeof value !== 'object')) {
95
102
  throw new Error(createErrorMessage(
96
103
  `sort() requires an object or array, got: ${typeof value}\n` +
97
- `Usage: .sort({ price: -1, name: 1 })\n` +
98
- `Note: Use 1 for ascending, -1 for descending`,
104
+ 'Usage: .sort({ price: -1, name: 1 })\n' +
105
+ 'Note: Use 1 for ascending, -1 for descending',
99
106
  'chaining.sort'
100
107
  ));
101
108
  }
@@ -111,14 +118,14 @@ class FindChain {
111
118
  project(value) {
112
119
  if (this._executed) {
113
120
  throw new Error(createErrorMessage(
114
- "Cannot call .project() after query execution.",
121
+ 'Cannot call .project() after query execution.',
115
122
  'chaining.project'
116
123
  ));
117
124
  }
118
125
  if (!value || typeof value !== 'object') {
119
126
  throw new Error(createErrorMessage(
120
127
  `project() requires an object or array, got: ${typeof value}\n` +
121
- `Usage: .project({ name: 1, price: 1 })`,
128
+ 'Usage: .project({ name: 1, price: 1 })',
122
129
  'chaining.project'
123
130
  ));
124
131
  }
@@ -134,14 +141,14 @@ class FindChain {
134
141
  hint(value) {
135
142
  if (this._executed) {
136
143
  throw new Error(createErrorMessage(
137
- "Cannot call .hint() after query execution.",
144
+ 'Cannot call .hint() after query execution.',
138
145
  'chaining.hint'
139
146
  ));
140
147
  }
141
148
  if (!value) {
142
149
  throw new Error(createErrorMessage(
143
- "hint() requires an index name or specification\n" +
144
- `Usage: .hint({ category: 1, price: -1 }) or .hint('category_1_price_-1')`,
150
+ 'hint() requires an index name or specification\n' +
151
+ 'Usage: .hint({ category: 1, price: -1 }) or .hint(\'category_1_price_-1\')',
145
152
  'chaining.hint'
146
153
  ));
147
154
  }
@@ -157,14 +164,14 @@ class FindChain {
157
164
  collation(value) {
158
165
  if (this._executed) {
159
166
  throw new Error(createErrorMessage(
160
- "Cannot call .collation() after query execution.",
167
+ 'Cannot call .collation() after query execution.',
161
168
  'chaining.collation'
162
169
  ));
163
170
  }
164
171
  if (!value || typeof value !== 'object') {
165
172
  throw new Error(createErrorMessage(
166
173
  `collation() requires an object, got: ${typeof value}\n` +
167
- `Usage: .collation({ locale: 'zh', strength: 2 })`,
174
+ 'Usage: .collation({ locale: \'zh\', strength: 2 })',
168
175
  'chaining.collation'
169
176
  ));
170
177
  }
@@ -180,14 +187,14 @@ class FindChain {
180
187
  comment(value) {
181
188
  if (this._executed) {
182
189
  throw new Error(createErrorMessage(
183
- "Cannot call .comment() after query execution.",
190
+ 'Cannot call .comment() after query execution.',
184
191
  'chaining.comment'
185
192
  ));
186
193
  }
187
194
  if (typeof value !== 'string') {
188
195
  throw new Error(createErrorMessage(
189
196
  `comment() requires a string, got: ${typeof value}\n` +
190
- `Usage: .comment('UserAPI:getProducts:user_123')`,
197
+ 'Usage: .comment(\'UserAPI:getProducts:user_123\')',
191
198
  'chaining.comment'
192
199
  ));
193
200
  }
@@ -203,14 +210,14 @@ class FindChain {
203
210
  maxTimeMS(value) {
204
211
  if (this._executed) {
205
212
  throw new Error(createErrorMessage(
206
- "Cannot call .maxTimeMS() after query execution.",
213
+ 'Cannot call .maxTimeMS() after query execution.',
207
214
  'chaining.maxTimeMS'
208
215
  ));
209
216
  }
210
217
  if (typeof value !== 'number' || value < 0) {
211
218
  throw new Error(createErrorMessage(
212
219
  `maxTimeMS() requires a non-negative number, got: ${typeof value} (${value})\n` +
213
- `Usage: .maxTimeMS(5000) // 5 seconds`,
220
+ 'Usage: .maxTimeMS(5000) // 5 seconds',
214
221
  'chaining.maxTimeMS'
215
222
  ));
216
223
  }
@@ -226,14 +233,14 @@ class FindChain {
226
233
  batchSize(value) {
227
234
  if (this._executed) {
228
235
  throw new Error(createErrorMessage(
229
- "Cannot call .batchSize() after query execution.",
236
+ 'Cannot call .batchSize() after query execution.',
230
237
  'chaining.batchSize'
231
238
  ));
232
239
  }
233
240
  if (typeof value !== 'number' || value < 0) {
234
241
  throw new Error(createErrorMessage(
235
242
  `batchSize() requires a non-negative number, got: ${typeof value} (${value})\n` +
236
- `Usage: .batchSize(1000)`,
243
+ 'Usage: .batchSize(1000)',
237
244
  'chaining.batchSize'
238
245
  ));
239
246
  }
@@ -301,8 +308,8 @@ class FindChain {
301
308
  async toArray() {
302
309
  if (this._executed) {
303
310
  throw new Error(createErrorMessage(
304
- "Query already executed. Create a new chain for another query.\n" +
305
- "Tip: Each chain can only be executed once:\n" +
311
+ 'Query already executed. Create a new chain for another query.\n' +
312
+ 'Tip: Each chain can only be executed once:\n' +
306
313
  " const results1 = await collection('products').find({}).limit(10);\n" +
307
314
  " const results2 = await collection('products').find({}).limit(20); // Create new chain",
308
315
  'chaining.toArray'
@@ -319,11 +326,11 @@ class FindChain {
319
326
  const skip = this._options.skip;
320
327
  const maxTimeMS = this._options.maxTimeMS !== undefined ? this._options.maxTimeMS : defaults.maxTimeMS;
321
328
 
322
- const driverOpts = {
323
- projection: this._options.projection,
324
- sort,
325
- skip,
326
- maxTimeMS
329
+ const driverOpts = {
330
+ projection: this._options.projection,
331
+ sort,
332
+ skip,
333
+ maxTimeMS
327
334
  };
328
335
  if (this._options.hint) driverOpts.hint = this._options.hint;
329
336
  if (this._options.collation) driverOpts.collation = this._options.collation;
@@ -389,14 +396,14 @@ class AggregateChain {
389
396
  hint(value) {
390
397
  if (this._executed) {
391
398
  throw new Error(createErrorMessage(
392
- "Cannot call .hint() after query execution.",
399
+ 'Cannot call .hint() after query execution.',
393
400
  'chaining.hint'
394
401
  ));
395
402
  }
396
403
  if (!value) {
397
404
  throw new Error(createErrorMessage(
398
- "hint() requires an index name or specification\n" +
399
- `Usage: .hint({ status: 1, createdAt: -1 })`,
405
+ 'hint() requires an index name or specification\n' +
406
+ 'Usage: .hint({ status: 1, createdAt: -1 })',
400
407
  'chaining.hint'
401
408
  ));
402
409
  }
@@ -412,14 +419,14 @@ class AggregateChain {
412
419
  collation(value) {
413
420
  if (this._executed) {
414
421
  throw new Error(createErrorMessage(
415
- "Cannot call .collation() after query execution.",
422
+ 'Cannot call .collation() after query execution.',
416
423
  'chaining.collation'
417
424
  ));
418
425
  }
419
426
  if (!value || typeof value !== 'object') {
420
427
  throw new Error(createErrorMessage(
421
428
  `collation() requires an object, got: ${typeof value}\n` +
422
- `Usage: .collation({ locale: 'zh', strength: 2 })`,
429
+ 'Usage: .collation({ locale: \'zh\', strength: 2 })',
423
430
  'chaining.collation'
424
431
  ));
425
432
  }
@@ -435,14 +442,14 @@ class AggregateChain {
435
442
  comment(value) {
436
443
  if (this._executed) {
437
444
  throw new Error(createErrorMessage(
438
- "Cannot call .comment() after query execution.",
445
+ 'Cannot call .comment() after query execution.',
439
446
  'chaining.comment'
440
447
  ));
441
448
  }
442
449
  if (typeof value !== 'string') {
443
450
  throw new Error(createErrorMessage(
444
451
  `comment() requires a string, got: ${typeof value}\n` +
445
- `Usage: .comment('OrderAPI:aggregateSales')`,
452
+ 'Usage: .comment(\'OrderAPI:aggregateSales\')',
446
453
  'chaining.comment'
447
454
  ));
448
455
  }
@@ -458,14 +465,14 @@ class AggregateChain {
458
465
  maxTimeMS(value) {
459
466
  if (this._executed) {
460
467
  throw new Error(createErrorMessage(
461
- "Cannot call .maxTimeMS() after query execution.",
468
+ 'Cannot call .maxTimeMS() after query execution.',
462
469
  'chaining.maxTimeMS'
463
470
  ));
464
471
  }
465
472
  if (typeof value !== 'number' || value < 0) {
466
473
  throw new Error(createErrorMessage(
467
474
  `maxTimeMS() requires a non-negative number, got: ${typeof value} (${value})\n` +
468
- `Usage: .maxTimeMS(10000) // 10 seconds`,
475
+ 'Usage: .maxTimeMS(10000) // 10 seconds',
469
476
  'chaining.maxTimeMS'
470
477
  ));
471
478
  }
@@ -481,14 +488,14 @@ class AggregateChain {
481
488
  allowDiskUse(value) {
482
489
  if (this._executed) {
483
490
  throw new Error(createErrorMessage(
484
- "Cannot call .allowDiskUse() after query execution.",
491
+ 'Cannot call .allowDiskUse() after query execution.',
485
492
  'chaining.allowDiskUse'
486
493
  ));
487
494
  }
488
495
  if (typeof value !== 'boolean') {
489
496
  throw new Error(createErrorMessage(
490
497
  `allowDiskUse() requires a boolean, got: ${typeof value}\n` +
491
- `Usage: .allowDiskUse(true)`,
498
+ 'Usage: .allowDiskUse(true)',
492
499
  'chaining.allowDiskUse'
493
500
  ));
494
501
  }
@@ -504,14 +511,14 @@ class AggregateChain {
504
511
  batchSize(value) {
505
512
  if (this._executed) {
506
513
  throw new Error(createErrorMessage(
507
- "Cannot call .batchSize() after query execution.",
514
+ 'Cannot call .batchSize() after query execution.',
508
515
  'chaining.batchSize'
509
516
  ));
510
517
  }
511
518
  if (typeof value !== 'number' || value < 0) {
512
519
  throw new Error(createErrorMessage(
513
520
  `batchSize() requires a non-negative number, got: ${typeof value} (${value})\n` +
514
- `Usage: .batchSize(1000)`,
521
+ 'Usage: .batchSize(1000)',
515
522
  'chaining.batchSize'
516
523
  ));
517
524
  }
@@ -567,8 +574,8 @@ class AggregateChain {
567
574
  async toArray() {
568
575
  if (this._executed) {
569
576
  throw new Error(createErrorMessage(
570
- "Query already executed. Create a new chain for another query.\n" +
571
- "Tip: Each chain can only be executed once:\n" +
577
+ 'Query already executed. Create a new chain for another query.\n' +
578
+ 'Tip: Each chain can only be executed once:\n' +
572
579
  " const results1 = await collection('orders').aggregate([...]).allowDiskUse(true);\n" +
573
580
  " const results2 = await collection('orders').aggregate([...]).maxTimeMS(5000); // Create new chain",
574
581
  'chaining.toArray'
@@ -3,6 +3,8 @@
3
3
  * @description 提供文档计数功能,使用 MongoDB 原生推荐的 countDocuments() 和 estimatedDocumentCount() 方法
4
4
  */
5
5
 
6
+ const { convertObjectIdStrings } = require('../../utils/objectid-converter');
7
+
6
8
  /**
7
9
  * 创建 count 查询操作
8
10
  * @param {Object} context - 上下文对象
@@ -28,11 +30,19 @@ function createCountOps(context) {
28
30
  * @returns {Promise<number>} 匹配的文档数量;当 explain=true 时返回执行计划对象
29
31
  */
30
32
  count: async (query = {}, options = {}) => {
33
+ // ✅ v1.3.0: 自动转换 ObjectId 字符串
34
+ const convertedQuery = convertObjectIdStrings(query, 'query', 0, new WeakSet(), {
35
+ logger: context.logger,
36
+ excludeFields: context.autoConvertConfig?.excludeFields,
37
+ customFieldPatterns: context.autoConvertConfig?.customFieldPatterns,
38
+ maxDepth: context.autoConvertConfig?.maxDepth
39
+ });
40
+
31
41
  const { maxTimeMS = defaults.maxTimeMS, explain, comment } = options;
32
42
 
33
43
  // 如果启用 explain,直接返回查询执行计划(不缓存)
34
44
  if (explain) {
35
- const verbosity = typeof explain === "string" ? explain : "queryPlanner";
45
+ const verbosity = typeof explain === 'string' ? explain : 'queryPlanner';
36
46
  const isEmptyQuery = !query || Object.keys(query).length === 0;
37
47
 
38
48
  if (isEmptyQuery) {
@@ -44,7 +54,7 @@ function createCountOps(context) {
44
54
  };
45
55
  } else {
46
56
  // countDocuments 通过聚合管道实现,使用 aggregate 获取 explain
47
- const pipeline = [{ $match: query }, { $count: "total" }];
57
+ const pipeline = [{ $match: convertedQuery }, { $count: 'total' }];
48
58
  const aggOpts = {
49
59
  maxTimeMS,
50
60
  ...(options.hint && { hint: options.hint }),
@@ -56,11 +66,11 @@ function createCountOps(context) {
56
66
  }
57
67
 
58
68
  // 性能优化:判断是否为空查询
59
- const isEmptyQuery = !query || Object.keys(query).length === 0;
69
+ const isEmptyQuery = !convertedQuery || Object.keys(convertedQuery).length === 0;
60
70
 
61
71
  return run(
62
- "count",
63
- { query, ...options },
72
+ 'count',
73
+ { query: convertedQuery, ...options },
64
74
  () => {
65
75
  if (isEmptyQuery) {
66
76
  // 空查询使用 estimatedDocumentCount(快速,基于集合元数据)
@@ -77,7 +87,7 @@ function createCountOps(context) {
77
87
  ...(options.limit && { limit: options.limit })
78
88
  };
79
89
  if (comment) countOpts.comment = comment;
80
- return collection.countDocuments(query, countOpts);
90
+ return collection.countDocuments(convertedQuery, countOpts);
81
91
  }
82
92
  }
83
93
  );
@@ -3,6 +3,8 @@
3
3
  * @description 提供字段去重查询功能,使用 MongoDB 原生 distinct() 方法
4
4
  */
5
5
 
6
+ const { convertObjectIdStrings } = require('../../utils/objectid-converter');
7
+
6
8
  /**
7
9
  * 创建 distinct 查询操作
8
10
  * @param {Object} context - 上下文对象
@@ -26,6 +28,13 @@ function createDistinctOps(context) {
26
28
  * @returns {Promise<Array>} 返回去重后的值数组;当 explain=true 时返回执行计划对象
27
29
  */
28
30
  distinct: async (field, query = {}, options = {}) => {
31
+ // ✅ v1.3.0: 自动转换 ObjectId 字符串
32
+ const convertedQuery = convertObjectIdStrings(query, 'query', 0, new WeakSet(), {
33
+ logger: context.logger,
34
+ excludeFields: context.autoConvertConfig?.excludeFields,
35
+ customFieldPatterns: context.autoConvertConfig?.customFieldPatterns,
36
+ maxDepth: context.autoConvertConfig?.maxDepth
37
+ });
29
38
  const {
30
39
  maxTimeMS = defaults.maxTimeMS,
31
40
  collation,
@@ -41,11 +50,11 @@ function createDistinctOps(context) {
41
50
  // 如果启用 explain,通过 aggregate 模拟 distinct 并返回执行计划
42
51
  // 注意:MongoDB 原生 distinct 命令不支持 explain,需要通过聚合管道模拟
43
52
  if (explain) {
44
- const verbosity = typeof explain === "string" ? explain : "queryPlanner";
53
+ const verbosity = typeof explain === 'string' ? explain : 'queryPlanner';
45
54
  // distinct 命令通过聚合管道模拟:$match + $group
46
55
  const pipeline = [];
47
- if (query && Object.keys(query).length > 0) {
48
- pipeline.push({ $match: query });
56
+ if (convertedQuery && Object.keys(convertedQuery).length > 0) {
57
+ pipeline.push({ $match: convertedQuery });
49
58
  }
50
59
  pipeline.push({ $group: { _id: `$${field}` } });
51
60
 
@@ -57,9 +66,9 @@ function createDistinctOps(context) {
57
66
  }
58
67
 
59
68
  return run(
60
- "distinct",
61
- { field, query, ...options },
62
- () => collection.distinct(field, query, driverOpts)
69
+ 'distinct',
70
+ { field, query: convertedQuery, ...options },
71
+ () => collection.distinct(field, convertedQuery, driverOpts)
63
72
  );
64
73
  }
65
74
  };
@@ -4,6 +4,7 @@
4
4
  */
5
5
 
6
6
  const { createError, ErrorCodes } = require('../../errors');
7
+ const { convertObjectIdStrings } = require('../../utils/objectid-converter');
7
8
 
8
9
  /**
9
10
  * 创建 findAndCount 操作
@@ -70,6 +71,14 @@ function createFindAndCountOps(context) {
70
71
  query = {};
71
72
  }
72
73
 
74
+ // ✅ v1.3.0: 自动转换 ObjectId 字符串
75
+ const convertedQuery = convertObjectIdStrings(query, 'query', 0, new WeakSet(), {
76
+ logger: context.logger,
77
+ excludeFields: context.autoConvertConfig?.excludeFields,
78
+ customFieldPatterns: context.autoConvertConfig?.customFieldPatterns,
79
+ maxDepth: context.autoConvertConfig?.maxDepth
80
+ });
81
+
73
82
  // 2. 提取选项
74
83
  const projection = options.projection;
75
84
  const sort = options.sort;
@@ -80,7 +89,7 @@ function createFindAndCountOps(context) {
80
89
  const comment = options.comment;
81
90
 
82
91
  // 3. 缓存键(包含 query, projection, sort, limit, skip)
83
- const cacheKey = cache ? `${instanceId}:${type}:${effectiveDbName}:${collection.collectionName}:findAndCount:${JSON.stringify({ query, projection, sort, limit, skip })}` : null;
92
+ const cacheKey = cache ? `${instanceId}:${type}:${effectiveDbName}:${collection.collectionName}:findAndCount:${JSON.stringify({ query: convertedQuery, projection, sort, limit, skip })}` : null;
84
93
 
85
94
  // 4. 检查缓存
86
95
  if (cache && cacheTime > 0) {
@@ -90,7 +99,7 @@ function createFindAndCountOps(context) {
90
99
  if (cached !== null && cached !== undefined) {
91
100
  logger?.debug?.('[findAndCount] 缓存命中', {
92
101
  ns: `${effectiveDbName}.${collection.collectionName}`,
93
- query: query
102
+ query
94
103
  });
95
104
  return cached;
96
105
  }
@@ -117,8 +126,8 @@ function createFindAndCountOps(context) {
117
126
  let data, total;
118
127
  try {
119
128
  [data, total] = await Promise.all([
120
- collection.find(query, findOptions).toArray(),
121
- collection.countDocuments(query, countOptions)
129
+ collection.find(convertedQuery, findOptions).toArray(),
130
+ collection.countDocuments(convertedQuery, countOptions)
122
131
  ]);
123
132
  } catch (error) {
124
133
  throw error;
@@ -146,17 +155,17 @@ function createFindAndCountOps(context) {
146
155
  operation: 'findAndCount',
147
156
  durationMs: duration,
148
157
  iid: instanceId,
149
- type: type,
158
+ type,
150
159
  db: effectiveDbName,
151
160
  collection: collection.collectionName,
152
161
  dataCount: data.length,
153
- total: total,
162
+ total,
154
163
  query: mongoSlowLogShaper?.sanitize ? mongoSlowLogShaper.sanitize(query) : query,
155
- projection: projection,
156
- sort: sort,
157
- limit: limit,
158
- skip: skip,
159
- comment: comment
164
+ projection,
165
+ sort,
166
+ limit,
167
+ skip,
168
+ comment
160
169
  };
161
170
  logger?.warn?.('🐌 Slow query: findAndCount', meta);
162
171
  emit?.('slow-query', meta);
@@ -168,9 +177,9 @@ function createFindAndCountOps(context) {
168
177
  // 10. 日志记录
169
178
  logger?.debug?.('[findAndCount] 查询完成', {
170
179
  ns: `${effectiveDbName}.${collection.collectionName}`,
171
- duration: duration,
180
+ duration,
172
181
  dataCount: data.length,
173
- total: total
182
+ total
174
183
  });
175
184
 
176
185
  return result;
@@ -178,16 +178,16 @@ function createFindByIdsOps(context) {
178
178
  operation: 'findByIds',
179
179
  durationMs: duration,
180
180
  iid: instanceId,
181
- type: type,
181
+ type,
182
182
  db: effectiveDbName,
183
183
  collection: collection.collectionName,
184
184
  idsCount: ids.length,
185
185
  uniqueCount: uniqueIds.length,
186
186
  resultCount: results.length,
187
187
  query: mongoSlowLogShaper?.sanitize ? mongoSlowLogShaper.sanitize(query) : query,
188
- projection: projection,
189
- sort: sort,
190
- comment: comment
188
+ projection,
189
+ sort,
190
+ comment
191
191
  };
192
192
  logger?.warn?.('🐌 Slow query: findByIds', meta);
193
193
  emit?.('slow-query', meta);
@@ -199,7 +199,7 @@ function createFindByIdsOps(context) {
199
199
  // 12. 日志记录
200
200
  logger?.debug?.('[findByIds] 查询完成', {
201
201
  ns: `${effectiveDbName}.${collection.collectionName}`,
202
- duration: duration,
202
+ duration,
203
203
  idsCount: ids.length,
204
204
  uniqueCount: uniqueIds.length,
205
205
  resultCount: results.length
@@ -96,7 +96,7 @@ function createFindOneByIdOps(context) {
96
96
  // 拒绝其他类型(包括数字、对象等)
97
97
  throw createError(
98
98
  ErrorCodes.INVALID_ARGUMENT,
99
- `id 必须是字符串或 ObjectId 实例`,
99
+ 'id 必须是字符串或 ObjectId 实例',
100
100
  [{
101
101
  field: 'id',
102
102
  type: 'type',
@@ -4,6 +4,7 @@
4
4
  */
5
5
 
6
6
  const { normalizeProjection, normalizeSort } = require('../../common/normalize');
7
+ const { convertObjectIdStrings } = require('../../utils/objectid-converter');
7
8
 
8
9
  /**
9
10
  * 创建 findOne 查询操作
@@ -30,6 +31,14 @@ function createFindOneOps(context) {
30
31
  * @returns {Promise<Object|null>} 返回匹配的第一条记录对象,未找到时返回null;当 explain=true 时返回执行计划
31
32
  */
32
33
  findOne: async (query = {}, options = {}) => {
34
+ // ✅ v1.3.0: 自动转换 ObjectId 字符串
35
+ const convertedQuery = convertObjectIdStrings(query, 'query', 0, new WeakSet(), {
36
+ logger: context.logger,
37
+ excludeFields: context.autoConvertConfig?.excludeFields,
38
+ customFieldPatterns: context.autoConvertConfig?.customFieldPatterns,
39
+ maxDepth: context.autoConvertConfig?.maxDepth
40
+ });
41
+
33
42
  options.projection = normalizeProjection(options.projection);
34
43
  const {
35
44
  projection,
@@ -45,14 +54,14 @@ function createFindOneOps(context) {
45
54
  // 如果启用 explain,直接返回执行计划(不缓存)
46
55
  if (explain) {
47
56
  const verbosity = typeof explain === 'string' ? explain : 'queryPlanner';
48
- const cursor = collection.find(query, driverOpts).limit(1);
57
+ const cursor = collection.find(convertedQuery, driverOpts).limit(1);
49
58
  return await cursor.explain(verbosity);
50
59
  }
51
60
 
52
61
  return run(
53
62
  'findOne',
54
- { query, ...options },
55
- () => collection.findOne(query, driverOpts)
63
+ { query: convertedQuery, ...options },
64
+ () => collection.findOne(convertedQuery, driverOpts)
56
65
  );
57
66
  }
58
67
  };
@@ -11,6 +11,7 @@ const { decodeCursor } = require('../../common/cursor');
11
11
  const { validateLimitAfterBefore, assertCursorSortCompatible } = require('../../common/validation');
12
12
  const { makePageResult } = require('../../common/page-result');
13
13
  const { normalizeSort } = require('../../common/normalize');
14
+ const { convertObjectIdStrings } = require('../../utils/objectid-converter');
14
15
 
15
16
  // —— Count 队列支持(高并发控制)——
16
17
  let countQueue = null;
@@ -213,6 +214,17 @@ function createFindPage(ctx) {
213
214
 
214
215
  return async function findPage(options = {}) {
215
216
  const startTime = Date.now(); // 记录开始时间
217
+
218
+ // ✅ v1.3.0: 自动转换 ObjectId 字符串
219
+ if (options.query) {
220
+ options.query = convertObjectIdStrings(options.query, 'filter', 0, new WeakSet(), {
221
+ logger: ctx.logger,
222
+ excludeFields: ctx.autoConvertConfig?.excludeFields,
223
+ customFieldPatterns: ctx.autoConvertConfig?.customFieldPatterns,
224
+ maxDepth: ctx.autoConvertConfig?.maxDepth
225
+ });
226
+ }
227
+
216
228
  const MAX_LIMIT = defaults?.findPageMaxLimit ?? 500;
217
229
  // 基础校验:limit + after/before 互斥
218
230
  validateLimitAfterBefore(options, { maxLimit: MAX_LIMIT });