monsqlize 1.3.1 → 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 (113) hide show
  1. package/CHANGELOG.md +415 -146
  2. package/LICENSE +201 -21
  3. package/README.md +537 -928
  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 +69 -67
  27. package/index.d.ts +0 -1289
  28. package/lib/cache.js +0 -491
  29. package/lib/common/cursor.js +0 -58
  30. package/lib/common/docs-urls.js +0 -72
  31. package/lib/common/index-options.js +0 -222
  32. package/lib/common/log.js +0 -60
  33. package/lib/common/namespace.js +0 -21
  34. package/lib/common/normalize.js +0 -33
  35. package/lib/common/page-result.js +0 -42
  36. package/lib/common/runner.js +0 -56
  37. package/lib/common/server-features.js +0 -231
  38. package/lib/common/shape-builders.js +0 -26
  39. package/lib/common/validation.js +0 -112
  40. package/lib/connect.js +0 -76
  41. package/lib/constants.js +0 -54
  42. package/lib/count-queue.js +0 -187
  43. package/lib/distributed-cache-invalidator.js +0 -259
  44. package/lib/errors.js +0 -167
  45. package/lib/index.js +0 -461
  46. package/lib/infrastructure/ssh-tunnel-ssh2.js +0 -211
  47. package/lib/infrastructure/ssh-tunnel.js +0 -40
  48. package/lib/infrastructure/uri-parser.js +0 -35
  49. package/lib/lock/Lock.js +0 -66
  50. package/lib/lock/errors.js +0 -27
  51. package/lib/lock/index.js +0 -12
  52. package/lib/logger.js +0 -224
  53. package/lib/model/examples/test.js +0 -114
  54. package/lib/mongodb/common/accessor-helpers.js +0 -58
  55. package/lib/mongodb/common/agg-pipeline.js +0 -32
  56. package/lib/mongodb/common/iid.js +0 -27
  57. package/lib/mongodb/common/lexicographic-expr.js +0 -52
  58. package/lib/mongodb/common/shape.js +0 -31
  59. package/lib/mongodb/common/sort.js +0 -38
  60. package/lib/mongodb/common/transaction-aware.js +0 -24
  61. package/lib/mongodb/connect.js +0 -233
  62. package/lib/mongodb/index.js +0 -591
  63. package/lib/mongodb/management/admin-ops.js +0 -199
  64. package/lib/mongodb/management/bookmark-ops.js +0 -166
  65. package/lib/mongodb/management/cache-ops.js +0 -49
  66. package/lib/mongodb/management/collection-ops.js +0 -386
  67. package/lib/mongodb/management/database-ops.js +0 -201
  68. package/lib/mongodb/management/index-ops.js +0 -474
  69. package/lib/mongodb/management/index.js +0 -16
  70. package/lib/mongodb/management/namespace.js +0 -30
  71. package/lib/mongodb/management/validation-ops.js +0 -267
  72. package/lib/mongodb/queries/aggregate.js +0 -142
  73. package/lib/mongodb/queries/chain.js +0 -630
  74. package/lib/mongodb/queries/count.js +0 -98
  75. package/lib/mongodb/queries/distinct.js +0 -77
  76. package/lib/mongodb/queries/find-and-count.js +0 -192
  77. package/lib/mongodb/queries/find-by-ids.js +0 -235
  78. package/lib/mongodb/queries/find-one-by-id.js +0 -170
  79. package/lib/mongodb/queries/find-one.js +0 -70
  80. package/lib/mongodb/queries/find-page.js +0 -577
  81. package/lib/mongodb/queries/find.js +0 -170
  82. package/lib/mongodb/queries/index.js +0 -50
  83. package/lib/mongodb/queries/watch.js +0 -537
  84. package/lib/mongodb/writes/delete-many.js +0 -190
  85. package/lib/mongodb/writes/delete-one.js +0 -182
  86. package/lib/mongodb/writes/find-one-and-delete.js +0 -202
  87. package/lib/mongodb/writes/find-one-and-replace.js +0 -238
  88. package/lib/mongodb/writes/find-one-and-update.js +0 -239
  89. package/lib/mongodb/writes/increment-one.js +0 -252
  90. package/lib/mongodb/writes/index.js +0 -41
  91. package/lib/mongodb/writes/insert-batch.js +0 -507
  92. package/lib/mongodb/writes/insert-many.js +0 -227
  93. package/lib/mongodb/writes/insert-one.js +0 -180
  94. package/lib/mongodb/writes/replace-one.js +0 -215
  95. package/lib/mongodb/writes/result-handler.js +0 -236
  96. package/lib/mongodb/writes/update-many.js +0 -221
  97. package/lib/mongodb/writes/update-one.js +0 -223
  98. package/lib/mongodb/writes/upsert-one.js +0 -206
  99. package/lib/multi-level-cache.js +0 -189
  100. package/lib/operators.js +0 -330
  101. package/lib/redis-cache-adapter.js +0 -237
  102. package/lib/slow-query-log/base-storage.js +0 -69
  103. package/lib/slow-query-log/batch-queue.js +0 -96
  104. package/lib/slow-query-log/config-manager.js +0 -195
  105. package/lib/slow-query-log/index.js +0 -237
  106. package/lib/slow-query-log/mongodb-storage.js +0 -323
  107. package/lib/slow-query-log/query-hash.js +0 -38
  108. package/lib/transaction/CacheLockManager.js +0 -161
  109. package/lib/transaction/DistributedCacheLockManager.js +0 -474
  110. package/lib/transaction/Transaction.js +0 -314
  111. package/lib/transaction/TransactionManager.js +0 -266
  112. package/lib/transaction/index.js +0 -10
  113. package/lib/utils/objectid-converter.js +0 -566
@@ -1,474 +0,0 @@
1
- /**
2
- * 分布式缓存锁管理器
3
- * 基于 Redis 实现跨实例的事务缓存锁,确保事务隔离性
4
- *
5
- * v1.4.0 新增:业务级分布式锁支持
6
- * - withLock(): 自动管理锁生命周期
7
- * - acquireLock(): 手动获取锁(阻塞重试)
8
- * - tryAcquireLock(): 尝试获取锁(不阻塞)
9
- *
10
- * @example
11
- * // 事务缓存锁(原有功能)
12
- * await lockManager.addLock('user:1', session);
13
- * const isLocked = await lockManager.isLocked('user:1');
14
- * await lockManager.releaseLocks(session);
15
- *
16
- * // 业务锁(v1.4.0 新增)
17
- * await lockManager.withLock('inventory:SKU123', async () => {
18
- * // 临界区代码
19
- * });
20
- */
21
-
22
- const Lock = require('../lock/Lock');
23
- const { LockAcquireError } = require('../lock/errors');
24
-
25
- class DistributedCacheLockManager {
26
- /**
27
- * @param {Object} options
28
- * @param {Object} options.redis - ioredis 实例
29
- * @param {string} [options.lockKeyPrefix='monsqlize:cache:lock:'] - 锁键前缀
30
- * @param {number} [options.maxDuration=300000] - 锁最大持续时间(毫秒)
31
- * @param {Object} [options.logger] - 日志记录器
32
- */
33
- constructor(options = {}) {
34
- if (!options.redis) {
35
- throw new Error('DistributedCacheLockManager requires a Redis instance');
36
- }
37
-
38
- this.redis = options.redis;
39
- this.lockKeyPrefix = options.lockKeyPrefix || 'monsqlize:cache:lock:';
40
- this.maxDuration = options.maxDuration || 300000; // 5 分钟默认
41
- this.logger = options.logger;
42
-
43
- // 统计信息
44
- this.stats = {
45
- locksAcquired: 0,
46
- locksReleased: 0,
47
- lockChecks: 0,
48
- errors: 0
49
- };
50
-
51
- // 错误处理
52
- this.redis.on('error', (err) => {
53
- this.stats.errors++;
54
- if (this.logger) {
55
- this.logger.error('[DistributedCacheLockManager] Redis error:', err.message);
56
- }
57
- });
58
- }
59
-
60
- /**
61
- * 添加分布式缓存锁
62
- * @param {string} key - 缓存键(支持通配符 *)
63
- * @param {Object} session - MongoDB session 对象
64
- * @returns {Promise<boolean>} 是否成功获取锁
65
- */
66
- async addLock(key, session) {
67
- if (!session || !session.id) {
68
- if (this.logger) {
69
- this.logger.warn('[DistributedCacheLockManager] Invalid session, skip lock');
70
- }
71
- return false;
72
- }
73
-
74
- const lockKey = this.lockKeyPrefix + key;
75
- const sessionId = session.id.toString();
76
- const ttlSeconds = Math.ceil(this.maxDuration / 1000);
77
-
78
- try {
79
- // 使用 SET NX EX 原子操作获取锁
80
- const result = await this.redis.set(lockKey, sessionId, 'EX', ttlSeconds, 'NX');
81
-
82
- if (result === 'OK') {
83
- this.stats.locksAcquired++;
84
- if (this.logger) {
85
- this.logger.debug(`[DistributedCacheLockManager] Lock acquired: ${key}`);
86
- }
87
- return true;
88
- }
89
-
90
- return false;
91
- } catch (error) {
92
- this.stats.errors++;
93
- if (this.logger) {
94
- this.logger.error('[DistributedCacheLockManager] Add lock error:', error.message);
95
- }
96
- return false;
97
- }
98
- }
99
-
100
- /**
101
- * 检查缓存键是否被锁定
102
- * @param {string} key - 缓存键
103
- * @returns {Promise<boolean>}
104
- */
105
- async isLocked(key) {
106
- this.stats.lockChecks++;
107
-
108
- try {
109
- // 1. 检查精确匹配
110
- const lockKey = this.lockKeyPrefix + key;
111
- const exists = await this.redis.exists(lockKey);
112
- if (exists) {
113
- return true;
114
- }
115
-
116
- // 2. 检查通配符匹配
117
- // 注意:KEYS 命令在大数据量下性能较差,这里使用 SCAN 会更好
118
- // 但为了简单起见,暂时使用 KEYS,生产环境建议优化
119
- const pattern = this.lockKeyPrefix + '*';
120
- const keys = await this.redis.keys(pattern);
121
-
122
- for (const foundKey of keys) {
123
- const lockPattern = foundKey.replace(this.lockKeyPrefix, '');
124
-
125
- // 检查是否是通配符模式
126
- if (lockPattern.includes('*')) {
127
- const regex = this._patternToRegex(lockPattern);
128
- if (regex.test(key)) {
129
- return true;
130
- }
131
- }
132
- }
133
-
134
- return false;
135
- } catch (error) {
136
- this.stats.errors++;
137
- if (this.logger) {
138
- this.logger.error('[DistributedCacheLockManager] Is locked check error:', error.message);
139
- }
140
- // 发生错误时保守处理:假设没有锁(允许操作继续)
141
- return false;
142
- }
143
- }
144
-
145
- /**
146
- * 释放指定 session 的所有锁
147
- * @param {Object} session - MongoDB session 对象
148
- * @returns {Promise<number>} 释放的锁数量
149
- */
150
- async releaseLocks(session) {
151
- if (!session || !session.id) {
152
- return 0;
153
- }
154
-
155
- const sessionId = session.id.toString();
156
- const pattern = this.lockKeyPrefix + '*';
157
-
158
- try {
159
- const keys = await this.redis.keys(pattern);
160
-
161
- if (keys.length === 0) {
162
- return 0;
163
- }
164
-
165
- // 使用 Lua 脚本批量删除(原子操作)
166
- // 只删除属于当前 session 的锁
167
- const luaScript = `
168
- local deletedCount = 0
169
- for i, key in ipairs(KEYS) do
170
- local value = redis.call('GET', key)
171
- if value == ARGV[1] then
172
- redis.call('DEL', key)
173
- deletedCount = deletedCount + 1
174
- end
175
- end
176
- return deletedCount
177
- `;
178
-
179
- const deleted = await this.redis.eval(
180
- luaScript,
181
- keys.length,
182
- ...keys,
183
- sessionId
184
- );
185
-
186
- this.stats.locksReleased += deleted;
187
-
188
- if (this.logger && deleted > 0) {
189
- this.logger.debug(`[DistributedCacheLockManager] Released ${deleted} locks for session ${sessionId}`);
190
- }
191
-
192
- return deleted;
193
- } catch (error) {
194
- this.stats.errors++;
195
- if (this.logger) {
196
- this.logger.error('[DistributedCacheLockManager] Release locks error:', error.message);
197
- }
198
- return 0;
199
- }
200
- }
201
-
202
- /**
203
- * 清理所有过期锁(Redis TTL 会自动清理,此方法保留用于手动清理)
204
- * @returns {Promise<void>}
205
- */
206
- async cleanup() {
207
- // Redis TTL 会自动清理过期的键
208
- // 这里可以添加额外的清理逻辑,比如强制清理超时的锁
209
- if (this.logger) {
210
- this.logger.debug('[DistributedCacheLockManager] Cleanup called (Redis handles TTL automatically)');
211
- }
212
- }
213
-
214
- /**
215
- * 获取统计信息
216
- * @returns {Object}
217
- */
218
- getStats() {
219
- return {
220
- ...this.stats,
221
- lockKeyPrefix: this.lockKeyPrefix,
222
- maxDuration: this.maxDuration
223
- };
224
- }
225
-
226
- /**
227
- * 将通配符模式转换为正则表达式
228
- * @private
229
- */
230
- _patternToRegex(pattern) {
231
- const escaped = pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
232
- const wildcarded = escaped.replace(/\\\*/g, '.*');
233
- return new RegExp(`^${wildcarded}$`);
234
- }
235
-
236
- /**
237
- * 停止(清理资源)
238
- * 注意:不关闭 Redis 连接,因为连接是外部传入的
239
- */
240
- stop() {
241
- if (this.logger) {
242
- this.logger.info('[DistributedCacheLockManager] Stopped');
243
- }
244
- }
245
-
246
- // ==================== 业务锁 API (v1.4.0) ====================
247
-
248
- /**
249
- * 业务锁:自动管理锁生命周期(推荐)
250
- * @param {string} key - 锁的唯一标识
251
- * @param {Function} callback - 获取锁后执行的函数
252
- * @param {Object} [options] - 锁选项
253
- * @param {number} [options.ttl=10000] - 锁过期时间(毫秒)
254
- * @param {number} [options.retryTimes=3] - 重试次数
255
- * @param {number} [options.retryDelay=100] - 重试间隔(毫秒)
256
- * @param {boolean} [options.fallbackToNoLock=false] - Redis不可用时降级为无锁执行
257
- * @returns {Promise<*>} callback 的返回值
258
- */
259
- async withLock(key, callback, options = {}) {
260
- try {
261
- const lock = await this.acquireLock(key, options);
262
- try {
263
- return await callback();
264
- } finally {
265
- // 释放失败不应阻塞业务(锁会在 TTL 后自动过期)
266
- await lock.release().catch(err => {
267
- if (this.logger) {
268
- this.logger.warn(`[Lock] Release failed: ${key}`, err);
269
- }
270
- });
271
- }
272
- } catch (error) {
273
- // Redis 连接问题检测
274
- if (this._isRedisConnectionError(error)) {
275
- if (options.fallbackToNoLock) {
276
- if (this.logger) {
277
- this.logger.warn(`[Lock] Redis unavailable, proceeding without lock: ${key}`);
278
- }
279
- return callback();
280
- }
281
- throw new LockAcquireError(`Redis unavailable: ${error.message}`);
282
- }
283
- throw error;
284
- }
285
- }
286
-
287
- /**
288
- * 业务锁:手动获取锁(阻塞重试)
289
- * @param {string} key - 锁的唯一标识
290
- * @param {Object} [options] - 锁选项
291
- * @returns {Promise<Lock>}
292
- */
293
- async acquireLock(key, options = {}) {
294
- const ttl = options.ttl || 10000; // 默认 10 秒
295
- const retryTimes = options.retryTimes ?? 3;
296
- const retryDelay = options.retryDelay || 100;
297
- const lockId = this._generateLockId();
298
- const fullKey = this.lockKeyPrefix + key;
299
-
300
- for (let attempt = 0; attempt <= retryTimes; attempt++) {
301
- try {
302
- // 使用 SET NX PX 原子操作(毫秒级 TTL)
303
- const result = await this.redis.set(
304
- fullKey,
305
- lockId,
306
- 'PX', ttl, // PX = 毫秒
307
- 'NX'
308
- );
309
-
310
- if (result === 'OK') {
311
- this.stats.locksAcquired++;
312
- if (this.logger) {
313
- this.logger.debug(`[Lock] Acquired: ${key}`);
314
- }
315
- return new Lock(key, lockId, this, ttl);
316
- }
317
-
318
- // 最后一次尝试失败
319
- if (attempt === retryTimes) {
320
- break;
321
- }
322
-
323
- // 等待后重试
324
- await this._sleep(retryDelay);
325
- } catch (error) {
326
- // 如果是最后一次尝试,抛出错误
327
- if (attempt === retryTimes) {
328
- throw error;
329
- }
330
- // 否则等待后重试
331
- await this._sleep(retryDelay);
332
- }
333
- }
334
-
335
- this.stats.errors++;
336
- throw new LockAcquireError(`Failed to acquire lock: ${key}`);
337
- }
338
-
339
- /**
340
- * 业务锁:尝试获取锁(不阻塞)
341
- * @param {string} key - 锁的唯一标识
342
- * @param {Object} [options] - 锁选项(不包含 retryTimes)
343
- * @returns {Promise<Lock|null>} Lock对象或null
344
- */
345
- async tryAcquireLock(key, options = {}) {
346
- const ttl = options.ttl || 10000;
347
- const lockId = this._generateLockId();
348
- const fullKey = this.lockKeyPrefix + key;
349
-
350
- try {
351
- const result = await this.redis.set(
352
- fullKey,
353
- lockId,
354
- 'PX', ttl,
355
- 'NX'
356
- );
357
-
358
- if (result === 'OK') {
359
- this.stats.locksAcquired++;
360
- if (this.logger) {
361
- this.logger.debug(`[Lock] Acquired (try): ${key}`);
362
- }
363
- return new Lock(key, lockId, this, ttl);
364
- }
365
-
366
- return null;
367
- } catch (error) {
368
- if (this.logger) {
369
- this.logger.error(`[Lock] Try acquire error: ${key}`, error.message);
370
- }
371
- return null;
372
- }
373
- }
374
-
375
- /**
376
- * 释放单个锁(由 Lock 对象调用)
377
- * @param {string} key - 锁标识
378
- * @param {string} lockId - 锁ID
379
- * @returns {Promise<boolean>}
380
- */
381
- async releaseLock(key, lockId) {
382
- const fullKey = this.lockKeyPrefix + key;
383
-
384
- try {
385
- // 使用 Lua 脚本确保原子性(只释放自己的锁)
386
- const luaScript = `
387
- if redis.call("get", KEYS[1]) == ARGV[1] then
388
- return redis.call("del", KEYS[1])
389
- else
390
- return 0
391
- end
392
- `;
393
-
394
- const result = await this.redis.eval(luaScript, 1, fullKey, lockId);
395
-
396
- if (result === 1) {
397
- this.stats.locksReleased++;
398
- if (this.logger) {
399
- this.logger.debug(`[Lock] Released: ${key}`);
400
- }
401
- return true;
402
- }
403
-
404
- return false;
405
- } catch (error) {
406
- this.stats.errors++;
407
- if (this.logger) {
408
- this.logger.error(`[Lock] Release error: ${key}`, error.message);
409
- }
410
- return false;
411
- }
412
- }
413
-
414
- /**
415
- * 续期(延长锁的过期时间)
416
- * @param {string} key - 锁标识
417
- * @param {string} lockId - 锁ID
418
- * @param {number} ttl - 新的过期时间(毫秒)
419
- * @returns {Promise<boolean>}
420
- */
421
- async renewLock(key, lockId, ttl) {
422
- const fullKey = this.lockKeyPrefix + key;
423
-
424
- try {
425
- // 使用 Lua 脚本确保只续期自己的锁
426
- const luaScript = `
427
- if redis.call("get", KEYS[1]) == ARGV[1] then
428
- return redis.call("pexpire", KEYS[1], ARGV[2])
429
- else
430
- return 0
431
- end
432
- `;
433
-
434
- const result = await this.redis.eval(luaScript, 1, fullKey, lockId, ttl);
435
- return result === 1;
436
- } catch (error) {
437
- if (this.logger) {
438
- this.logger.error(`[Lock] Renew error: ${key}`, error.message);
439
- }
440
- return false;
441
- }
442
- }
443
-
444
- /**
445
- * 生成唯一锁ID
446
- * @private
447
- */
448
- _generateLockId() {
449
- return `${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
450
- }
451
-
452
- /**
453
- * 延迟
454
- * @private
455
- */
456
- _sleep(ms) {
457
- return new Promise(resolve => setTimeout(resolve, ms));
458
- }
459
-
460
- /**
461
- * 检测是否是 Redis 连接错误
462
- * @private
463
- */
464
- _isRedisConnectionError(error) {
465
- const msg = error.message || '';
466
- return msg.includes('ECONNREFUSED') ||
467
- msg.includes('ETIMEDOUT') ||
468
- msg.includes('ENOTFOUND') ||
469
- msg.includes('Connection is closed');
470
- }
471
- }
472
-
473
- module.exports = DistributedCacheLockManager;
474
-