hedgequantx 1.8.49 → 2.3.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 (103) hide show
  1. package/README.md +7 -6
  2. package/bin/cli.js +13 -7
  3. package/dist/algo/copy-engine.js +3 -0
  4. package/dist/algo/copy-engine.jsc +0 -0
  5. package/dist/algo/engine.js +3 -0
  6. package/dist/algo/engine.jsc +0 -0
  7. package/dist/algo/market-data-rithmic.js +3 -0
  8. package/dist/algo/market-data-rithmic.jsc +0 -0
  9. package/dist/algo/market-data.js +3 -0
  10. package/dist/algo/market-data.jsc +0 -0
  11. package/dist/algo/rithmic/connection.js +3 -0
  12. package/dist/algo/rithmic/connection.jsc +0 -0
  13. package/dist/algo/rithmic/constants.js +3 -0
  14. package/dist/algo/rithmic/constants.jsc +0 -0
  15. package/dist/algo/rithmic/index.js +3 -0
  16. package/dist/algo/rithmic/index.jsc +0 -0
  17. package/dist/algo/rithmic/market-data.js +3 -0
  18. package/dist/algo/rithmic/market-data.jsc +0 -0
  19. package/dist/algo/rithmic/pnl.js +3 -0
  20. package/dist/algo/rithmic/pnl.jsc +0 -0
  21. package/dist/algo/rithmic/pool.js +3 -0
  22. package/dist/algo/rithmic/pool.jsc +0 -0
  23. package/dist/algo/rithmic/trading.js +3 -0
  24. package/dist/algo/rithmic/trading.jsc +0 -0
  25. package/dist/algo/rithmic-decoder.js +3 -0
  26. package/dist/algo/rithmic-decoder.jsc +0 -0
  27. package/dist/algo/strategies/ultra-scalping-v2.js +3 -0
  28. package/dist/algo/strategies/ultra-scalping-v2.jsc +0 -0
  29. package/dist/algo/strategies/ultra-scalping.js +3 -0
  30. package/dist/algo/strategies/ultra-scalping.jsc +0 -0
  31. package/dist/algo/trading-api-rithmic.js +3 -0
  32. package/dist/algo/trading-api-rithmic.jsc +0 -0
  33. package/dist/algo/trading-api.js +3 -0
  34. package/dist/algo/trading-api.jsc +0 -0
  35. package/dist/algo/utils/smart-logger.js +3 -0
  36. package/dist/algo/utils/smart-logger.jsc +0 -0
  37. package/dist/algo/utils/smart-logs.js +3 -0
  38. package/dist/algo/utils/smart-logs.jsc +0 -0
  39. package/package.json +33 -10
  40. package/protos/rithmic/account_pnl_position_update.proto +59 -0
  41. package/protos/rithmic/base.proto +7 -0
  42. package/protos/rithmic/best_bid_offer.proto +39 -0
  43. package/protos/rithmic/exchange_order_notification.proto +140 -0
  44. package/protos/rithmic/instrument_pnl_position_update.proto +50 -0
  45. package/protos/rithmic/last_trade.proto +53 -0
  46. package/protos/rithmic/request_account_list.proto +20 -0
  47. package/protos/rithmic/request_cancel_all_orders.proto +15 -0
  48. package/protos/rithmic/request_front_month_contract.proto +10 -0
  49. package/protos/rithmic/request_heartbeat.proto +13 -0
  50. package/protos/rithmic/request_login.proto +28 -0
  51. package/protos/rithmic/request_login_info.proto +10 -0
  52. package/protos/rithmic/request_logout.proto +10 -0
  53. package/protos/rithmic/request_market_data_update.proto +42 -0
  54. package/protos/rithmic/request_new_order.proto +84 -0
  55. package/protos/rithmic/request_pnl_position_snapshot.proto +14 -0
  56. package/protos/rithmic/request_pnl_position_updates.proto +20 -0
  57. package/protos/rithmic/request_product_codes.proto +9 -0
  58. package/protos/rithmic/request_rithmic_system_info.proto +8 -0
  59. package/protos/rithmic/request_show_order_history.proto +16 -0
  60. package/protos/rithmic/request_show_order_history_dates.proto +10 -0
  61. package/protos/rithmic/request_show_order_history_summary.proto +14 -0
  62. package/protos/rithmic/request_show_orders.proto +14 -0
  63. package/protos/rithmic/request_subscribe_for_order_updates.proto +14 -0
  64. package/protos/rithmic/request_tick_bar_replay.proto +48 -0
  65. package/protos/rithmic/request_trade_routes.proto +11 -0
  66. package/protos/rithmic/response_account_list.proto +18 -0
  67. package/protos/rithmic/response_front_month_contract.proto +13 -0
  68. package/protos/rithmic/response_heartbeat.proto +14 -0
  69. package/protos/rithmic/response_login.proto +18 -0
  70. package/protos/rithmic/response_login_info.proto +24 -0
  71. package/protos/rithmic/response_logout.proto +11 -0
  72. package/protos/rithmic/response_market_data_update.proto +9 -0
  73. package/protos/rithmic/response_new_order.proto +18 -0
  74. package/protos/rithmic/response_pnl_position_snapshot.proto +11 -0
  75. package/protos/rithmic/response_pnl_position_updates.proto +11 -0
  76. package/protos/rithmic/response_product_codes.proto +12 -0
  77. package/protos/rithmic/response_rithmic_system_info.proto +12 -0
  78. package/protos/rithmic/response_show_order_history.proto +11 -0
  79. package/protos/rithmic/response_show_order_history_dates.proto +13 -0
  80. package/protos/rithmic/response_show_order_history_summary.proto +11 -0
  81. package/protos/rithmic/response_show_orders.proto +11 -0
  82. package/protos/rithmic/response_subscribe_for_order_updates.proto +11 -0
  83. package/protos/rithmic/response_tick_bar_replay.proto +40 -0
  84. package/protos/rithmic/response_trade_routes.proto +19 -0
  85. package/protos/rithmic/rithmic_order_notification.proto +124 -0
  86. package/src/app.js +136 -89
  87. package/src/config/index.js +27 -8
  88. package/src/config/settings.js +155 -0
  89. package/src/pages/algo/copy-trading.js +293 -200
  90. package/src/pages/algo/one-account.js +1 -1
  91. package/src/security/encryption.js +81 -46
  92. package/src/security/index.js +12 -8
  93. package/src/security/rateLimit.js +68 -65
  94. package/src/security/validation.js +93 -79
  95. package/src/services/hqx-server.js +538 -206
  96. package/src/services/projectx/index.js +327 -204
  97. package/src/services/rithmic/index.js +288 -285
  98. package/src/services/session.js +184 -114
  99. package/src/services/tradovate/index.js +286 -297
  100. package/src/utils/http.js +236 -0
  101. package/src/utils/index.js +11 -2
  102. package/src/utils/logger.js +64 -33
  103. package/src/utils/prompts.js +79 -71
@@ -5,76 +5,87 @@
5
5
 
6
6
  const crypto = require('crypto');
7
7
  const os = require('os');
8
+ const { SECURITY } = require('../config/settings');
8
9
 
9
- // Algorithm configuration
10
- const ALGORITHM = 'aes-256-gcm';
11
- const IV_LENGTH = 16;
12
- const AUTH_TAG_LENGTH = 16;
13
- const SALT_LENGTH = 32;
14
- const KEY_LENGTH = 32;
15
- const ITERATIONS = 100000;
10
+ const {
11
+ ALGORITHM,
12
+ IV_LENGTH,
13
+ SALT_LENGTH,
14
+ KEY_LENGTH,
15
+ PBKDF2_ITERATIONS,
16
+ TOKEN_VISIBLE_CHARS,
17
+ } = SECURITY;
18
+
19
+ /** @type {Buffer|null} Cached machine key */
20
+ let cachedMachineKey = null;
16
21
 
17
22
  /**
18
23
  * Derives a unique machine key from hardware identifiers
19
- * @returns {string} Machine-specific key
24
+ * @returns {Buffer} Machine-specific key (cached)
20
25
  * @private
21
26
  */
22
27
  const getMachineKey = () => {
28
+ if (cachedMachineKey) return cachedMachineKey;
29
+
23
30
  const components = [
24
31
  os.hostname(),
25
32
  os.platform(),
26
33
  os.arch(),
27
- os.cpus()[0]?.model || 'unknown',
34
+ os.cpus()[0]?.model || 'cpu',
28
35
  os.homedir(),
29
- process.env.USER || process.env.USERNAME || 'user'
30
- ];
31
- return crypto.createHash('sha256').update(components.join('|')).digest('hex');
36
+ process.env.USER || process.env.USERNAME || 'user',
37
+ ].join('|');
38
+
39
+ cachedMachineKey = crypto.createHash('sha256').update(components).digest();
40
+ return cachedMachineKey;
32
41
  };
33
42
 
34
43
  /**
35
44
  * Derives encryption key from password and salt using PBKDF2
36
- * @param {string} password - Password to derive key from
45
+ * @param {Buffer|string} password - Password to derive key from
37
46
  * @param {Buffer} salt - Salt for key derivation
38
47
  * @returns {Buffer} Derived key
39
48
  * @private
40
49
  */
41
50
  const deriveKey = (password, salt) => {
42
- return crypto.pbkdf2Sync(password, salt, ITERATIONS, KEY_LENGTH, 'sha512');
51
+ const pwd = Buffer.isBuffer(password) ? password : Buffer.from(password);
52
+ return crypto.pbkdf2Sync(pwd, salt, PBKDF2_ITERATIONS, KEY_LENGTH, 'sha512');
43
53
  };
44
54
 
45
55
  /**
46
56
  * Encrypts data using AES-256-GCM
47
57
  * @param {string} plaintext - Data to encrypt
48
- * @param {string} [password] - Optional password (uses machine key if not provided)
58
+ * @param {Buffer|string} [password] - Optional password (uses machine key if not provided)
49
59
  * @returns {string} Encrypted data as hex string (salt:iv:authTag:ciphertext)
50
60
  */
51
61
  const encrypt = (plaintext, password = null) => {
52
62
  if (!plaintext) return '';
53
63
 
54
- const secret = password || getMachineKey();
64
+ const secret = password ? Buffer.from(password) : getMachineKey();
55
65
  const salt = crypto.randomBytes(SALT_LENGTH);
56
- const key = deriveKey(secret, salt);
57
66
  const iv = crypto.randomBytes(IV_LENGTH);
67
+ const key = deriveKey(secret, salt);
58
68
 
59
69
  const cipher = crypto.createCipheriv(ALGORITHM, key, iv);
60
- let encrypted = cipher.update(plaintext, 'utf8', 'hex');
61
- encrypted += cipher.final('hex');
70
+ const encrypted = Buffer.concat([
71
+ cipher.update(plaintext, 'utf8'),
72
+ cipher.final(),
73
+ ]);
62
74
 
63
75
  const authTag = cipher.getAuthTag();
64
76
 
65
- // Format: salt:iv:authTag:ciphertext (all in hex)
66
77
  return [
67
78
  salt.toString('hex'),
68
79
  iv.toString('hex'),
69
80
  authTag.toString('hex'),
70
- encrypted
81
+ encrypted.toString('hex'),
71
82
  ].join(':');
72
83
  };
73
84
 
74
85
  /**
75
86
  * Decrypts data encrypted with AES-256-GCM
76
87
  * @param {string} encryptedData - Encrypted data as hex string
77
- * @param {string} [password] - Optional password (uses machine key if not provided)
88
+ * @param {Buffer|string} [password] - Optional password (uses machine key if not provided)
78
89
  * @returns {string|null} Decrypted plaintext or null if decryption fails
79
90
  */
80
91
  const decrypt = (encryptedData, password = null) => {
@@ -85,40 +96,41 @@ const decrypt = (encryptedData, password = null) => {
85
96
  if (parts.length !== 4) return null;
86
97
 
87
98
  const [saltHex, ivHex, authTagHex, ciphertext] = parts;
88
-
89
99
  const salt = Buffer.from(saltHex, 'hex');
90
100
  const iv = Buffer.from(ivHex, 'hex');
91
101
  const authTag = Buffer.from(authTagHex, 'hex');
102
+ const encrypted = Buffer.from(ciphertext, 'hex');
92
103
 
93
- const secret = password || getMachineKey();
104
+ const secret = password ? Buffer.from(password) : getMachineKey();
94
105
  const key = deriveKey(secret, salt);
95
106
 
96
107
  const decipher = crypto.createDecipheriv(ALGORITHM, key, iv);
97
108
  decipher.setAuthTag(authTag);
98
109
 
99
- let decrypted = decipher.update(ciphertext, 'hex', 'utf8');
100
- decrypted += decipher.final('utf8');
110
+ const decrypted = Buffer.concat([
111
+ decipher.update(encrypted),
112
+ decipher.final(),
113
+ ]);
101
114
 
102
- return decrypted;
103
- } catch (error) {
104
- // Decryption failed (wrong key, tampered data, etc.)
115
+ return decrypted.toString('utf8');
116
+ } catch {
105
117
  return null;
106
118
  }
107
119
  };
108
120
 
109
121
  /**
110
- * Hashes a password using SHA-512 with salt
122
+ * Hashes a password using PBKDF2-SHA512
111
123
  * @param {string} password - Password to hash
112
124
  * @returns {string} Hashed password (salt:hash in hex)
113
125
  */
114
126
  const hashPassword = (password) => {
115
127
  const salt = crypto.randomBytes(SALT_LENGTH);
116
- const hash = crypto.pbkdf2Sync(password, salt, ITERATIONS, 64, 'sha512');
117
- return salt.toString('hex') + ':' + hash.toString('hex');
128
+ const hash = crypto.pbkdf2Sync(password, salt, PBKDF2_ITERATIONS, 64, 'sha512');
129
+ return `${salt.toString('hex')}:${hash.toString('hex')}`;
118
130
  };
119
131
 
120
132
  /**
121
- * Verifies a password against a hash
133
+ * Verifies a password against a hash using constant-time comparison
122
134
  * @param {string} password - Password to verify
123
135
  * @param {string} storedHash - Stored hash (salt:hash)
124
136
  * @returns {boolean} True if password matches
@@ -126,9 +138,13 @@ const hashPassword = (password) => {
126
138
  const verifyPassword = (password, storedHash) => {
127
139
  try {
128
140
  const [saltHex, hashHex] = storedHash.split(':');
141
+ if (!saltHex || !hashHex) return false;
142
+
129
143
  const salt = Buffer.from(saltHex, 'hex');
130
- const hash = crypto.pbkdf2Sync(password, salt, ITERATIONS, 64, 'sha512');
131
- return hash.toString('hex') === hashHex;
144
+ const storedHashBuffer = Buffer.from(hashHex, 'hex');
145
+ const hash = crypto.pbkdf2Sync(password, salt, PBKDF2_ITERATIONS, 64, 'sha512');
146
+
147
+ return crypto.timingSafeEqual(hash, storedHashBuffer);
132
148
  } catch {
133
149
  return false;
134
150
  }
@@ -139,23 +155,40 @@ const verifyPassword = (password, storedHash) => {
139
155
  * @param {number} [length=32] - Token length in bytes
140
156
  * @returns {string} Random token as hex string
141
157
  */
142
- const generateToken = (length = 32) => {
143
- return crypto.randomBytes(length).toString('hex');
144
- };
158
+ const generateToken = (length = 32) => crypto.randomBytes(length).toString('hex');
145
159
 
146
160
  /**
147
161
  * Masks sensitive data for logging
148
162
  * @param {string} data - Data to mask
149
- * @param {number} [visibleChars=4] - Number of visible characters at start/end
163
+ * @param {number} [visibleChars] - Number of visible characters at start/end
150
164
  * @returns {string} Masked data
151
165
  */
152
- const maskSensitive = (data, visibleChars = 4) => {
153
- if (!data || data.length <= visibleChars * 2) {
154
- return '****';
166
+ const maskSensitive = (data, visibleChars = TOKEN_VISIBLE_CHARS) => {
167
+ if (!data || typeof data !== 'string') return '****';
168
+ if (data.length <= visibleChars * 2) return '****';
169
+
170
+ return `${data.slice(0, visibleChars)}****${data.slice(-visibleChars)}`;
171
+ };
172
+
173
+ /**
174
+ * Securely clears sensitive data from a buffer
175
+ * @param {Buffer} buffer - Buffer to clear
176
+ */
177
+ const secureWipe = (buffer) => {
178
+ if (Buffer.isBuffer(buffer)) {
179
+ crypto.randomFillSync(buffer);
180
+ buffer.fill(0);
181
+ }
182
+ };
183
+
184
+ /**
185
+ * Clears cached machine key (call on logout for extra security)
186
+ */
187
+ const clearCache = () => {
188
+ if (cachedMachineKey) {
189
+ secureWipe(cachedMachineKey);
190
+ cachedMachineKey = null;
155
191
  }
156
- const start = data.substring(0, visibleChars);
157
- const end = data.substring(data.length - visibleChars);
158
- return `${start}****${end}`;
159
192
  };
160
193
 
161
194
  module.exports = {
@@ -164,5 +197,7 @@ module.exports = {
164
197
  hashPassword,
165
198
  verifyPassword,
166
199
  generateToken,
167
- maskSensitive
200
+ maskSensitive,
201
+ secureWipe,
202
+ clearCache,
168
203
  };
@@ -9,7 +9,9 @@ const {
9
9
  hashPassword,
10
10
  verifyPassword,
11
11
  generateToken,
12
- maskSensitive
12
+ maskSensitive,
13
+ secureWipe,
14
+ clearCache,
13
15
  } = require('./encryption');
14
16
 
15
17
  const {
@@ -22,14 +24,14 @@ const {
22
24
  validatePrice,
23
25
  validateSymbol,
24
26
  sanitizeString,
25
- validateObject
27
+ validateObject,
26
28
  } = require('./validation');
27
29
 
28
30
  const {
29
31
  RateLimiter,
30
- rateLimiters,
31
32
  getLimiter,
32
- withRateLimit
33
+ withRateLimit,
34
+ resetAll,
33
35
  } = require('./rateLimit');
34
36
 
35
37
  module.exports = {
@@ -40,7 +42,9 @@ module.exports = {
40
42
  verifyPassword,
41
43
  generateToken,
42
44
  maskSensitive,
43
-
45
+ secureWipe,
46
+ clearCache,
47
+
44
48
  // Validation
45
49
  ValidationError,
46
50
  validateUsername,
@@ -52,10 +56,10 @@ module.exports = {
52
56
  validateSymbol,
53
57
  sanitizeString,
54
58
  validateObject,
55
-
59
+
56
60
  // Rate Limiting
57
61
  RateLimiter,
58
- rateLimiters,
59
62
  getLimiter,
60
- withRateLimit
63
+ withRateLimit,
64
+ resetAll,
61
65
  };
@@ -3,95 +3,101 @@
3
3
  * @module security/rateLimit
4
4
  */
5
5
 
6
+ const { RATE_LIMITS } = require('../config/settings');
7
+
6
8
  /**
7
- * Rate limiter class for controlling request frequency
9
+ * High-performance rate limiter using sliding window
8
10
  */
9
11
  class RateLimiter {
10
12
  /**
11
- * Creates a new rate limiter
12
13
  * @param {Object} options - Rate limiter options
13
14
  * @param {number} [options.maxRequests=60] - Maximum requests per window
14
15
  * @param {number} [options.windowMs=60000] - Time window in milliseconds
15
- * @param {number} [options.minInterval=100] - Minimum interval between requests in ms
16
+ * @param {number} [options.minInterval=100] - Minimum interval between requests
16
17
  */
17
- constructor(options = {}) {
18
- this.maxRequests = options.maxRequests || 60;
19
- this.windowMs = options.windowMs || 60000;
20
- this.minInterval = options.minInterval || 100;
21
- this.requests = [];
18
+ constructor({ maxRequests = 60, windowMs = 60000, minInterval = 100 } = {}) {
19
+ this.maxRequests = maxRequests;
20
+ this.windowMs = windowMs;
21
+ this.minInterval = minInterval;
22
+ this.timestamps = [];
22
23
  this.lastRequest = 0;
23
24
  }
24
25
 
25
26
  /**
26
- * Cleans up old requests outside the current window
27
+ * Removes expired timestamps from the window
27
28
  * @private
28
29
  */
29
- _cleanup() {
30
- const now = Date.now();
31
- const windowStart = now - this.windowMs;
32
- this.requests = this.requests.filter(time => time > windowStart);
30
+ _prune() {
31
+ const cutoff = Date.now() - this.windowMs;
32
+ // Binary search would be overkill for typical request counts
33
+ while (this.timestamps.length && this.timestamps[0] <= cutoff) {
34
+ this.timestamps.shift();
35
+ }
33
36
  }
34
37
 
35
38
  /**
36
- * Checks if a request is allowed
37
- * @returns {boolean} True if request is allowed
39
+ * Checks if a request is allowed without recording it
40
+ * @returns {boolean}
38
41
  */
39
42
  canRequest() {
40
- this._cleanup();
43
+ this._prune();
41
44
  const now = Date.now();
42
45
 
43
- // Check minimum interval
44
46
  if (now - this.lastRequest < this.minInterval) {
45
47
  return false;
46
48
  }
47
49
 
48
- // Check max requests in window
49
- return this.requests.length < this.maxRequests;
50
+ return this.timestamps.length < this.maxRequests;
50
51
  }
51
52
 
52
53
  /**
53
- * Records a request
54
+ * Records a request timestamp
54
55
  */
55
56
  recordRequest() {
56
57
  const now = Date.now();
57
- this.requests.push(now);
58
+ this.timestamps.push(now);
58
59
  this.lastRequest = now;
59
60
  }
60
61
 
61
62
  /**
62
63
  * Gets remaining requests in current window
63
- * @returns {number} Remaining requests
64
+ * @returns {number}
64
65
  */
65
- getRemainingRequests() {
66
- this._cleanup();
67
- return Math.max(0, this.maxRequests - this.requests.length);
66
+ get remaining() {
67
+ this._prune();
68
+ return Math.max(0, this.maxRequests - this.timestamps.length);
68
69
  }
69
70
 
70
71
  /**
71
- * Gets time until rate limit resets
72
- * @returns {number} Milliseconds until reset
72
+ * Gets milliseconds until next slot is available
73
+ * @returns {number}
73
74
  */
74
- getResetTime() {
75
- if (this.requests.length === 0) return 0;
76
- const oldestRequest = Math.min(...this.requests);
77
- return Math.max(0, (oldestRequest + this.windowMs) - Date.now());
75
+ get resetIn() {
76
+ if (!this.timestamps.length) return 0;
77
+ this._prune();
78
+ if (this.timestamps.length < this.maxRequests) return 0;
79
+ return Math.max(0, this.timestamps[0] + this.windowMs - Date.now());
78
80
  }
79
81
 
80
82
  /**
81
- * Waits until a request is allowed
82
- * @returns {Promise<void>} Resolves when request is allowed
83
+ * Waits until a request slot is available
84
+ * @returns {Promise<void>}
83
85
  */
84
86
  async waitForSlot() {
85
87
  while (!this.canRequest()) {
86
- const waitTime = Math.max(this.minInterval, this.getResetTime());
87
- await new Promise(resolve => setTimeout(resolve, Math.min(waitTime, 1000)));
88
+ const waitTime = Math.max(
89
+ this.minInterval - (Date.now() - this.lastRequest),
90
+ Math.min(this.resetIn, 1000)
91
+ );
92
+ await new Promise(r => setTimeout(r, Math.max(1, waitTime)));
88
93
  }
89
94
  }
90
95
 
91
96
  /**
92
97
  * Executes a function with rate limiting
93
- * @param {Function} fn - Function to execute
94
- * @returns {Promise<any>} Result of the function
98
+ * @template T
99
+ * @param {() => Promise<T>} fn - Function to execute
100
+ * @returns {Promise<T>}
95
101
  */
96
102
  async execute(fn) {
97
103
  await this.waitForSlot();
@@ -103,53 +109,50 @@ class RateLimiter {
103
109
  * Resets the rate limiter
104
110
  */
105
111
  reset() {
106
- this.requests = [];
112
+ this.timestamps = [];
107
113
  this.lastRequest = 0;
108
114
  }
109
115
  }
110
116
 
111
- /**
112
- * Creates rate limiters for different API endpoints
113
- */
114
- const rateLimiters = {
115
- // General API calls - 60 per minute
116
- api: new RateLimiter({ maxRequests: 60, windowMs: 60000, minInterval: 100 }),
117
-
118
- // Login attempts - 5 per minute (stricter for security)
119
- login: new RateLimiter({ maxRequests: 5, windowMs: 60000, minInterval: 2000 }),
120
-
121
- // Order placement - 30 per minute
122
- orders: new RateLimiter({ maxRequests: 30, windowMs: 60000, minInterval: 200 }),
123
-
124
- // Data fetching - 120 per minute
125
- data: new RateLimiter({ maxRequests: 120, windowMs: 60000, minInterval: 50 })
126
- };
117
+ /** @type {Map<string, RateLimiter>} */
118
+ const limiters = new Map([
119
+ ['api', new RateLimiter(RATE_LIMITS.API)],
120
+ ['login', new RateLimiter(RATE_LIMITS.LOGIN)],
121
+ ['orders', new RateLimiter(RATE_LIMITS.ORDERS)],
122
+ ['data', new RateLimiter(RATE_LIMITS.DATA)],
123
+ ]);
127
124
 
128
125
  /**
129
126
  * Gets a rate limiter by name
130
127
  * @param {string} name - Rate limiter name
131
- * @returns {RateLimiter} Rate limiter instance
128
+ * @returns {RateLimiter}
132
129
  */
133
- const getLimiter = (name) => {
134
- return rateLimiters[name] || rateLimiters.api;
135
- };
130
+ const getLimiter = (name) => limiters.get(name) || limiters.get('api');
136
131
 
137
132
  /**
138
133
  * Wraps an async function with rate limiting
139
- * @param {Function} fn - Function to wrap
134
+ * @template T
135
+ * @param {(...args: any[]) => Promise<T>} fn - Function to wrap
140
136
  * @param {string} [limiterName='api'] - Rate limiter to use
141
- * @returns {Function} Rate-limited function
137
+ * @returns {(...args: any[]) => Promise<T>}
142
138
  */
143
139
  const withRateLimit = (fn, limiterName = 'api') => {
144
140
  const limiter = getLimiter(limiterName);
145
- return async (...args) => {
146
- return limiter.execute(() => fn(...args));
147
- };
141
+ return (...args) => limiter.execute(() => fn(...args));
142
+ };
143
+
144
+ /**
145
+ * Resets all rate limiters
146
+ */
147
+ const resetAll = () => {
148
+ for (const limiter of limiters.values()) {
149
+ limiter.reset();
150
+ }
148
151
  };
149
152
 
150
153
  module.exports = {
151
154
  RateLimiter,
152
- rateLimiters,
153
155
  getLimiter,
154
- withRateLimit
156
+ withRateLimit,
157
+ resetAll,
155
158
  };