koatty_store 1.8.0 → 1.9.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.
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  /*!
2
2
  * @Author: richen
3
- * @Date: 2025-06-09 12:32:48
3
+ * @Date: 2025-11-02 08:37:33
4
4
  * @License: BSD (3-Clause)
5
5
  * @Copyright (c) - <richenlin(at)gmail.com>
6
6
  * @HomePage: https://koatty.org/
@@ -11,6 +11,7 @@ var lodash = require('lodash');
11
11
  var helper = require('koatty_lib');
12
12
  var events = require('events');
13
13
  var lruCache = require('lru-cache');
14
+ var AsyncLock = require('async-lock');
14
15
  var koatty_logger = require('koatty_logger');
15
16
  var ioredis = require('ioredis');
16
17
  var genericPool = require('generic-pool');
@@ -71,15 +72,6 @@ var messages;
71
72
  messages["mutuallyExclusiveNXXX"] = "ERR XX and NX options at the same time are not compatible";
72
73
  })(messages || (messages = {}));
73
74
  class MemoryCache extends events.EventEmitter {
74
- databases = new Map();
75
- options;
76
- currentDBIndex;
77
- connected;
78
- lastSave;
79
- multiMode;
80
- cache;
81
- responseMessages;
82
- ttlCheckTimer = null;
83
75
  /**
84
76
  * Creates an instance of MemoryCache.
85
77
  * @param {MemoryCacheOptions} options
@@ -87,6 +79,8 @@ class MemoryCache extends events.EventEmitter {
87
79
  */
88
80
  constructor(options) {
89
81
  super();
82
+ this.databases = new Map();
83
+ this.ttlCheckTimer = null;
90
84
  this.options = {
91
85
  database: 0,
92
86
  maxKeys: 1000,
@@ -100,6 +94,7 @@ class MemoryCache extends events.EventEmitter {
100
94
  this.lastSave = Date.now();
101
95
  this.multiMode = false;
102
96
  this.responseMessages = [];
97
+ this.lock = new AsyncLock();
103
98
  // 初始化数据库和缓存
104
99
  if (!this.databases.has(this.currentDBIndex)) {
105
100
  this.databases.set(this.currentDBIndex, this.createLRUCache());
@@ -163,6 +158,15 @@ class MemoryCache extends events.EventEmitter {
163
158
  this.ttlCheckTimer = null;
164
159
  }
165
160
  }
161
+ /**
162
+ * 清理所有资源
163
+ * @private
164
+ */
165
+ cleanup() {
166
+ this.stopTTLCheck();
167
+ this.databases.clear();
168
+ this.removeAllListeners();
169
+ }
166
170
  /**
167
171
  *
168
172
  *
@@ -188,8 +192,8 @@ class MemoryCache extends events.EventEmitter {
188
192
  * @memberof MemoryCache
189
193
  */
190
194
  quit() {
195
+ this.cleanup(); // 调用清理方法
191
196
  this.connected = false;
192
- this.stopTTLCheck();
193
197
  // exit multi mode if we are in it
194
198
  this.discard(null, true);
195
199
  this.emit('end');
@@ -452,14 +456,18 @@ class MemoryCache extends events.EventEmitter {
452
456
  * @memberof MemoryCache
453
457
  */
454
458
  incr(key, callback) {
455
- let retVal = null;
456
- try {
457
- retVal = this._addToKey(key, 1);
458
- }
459
- catch (err) {
460
- return this._handleCallback(callback, null, err);
461
- }
462
- return this._handleCallback(callback, retVal);
459
+ // 使用锁保护原子操作
460
+ const lockKey = `incr:${key}`;
461
+ return this.lock.acquire(lockKey, () => {
462
+ let retVal = null;
463
+ try {
464
+ retVal = this._addToKey(key, 1);
465
+ }
466
+ catch (err) {
467
+ return this._handleCallback(callback, null, err);
468
+ }
469
+ return this._handleCallback(callback, retVal);
470
+ });
463
471
  }
464
472
  /**
465
473
  *
@@ -471,14 +479,18 @@ class MemoryCache extends events.EventEmitter {
471
479
  * @memberof MemoryCache
472
480
  */
473
481
  incrby(key, amount, callback) {
474
- let retVal = null;
475
- try {
476
- retVal = this._addToKey(key, amount);
477
- }
478
- catch (err) {
479
- return this._handleCallback(callback, null, err);
480
- }
481
- return this._handleCallback(callback, retVal);
482
+ // 使用锁保护原子操作
483
+ const lockKey = `incrby:${key}`;
484
+ return this.lock.acquire(lockKey, () => {
485
+ let retVal = null;
486
+ try {
487
+ retVal = this._addToKey(key, amount);
488
+ }
489
+ catch (err) {
490
+ return this._handleCallback(callback, null, err);
491
+ }
492
+ return this._handleCallback(callback, retVal);
493
+ });
482
494
  }
483
495
  /**
484
496
  *
@@ -489,14 +501,18 @@ class MemoryCache extends events.EventEmitter {
489
501
  * @memberof MemoryCache
490
502
  */
491
503
  decr(key, callback) {
492
- let retVal = null;
493
- try {
494
- retVal = this._addToKey(key, -1);
495
- }
496
- catch (err) {
497
- return this._handleCallback(callback, null, err);
498
- }
499
- return this._handleCallback(callback, retVal);
504
+ // 使用锁保护原子操作
505
+ const lockKey = `decr:${key}`;
506
+ return this.lock.acquire(lockKey, () => {
507
+ let retVal = null;
508
+ try {
509
+ retVal = this._addToKey(key, -1);
510
+ }
511
+ catch (err) {
512
+ return this._handleCallback(callback, null, err);
513
+ }
514
+ return this._handleCallback(callback, retVal);
515
+ });
500
516
  }
501
517
  /**
502
518
  *
@@ -508,19 +524,23 @@ class MemoryCache extends events.EventEmitter {
508
524
  * @memberof MemoryCache
509
525
  */
510
526
  decrby(key, amount, callback) {
511
- let retVal = null;
512
- try {
513
- retVal = this._addToKey(key, 0 - amount);
514
- }
515
- catch (err) {
516
- return this._handleCallback(callback, null, err);
517
- }
518
- return this._handleCallback(callback, retVal);
527
+ // 使用锁保护原子操作
528
+ const lockKey = `decrby:${key}`;
529
+ return this.lock.acquire(lockKey, () => {
530
+ let retVal = null;
531
+ try {
532
+ retVal = this._addToKey(key, 0 - amount);
533
+ }
534
+ catch (err) {
535
+ return this._handleCallback(callback, null, err);
536
+ }
537
+ return this._handleCallback(callback, retVal);
538
+ });
519
539
  }
520
540
  // ---------------------------------------
521
541
  // ## Hash ##
522
542
  // ---------------------------------------
523
- hset(key, field, value, callback) {
543
+ hset(key, field, value, timeout, callback) {
524
544
  let retVal = 0;
525
545
  if (this._hasKey(key)) {
526
546
  this._testType(key, 'hash', true, callback);
@@ -532,6 +552,14 @@ class MemoryCache extends events.EventEmitter {
532
552
  retVal = 1;
533
553
  }
534
554
  this._setField(key, field, value.toString());
555
+ // 如果指定了 timeout,存储字段级别的过期时间
556
+ if (typeof timeout === 'number') {
557
+ const hashObj = this.cache.get(key).value;
558
+ if (!hashObj._fieldTTL) {
559
+ hashObj._fieldTTL = {};
560
+ }
561
+ hashObj._fieldTTL[field] = Date.now() + (timeout * 1000);
562
+ }
535
563
  this.persist(key);
536
564
  return this._handleCallback(callback, retVal);
537
565
  }
@@ -548,8 +576,17 @@ class MemoryCache extends events.EventEmitter {
548
576
  let retVal = null;
549
577
  if (this._hasKey(key)) {
550
578
  this._testType(key, 'hash', true, callback);
579
+ // 检查字段级别的过期时间
580
+ const hashObj = this._getKey(key);
581
+ if (hashObj && hashObj._fieldTTL && hashObj._fieldTTL[field]) {
582
+ if (hashObj._fieldTTL[field] <= Date.now()) {
583
+ // 过期,删除字段
584
+ this.hdel(key, field);
585
+ return this._handleCallback(callback, null);
586
+ }
587
+ }
551
588
  if (this._hasField(key, field)) {
552
- retVal = this._getKey(key)[field];
589
+ retVal = hashObj[field];
553
590
  }
554
591
  }
555
592
  return this._handleCallback(callback, retVal);
@@ -567,6 +604,15 @@ class MemoryCache extends events.EventEmitter {
567
604
  let retVal = 0;
568
605
  if (this._hasKey(key)) {
569
606
  this._testType(key, 'hash', true, callback);
607
+ // 检查字段级别的过期时间
608
+ const hashObj = this._getKey(key);
609
+ if (hashObj && hashObj._fieldTTL && hashObj._fieldTTL[field]) {
610
+ if (hashObj._fieldTTL[field] <= Date.now()) {
611
+ // 过期,删除字段
612
+ this.hdel(key, field);
613
+ return this._handleCallback(callback, 0);
614
+ }
615
+ }
570
616
  if (this._hasField(key, field)) {
571
617
  retVal = 1;
572
618
  }
@@ -586,10 +632,15 @@ class MemoryCache extends events.EventEmitter {
586
632
  const callback = this._retrieveCallback(fields);
587
633
  if (this._hasKey(key)) {
588
634
  this._testType(key, 'hash', true, callback);
635
+ const hashObj = this.cache.get(key).value;
589
636
  for (let itr = 0; itr < fields.length; itr++) {
590
637
  const field = fields[itr];
591
638
  if (this._hasField(key, field)) {
592
- delete this.cache.get(key).value[field];
639
+ delete hashObj[field];
640
+ // 清理过期时间记录
641
+ if (hashObj._fieldTTL && hashObj._fieldTTL[field]) {
642
+ delete hashObj._fieldTTL[field];
643
+ }
593
644
  retVal++;
594
645
  }
595
646
  }
@@ -619,14 +670,18 @@ class MemoryCache extends events.EventEmitter {
619
670
  * @memberof MemoryCache
620
671
  */
621
672
  hincrby(key, field, value, callback) {
622
- let retVal;
623
- try {
624
- retVal = this._addToField(key, field, value, false);
625
- }
626
- catch (err) {
627
- return this._handleCallback(callback, null, err);
628
- }
629
- return this._handleCallback(callback, retVal);
673
+ // 使用锁保护原子操作,使用组合 key
674
+ const lockKey = `hincrby:${key}:${field}`;
675
+ return this.lock.acquire(lockKey, () => {
676
+ let retVal;
677
+ try {
678
+ retVal = this._addToField(key, field, value, false);
679
+ }
680
+ catch (err) {
681
+ return this._handleCallback(callback, null, err);
682
+ }
683
+ return this._handleCallback(callback, retVal);
684
+ });
630
685
  }
631
686
  /**
632
687
  *
@@ -640,7 +695,11 @@ class MemoryCache extends events.EventEmitter {
640
695
  let retVals = {};
641
696
  if (this._hasKey(key)) {
642
697
  this._testType(key, 'hash', true, callback);
643
- retVals = this._getKey(key);
698
+ const hashObj = this._getKey(key);
699
+ // 排除内部属性 _fieldTTL
700
+ retVals = Object.entries(hashObj)
701
+ .filter(([k]) => k !== '_fieldTTL')
702
+ .reduce((acc, [k, v]) => ({ ...acc, [k]: v }), {});
644
703
  }
645
704
  return this._handleCallback(callback, retVals);
646
705
  }
@@ -656,7 +715,9 @@ class MemoryCache extends events.EventEmitter {
656
715
  let retVals = [];
657
716
  if (this._hasKey(key)) {
658
717
  this._testType(key, 'hash', true, callback);
659
- retVals = Object.keys(this._getKey(key));
718
+ const hashObj = this._getKey(key);
719
+ // 排除内部属性 _fieldTTL
720
+ retVals = Object.keys(hashObj).filter(k => k !== '_fieldTTL');
660
721
  }
661
722
  return this._handleCallback(callback, retVals);
662
723
  }
@@ -672,7 +733,11 @@ class MemoryCache extends events.EventEmitter {
672
733
  let retVals = [];
673
734
  if (this._hasKey(key)) {
674
735
  this._testType(key, 'hash', true, callback);
675
- retVals = Object.values(this._getKey(key));
736
+ const hashObj = this._getKey(key);
737
+ // 排除内部属性 _fieldTTL 的值
738
+ retVals = Object.entries(hashObj)
739
+ .filter(([k]) => k !== '_fieldTTL')
740
+ .map(([, v]) => v);
676
741
  }
677
742
  return this._handleCallback(callback, retVals);
678
743
  }
@@ -823,8 +888,7 @@ class MemoryCache extends events.EventEmitter {
823
888
  stop = length - 1;
824
889
  }
825
890
  if (stop >= 0 && stop >= start) {
826
- const size = stop - start + 1;
827
- for (let itr = start; itr < size; itr++) {
891
+ for (let itr = start; itr <= stop; itr++) {
828
892
  retVal.push(list[itr]);
829
893
  }
830
894
  }
@@ -1031,9 +1095,10 @@ class MemoryCache extends events.EventEmitter {
1031
1095
  * @memberof MemoryCache
1032
1096
  */
1033
1097
  pttl(key, _callback) {
1098
+ var _a;
1034
1099
  let retVal = -2;
1035
1100
  if (this._hasKey(key)) {
1036
- if (!lodash.isNil(this.cache.get(key)?.timeout)) {
1101
+ if (!lodash.isNil((_a = this.cache.get(key)) === null || _a === void 0 ? void 0 : _a.timeout)) {
1037
1102
  retVal = this.cache.get(key).timeout - Date.now();
1038
1103
  // Prevent unexpected errors if the actual ttl just happens to be -2 or -1
1039
1104
  if (retVal < 0 && retVal > -3) {
@@ -1242,7 +1307,8 @@ class MemoryCache extends events.EventEmitter {
1242
1307
  if (key && field) {
1243
1308
  const ky = this._getKey(key);
1244
1309
  if (ky) {
1245
- retVal = ky.hasOwnProperty(field);
1310
+ // 排除内部属性 _fieldTTL
1311
+ retVal = field !== '_fieldTTL' && ky.hasOwnProperty(field);
1246
1312
  }
1247
1313
  }
1248
1314
  return retVal;
@@ -1651,9 +1717,6 @@ class MemoryCache extends events.EventEmitter {
1651
1717
  * @LastEditTime: 2023-02-18 23:52:47
1652
1718
  */
1653
1719
  class MemoryStore {
1654
- client;
1655
- pool;
1656
- options;
1657
1720
  /**
1658
1721
  * Creates an instance of MemoryStore.
1659
1722
  * @param {MemoryStoreOpt} options
@@ -1666,7 +1729,15 @@ class MemoryStore {
1666
1729
  ttlCheckInterval: 60000, // 1分钟
1667
1730
  ...options
1668
1731
  };
1669
- this.client = null;
1732
+ // 直接创建 MemoryCache 实例
1733
+ this.client = new MemoryCache({
1734
+ database: this.options.db || 0,
1735
+ maxKeys: this.options.maxKeys,
1736
+ maxMemory: this.options.maxMemory,
1737
+ evictionPolicy: this.options.evictionPolicy,
1738
+ ttlCheckInterval: this.options.ttlCheckInterval
1739
+ });
1740
+ this.client.createClient();
1670
1741
  }
1671
1742
  /**
1672
1743
  * getConnection
@@ -1675,19 +1746,7 @@ class MemoryStore {
1675
1746
  * @memberof MemoryStore
1676
1747
  */
1677
1748
  getConnection() {
1678
- if (!this.pool) {
1679
- this.pool = new MemoryCache({
1680
- database: this.options.db || 0,
1681
- maxKeys: this.options.maxKeys,
1682
- maxMemory: this.options.maxMemory,
1683
- evictionPolicy: this.options.evictionPolicy,
1684
- ttlCheckInterval: this.options.ttlCheckInterval
1685
- });
1686
- }
1687
- if (!this.client) {
1688
- this.client = this.pool.createClient();
1689
- this.client.status = "ready";
1690
- }
1749
+ // 直接返回 MemoryCache 实例
1691
1750
  return this.client;
1692
1751
  }
1693
1752
  /**
@@ -1710,7 +1769,8 @@ class MemoryStore {
1710
1769
  * @memberof MemoryStore
1711
1770
  */
1712
1771
  async release(_conn) {
1713
- return;
1772
+ // 对于内存存储,不需要释放连接
1773
+ return Promise.resolve();
1714
1774
  }
1715
1775
  /**
1716
1776
  * defineCommand
@@ -1774,18 +1834,15 @@ class MemoryStore {
1774
1834
  * @class RedisStore
1775
1835
  */
1776
1836
  class RedisStore {
1777
- options;
1778
- pool;
1779
- client;
1780
- reconnectAttempts = 0;
1781
- maxReconnectAttempts = 5;
1782
- reconnectDelay = 1000; // 初始重连延迟1秒
1783
1837
  /**
1784
1838
  * Creates an instance of RedisStore.
1785
1839
  * @param {RedisStoreOpt} options
1786
1840
  * @memberof RedisStore
1787
1841
  */
1788
1842
  constructor(options) {
1843
+ this.reconnectAttempts = 0;
1844
+ this.maxReconnectAttempts = 5;
1845
+ this.reconnectDelay = 1000; // 初始重连延迟1秒
1789
1846
  this.options = this.parseOpt(options);
1790
1847
  this.pool = null;
1791
1848
  }
@@ -2072,9 +2129,6 @@ const defaultOptions = {
2072
2129
  * @class Store
2073
2130
  */
2074
2131
  class CacheStore {
2075
- client;
2076
- options;
2077
- static instances = new Map();
2078
2132
  /**
2079
2133
  * Creates an instance of CacheStore.
2080
2134
  * @param {StoreOptions} options
@@ -2192,10 +2246,22 @@ class CacheStore {
2192
2246
  return res;
2193
2247
  }
2194
2248
  catch (err) {
2195
- throw err;
2249
+ // 添加详细的错误信息
2250
+ const error = new Error(`Cache operation failed: ${name}(${data.slice(0, 2).join(', ')}${data.length > 2 ? '...' : ''}) - ${err.message}`);
2251
+ error.stack = err.stack;
2252
+ throw error;
2196
2253
  }
2197
2254
  finally {
2198
- this.release(conn);
2255
+ // 安全地释放连接
2256
+ if (conn) {
2257
+ try {
2258
+ await this.release(conn);
2259
+ }
2260
+ catch (releaseErr) {
2261
+ // 记录但不抛出,避免掩盖原始错误
2262
+ koatty_logger.DefaultLogger.Error(`Failed to release connection: ${releaseErr.message}`);
2263
+ }
2264
+ }
2199
2265
  }
2200
2266
  }
2201
2267
  /**
@@ -2291,15 +2357,8 @@ class CacheStore {
2291
2357
  * @param timeout
2292
2358
  */
2293
2359
  async hset(name, key, value, timeout) {
2294
- const result = await this.wrap('hset', [`${this.options.keyPrefix || ""}${name}`, key, value]);
2295
- if (typeof timeout === 'number') {
2296
- await this.set(`${name}:${key}_ex`, 1, timeout);
2297
- }
2298
- else {
2299
- // 如果没有指定timeout,设置一个永久标记,避免hget时误删
2300
- await this.set(`${name}:${key}_ex`, 1);
2301
- }
2302
- return result;
2360
+ // MemoryStore 直接支持字段级 TTL,传递 timeout 参数
2361
+ return this.wrap('hset', [`${this.options.keyPrefix || ""}${name}`, key, value, timeout]);
2303
2362
  }
2304
2363
  /**
2305
2364
  * 哈希获取
@@ -2308,15 +2367,8 @@ class CacheStore {
2308
2367
  * @returns {*}
2309
2368
  */
2310
2369
  hget(name, key) {
2311
- const setP = [this.get(`${name}:${key}_ex`)];
2312
- setP.push(this.wrap('hget', [`${this.options.keyPrefix || ""}${name}`, key]));
2313
- return Promise.all(setP).then(dataArr => {
2314
- if (dataArr[0] === null) {
2315
- this.hdel(name, key);
2316
- return null;
2317
- }
2318
- return dataArr[1] || null;
2319
- });
2370
+ // 直接调用底层实现,TTL 检查在 MemoryCache 中处理
2371
+ return this.wrap('hget', [`${this.options.keyPrefix || ""}${name}`, key]);
2320
2372
  }
2321
2373
  /**
2322
2374
  * 查看哈希表 hashKey 中,给定域 key 是否存在
@@ -2325,15 +2377,8 @@ class CacheStore {
2325
2377
  * @returns {*}
2326
2378
  */
2327
2379
  hexists(name, key) {
2328
- const setP = [this.get(`${name}:${key}_ex`)];
2329
- setP.push(this.wrap('hexists', [`${this.options.keyPrefix || ""}${name}`, key]));
2330
- return Promise.all(setP).then(dataArr => {
2331
- if (dataArr[0] === null) {
2332
- this.hdel(name, key);
2333
- return 0;
2334
- }
2335
- return Number(dataArr[1]) || 0;
2336
- });
2380
+ // 直接调用底层实现,TTL 检查在 MemoryCache 中处理
2381
+ return this.wrap('hexists', [`${this.options.keyPrefix || ""}${name}`, key]);
2337
2382
  }
2338
2383
  /**
2339
2384
  * 哈希删除
@@ -2342,9 +2387,8 @@ class CacheStore {
2342
2387
  * @returns {*}
2343
2388
  */
2344
2389
  async hdel(name, key) {
2345
- await this.del(`${name}:${key}_ex`);
2346
- const result = await this.wrap('hdel', [`${this.options.keyPrefix || ""}${name}`, key]);
2347
- return result;
2390
+ // 直接调用底层实现,TTL 清理在 MemoryCache 中处理
2391
+ return this.wrap('hdel', [`${this.options.keyPrefix || ""}${name}`, key]);
2348
2392
  }
2349
2393
  /**
2350
2394
  * 返回哈希表 key 中域的数量
@@ -2511,5 +2555,6 @@ class CacheStore {
2511
2555
  return this.wrap('smove', [`${this.options.keyPrefix || ""}${source}`, `${this.options.keyPrefix || ""}${destination}`, member]);
2512
2556
  }
2513
2557
  }
2558
+ CacheStore.instances = new Map();
2514
2559
 
2515
2560
  exports.CacheStore = CacheStore;