monsqlize 1.3.0 → 2.0.0

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 (172) hide show
  1. package/CHANGELOG.md +56 -60
  2. package/LICENSE +201 -21
  3. package/README.md +537 -1828
  4. package/changelogs/README.md +160 -0
  5. package/changelogs/v2.0.0.md +222 -0
  6. package/dist/cjs/index.cjs +10600 -0
  7. package/dist/cjs/mongodb/common/transaction-aware.cjs +10 -0
  8. package/dist/cjs/transaction/CacheLockManager.cjs +100 -0
  9. package/dist/cjs/transaction/Transaction.cjs +158 -0
  10. package/dist/cjs/transaction/TransactionManager.cjs +298 -0
  11. package/dist/esm/index.mjs +10650 -0
  12. package/dist/types/base.d.ts +81 -0
  13. package/dist/types/collection.d.ts +1031 -0
  14. package/dist/types/expression.d.ts +115 -0
  15. package/dist/types/index.d.ts +23 -0
  16. package/dist/types/lock.d.ts +74 -0
  17. package/dist/types/model.d.ts +526 -0
  18. package/dist/types/mongodb.d.ts +49 -0
  19. package/dist/types/monsqlize.d.ts +491 -0
  20. package/dist/types/pool.d.ts +84 -0
  21. package/dist/types/runtime.d.ts +362 -0
  22. package/dist/types/saga.d.ts +143 -0
  23. package/dist/types/slow-query-log.d.ts +126 -0
  24. package/dist/types/sync.d.ts +103 -0
  25. package/dist/types/transaction.d.ts +132 -0
  26. package/package.json +67 -69
  27. package/index.d.ts +0 -206
  28. package/index.mjs +0 -52
  29. package/lib/cache-invalidation.js +0 -279
  30. package/lib/cache.js +0 -530
  31. package/lib/common/cursor.js +0 -59
  32. package/lib/common/docs-urls.js +0 -73
  33. package/lib/common/index-options.js +0 -223
  34. package/lib/common/log.js +0 -61
  35. package/lib/common/namespace.js +0 -22
  36. package/lib/common/normalize.js +0 -34
  37. package/lib/common/page-result.js +0 -43
  38. package/lib/common/runner.js +0 -57
  39. package/lib/common/server-features.js +0 -232
  40. package/lib/common/shape-builders.js +0 -27
  41. package/lib/common/validation.js +0 -113
  42. package/lib/connect.js +0 -99
  43. package/lib/constants.js +0 -55
  44. package/lib/count-queue.js +0 -188
  45. package/lib/distributed-cache-invalidator.js +0 -260
  46. package/lib/errors.js +0 -168
  47. package/lib/expression/cache/ExpressionCache.js +0 -114
  48. package/lib/expression/compiler/ExpressionCompiler.js +0 -1090
  49. package/lib/expression/compiler/ExpressionCompilerExtensions.js +0 -531
  50. package/lib/expression/detector.js +0 -84
  51. package/lib/expression/factory.js +0 -29
  52. package/lib/expression/index.js +0 -19
  53. package/lib/function-cache.js +0 -533
  54. package/lib/index.js +0 -1251
  55. package/lib/infrastructure/ConnectionPoolManager.js +0 -464
  56. package/lib/infrastructure/HealthChecker.js +0 -281
  57. package/lib/infrastructure/PoolConfig.js +0 -199
  58. package/lib/infrastructure/PoolSelector.js +0 -225
  59. package/lib/infrastructure/PoolStats.js +0 -244
  60. package/lib/infrastructure/ssh-tunnel-ssh2.js +0 -212
  61. package/lib/infrastructure/ssh-tunnel.js +0 -41
  62. package/lib/infrastructure/uri-parser.js +0 -36
  63. package/lib/lock/Lock.js +0 -67
  64. package/lib/lock/errors.js +0 -28
  65. package/lib/lock/index.js +0 -13
  66. package/lib/logger.js +0 -225
  67. package/lib/model/examples/test.js +0 -311
  68. package/lib/model/features/defaults.js +0 -161
  69. package/lib/model/features/populate.js +0 -568
  70. package/lib/model/features/relations.js +0 -120
  71. package/lib/model/features/soft-delete.js +0 -349
  72. package/lib/model/features/version.js +0 -157
  73. package/lib/model/features/virtuals.js +0 -219
  74. package/lib/model/index.js +0 -1265
  75. package/lib/mongodb/common/accessor-helpers.js +0 -59
  76. package/lib/mongodb/common/agg-pipeline.js +0 -36
  77. package/lib/mongodb/common/aggregation-validator.js +0 -127
  78. package/lib/mongodb/common/iid.js +0 -28
  79. package/lib/mongodb/common/lexicographic-expr.js +0 -53
  80. package/lib/mongodb/common/shape.js +0 -32
  81. package/lib/mongodb/common/sort.js +0 -39
  82. package/lib/mongodb/common/transaction-aware.js +0 -25
  83. package/lib/mongodb/connect.js +0 -234
  84. package/lib/mongodb/index.js +0 -639
  85. package/lib/mongodb/management/admin-ops.js +0 -200
  86. package/lib/mongodb/management/bookmark-ops.js +0 -167
  87. package/lib/mongodb/management/cache-ops.js +0 -50
  88. package/lib/mongodb/management/collection-ops.js +0 -387
  89. package/lib/mongodb/management/database-ops.js +0 -202
  90. package/lib/mongodb/management/index-ops.js +0 -475
  91. package/lib/mongodb/management/index.js +0 -17
  92. package/lib/mongodb/management/namespace.js +0 -31
  93. package/lib/mongodb/management/validation-ops.js +0 -268
  94. package/lib/mongodb/queries/aggregate.js +0 -172
  95. package/lib/mongodb/queries/chain.js +0 -631
  96. package/lib/mongodb/queries/count.js +0 -99
  97. package/lib/mongodb/queries/distinct.js +0 -78
  98. package/lib/mongodb/queries/find-and-count.js +0 -193
  99. package/lib/mongodb/queries/find-by-ids.js +0 -236
  100. package/lib/mongodb/queries/find-one-by-id.js +0 -171
  101. package/lib/mongodb/queries/find-one.js +0 -71
  102. package/lib/mongodb/queries/find-page.js +0 -618
  103. package/lib/mongodb/queries/find.js +0 -171
  104. package/lib/mongodb/queries/index.js +0 -51
  105. package/lib/mongodb/queries/watch.js +0 -538
  106. package/lib/mongodb/writes/common/batch-retry.js +0 -65
  107. package/lib/mongodb/writes/delete-batch.js +0 -323
  108. package/lib/mongodb/writes/delete-many.js +0 -181
  109. package/lib/mongodb/writes/delete-one.js +0 -173
  110. package/lib/mongodb/writes/find-one-and-delete.js +0 -203
  111. package/lib/mongodb/writes/find-one-and-replace.js +0 -239
  112. package/lib/mongodb/writes/find-one-and-update.js +0 -240
  113. package/lib/mongodb/writes/increment-one.js +0 -259
  114. package/lib/mongodb/writes/index.js +0 -46
  115. package/lib/mongodb/writes/insert-batch.js +0 -508
  116. package/lib/mongodb/writes/insert-many.js +0 -223
  117. package/lib/mongodb/writes/insert-one.js +0 -169
  118. package/lib/mongodb/writes/replace-one.js +0 -226
  119. package/lib/mongodb/writes/result-handler.js +0 -237
  120. package/lib/mongodb/writes/update-batch.js +0 -416
  121. package/lib/mongodb/writes/update-many.js +0 -275
  122. package/lib/mongodb/writes/update-one.js +0 -273
  123. package/lib/mongodb/writes/upsert-one.js +0 -203
  124. package/lib/multi-level-cache.js +0 -244
  125. package/lib/operators.js +0 -330
  126. package/lib/redis-cache-adapter.js +0 -267
  127. package/lib/saga/SagaContext.js +0 -67
  128. package/lib/saga/SagaDefinition.js +0 -32
  129. package/lib/saga/SagaExecutor.js +0 -201
  130. package/lib/saga/SagaOrchestrator.js +0 -186
  131. package/lib/saga/index.js +0 -11
  132. package/lib/slow-query-log/base-storage.js +0 -70
  133. package/lib/slow-query-log/batch-queue.js +0 -97
  134. package/lib/slow-query-log/config-manager.js +0 -196
  135. package/lib/slow-query-log/index.js +0 -238
  136. package/lib/slow-query-log/mongodb-storage.js +0 -324
  137. package/lib/slow-query-log/query-hash.js +0 -39
  138. package/lib/sync/ChangeStreamSyncManager.js +0 -405
  139. package/lib/sync/ResumeTokenStore.js +0 -192
  140. package/lib/sync/SyncConfig.js +0 -127
  141. package/lib/sync/SyncTarget.js +0 -240
  142. package/lib/sync/index.js +0 -20
  143. package/lib/transaction/CacheLockManager.js +0 -162
  144. package/lib/transaction/DistributedCacheLockManager.js +0 -475
  145. package/lib/transaction/Transaction.js +0 -315
  146. package/lib/transaction/TransactionManager.js +0 -267
  147. package/lib/transaction/index.js +0 -11
  148. package/lib/utils/objectid-converter.js +0 -632
  149. package/types/README.md +0 -122
  150. package/types/base.ts +0 -94
  151. package/types/batch.ts +0 -187
  152. package/types/cache.ts +0 -71
  153. package/types/chain.ts +0 -254
  154. package/types/collection.ts +0 -357
  155. package/types/expression.ts +0 -109
  156. package/types/function-cache.d.ts +0 -135
  157. package/types/lock.ts +0 -95
  158. package/types/model/definition.ts +0 -152
  159. package/types/model/index.ts +0 -10
  160. package/types/model/instance.ts +0 -121
  161. package/types/model/relations.ts +0 -121
  162. package/types/model/virtuals.ts +0 -32
  163. package/types/monsqlize.ts +0 -245
  164. package/types/options.ts +0 -192
  165. package/types/pagination.ts +0 -154
  166. package/types/pool.ts +0 -125
  167. package/types/query.ts +0 -71
  168. package/types/saga.ts +0 -125
  169. package/types/stream.ts +0 -64
  170. package/types/sync.ts +0 -79
  171. package/types/transaction.ts +0 -79
  172. package/types/write.ts +0 -77
@@ -1,632 +0,0 @@
1
- /**
2
- * monSQLize - ObjectId 自动转换工具
3
- * @description 自动将 ObjectId 字符串转换为 ObjectId 实例
4
- * @version 1.0.0
5
- *
6
- * 核心特性:
7
- * - 字段白名单:只转换 _id, *Id, *Ids 等字段
8
- * - 官方验证:使用 ObjectId.isValid() 确保有效性
9
- * - 循环引用检测:使用 WeakSet 防止无限递归
10
- * - 深度限制:最大递归深度10层
11
- * - 特殊处理:$expr, $function, 字段引用不转换
12
- * - 性能优化:无转换时返回原对象(不克隆)
13
- * - 异常降级:转换失败返回原值
14
- */
15
-
16
- const { ObjectId } = require('mongodb');
17
-
18
- /**
19
- * 字段白名单模式
20
- * 只有匹配这些模式的字段才会被转换
21
- */
22
- const OBJECTID_FIELD_PATTERNS = [
23
- '_id', // 精确匹配:_id
24
- /^.*Id$/, // 后缀匹配:userId, authorId, productId
25
- /^.*Ids$/, // 后缀匹配:userIds, authorIds
26
- /^.*_id$/, // 后缀匹配:user_id, author_id
27
- /^.*_ids$/, // 后缀匹配:user_ids, author_ids
28
- ];
29
-
30
- /**
31
- * 特殊操作符/字段,不进行转换
32
- */
33
- const SPECIAL_OPERATORS = new Set([
34
- '$expr', // 聚合表达式
35
- '$function', // 自定义函数(MongoDB 4.4+)
36
- '$where', // JavaScript 表达式(不推荐,但需支持)
37
- '$accumulator', // 自定义累加器(MongoDB 4.4+)
38
- ]);
39
-
40
- /**
41
- * 检查字段名是否应该转换
42
- * @param {string} fieldName - 字段名
43
- * @param {Array} customPatterns - 自定义字段模式(可选)
44
- * @returns {boolean}
45
- */
46
- function shouldConvertField(fieldName, customPatterns = []) {
47
- if (!fieldName || typeof fieldName !== 'string') {
48
- return false;
49
- }
50
-
51
- const allPatterns = [...OBJECTID_FIELD_PATTERNS, ...customPatterns];
52
-
53
- return allPatterns.some(pattern => {
54
- if (typeof pattern === 'string') {
55
- return fieldName === pattern;
56
- }
57
- if (pattern instanceof RegExp) {
58
- return pattern.test(fieldName);
59
- }
60
- return false;
61
- });
62
- }
63
-
64
- /**
65
- * 检查字符串是否为有效的 ObjectId
66
- * @param {*} str - 待检测的值
67
- * @returns {boolean}
68
- */
69
- function isValidObjectIdString(str) {
70
- if (typeof str !== 'string') {
71
- return false;
72
- }
73
-
74
- // 快速格式检测(性能优化)
75
- if (!/^[0-9a-fA-F]{24}$/.test(str)) {
76
- return false;
77
- }
78
-
79
- // 官方验证(确保有效性)
80
- return ObjectId.isValid(str);
81
- }
82
-
83
- /**
84
- * 检查值是否为 MongoDB 字段引用或变量
85
- * @param {*} value - 待检测的值
86
- * @returns {boolean}
87
- */
88
- function isFieldReference(value) {
89
- if (typeof value !== 'string') {
90
- return false;
91
- }
92
-
93
- // MongoDB 字段引用($ 开头,但不是操作符)
94
- // 例如:'$userId', '$items.productId'
95
- if (value.startsWith('$') && !value.startsWith('$$')) {
96
- return true;
97
- }
98
-
99
- // MongoDB 聚合变量($$ 开头)
100
- // 例如:'$$userId', '$$ROOT'
101
- if (value.startsWith('$$')) {
102
- return true;
103
- }
104
-
105
- return false;
106
- }
107
-
108
- /**
109
- * 递归转换对象中的 ObjectId 字符串
110
- * @param {*} obj - 待转换的对象
111
- * @param {string} fieldPath - 当前字段路径(用于日志和调试)
112
- * @param {number} depth - 递归深度(防止栈溢出)
113
- * @param {WeakSet} visited - 已访问对象集合(防止循环引用)
114
- * @param {Object} options - 配置选项
115
- * @param {Object} options.logger - 日志记录器
116
- * @param {Array} options.excludeFields - 排除的字段
117
- * @param {Array} options.customFieldPatterns - 自定义字段模式
118
- * @param {number} options.maxDepth - 最大递归深度
119
- * @returns {*} 转换后的对象
120
- */
121
- function convertObjectIdStrings(obj, fieldPath = '', depth = 0, visited = new WeakSet(), options = {}) {
122
- const {
123
- logger = null,
124
- excludeFields = [],
125
- customFieldPatterns = [],
126
- maxDepth = 10,
127
- verbose = false, // 详细日志模式(默认关闭)
128
- silent = true, // 🆕 静默模式(默认开启,不输出任何日志)
129
- _conversionStats = null // 内部统计对象(递归传递)
130
- } = options;
131
-
132
- // 初始化统计(仅在顶层调用)
133
- const stats = _conversionStats || { count: 0, fields: [] };
134
- const isTopLevel = depth === 0 && !_conversionStats;
135
-
136
- try {
137
- // 1. 深度保护(防止栈溢出)
138
- if (depth > maxDepth) {
139
- if (logger && logger.warn) {
140
- logger.warn('[ObjectId Converter] Depth limit reached', {
141
- depth,
142
- fieldPath,
143
- message: 'Object nesting too deep, skipping conversion'
144
- });
145
- }
146
- return obj;
147
- }
148
-
149
- // 2. null/undefined 检测
150
- if (obj === null || obj === undefined) {
151
- return obj;
152
- }
153
-
154
- // 3. 已经是当前版本的 ObjectId 实例
155
- if (obj instanceof ObjectId) {
156
- return obj;
157
- }
158
-
159
- // 🆕 3.5 兼容其他版本的 ObjectId 实例(跨 BSON 版本兼容)
160
- // 场景:mongoose (bson@4.x/5.x) 与 monSQLize (bson@6.x) 混用
161
- if (obj && typeof obj === 'object' && obj.constructor && obj.constructor.name === 'ObjectId') {
162
- try {
163
- // 通过 toString() 方法获取 hex 字符串,再构造为当前版本的 ObjectId
164
- const hexString = obj.toString();
165
- if (isValidObjectIdString(hexString)) {
166
- const converted = new ObjectId(hexString);
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) {
176
- logger.debug('[ObjectId Converter] Cross-version ObjectId converted', {
177
- from: obj.constructor.name,
178
- to: 'ObjectId',
179
- hex: hexString,
180
- fieldPath
181
- });
182
- }
183
-
184
- return converted;
185
- }
186
- } catch (error) {
187
- // 转换失败,返回原对象
188
- if (logger && logger.warn) {
189
- logger.warn('[ObjectId Converter] Cross-version ObjectId conversion failed', {
190
- error: error.message,
191
- fieldPath,
192
- objectType: obj.constructor.name
193
- });
194
- }
195
- return obj;
196
- }
197
- }
198
-
199
- // 4. 字符串处理
200
- if (typeof obj === 'string') {
201
- // 4.1 字段引用不转换
202
- if (isFieldReference(obj)) {
203
- return obj;
204
- }
205
-
206
- // 4.2 有效的 ObjectId 字符串
207
- if (isValidObjectIdString(obj)) {
208
- try {
209
- return new ObjectId(obj);
210
- } catch (error) {
211
- // 构造失败,返回原字符串
212
- if (logger && logger.debug) {
213
- logger.debug('[ObjectId Converter] Construction failed', {
214
- value: obj,
215
- error: error.message
216
- });
217
- }
218
- return obj;
219
- }
220
- }
221
-
222
- // 4.3 其他字符串
223
- return obj;
224
- }
225
-
226
- // 5. 数组处理
227
- if (Array.isArray(obj)) {
228
- let hasConverted = false;
229
- const converted = obj.map((item, index) => {
230
- const itemPath = `${fieldPath}[${index}]`;
231
- const newItem = convertObjectIdStrings(item, itemPath, depth + 1, visited, {
232
- ...options,
233
- _conversionStats: stats // 传递统计对象
234
- });
235
- if (newItem !== item) {
236
- hasConverted = true;
237
- }
238
- return newItem;
239
- });
240
-
241
- // 性能优化:无转换时返回原数组
242
- return hasConverted ? converted : obj;
243
- }
244
-
245
- // 6. 对象处理
246
- if (typeof obj === 'object') {
247
- // 6.1 循环引用检测
248
- if (visited.has(obj)) {
249
- if (logger && logger.warn) {
250
- logger.warn('[ObjectId Converter] Circular reference detected', {
251
- fieldPath,
252
- message: 'Object has circular reference, skipping conversion'
253
- });
254
- }
255
- return obj;
256
- }
257
- visited.add(obj);
258
-
259
- let hasConverted = false;
260
- const converted = {};
261
-
262
- for (const [key, value] of Object.entries(obj)) {
263
- const currentPath = fieldPath ? `${fieldPath}.${key}` : key;
264
-
265
- // 6.2 特殊操作符不转换($expr, $function, $where)
266
- if (SPECIAL_OPERATORS.has(key)) {
267
- converted[key] = value;
268
- continue;
269
- }
270
-
271
- // 6.3 排除字段不转换
272
- if (excludeFields.includes(key)) {
273
- converted[key] = value;
274
- continue;
275
- }
276
-
277
- // 6.4 字段名匹配 + 字符串值 → 尝试转换
278
- if (typeof value === 'string' &&
279
- shouldConvertField(key, customFieldPatterns) &&
280
- !isFieldReference(value) &&
281
- isValidObjectIdString(value)) {
282
- try {
283
- converted[key] = new ObjectId(value);
284
- hasConverted = true;
285
- } catch (error) {
286
- // 转换失败,保持原值
287
- if (logger && logger.debug) {
288
- logger.debug('[ObjectId Converter] Field conversion failed', {
289
- field: key,
290
- value,
291
- error: error.message
292
- });
293
- }
294
- converted[key] = value;
295
- }
296
- } else {
297
- // 6.5 递归处理
298
- const newValue = convertObjectIdStrings(value, currentPath, depth + 1, visited, {
299
- ...options,
300
- _conversionStats: stats // 传递统计对象
301
- });
302
- if (newValue !== value) {
303
- hasConverted = true;
304
- }
305
- converted[key] = newValue;
306
- }
307
- }
308
-
309
- // 性能优化:无转换时返回原对象
310
- return hasConverted ? converted : obj;
311
- }
312
-
313
- // 7. 其他类型(数字、布尔、Date 等)
314
- return obj;
315
-
316
- } catch (error) {
317
- // 顶层异常捕获
318
- if (logger && logger.error) {
319
- logger.error('[ObjectId Converter] Unexpected error', {
320
- error: error.message,
321
- stack: error.stack,
322
- fieldPath
323
- });
324
- }
325
- // 异常时返回原值,确保不中断流程
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
- }
339
- }
340
- }
341
-
342
- /**
343
- * 转换聚合管道中的 ObjectId 字符串
344
- * @param {Array} pipeline - 聚合管道数组
345
- * @param {number} depth - 递归深度(防止嵌套 pipeline 栈溢出)
346
- * @param {Object} options - 配置选项
347
- * @returns {Array} 转换后的聚合管道
348
- */
349
- function convertAggregationPipeline(pipeline, depth = 0, options = {}) {
350
- const { logger = null, maxDepth = 5 } = options;
351
-
352
- if (!Array.isArray(pipeline)) {
353
- return pipeline;
354
- }
355
-
356
- // 深度保护(防止 $lookup 等嵌套 pipeline 导致栈溢出)
357
- if (depth > maxDepth) {
358
- if (logger && logger.warn) {
359
- logger.warn('[ObjectId Converter] Pipeline depth limit reached', {
360
- depth,
361
- message: 'Pipeline nesting too deep, skipping conversion'
362
- });
363
- }
364
- return pipeline;
365
- }
366
-
367
- let hasConverted = false;
368
-
369
- const converted = pipeline.map((stage, index) => {
370
- if (!stage || typeof stage !== 'object') {
371
- return stage;
372
- }
373
-
374
- const convertedStage = {};
375
-
376
- for (const [op, value] of Object.entries(stage)) {
377
- // $match - 转换查询条件
378
- if (op === '$match') {
379
- const convertedValue = convertObjectIdStrings(value, `pipeline[${index}].$match`, 0, new WeakSet(), options);
380
- if (convertedValue !== value) hasConverted = true;
381
- convertedStage[op] = convertedValue;
382
- }
383
-
384
- // $addFields / $set - 转换字段值
385
- else if (op === '$addFields' || op === '$set') {
386
- const convertedValue = convertObjectIdStrings(value, `pipeline[${index}].${op}`, 0, new WeakSet(), options);
387
- if (convertedValue !== value) hasConverted = true;
388
- convertedStage[op] = convertedValue;
389
- }
390
-
391
- // $project - 转换计算字段
392
- else if (op === '$project') {
393
- const convertedValue = convertObjectIdStrings(value, `pipeline[${index}].$project`, 0, new WeakSet(), options);
394
- if (convertedValue !== value) hasConverted = true;
395
- convertedStage[op] = convertedValue;
396
- }
397
-
398
- // $group - 转换分组字段
399
- else if (op === '$group') {
400
- const convertedValue = convertObjectIdStrings(value, `pipeline[${index}].$group`, 0, new WeakSet(), options);
401
- if (convertedValue !== value) hasConverted = true;
402
- convertedStage[op] = convertedValue;
403
- }
404
-
405
- // $lookup - 特殊处理(嵌套 pipeline)
406
- else if (op === '$lookup') {
407
- const lookup = { ...value };
408
- let lookupConverted = false;
409
-
410
- // 转换 let 变量
411
- if (lookup.let) {
412
- const convertedLet = convertObjectIdStrings(lookup.let, `pipeline[${index}].$lookup.let`, 0, new WeakSet(), options);
413
- if (convertedLet !== lookup.let) {
414
- lookup.let = convertedLet;
415
- lookupConverted = true;
416
- }
417
- }
418
-
419
- // 递归转换嵌套 pipeline
420
- if (lookup.pipeline) {
421
- const convertedPipeline = convertAggregationPipeline(lookup.pipeline, depth + 1, options);
422
- if (convertedPipeline !== lookup.pipeline) {
423
- lookup.pipeline = convertedPipeline;
424
- lookupConverted = true;
425
- }
426
- }
427
-
428
- if (lookupConverted) hasConverted = true;
429
- convertedStage[op] = lookup;
430
- }
431
-
432
- // $facet - 多个子 pipeline
433
- else if (op === '$facet') {
434
- const facet = {};
435
- let facetConverted = false;
436
-
437
- for (const [name, subPipeline] of Object.entries(value)) {
438
- if (Array.isArray(subPipeline)) {
439
- const convertedSubPipeline = convertAggregationPipeline(subPipeline, depth + 1, options);
440
- if (convertedSubPipeline !== subPipeline) {
441
- facetConverted = true;
442
- }
443
- facet[name] = convertedSubPipeline;
444
- } else {
445
- facet[name] = subPipeline;
446
- }
447
- }
448
-
449
- if (facetConverted) hasConverted = true;
450
- convertedStage[op] = facet;
451
- }
452
-
453
- // $graphLookup - 图查询
454
- else if (op === '$graphLookup') {
455
- const graphLookup = { ...value };
456
- let graphConverted = false;
457
-
458
- if (graphLookup.startWith) {
459
- const convertedStartWith = convertObjectIdStrings(
460
- graphLookup.startWith,
461
- `pipeline[${index}].$graphLookup.startWith`,
462
- 0,
463
- new WeakSet(),
464
- options
465
- );
466
- if (convertedStartWith !== graphLookup.startWith) {
467
- graphLookup.startWith = convertedStartWith;
468
- graphConverted = true;
469
- }
470
- }
471
-
472
- if (graphLookup.restrictSearchWithMatch) {
473
- const convertedMatch = convertObjectIdStrings(
474
- graphLookup.restrictSearchWithMatch,
475
- `pipeline[${index}].$graphLookup.restrictSearchWithMatch`,
476
- 0,
477
- new WeakSet(),
478
- options
479
- );
480
- if (convertedMatch !== graphLookup.restrictSearchWithMatch) {
481
- graphLookup.restrictSearchWithMatch = convertedMatch;
482
- graphConverted = true;
483
- }
484
- }
485
-
486
- if (graphConverted) hasConverted = true;
487
- convertedStage[op] = graphLookup;
488
- }
489
-
490
- // $merge - 合并到另一个集合
491
- else if (op === '$merge') {
492
- const merge = { ...value };
493
- let mergeConverted = false;
494
-
495
- // whenMatched 可能是 pipeline
496
- if (merge.whenMatched && Array.isArray(merge.whenMatched)) {
497
- const convertedWhenMatched = convertAggregationPipeline(merge.whenMatched, depth + 1, options);
498
- if (convertedWhenMatched !== merge.whenMatched) {
499
- merge.whenMatched = convertedWhenMatched;
500
- mergeConverted = true;
501
- }
502
- }
503
-
504
- if (mergeConverted) hasConverted = true;
505
- convertedStage[op] = merge;
506
- }
507
-
508
- // 其他操作符保持不变
509
- else {
510
- convertedStage[op] = value;
511
- }
512
- }
513
-
514
- return convertedStage;
515
- });
516
-
517
- // 性能优化:无转换时返回原 pipeline
518
- return hasConverted ? converted : pipeline;
519
- }
520
-
521
- /**
522
- * 转换 update 文档中的 ObjectId 字符串
523
- * @param {Object} update - update 文档
524
- * @param {Object} options - 配置选项
525
- * @returns {Object} 转换后的 update 文档
526
- */
527
- function convertUpdateDocument(update, options = {}) {
528
- if (!update || typeof update !== 'object') {
529
- return update;
530
- }
531
-
532
- const converted = {};
533
- let hasConverted = false;
534
-
535
- for (const [op, value] of Object.entries(update)) {
536
- // $set / $setOnInsert - 转换设置的值
537
- if (op === '$set' || op === '$setOnInsert') {
538
- const convertedValue = convertObjectIdStrings(value, `update.${op}`, 0, new WeakSet(), options);
539
- if (convertedValue !== value) hasConverted = true;
540
- converted[op] = convertedValue;
541
- }
542
-
543
- // $push - 转换数组元素
544
- else if (op === '$push') {
545
- const convertedValue = convertObjectIdStrings(value, 'update.$push', 0, new WeakSet(), options);
546
- if (convertedValue !== value) hasConverted = true;
547
- converted[op] = convertedValue;
548
- }
549
-
550
- // $addToSet - 转换集合元素
551
- else if (op === '$addToSet') {
552
- const convertedValue = convertObjectIdStrings(value, 'update.$addToSet', 0, new WeakSet(), options);
553
- if (convertedValue !== value) hasConverted = true;
554
- converted[op] = convertedValue;
555
- }
556
-
557
- // $pull - 转换匹配条件
558
- else if (op === '$pull') {
559
- const convertedValue = convertObjectIdStrings(value, 'update.$pull', 0, new WeakSet(), options);
560
- if (convertedValue !== value) hasConverted = true;
561
- converted[op] = convertedValue;
562
- }
563
-
564
- // $inc, $mul, $min, $max, $currentDate - 数值/日期操作,不转换
565
- else if (['$inc', '$mul', '$min', '$max', '$currentDate'].includes(op)) {
566
- converted[op] = value;
567
- }
568
-
569
- // $unset, $rename - 字段名操作,不转换值
570
- else if (op === '$unset' || op === '$rename') {
571
- converted[op] = value;
572
- }
573
-
574
- // 其他操作符,保持原样
575
- else {
576
- converted[op] = value;
577
- }
578
- }
579
-
580
- // 性能优化:无转换时返回原对象
581
- return hasConverted ? converted : update;
582
- }
583
-
584
- /**
585
- * 标准化对象用于缓存键生成
586
- * 将所有 ObjectId 实例转换为字符串,确保缓存键一致
587
- * @param {*} obj - 待标准化的对象
588
- * @returns {*} 标准化后的对象
589
- */
590
- function normalizeForCache(obj) {
591
- if (obj === null || obj === undefined) {
592
- return obj;
593
- }
594
-
595
- // ObjectId 转字符串
596
- if (obj instanceof ObjectId) {
597
- return obj.toString();
598
- }
599
-
600
- // 数组处理
601
- if (Array.isArray(obj)) {
602
- return obj.map(normalizeForCache);
603
- }
604
-
605
- // 对象处理
606
- if (typeof obj === 'object') {
607
- const normalized = {};
608
- for (const [key, value] of Object.entries(obj)) {
609
- normalized[key] = normalizeForCache(value);
610
- }
611
- return normalized;
612
- }
613
-
614
- return obj;
615
- }
616
-
617
- // 导出
618
- module.exports = {
619
- convertObjectIdStrings,
620
- convertAggregationPipeline,
621
- convertUpdateDocument,
622
- normalizeForCache,
623
- isValidObjectIdString,
624
- shouldConvertField,
625
- isFieldReference,
626
-
627
- // 导出配置(用于测试和自定义)
628
- OBJECTID_FIELD_PATTERNS,
629
- SPECIAL_OPERATORS
630
- };
631
-
632
-