monsqlize 1.0.1 → 1.0.5
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 +204 -2464
- package/README.md +735 -1198
- package/index.d.ts +942 -18
- 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 +173 -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 +225 -29
- package/lib/model/features/soft-delete.js +348 -0
- package/lib/model/features/version.js +156 -0
- package/lib/model/index.js +756 -0
- package/lib/mongodb/common/accessor-helpers.js +17 -3
- package/lib/mongodb/connect.js +68 -13
- package/lib/mongodb/index.js +153 -6
- 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/watch.js +11 -2
- package/lib/mongodb/writes/common/batch-retry.js +64 -0
- package/lib/mongodb/writes/delete-batch.js +322 -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 +22 -7
- package/lib/mongodb/writes/index.js +17 -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-batch.js +358 -0
- 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 +18 -6
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Soft Delete Feature for Model
|
|
3
|
+
*
|
|
4
|
+
* Provides soft delete functionality:
|
|
5
|
+
* - Mark documents as deleted instead of physical deletion
|
|
6
|
+
* - Auto-filter deleted documents in queries
|
|
7
|
+
* - Restore deleted documents
|
|
8
|
+
* - Force physical deletion
|
|
9
|
+
* - TTL index for auto-cleanup
|
|
10
|
+
*
|
|
11
|
+
* @module lib/model/features/soft-delete
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Parse soft delete configuration
|
|
16
|
+
* @param {Object|boolean} config - Soft delete config
|
|
17
|
+
* @returns {Object|null} Parsed config or null if disabled
|
|
18
|
+
*/
|
|
19
|
+
function parseSoftDeleteConfig(config) {
|
|
20
|
+
if (!config) return null;
|
|
21
|
+
|
|
22
|
+
// shorthand: softDelete: true
|
|
23
|
+
if (config === true) {
|
|
24
|
+
return {
|
|
25
|
+
enabled: true,
|
|
26
|
+
field: 'deletedAt',
|
|
27
|
+
type: 'timestamp',
|
|
28
|
+
ttl: null
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// full config: softDelete: { ... }
|
|
33
|
+
return {
|
|
34
|
+
enabled: config.enabled !== false,
|
|
35
|
+
field: config.field || 'deletedAt',
|
|
36
|
+
type: config.type || 'timestamp',
|
|
37
|
+
ttl: config.ttl || null
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Get delete value based on type
|
|
43
|
+
* @param {string} type - 'timestamp' or 'boolean'
|
|
44
|
+
* @returns {Date|boolean} Delete value
|
|
45
|
+
*/
|
|
46
|
+
function getDeleteValue(type) {
|
|
47
|
+
return type === 'boolean' ? true : new Date();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Apply soft delete filter to query
|
|
52
|
+
* @param {Object} filter - Original filter
|
|
53
|
+
* @param {Object} config - Soft delete config
|
|
54
|
+
* @param {Object} options - Query options
|
|
55
|
+
* @returns {Object} Modified filter
|
|
56
|
+
*/
|
|
57
|
+
function applySoftDeleteFilter(filter, config, options = {}) {
|
|
58
|
+
if (!config?.enabled) return filter;
|
|
59
|
+
|
|
60
|
+
const field = config.field;
|
|
61
|
+
|
|
62
|
+
// Already has explicit deletedAt filter - don't modify
|
|
63
|
+
if (filter[field] !== undefined) {
|
|
64
|
+
return filter;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// withDeleted: include all (no filter)
|
|
68
|
+
if (options.withDeleted) {
|
|
69
|
+
return filter;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// onlyDeleted: only deleted documents
|
|
73
|
+
if (options.onlyDeleted) {
|
|
74
|
+
return {
|
|
75
|
+
...filter,
|
|
76
|
+
[field]: { $ne: null }
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// default: only non-deleted documents
|
|
81
|
+
return {
|
|
82
|
+
...filter,
|
|
83
|
+
[field]: null
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Register soft delete hooks
|
|
89
|
+
* @param {Object} modelInstance - Model instance
|
|
90
|
+
* @param {Object} config - Soft delete config
|
|
91
|
+
*/
|
|
92
|
+
function registerSoftDeleteHooks(modelInstance, config) {
|
|
93
|
+
if (!config?.enabled) return;
|
|
94
|
+
|
|
95
|
+
const field = config.field;
|
|
96
|
+
const type = config.type;
|
|
97
|
+
|
|
98
|
+
// Store original deleteOne and deleteMany methods
|
|
99
|
+
const originalDeleteOne = modelInstance.collection.deleteOne.bind(modelInstance.collection);
|
|
100
|
+
const originalDeleteMany = modelInstance.collection.deleteMany.bind(modelInstance.collection);
|
|
101
|
+
|
|
102
|
+
// Override deleteOne - convert to updateOne
|
|
103
|
+
modelInstance.collection.deleteOne = async function(filter, options = {}) {
|
|
104
|
+
// Check if force delete (bypass soft delete)
|
|
105
|
+
if (options._forceDelete) {
|
|
106
|
+
delete options._forceDelete;
|
|
107
|
+
return await originalDeleteOne(filter, options);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Soft delete: convert to updateOne
|
|
111
|
+
const updateResult = await modelInstance.collection.updateOne(
|
|
112
|
+
filter,
|
|
113
|
+
{ $set: { [field]: getDeleteValue(type) } },
|
|
114
|
+
options
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
// Convert updateOne result to deleteOne result format
|
|
118
|
+
return {
|
|
119
|
+
acknowledged: updateResult.acknowledged,
|
|
120
|
+
deletedCount: updateResult.modifiedCount
|
|
121
|
+
};
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
// Override deleteMany - convert to updateMany
|
|
125
|
+
modelInstance.collection.deleteMany = async function(filter, options = {}) {
|
|
126
|
+
// Check if force delete (bypass soft delete)
|
|
127
|
+
if (options._forceDelete) {
|
|
128
|
+
delete options._forceDelete;
|
|
129
|
+
return await originalDeleteMany(filter, options);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Soft delete: convert to updateMany
|
|
133
|
+
const updateResult = await modelInstance.collection.updateMany(
|
|
134
|
+
filter,
|
|
135
|
+
{ $set: { [field]: getDeleteValue(type) } },
|
|
136
|
+
options
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
// Convert updateMany result to deleteMany result format
|
|
140
|
+
return {
|
|
141
|
+
acknowledged: updateResult.acknowledged,
|
|
142
|
+
deletedCount: updateResult.modifiedCount
|
|
143
|
+
};
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
// Store original find/findOne/count methods
|
|
147
|
+
const originalFind = modelInstance.collection.find.bind(modelInstance.collection);
|
|
148
|
+
const originalFindOne = modelInstance.collection.findOne.bind(modelInstance.collection);
|
|
149
|
+
const originalCount = modelInstance.collection.count.bind(modelInstance.collection);
|
|
150
|
+
|
|
151
|
+
// Override find - auto-filter deleted
|
|
152
|
+
modelInstance.collection.find = async function(filter = {}, options = {}) {
|
|
153
|
+
const modifiedFilter = applySoftDeleteFilter(filter, config, options);
|
|
154
|
+
return await originalFind(modifiedFilter, options);
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
// Override findOne - auto-filter deleted
|
|
158
|
+
modelInstance.collection.findOne = async function(filter = {}, options = {}) {
|
|
159
|
+
const modifiedFilter = applySoftDeleteFilter(filter, config, options);
|
|
160
|
+
return await originalFindOne(modifiedFilter, options);
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
// Override count - auto-filter deleted
|
|
164
|
+
modelInstance.collection.count = async function(filter = {}, options = {}) {
|
|
165
|
+
const modifiedFilter = applySoftDeleteFilter(filter, config, options);
|
|
166
|
+
return await originalCount(modifiedFilter, options);
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Add soft delete methods to ModelInstance
|
|
172
|
+
* @param {Object} modelInstance - Model instance
|
|
173
|
+
* @param {Object} config - Soft delete config
|
|
174
|
+
*/
|
|
175
|
+
function addSoftDeleteMethods(modelInstance, config) {
|
|
176
|
+
if (!config?.enabled) return;
|
|
177
|
+
|
|
178
|
+
const field = config.field;
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Find documents including deleted ones
|
|
182
|
+
* @param {Object} filter - Query filter
|
|
183
|
+
* @param {Object} options - Query options
|
|
184
|
+
* @returns {Promise<Array>} Documents
|
|
185
|
+
*/
|
|
186
|
+
modelInstance.findWithDeleted = async function(filter = {}, options = {}) {
|
|
187
|
+
return await this.find(filter, { ...options, withDeleted: true });
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Find only deleted documents
|
|
192
|
+
* @param {Object} filter - Query filter
|
|
193
|
+
* @param {Object} options - Query options
|
|
194
|
+
* @returns {Promise<Array>} Deleted documents
|
|
195
|
+
*/
|
|
196
|
+
modelInstance.findOnlyDeleted = async function(filter = {}, options = {}) {
|
|
197
|
+
return await this.find(filter, { ...options, onlyDeleted: true });
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Find one document including deleted ones
|
|
202
|
+
* @param {Object} filter - Query filter
|
|
203
|
+
* @param {Object} options - Query options
|
|
204
|
+
* @returns {Promise<Object|null>} Document
|
|
205
|
+
*/
|
|
206
|
+
modelInstance.findOneWithDeleted = async function(filter = {}, options = {}) {
|
|
207
|
+
return await this.findOne(filter, { ...options, withDeleted: true });
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Find one deleted document
|
|
212
|
+
* @param {Object} filter - Query filter
|
|
213
|
+
* @param {Object} options - Query options
|
|
214
|
+
* @returns {Promise<Object|null>} Deleted document
|
|
215
|
+
*/
|
|
216
|
+
modelInstance.findOneOnlyDeleted = async function(filter = {}, options = {}) {
|
|
217
|
+
return await this.findOne(filter, { ...options, onlyDeleted: true });
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Count documents including deleted ones
|
|
222
|
+
* @param {Object} filter - Query filter
|
|
223
|
+
* @param {Object} options - Query options
|
|
224
|
+
* @returns {Promise<number>} Count
|
|
225
|
+
*/
|
|
226
|
+
modelInstance.countWithDeleted = async function(filter = {}, options = {}) {
|
|
227
|
+
return await this.count(filter, { ...options, withDeleted: true });
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Count only deleted documents
|
|
232
|
+
* @param {Object} filter - Query filter
|
|
233
|
+
* @param {Object} options - Query options
|
|
234
|
+
* @returns {Promise<number>} Count
|
|
235
|
+
*/
|
|
236
|
+
modelInstance.countOnlyDeleted = async function(filter = {}, options = {}) {
|
|
237
|
+
return await this.count(filter, { ...options, onlyDeleted: true });
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Restore a deleted document
|
|
242
|
+
* @param {Object} filter - Query filter
|
|
243
|
+
* @param {Object} options - Update options
|
|
244
|
+
* @returns {Promise<Object>} Update result
|
|
245
|
+
*/
|
|
246
|
+
modelInstance.restore = async function(filter, options) {
|
|
247
|
+
// Add deleted filter to ensure we only restore deleted documents
|
|
248
|
+
const restoreFilter = {
|
|
249
|
+
...filter,
|
|
250
|
+
[field]: { $ne: null }
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
return await this.updateOne(
|
|
254
|
+
restoreFilter,
|
|
255
|
+
{ $unset: { [field]: 1 } },
|
|
256
|
+
options
|
|
257
|
+
);
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Restore multiple deleted documents
|
|
262
|
+
* @param {Object} filter - Query filter
|
|
263
|
+
* @param {Object} options - Update options
|
|
264
|
+
* @returns {Promise<Object>} Update result
|
|
265
|
+
*/
|
|
266
|
+
modelInstance.restoreMany = async function(filter, options) {
|
|
267
|
+
// Add deleted filter to ensure we only restore deleted documents
|
|
268
|
+
const restoreFilter = {
|
|
269
|
+
...filter,
|
|
270
|
+
[field]: { $ne: null }
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
return await this.updateMany(
|
|
274
|
+
restoreFilter,
|
|
275
|
+
{ $unset: { [field]: 1 } },
|
|
276
|
+
options
|
|
277
|
+
);
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Force physical deletion (bypass soft delete)
|
|
282
|
+
* @param {Object} filter - Query filter
|
|
283
|
+
* @param {Object} options - Delete options
|
|
284
|
+
* @returns {Promise<Object>} Delete result
|
|
285
|
+
*/
|
|
286
|
+
modelInstance.forceDelete = async function(filter, options = {}) {
|
|
287
|
+
// Set flag to bypass soft delete
|
|
288
|
+
return await this.collection.deleteOne(filter, { ...options, _forceDelete: true });
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Force physical deletion of multiple documents
|
|
293
|
+
* @param {Object} filter - Query filter
|
|
294
|
+
* @param {Object} options - Delete options
|
|
295
|
+
* @returns {Promise<Object>} Delete result
|
|
296
|
+
*/
|
|
297
|
+
modelInstance.forceDeleteMany = async function(filter, options = {}) {
|
|
298
|
+
// Set flag to bypass soft delete
|
|
299
|
+
return await this.collection.deleteMany(filter, { ...options, _forceDelete: true });
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Add TTL index for soft deleted documents
|
|
305
|
+
* @param {Object} modelInstance - Model instance
|
|
306
|
+
* @param {Object} config - Soft delete config
|
|
307
|
+
*/
|
|
308
|
+
function addTTLIndex(modelInstance, config) {
|
|
309
|
+
if (!config?.enabled || !config.ttl) return;
|
|
310
|
+
|
|
311
|
+
// Add TTL index to automatically clean up old deleted documents
|
|
312
|
+
modelInstance.indexes.push({
|
|
313
|
+
key: { [config.field]: 1 },
|
|
314
|
+
expireAfterSeconds: config.ttl,
|
|
315
|
+
name: `${config.field}_ttl`
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Setup soft delete feature for a model
|
|
321
|
+
* @param {Object} modelInstance - Model instance
|
|
322
|
+
* @param {Object|boolean} config - Soft delete config
|
|
323
|
+
*/
|
|
324
|
+
function setupSoftDelete(modelInstance, config) {
|
|
325
|
+
const parsedConfig = parseSoftDeleteConfig(config);
|
|
326
|
+
|
|
327
|
+
if (!parsedConfig) return;
|
|
328
|
+
|
|
329
|
+
// Store config on model instance
|
|
330
|
+
modelInstance.softDeleteConfig = parsedConfig;
|
|
331
|
+
|
|
332
|
+
// Register hooks
|
|
333
|
+
registerSoftDeleteHooks(modelInstance, parsedConfig);
|
|
334
|
+
|
|
335
|
+
// Add methods
|
|
336
|
+
addSoftDeleteMethods(modelInstance, parsedConfig);
|
|
337
|
+
|
|
338
|
+
// Add TTL index
|
|
339
|
+
addTTLIndex(modelInstance, parsedConfig);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
module.exports = {
|
|
343
|
+
setupSoftDelete,
|
|
344
|
+
parseSoftDeleteConfig,
|
|
345
|
+
applySoftDeleteFilter,
|
|
346
|
+
getDeleteValue
|
|
347
|
+
};
|
|
348
|
+
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model 乐观锁版本控制功能
|
|
3
|
+
*
|
|
4
|
+
* 功能:
|
|
5
|
+
* 1. 插入时自动初始化 version: 0
|
|
6
|
+
* 2. 更新时自动递增版本号
|
|
7
|
+
* 3. 支持并发冲突检测
|
|
8
|
+
* 4. 支持自定义字段名
|
|
9
|
+
*
|
|
10
|
+
* 使用示例:
|
|
11
|
+
* ```javascript
|
|
12
|
+
* Model.define('users', {
|
|
13
|
+
* options: {
|
|
14
|
+
* version: true // 或 { enabled: true, field: '__v' }
|
|
15
|
+
* }
|
|
16
|
+
* });
|
|
17
|
+
*
|
|
18
|
+
* // 插入时自动初始化
|
|
19
|
+
* await User.insertOne({ username: 'john' });
|
|
20
|
+
* // { _id, username: 'john', version: 0 }
|
|
21
|
+
*
|
|
22
|
+
* // 更新时自动递增
|
|
23
|
+
* await User.updateOne({ _id, version: 0 }, { $set: { status: 'active' } });
|
|
24
|
+
* // 实际执行:{ $set: { status: 'active' }, $inc: { version: 1 } }
|
|
25
|
+
*
|
|
26
|
+
* // 并发冲突检测
|
|
27
|
+
* const result = await User.updateOne({ _id, version: 0 }, { $set: { status: 'inactive' } });
|
|
28
|
+
* // result.modifiedCount === 0(版本号不匹配,更新失败)
|
|
29
|
+
* ```
|
|
30
|
+
*
|
|
31
|
+
* @module lib/model/features/version
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* 解析版本控制配置
|
|
36
|
+
*
|
|
37
|
+
* @param {boolean|object} versionConfig - 版本控制配置
|
|
38
|
+
* @returns {object|null} 解析后的配置对象
|
|
39
|
+
*/
|
|
40
|
+
function parseVersionConfig(versionConfig) {
|
|
41
|
+
if (!versionConfig) {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// 简单模式:version: true
|
|
46
|
+
if (versionConfig === true) {
|
|
47
|
+
return {
|
|
48
|
+
enabled: true,
|
|
49
|
+
field: 'version'
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// 完整配置模式
|
|
54
|
+
if (typeof versionConfig === 'object') {
|
|
55
|
+
return {
|
|
56
|
+
enabled: versionConfig.enabled !== false,
|
|
57
|
+
field: versionConfig.field || 'version'
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* 为 Model 实例设置版本控制功能
|
|
66
|
+
*
|
|
67
|
+
* @param {object} modelInstance - Model 实例
|
|
68
|
+
* @param {boolean|object} versionConfig - 版本控制配置
|
|
69
|
+
*/
|
|
70
|
+
function setupVersion(modelInstance, versionConfig) {
|
|
71
|
+
// 解析配置
|
|
72
|
+
const config = parseVersionConfig(versionConfig);
|
|
73
|
+
|
|
74
|
+
if (!config || !config.enabled) {
|
|
75
|
+
return; // 未启用,直接返回
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const { field } = config;
|
|
79
|
+
|
|
80
|
+
// 保存配置到 Model 实例(供测试验证)
|
|
81
|
+
modelInstance._versionConfig = config;
|
|
82
|
+
|
|
83
|
+
// ========== 功能1: 插入时初始化版本号 ==========
|
|
84
|
+
|
|
85
|
+
// 保存原始方法
|
|
86
|
+
const originalInsertOne = modelInstance.collection.insertOne.bind(modelInstance.collection);
|
|
87
|
+
const originalInsertMany = modelInstance.collection.insertMany.bind(modelInstance.collection);
|
|
88
|
+
|
|
89
|
+
// 覆盖 insertOne
|
|
90
|
+
modelInstance.collection.insertOne = async function(doc, options) {
|
|
91
|
+
// 只在用户未手动设置时添加版本号
|
|
92
|
+
if (doc && typeof doc === 'object' && doc[field] === undefined) {
|
|
93
|
+
doc[field] = 0;
|
|
94
|
+
}
|
|
95
|
+
return await originalInsertOne(doc, options);
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
// 覆盖 insertMany
|
|
99
|
+
modelInstance.collection.insertMany = async function(docs, options) {
|
|
100
|
+
if (Array.isArray(docs)) {
|
|
101
|
+
docs.forEach(doc => {
|
|
102
|
+
if (doc && typeof doc === 'object' && doc[field] === undefined) {
|
|
103
|
+
doc[field] = 0;
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
return await originalInsertMany(docs, options);
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
// ========== 功能2: 更新时自动递增版本号 ==========
|
|
111
|
+
|
|
112
|
+
// 保存原始方法
|
|
113
|
+
const originalUpdateOne = modelInstance.collection.updateOne.bind(modelInstance.collection);
|
|
114
|
+
const originalUpdateMany = modelInstance.collection.updateMany.bind(modelInstance.collection);
|
|
115
|
+
|
|
116
|
+
// 覆盖 updateOne
|
|
117
|
+
modelInstance.collection.updateOne = async function(filter, update, options) {
|
|
118
|
+
// 检查 update 是否为空
|
|
119
|
+
if (update && typeof update === 'object') {
|
|
120
|
+
// 检查用户是否已手动设置 $inc.version
|
|
121
|
+
if (!update.$inc || update.$inc[field] === undefined) {
|
|
122
|
+
// 自动添加 $inc
|
|
123
|
+
if (!update.$inc) {
|
|
124
|
+
update.$inc = {};
|
|
125
|
+
}
|
|
126
|
+
update.$inc[field] = 1;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return await originalUpdateOne(filter, update, options);
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
// 覆盖 updateMany
|
|
133
|
+
modelInstance.collection.updateMany = async function(filter, update, options) {
|
|
134
|
+
// 检查 update 是否为空
|
|
135
|
+
if (update && typeof update === 'object') {
|
|
136
|
+
// 检查用户是否已手动设置 $inc.version
|
|
137
|
+
if (!update.$inc || update.$inc[field] === undefined) {
|
|
138
|
+
// 自动添加 $inc
|
|
139
|
+
if (!update.$inc) {
|
|
140
|
+
update.$inc = {};
|
|
141
|
+
}
|
|
142
|
+
update.$inc[field] = 1;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return await originalUpdateMany(filter, update, options);
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
// 注意:并发冲突检测通过 filter 中的版本号自然实现
|
|
149
|
+
// 用户提供 { _id, version: 0 } 时,如果版本号不匹配,matchedCount 为 0
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
module.exports = {
|
|
153
|
+
setupVersion,
|
|
154
|
+
parseVersionConfig // 导出供测试使用
|
|
155
|
+
};
|
|
156
|
+
|