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.
- package/CHANGELOG.md +92 -2419
- package/README.md +630 -1070
- package/index.d.ts +252 -15
- package/lib/cache.js +8 -8
- package/lib/common/validation.js +64 -1
- package/lib/connect.js +3 -3
- package/lib/errors.js +10 -0
- package/lib/index.js +118 -9
- package/lib/infrastructure/ssh-tunnel-ssh2.js +211 -0
- package/lib/infrastructure/ssh-tunnel.js +40 -0
- package/lib/infrastructure/uri-parser.js +35 -0
- package/lib/lock/Lock.js +66 -0
- package/lib/lock/errors.js +27 -0
- package/lib/lock/index.js +12 -0
- package/lib/logger.js +1 -1
- package/lib/model/examples/test.js +4 -4
- package/lib/mongodb/common/accessor-helpers.js +17 -3
- package/lib/mongodb/connect.js +68 -13
- package/lib/mongodb/index.js +140 -7
- package/lib/mongodb/management/collection-ops.js +4 -4
- package/lib/mongodb/management/index-ops.js +18 -18
- package/lib/mongodb/management/validation-ops.js +3 -3
- package/lib/mongodb/queries/aggregate.js +14 -5
- package/lib/mongodb/queries/chain.js +52 -45
- package/lib/mongodb/queries/count.js +16 -6
- package/lib/mongodb/queries/distinct.js +15 -6
- package/lib/mongodb/queries/find-and-count.js +22 -13
- package/lib/mongodb/queries/find-by-ids.js +5 -5
- package/lib/mongodb/queries/find-one-by-id.js +1 -1
- package/lib/mongodb/queries/find-one.js +12 -3
- package/lib/mongodb/queries/find-page.js +12 -0
- package/lib/mongodb/queries/find.js +15 -6
- package/lib/mongodb/queries/index.js +1 -0
- package/lib/mongodb/queries/watch.js +537 -0
- package/lib/mongodb/writes/delete-many.js +20 -11
- package/lib/mongodb/writes/delete-one.js +18 -9
- package/lib/mongodb/writes/find-one-and-delete.js +19 -10
- package/lib/mongodb/writes/find-one-and-replace.js +36 -20
- package/lib/mongodb/writes/find-one-and-update.js +36 -20
- package/lib/mongodb/writes/increment-one.js +16 -7
- package/lib/mongodb/writes/index.js +13 -13
- package/lib/mongodb/writes/insert-batch.js +46 -37
- package/lib/mongodb/writes/insert-many.js +22 -13
- package/lib/mongodb/writes/insert-one.js +18 -9
- package/lib/mongodb/writes/replace-one.js +33 -17
- package/lib/mongodb/writes/result-handler.js +14 -14
- package/lib/mongodb/writes/update-many.js +34 -18
- package/lib/mongodb/writes/update-one.js +33 -17
- package/lib/mongodb/writes/upsert-one.js +25 -9
- package/lib/operators.js +1 -1
- package/lib/redis-cache-adapter.js +3 -3
- package/lib/slow-query-log/base-storage.js +69 -0
- package/lib/slow-query-log/batch-queue.js +96 -0
- package/lib/slow-query-log/config-manager.js +195 -0
- package/lib/slow-query-log/index.js +237 -0
- package/lib/slow-query-log/mongodb-storage.js +323 -0
- package/lib/slow-query-log/query-hash.js +38 -0
- package/lib/transaction/DistributedCacheLockManager.js +240 -5
- package/lib/transaction/Transaction.js +1 -1
- package/lib/utils/objectid-converter.js +566 -0
- package/package.json +11 -5
|
@@ -0,0 +1,566 @@
|
|
|
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
|
+
} = options;
|
|
128
|
+
|
|
129
|
+
try {
|
|
130
|
+
// 1. 深度保护(防止栈溢出)
|
|
131
|
+
if (depth > maxDepth) {
|
|
132
|
+
if (logger && logger.warn) {
|
|
133
|
+
logger.warn('[ObjectId Converter] Depth limit reached', {
|
|
134
|
+
depth,
|
|
135
|
+
fieldPath,
|
|
136
|
+
message: 'Object nesting too deep, skipping conversion'
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
return obj;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// 2. null/undefined 检测
|
|
143
|
+
if (obj === null || obj === undefined) {
|
|
144
|
+
return obj;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// 3. 已经是 ObjectId 实例
|
|
148
|
+
if (obj instanceof ObjectId) {
|
|
149
|
+
return obj;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// 4. 字符串处理
|
|
153
|
+
if (typeof obj === 'string') {
|
|
154
|
+
// 4.1 字段引用不转换
|
|
155
|
+
if (isFieldReference(obj)) {
|
|
156
|
+
return obj;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// 4.2 有效的 ObjectId 字符串
|
|
160
|
+
if (isValidObjectIdString(obj)) {
|
|
161
|
+
try {
|
|
162
|
+
return new ObjectId(obj);
|
|
163
|
+
} catch (error) {
|
|
164
|
+
// 构造失败,返回原字符串
|
|
165
|
+
if (logger && logger.debug) {
|
|
166
|
+
logger.debug('[ObjectId Converter] Construction failed', {
|
|
167
|
+
value: obj,
|
|
168
|
+
error: error.message
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
return obj;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// 4.3 其他字符串
|
|
176
|
+
return obj;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// 5. 数组处理
|
|
180
|
+
if (Array.isArray(obj)) {
|
|
181
|
+
let hasConverted = false;
|
|
182
|
+
const converted = obj.map((item, index) => {
|
|
183
|
+
const itemPath = `${fieldPath}[${index}]`;
|
|
184
|
+
const newItem = convertObjectIdStrings(item, itemPath, depth + 1, visited, options);
|
|
185
|
+
if (newItem !== item) {
|
|
186
|
+
hasConverted = true;
|
|
187
|
+
}
|
|
188
|
+
return newItem;
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
// 性能优化:无转换时返回原数组
|
|
192
|
+
return hasConverted ? converted : obj;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// 6. 对象处理
|
|
196
|
+
if (typeof obj === 'object') {
|
|
197
|
+
// 6.1 循环引用检测
|
|
198
|
+
if (visited.has(obj)) {
|
|
199
|
+
if (logger && logger.warn) {
|
|
200
|
+
logger.warn('[ObjectId Converter] Circular reference detected', {
|
|
201
|
+
fieldPath,
|
|
202
|
+
message: 'Object has circular reference, skipping conversion'
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
return obj;
|
|
206
|
+
}
|
|
207
|
+
visited.add(obj);
|
|
208
|
+
|
|
209
|
+
let hasConverted = false;
|
|
210
|
+
const converted = {};
|
|
211
|
+
|
|
212
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
213
|
+
const currentPath = fieldPath ? `${fieldPath}.${key}` : key;
|
|
214
|
+
|
|
215
|
+
// 6.2 特殊操作符不转换($expr, $function, $where)
|
|
216
|
+
if (SPECIAL_OPERATORS.has(key)) {
|
|
217
|
+
converted[key] = value;
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// 6.3 排除字段不转换
|
|
222
|
+
if (excludeFields.includes(key)) {
|
|
223
|
+
converted[key] = value;
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// 6.4 字段名匹配 + 字符串值 → 尝试转换
|
|
228
|
+
if (typeof value === 'string' &&
|
|
229
|
+
shouldConvertField(key, customFieldPatterns) &&
|
|
230
|
+
!isFieldReference(value) &&
|
|
231
|
+
isValidObjectIdString(value)) {
|
|
232
|
+
try {
|
|
233
|
+
converted[key] = new ObjectId(value);
|
|
234
|
+
hasConverted = true;
|
|
235
|
+
} catch (error) {
|
|
236
|
+
// 转换失败,保持原值
|
|
237
|
+
if (logger && logger.debug) {
|
|
238
|
+
logger.debug('[ObjectId Converter] Field conversion failed', {
|
|
239
|
+
field: key,
|
|
240
|
+
value,
|
|
241
|
+
error: error.message
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
converted[key] = value;
|
|
245
|
+
}
|
|
246
|
+
} else {
|
|
247
|
+
// 6.5 递归处理
|
|
248
|
+
const newValue = convertObjectIdStrings(value, currentPath, depth + 1, visited, options);
|
|
249
|
+
if (newValue !== value) {
|
|
250
|
+
hasConverted = true;
|
|
251
|
+
}
|
|
252
|
+
converted[key] = newValue;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// 性能优化:无转换时返回原对象
|
|
257
|
+
return hasConverted ? converted : obj;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// 7. 其他类型(数字、布尔、Date 等)
|
|
261
|
+
return obj;
|
|
262
|
+
|
|
263
|
+
} catch (error) {
|
|
264
|
+
// 顶层异常捕获
|
|
265
|
+
if (logger && logger.error) {
|
|
266
|
+
logger.error('[ObjectId Converter] Unexpected error', {
|
|
267
|
+
error: error.message,
|
|
268
|
+
stack: error.stack,
|
|
269
|
+
fieldPath
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
// 异常时返回原值,确保不中断流程
|
|
273
|
+
return obj;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* 转换聚合管道中的 ObjectId 字符串
|
|
279
|
+
* @param {Array} pipeline - 聚合管道数组
|
|
280
|
+
* @param {number} depth - 递归深度(防止嵌套 pipeline 栈溢出)
|
|
281
|
+
* @param {Object} options - 配置选项
|
|
282
|
+
* @returns {Array} 转换后的聚合管道
|
|
283
|
+
*/
|
|
284
|
+
function convertAggregationPipeline(pipeline, depth = 0, options = {}) {
|
|
285
|
+
const { logger = null, maxDepth = 5 } = options;
|
|
286
|
+
|
|
287
|
+
if (!Array.isArray(pipeline)) {
|
|
288
|
+
return pipeline;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// 深度保护(防止 $lookup 等嵌套 pipeline 导致栈溢出)
|
|
292
|
+
if (depth > maxDepth) {
|
|
293
|
+
if (logger && logger.warn) {
|
|
294
|
+
logger.warn('[ObjectId Converter] Pipeline depth limit reached', {
|
|
295
|
+
depth,
|
|
296
|
+
message: 'Pipeline nesting too deep, skipping conversion'
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
return pipeline;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
let hasConverted = false;
|
|
303
|
+
|
|
304
|
+
const converted = pipeline.map((stage, index) => {
|
|
305
|
+
if (!stage || typeof stage !== 'object') {
|
|
306
|
+
return stage;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const convertedStage = {};
|
|
310
|
+
|
|
311
|
+
for (const [op, value] of Object.entries(stage)) {
|
|
312
|
+
// $match - 转换查询条件
|
|
313
|
+
if (op === '$match') {
|
|
314
|
+
const convertedValue = convertObjectIdStrings(value, `pipeline[${index}].$match`, 0, new WeakSet(), options);
|
|
315
|
+
if (convertedValue !== value) hasConverted = true;
|
|
316
|
+
convertedStage[op] = convertedValue;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// $addFields / $set - 转换字段值
|
|
320
|
+
else if (op === '$addFields' || op === '$set') {
|
|
321
|
+
const convertedValue = convertObjectIdStrings(value, `pipeline[${index}].${op}`, 0, new WeakSet(), options);
|
|
322
|
+
if (convertedValue !== value) hasConverted = true;
|
|
323
|
+
convertedStage[op] = convertedValue;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// $project - 转换计算字段
|
|
327
|
+
else if (op === '$project') {
|
|
328
|
+
const convertedValue = convertObjectIdStrings(value, `pipeline[${index}].$project`, 0, new WeakSet(), options);
|
|
329
|
+
if (convertedValue !== value) hasConverted = true;
|
|
330
|
+
convertedStage[op] = convertedValue;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// $group - 转换分组字段
|
|
334
|
+
else if (op === '$group') {
|
|
335
|
+
const convertedValue = convertObjectIdStrings(value, `pipeline[${index}].$group`, 0, new WeakSet(), options);
|
|
336
|
+
if (convertedValue !== value) hasConverted = true;
|
|
337
|
+
convertedStage[op] = convertedValue;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// $lookup - 特殊处理(嵌套 pipeline)
|
|
341
|
+
else if (op === '$lookup') {
|
|
342
|
+
const lookup = { ...value };
|
|
343
|
+
let lookupConverted = false;
|
|
344
|
+
|
|
345
|
+
// 转换 let 变量
|
|
346
|
+
if (lookup.let) {
|
|
347
|
+
const convertedLet = convertObjectIdStrings(lookup.let, `pipeline[${index}].$lookup.let`, 0, new WeakSet(), options);
|
|
348
|
+
if (convertedLet !== lookup.let) {
|
|
349
|
+
lookup.let = convertedLet;
|
|
350
|
+
lookupConverted = true;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// 递归转换嵌套 pipeline
|
|
355
|
+
if (lookup.pipeline) {
|
|
356
|
+
const convertedPipeline = convertAggregationPipeline(lookup.pipeline, depth + 1, options);
|
|
357
|
+
if (convertedPipeline !== lookup.pipeline) {
|
|
358
|
+
lookup.pipeline = convertedPipeline;
|
|
359
|
+
lookupConverted = true;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
if (lookupConverted) hasConverted = true;
|
|
364
|
+
convertedStage[op] = lookup;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// $facet - 多个子 pipeline
|
|
368
|
+
else if (op === '$facet') {
|
|
369
|
+
const facet = {};
|
|
370
|
+
let facetConverted = false;
|
|
371
|
+
|
|
372
|
+
for (const [name, subPipeline] of Object.entries(value)) {
|
|
373
|
+
if (Array.isArray(subPipeline)) {
|
|
374
|
+
const convertedSubPipeline = convertAggregationPipeline(subPipeline, depth + 1, options);
|
|
375
|
+
if (convertedSubPipeline !== subPipeline) {
|
|
376
|
+
facetConverted = true;
|
|
377
|
+
}
|
|
378
|
+
facet[name] = convertedSubPipeline;
|
|
379
|
+
} else {
|
|
380
|
+
facet[name] = subPipeline;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
if (facetConverted) hasConverted = true;
|
|
385
|
+
convertedStage[op] = facet;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// $graphLookup - 图查询
|
|
389
|
+
else if (op === '$graphLookup') {
|
|
390
|
+
const graphLookup = { ...value };
|
|
391
|
+
let graphConverted = false;
|
|
392
|
+
|
|
393
|
+
if (graphLookup.startWith) {
|
|
394
|
+
const convertedStartWith = convertObjectIdStrings(
|
|
395
|
+
graphLookup.startWith,
|
|
396
|
+
`pipeline[${index}].$graphLookup.startWith`,
|
|
397
|
+
0,
|
|
398
|
+
new WeakSet(),
|
|
399
|
+
options
|
|
400
|
+
);
|
|
401
|
+
if (convertedStartWith !== graphLookup.startWith) {
|
|
402
|
+
graphLookup.startWith = convertedStartWith;
|
|
403
|
+
graphConverted = true;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
if (graphLookup.restrictSearchWithMatch) {
|
|
408
|
+
const convertedMatch = convertObjectIdStrings(
|
|
409
|
+
graphLookup.restrictSearchWithMatch,
|
|
410
|
+
`pipeline[${index}].$graphLookup.restrictSearchWithMatch`,
|
|
411
|
+
0,
|
|
412
|
+
new WeakSet(),
|
|
413
|
+
options
|
|
414
|
+
);
|
|
415
|
+
if (convertedMatch !== graphLookup.restrictSearchWithMatch) {
|
|
416
|
+
graphLookup.restrictSearchWithMatch = convertedMatch;
|
|
417
|
+
graphConverted = true;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
if (graphConverted) hasConverted = true;
|
|
422
|
+
convertedStage[op] = graphLookup;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// $merge - 合并到另一个集合
|
|
426
|
+
else if (op === '$merge') {
|
|
427
|
+
const merge = { ...value };
|
|
428
|
+
let mergeConverted = false;
|
|
429
|
+
|
|
430
|
+
// whenMatched 可能是 pipeline
|
|
431
|
+
if (merge.whenMatched && Array.isArray(merge.whenMatched)) {
|
|
432
|
+
const convertedWhenMatched = convertAggregationPipeline(merge.whenMatched, depth + 1, options);
|
|
433
|
+
if (convertedWhenMatched !== merge.whenMatched) {
|
|
434
|
+
merge.whenMatched = convertedWhenMatched;
|
|
435
|
+
mergeConverted = true;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
if (mergeConverted) hasConverted = true;
|
|
440
|
+
convertedStage[op] = merge;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// 其他操作符保持不变
|
|
444
|
+
else {
|
|
445
|
+
convertedStage[op] = value;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
return convertedStage;
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
// 性能优化:无转换时返回原 pipeline
|
|
453
|
+
return hasConverted ? converted : pipeline;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* 转换 update 文档中的 ObjectId 字符串
|
|
458
|
+
* @param {Object} update - update 文档
|
|
459
|
+
* @param {Object} options - 配置选项
|
|
460
|
+
* @returns {Object} 转换后的 update 文档
|
|
461
|
+
*/
|
|
462
|
+
function convertUpdateDocument(update, options = {}) {
|
|
463
|
+
if (!update || typeof update !== 'object') {
|
|
464
|
+
return update;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
const converted = {};
|
|
468
|
+
let hasConverted = false;
|
|
469
|
+
|
|
470
|
+
for (const [op, value] of Object.entries(update)) {
|
|
471
|
+
// $set / $setOnInsert - 转换设置的值
|
|
472
|
+
if (op === '$set' || op === '$setOnInsert') {
|
|
473
|
+
const convertedValue = convertObjectIdStrings(value, `update.${op}`, 0, new WeakSet(), options);
|
|
474
|
+
if (convertedValue !== value) hasConverted = true;
|
|
475
|
+
converted[op] = convertedValue;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// $push - 转换数组元素
|
|
479
|
+
else if (op === '$push') {
|
|
480
|
+
const convertedValue = convertObjectIdStrings(value, 'update.$push', 0, new WeakSet(), options);
|
|
481
|
+
if (convertedValue !== value) hasConverted = true;
|
|
482
|
+
converted[op] = convertedValue;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// $addToSet - 转换集合元素
|
|
486
|
+
else if (op === '$addToSet') {
|
|
487
|
+
const convertedValue = convertObjectIdStrings(value, 'update.$addToSet', 0, new WeakSet(), options);
|
|
488
|
+
if (convertedValue !== value) hasConverted = true;
|
|
489
|
+
converted[op] = convertedValue;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// $pull - 转换匹配条件
|
|
493
|
+
else if (op === '$pull') {
|
|
494
|
+
const convertedValue = convertObjectIdStrings(value, 'update.$pull', 0, new WeakSet(), options);
|
|
495
|
+
if (convertedValue !== value) hasConverted = true;
|
|
496
|
+
converted[op] = convertedValue;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// $inc, $mul, $min, $max, $currentDate - 数值/日期操作,不转换
|
|
500
|
+
else if (['$inc', '$mul', '$min', '$max', '$currentDate'].includes(op)) {
|
|
501
|
+
converted[op] = value;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// $unset, $rename - 字段名操作,不转换值
|
|
505
|
+
else if (op === '$unset' || op === '$rename') {
|
|
506
|
+
converted[op] = value;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// 其他操作符,保持原样
|
|
510
|
+
else {
|
|
511
|
+
converted[op] = value;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// 性能优化:无转换时返回原对象
|
|
516
|
+
return hasConverted ? converted : update;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
/**
|
|
520
|
+
* 标准化对象用于缓存键生成
|
|
521
|
+
* 将所有 ObjectId 实例转换为字符串,确保缓存键一致
|
|
522
|
+
* @param {*} obj - 待标准化的对象
|
|
523
|
+
* @returns {*} 标准化后的对象
|
|
524
|
+
*/
|
|
525
|
+
function normalizeForCache(obj) {
|
|
526
|
+
if (obj === null || obj === undefined) {
|
|
527
|
+
return obj;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// ObjectId 转字符串
|
|
531
|
+
if (obj instanceof ObjectId) {
|
|
532
|
+
return obj.toString();
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// 数组处理
|
|
536
|
+
if (Array.isArray(obj)) {
|
|
537
|
+
return obj.map(normalizeForCache);
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
// 对象处理
|
|
541
|
+
if (typeof obj === 'object') {
|
|
542
|
+
const normalized = {};
|
|
543
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
544
|
+
normalized[key] = normalizeForCache(value);
|
|
545
|
+
}
|
|
546
|
+
return normalized;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
return obj;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
// 导出
|
|
553
|
+
module.exports = {
|
|
554
|
+
convertObjectIdStrings,
|
|
555
|
+
convertAggregationPipeline,
|
|
556
|
+
convertUpdateDocument,
|
|
557
|
+
normalizeForCache,
|
|
558
|
+
isValidObjectIdString,
|
|
559
|
+
shouldConvertField,
|
|
560
|
+
isFieldReference,
|
|
561
|
+
|
|
562
|
+
// 导出配置(用于测试和自定义)
|
|
563
|
+
OBJECTID_FIELD_PATTERNS,
|
|
564
|
+
SPECIAL_OPERATORS
|
|
565
|
+
};
|
|
566
|
+
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "monsqlize",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "A lightweight MongoDB ORM with multi-level caching, transaction support, and distributed features",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"module": "index.mjs",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
},
|
|
47
47
|
"homepage": "https://github.com/vextjs/monSQLize#readme",
|
|
48
48
|
"engines": {
|
|
49
|
-
"node": ">=
|
|
49
|
+
"node": ">=16.0.0"
|
|
50
50
|
},
|
|
51
51
|
"scripts": {
|
|
52
52
|
"test": "node test/run-tests.js",
|
|
@@ -65,6 +65,7 @@
|
|
|
65
65
|
"test:compatibility:server:docker": "node scripts/test-server-versions.js",
|
|
66
66
|
"lint": "eslint lib/ test/ --ext .js",
|
|
67
67
|
"lint:fix": "eslint lib/ test/ --ext .js --fix",
|
|
68
|
+
"type-check": "tsc",
|
|
68
69
|
"postpublish": "echo '✅ 发布成功!请创建 GitHub Release: https://github.com/vextjs/monSQLize/releases/new?tag=v'$npm_package_version"
|
|
69
70
|
},
|
|
70
71
|
"peerDependenciesMeta": {
|
|
@@ -76,13 +77,17 @@
|
|
|
76
77
|
"ioredis": "^5.8.2"
|
|
77
78
|
},
|
|
78
79
|
"devDependencies": {
|
|
80
|
+
"@eslint/js": "^9.39.1",
|
|
81
|
+
"@types/node": "^25.0.1",
|
|
79
82
|
"benchmark": "^2.1.4",
|
|
80
83
|
"chai": "^6.2.1",
|
|
81
|
-
"eslint": "^
|
|
84
|
+
"eslint": "^9.39.1",
|
|
82
85
|
"mocha": "^11.7.5",
|
|
83
86
|
"mongodb-memory-server": "^10.1.2",
|
|
84
87
|
"nyc": "^15.1.0",
|
|
85
|
-
"sinon": "^21.0.0"
|
|
88
|
+
"sinon": "^21.0.0",
|
|
89
|
+
"tsd": "^0.33.0",
|
|
90
|
+
"typescript": "^5.9.3"
|
|
86
91
|
},
|
|
87
92
|
"nyc": {
|
|
88
93
|
"include": [
|
|
@@ -106,6 +111,7 @@
|
|
|
106
111
|
"branches": 65
|
|
107
112
|
},
|
|
108
113
|
"dependencies": {
|
|
109
|
-
"mongodb": "^6.17.0"
|
|
114
|
+
"mongodb": "^6.17.0",
|
|
115
|
+
"ssh2": "^1.17.0"
|
|
110
116
|
}
|
|
111
117
|
}
|