@autonomaai/service-utils 1.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 (49) hide show
  1. package/dist/env.d.ts +7 -0
  2. package/dist/env.d.ts.map +1 -0
  3. package/dist/env.js +12 -0
  4. package/dist/env.js.map +1 -0
  5. package/dist/fastify.d.ts +15 -0
  6. package/dist/fastify.d.ts.map +1 -0
  7. package/dist/fastify.js +57 -0
  8. package/dist/fastify.js.map +1 -0
  9. package/dist/health.d.ts +17 -0
  10. package/dist/health.d.ts.map +1 -0
  11. package/dist/health.js +15 -0
  12. package/dist/health.js.map +1 -0
  13. package/dist/index.d.ts +6 -0
  14. package/dist/index.d.ts.map +1 -0
  15. package/dist/index.js +6 -0
  16. package/dist/index.js.map +1 -0
  17. package/dist/logger.d.ts +8 -0
  18. package/dist/logger.d.ts.map +1 -0
  19. package/dist/logger.js +22 -0
  20. package/dist/logger.js.map +1 -0
  21. package/dist/resilience/bulkhead.d.ts +95 -0
  22. package/dist/resilience/bulkhead.d.ts.map +1 -0
  23. package/dist/resilience/bulkhead.js +186 -0
  24. package/dist/resilience/bulkhead.js.map +1 -0
  25. package/dist/resilience/circuit-breaker.d.ts +111 -0
  26. package/dist/resilience/circuit-breaker.d.ts.map +1 -0
  27. package/dist/resilience/circuit-breaker.js +267 -0
  28. package/dist/resilience/circuit-breaker.js.map +1 -0
  29. package/dist/resilience/index.d.ts +15 -0
  30. package/dist/resilience/index.d.ts.map +1 -0
  31. package/dist/resilience/index.js +15 -0
  32. package/dist/resilience/index.js.map +1 -0
  33. package/dist/resilience/rate-limiter.d.ts +115 -0
  34. package/dist/resilience/rate-limiter.d.ts.map +1 -0
  35. package/dist/resilience/rate-limiter.js +257 -0
  36. package/dist/resilience/rate-limiter.js.map +1 -0
  37. package/dist/resilience/retry.d.ts +63 -0
  38. package/dist/resilience/retry.d.ts.map +1 -0
  39. package/dist/resilience/retry.js +190 -0
  40. package/dist/resilience/retry.js.map +1 -0
  41. package/dist/resilience/timeout.d.ts +62 -0
  42. package/dist/resilience/timeout.d.ts.map +1 -0
  43. package/dist/resilience/timeout.js +135 -0
  44. package/dist/resilience/timeout.js.map +1 -0
  45. package/dist/resilience/types.d.ts +163 -0
  46. package/dist/resilience/types.d.ts.map +1 -0
  47. package/dist/resilience/types.js +64 -0
  48. package/dist/resilience/types.js.map +1 -0
  49. package/package.json +52 -0
@@ -0,0 +1,267 @@
1
+ /**
2
+ * Circuit Breaker Pattern
3
+ *
4
+ * Prevents cascading failures by failing fast when a service is down.
5
+ * After a threshold of failures, the circuit opens and rejects requests
6
+ * for a recovery period before allowing test requests through.
7
+ *
8
+ * States:
9
+ * - CLOSED: Normal operation, requests flow through
10
+ * - OPEN: Service is down, fail immediately
11
+ * - HALF_OPEN: Testing if service recovered
12
+ *
13
+ * @example
14
+ * ```typescript
15
+ * const breaker = new CircuitBreaker({
16
+ * name: 'exchange-api',
17
+ * failureThreshold: 5,
18
+ * recoveryTimeout: 30000,
19
+ * halfOpenMaxCalls: 3
20
+ * });
21
+ *
22
+ * try {
23
+ * const result = await breaker.execute(() => fetchData());
24
+ * } catch (error) {
25
+ * if (error instanceof CircuitBreakerOpenError) {
26
+ * // Use fallback
27
+ * return getCachedData();
28
+ * }
29
+ * throw error;
30
+ * }
31
+ * ```
32
+ */
33
+ import { CircuitState, CircuitBreakerOpenError } from './types.js';
34
+ const DEFAULT_CONFIG = {
35
+ failureThreshold: 5,
36
+ recoveryTimeout: 30000,
37
+ halfOpenMaxCalls: 3,
38
+ failureRateThreshold: 0.5,
39
+ excludedErrors: []
40
+ };
41
+ /**
42
+ * Circuit Breaker implementation
43
+ */
44
+ export class CircuitBreaker {
45
+ static instances = new Map();
46
+ config;
47
+ state = CircuitState.CLOSED;
48
+ failureCount = 0;
49
+ successCount = 0;
50
+ lastFailureTime;
51
+ halfOpenCalls = 0;
52
+ stats = {
53
+ totalCalls: 0,
54
+ successfulCalls: 0,
55
+ failedCalls: 0,
56
+ rejectedCalls: 0,
57
+ state: CircuitState.CLOSED
58
+ };
59
+ constructor(config) {
60
+ this.config = { ...DEFAULT_CONFIG, ...config };
61
+ CircuitBreaker.instances.set(this.config.name, this);
62
+ }
63
+ /**
64
+ * Get current circuit state
65
+ */
66
+ getState() {
67
+ if (this.state === CircuitState.OPEN) {
68
+ if (this.shouldAttemptRecovery()) {
69
+ this.transitionTo(CircuitState.HALF_OPEN);
70
+ this.halfOpenCalls = 0;
71
+ }
72
+ }
73
+ return this.state;
74
+ }
75
+ /**
76
+ * Get circuit breaker statistics
77
+ */
78
+ getStats() {
79
+ return {
80
+ ...this.stats,
81
+ state: this.getState()
82
+ };
83
+ }
84
+ /**
85
+ * Execute a function through the circuit breaker
86
+ */
87
+ async execute(fn) {
88
+ // Wait for any concurrent state transition
89
+ const release = await this.acquireLock();
90
+ try {
91
+ const currentState = this.getState();
92
+ this.stats.totalCalls++;
93
+ if (currentState === CircuitState.OPEN) {
94
+ this.stats.rejectedCalls++;
95
+ const retryAfter = this.config.recoveryTimeout -
96
+ (Date.now() - (this.lastFailureTime || 0));
97
+ throw new CircuitBreakerOpenError(this.config.name, Math.max(0, retryAfter));
98
+ }
99
+ if (currentState === CircuitState.HALF_OPEN) {
100
+ if (this.halfOpenCalls >= this.config.halfOpenMaxCalls) {
101
+ this.stats.rejectedCalls++;
102
+ throw new CircuitBreakerOpenError(this.config.name, this.config.recoveryTimeout);
103
+ }
104
+ this.halfOpenCalls++;
105
+ }
106
+ }
107
+ finally {
108
+ release();
109
+ }
110
+ try {
111
+ const result = await fn();
112
+ await this.onSuccess();
113
+ return result;
114
+ }
115
+ catch (error) {
116
+ // Check if error should be excluded
117
+ if (this.isExcludedError(error)) {
118
+ throw error;
119
+ }
120
+ await this.onFailure();
121
+ throw error;
122
+ }
123
+ }
124
+ /**
125
+ * Manually reset the circuit breaker
126
+ */
127
+ reset() {
128
+ this.transitionTo(CircuitState.CLOSED);
129
+ this.failureCount = 0;
130
+ this.successCount = 0;
131
+ this.halfOpenCalls = 0;
132
+ this.lastFailureTime = undefined;
133
+ console.log(`Circuit breaker '${this.config.name}' manually reset`);
134
+ }
135
+ /**
136
+ * Check if recovery should be attempted
137
+ */
138
+ shouldAttemptRecovery() {
139
+ if (!this.lastFailureTime)
140
+ return true;
141
+ return Date.now() - this.lastFailureTime >= this.config.recoveryTimeout;
142
+ }
143
+ /**
144
+ * Handle successful execution
145
+ */
146
+ async onSuccess() {
147
+ const release = await this.acquireLock();
148
+ try {
149
+ this.successCount++;
150
+ this.stats.successfulCalls++;
151
+ this.stats.lastSuccessTime = Date.now();
152
+ if (this.state === CircuitState.HALF_OPEN) {
153
+ // Recovered - close the circuit
154
+ this.transitionTo(CircuitState.CLOSED);
155
+ this.failureCount = 0;
156
+ console.log(`Circuit breaker '${this.config.name}' recovered -> CLOSED`);
157
+ }
158
+ }
159
+ finally {
160
+ release();
161
+ }
162
+ }
163
+ /**
164
+ * Handle failed execution
165
+ */
166
+ async onFailure() {
167
+ const release = await this.acquireLock();
168
+ try {
169
+ this.failureCount++;
170
+ this.stats.failedCalls++;
171
+ this.stats.lastFailureTime = Date.now();
172
+ this.lastFailureTime = Date.now();
173
+ if (this.state === CircuitState.HALF_OPEN) {
174
+ // Failed during recovery - open again
175
+ this.transitionTo(CircuitState.OPEN);
176
+ console.warn(`Circuit breaker '${this.config.name}' failed during recovery -> OPEN`);
177
+ }
178
+ else if (this.state === CircuitState.CLOSED) {
179
+ if (this.failureCount >= this.config.failureThreshold) {
180
+ this.transitionTo(CircuitState.OPEN);
181
+ console.warn(`Circuit breaker '${this.config.name}' threshold reached -> OPEN`);
182
+ }
183
+ }
184
+ }
185
+ finally {
186
+ release();
187
+ }
188
+ }
189
+ /**
190
+ * Transition to a new state
191
+ */
192
+ transitionTo(newState) {
193
+ const oldState = this.state;
194
+ this.state = newState;
195
+ this.stats.state = newState;
196
+ // Call callbacks
197
+ switch (newState) {
198
+ case CircuitState.OPEN:
199
+ this.config.onOpen?.();
200
+ break;
201
+ case CircuitState.CLOSED:
202
+ this.config.onClose?.();
203
+ break;
204
+ case CircuitState.HALF_OPEN:
205
+ this.config.onHalfOpen?.();
206
+ break;
207
+ }
208
+ }
209
+ /**
210
+ * Check if error should be excluded from failure count
211
+ */
212
+ isExcludedError(error) {
213
+ if (!this.config.excludedErrors?.length)
214
+ return false;
215
+ return this.config.excludedErrors.some(ErrorClass => error instanceof ErrorClass);
216
+ }
217
+ /**
218
+ * Lock mechanism using a promise-based queue for atomic operations
219
+ */
220
+ lockQueue = Promise.resolve();
221
+ async acquireLock() {
222
+ let release = () => { };
223
+ const previousLock = this.lockQueue;
224
+ this.lockQueue = new Promise(resolve => {
225
+ release = resolve;
226
+ });
227
+ await previousLock;
228
+ return release;
229
+ }
230
+ /**
231
+ * Get stats for all circuit breakers (for monitoring)
232
+ */
233
+ static getAllStats() {
234
+ const stats = new Map();
235
+ for (const [name, breaker] of this.instances) {
236
+ stats.set(name, breaker.getStats());
237
+ }
238
+ return stats;
239
+ }
240
+ /**
241
+ * Get a circuit breaker by name
242
+ */
243
+ static get(name) {
244
+ return this.instances.get(name);
245
+ }
246
+ }
247
+ /**
248
+ * Decorator to apply circuit breaker to a method
249
+ */
250
+ export function withCircuitBreaker(config) {
251
+ const breaker = new CircuitBreaker(config);
252
+ return function (target, propertyKey, descriptor) {
253
+ const originalMethod = descriptor.value;
254
+ descriptor.value = async function (...args) {
255
+ return breaker.execute(() => originalMethod.apply(this, args));
256
+ };
257
+ return descriptor;
258
+ };
259
+ }
260
+ /**
261
+ * Create a circuit breaker wrapper function
262
+ */
263
+ export function createCircuitBreaker(config) {
264
+ const breaker = new CircuitBreaker(config);
265
+ return (fn) => breaker.execute(fn);
266
+ }
267
+ //# sourceMappingURL=circuit-breaker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"circuit-breaker.js","sourceRoot":"","sources":["../../src/resilience/circuit-breaker.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Resilience Patterns for TypeScript Services
3
+ *
4
+ * Provides circuit breaker, retry, bulkhead, rate limiting, and timeout patterns
5
+ * for building resilient microservices.
6
+ *
7
+ * @module @autonoma/service-utils/resilience
8
+ */
9
+ export * from './circuit-breaker.js';
10
+ export * from './retry.js';
11
+ export * from './bulkhead.js';
12
+ export * from './rate-limiter.js';
13
+ export * from './timeout.js';
14
+ export * from './types.js';
15
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/resilience/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,cAAc,sBAAsB,CAAC;AACrC,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC;AAC9B,cAAc,mBAAmB,CAAC;AAClC,cAAc,cAAc,CAAC;AAC7B,cAAc,YAAY,CAAC"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Resilience Patterns for TypeScript Services
3
+ *
4
+ * Provides circuit breaker, retry, bulkhead, rate limiting, and timeout patterns
5
+ * for building resilient microservices.
6
+ *
7
+ * @module @autonoma/service-utils/resilience
8
+ */
9
+ export * from './circuit-breaker.js';
10
+ export * from './retry.js';
11
+ export * from './bulkhead.js';
12
+ export * from './rate-limiter.js';
13
+ export * from './timeout.js';
14
+ export * from './types.js';
15
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/resilience/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,cAAc,sBAAsB,CAAC;AACrC,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC;AAC9B,cAAc,mBAAmB,CAAC;AAClC,cAAc,cAAc,CAAC;AAC7B,cAAc,YAAY,CAAC"}
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Rate Limiter Pattern
3
+ *
4
+ * Controls the rate of requests to prevent overload.
5
+ * Uses a sliding window algorithm for accurate rate limiting.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * const limiter = new RateLimiter({
10
+ * name: 'trading-api',
11
+ * windowMs: 1000,
12
+ * maxRequests: 10
13
+ * });
14
+ *
15
+ * try {
16
+ * await limiter.acquire();
17
+ * const result = await executeTrade();
18
+ * } catch (error) {
19
+ * if (error instanceof RateLimitExceededError) {
20
+ * // Wait and retry
21
+ * }
22
+ * }
23
+ * ```
24
+ */
25
+ import { RateLimiterConfig } from './types.js';
26
+ /**
27
+ * Rate Limiter using token bucket algorithm
28
+ */
29
+ export declare class RateLimiter {
30
+ private static instances;
31
+ private readonly config;
32
+ private buckets;
33
+ private stats;
34
+ constructor(config: Partial<RateLimiterConfig> & {
35
+ name: string;
36
+ });
37
+ /**
38
+ * Acquire a token (throws if rate limit exceeded)
39
+ */
40
+ acquire(context?: any): Promise<void>;
41
+ /**
42
+ * Try to acquire a token (returns false if rate limit exceeded)
43
+ */
44
+ tryAcquire(context?: any): boolean;
45
+ /**
46
+ * Execute a function with rate limiting
47
+ */
48
+ execute<T>(fn: () => Promise<T>, context?: any): Promise<T>;
49
+ /**
50
+ * Get remaining tokens for a key
51
+ */
52
+ getRemainingTokens(context?: any): number;
53
+ /**
54
+ * Get rate limiter statistics
55
+ */
56
+ getStats(): {
57
+ totalRequests: number;
58
+ allowedRequests: number;
59
+ rejectedRequests: number;
60
+ };
61
+ /**
62
+ * Reset rate limiter
63
+ */
64
+ reset(context?: any): void;
65
+ /**
66
+ * Get or create bucket for a key
67
+ */
68
+ private getOrCreateBucket;
69
+ /**
70
+ * Refill bucket based on elapsed time
71
+ */
72
+ private refillBucket;
73
+ /**
74
+ * Calculate when next token will be available
75
+ */
76
+ private calculateRetryAfter;
77
+ /**
78
+ * Get key for rate limiting
79
+ */
80
+ private getKey;
81
+ /**
82
+ * Get stats for all rate limiters
83
+ */
84
+ static getAllStats(): Map<string, ReturnType<RateLimiter['getStats']>>;
85
+ /**
86
+ * Get a rate limiter by name
87
+ */
88
+ static get(name: string): RateLimiter | undefined;
89
+ }
90
+ /**
91
+ * Decorator to apply rate limiting to a method
92
+ */
93
+ export declare function withRateLimit(config: Partial<RateLimiterConfig> & {
94
+ name: string;
95
+ }): <T extends (...args: any[]) => Promise<any>>(target: any, propertyKey: string, descriptor: TypedPropertyDescriptor<T>) => TypedPropertyDescriptor<T>;
96
+ /**
97
+ * Create a rate limiter wrapper function
98
+ */
99
+ export declare function createRateLimiter(config: Partial<RateLimiterConfig> & {
100
+ name: string;
101
+ }): <T>(fn: () => Promise<T>, context?: any) => Promise<T>;
102
+ /**
103
+ * Rate limiter presets for common use cases
104
+ */
105
+ export declare const rateLimiterPresets: {
106
+ /** Standard API rate limiting */
107
+ standard: Partial<RateLimiterConfig>;
108
+ /** Strict rate limiting for trading operations */
109
+ trading: Partial<RateLimiterConfig>;
110
+ /** Relaxed rate limiting for read operations */
111
+ relaxed: Partial<RateLimiterConfig>;
112
+ /** Per-user rate limiting */
113
+ perUser: Partial<RateLimiterConfig>;
114
+ };
115
+ //# sourceMappingURL=rate-limiter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rate-limiter.d.ts","sourceRoot":"","sources":["../../src/resilience/rate-limiter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAAE,iBAAiB,EAA0B,MAAM,YAAY,CAAC;AAcvE;;GAEG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,MAAM,CAAC,SAAS,CAAuC;IAE/D,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAuC;IAC9D,OAAO,CAAC,OAAO,CAAuC;IAEtD,OAAO,CAAC,KAAK,CAIX;gBAEU,MAAM,EAAE,OAAO,CAAC,iBAAiB,CAAC,GAAG;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE;IAKjE;;OAEG;IACG,OAAO,CAAC,OAAO,CAAC,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC;IAoB3C;;OAEG;IACH,UAAU,CAAC,OAAO,CAAC,EAAE,GAAG,GAAG,OAAO;IAkBlC;;OAEG;IACG,OAAO,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,EAAE,GAAG,GAAG,OAAO,CAAC,CAAC,CAAC;IAWjE;;OAEG;IACH,kBAAkB,CAAC,OAAO,CAAC,EAAE,GAAG,GAAG,MAAM;IASzC;;OAEG;IACH,QAAQ;;;;;IAIR;;OAEG;IACH,KAAK,CAAC,OAAO,CAAC,EAAE,GAAG,GAAG,IAAI;IAS1B;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAYzB;;OAEG;IACH,OAAO,CAAC,YAAY;IAWpB;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAO3B;;OAEG;IACH,OAAO,CAAC,MAAM;IA2Bd;;OAEG;IACH,MAAM,CAAC,WAAW,IAAI,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,WAAW,CAAC,UAAU,CAAC,CAAC,CAAC;IAQtE;;OAEG;IACH,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS;CAGlD;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,OAAO,CAAC,iBAAiB,CAAC,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,IAGhE,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,OAAO,CAAC,GAAG,CAAC,EACzD,QAAQ,GAAG,EACX,aAAa,MAAM,EACnB,YAAY,uBAAuB,CAAC,CAAC,CAAC,gCAUzC;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,OAAO,CAAC,iBAAiB,CAAC,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GACpD,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,EAAE,GAAG,KAAK,OAAO,CAAC,CAAC,CAAC,CAGxD;AAED;;GAEG;AACH,eAAO,MAAM,kBAAkB;IAC7B,iCAAiC;cAI5B,OAAO,CAAC,iBAAiB,CAAC;IAE/B,kDAAkD;aAI7C,OAAO,CAAC,iBAAiB,CAAC;IAE/B,gDAAgD;aAI3C,OAAO,CAAC,iBAAiB,CAAC;IAE/B,6BAA6B;aAKxB,OAAO,CAAC,iBAAiB,CAAC;CAChC,CAAC"}
@@ -0,0 +1,257 @@
1
+ /**
2
+ * Rate Limiter Pattern
3
+ *
4
+ * Controls the rate of requests to prevent overload.
5
+ * Uses a sliding window algorithm for accurate rate limiting.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * const limiter = new RateLimiter({
10
+ * name: 'trading-api',
11
+ * windowMs: 1000,
12
+ * maxRequests: 10
13
+ * });
14
+ *
15
+ * try {
16
+ * await limiter.acquire();
17
+ * const result = await executeTrade();
18
+ * } catch (error) {
19
+ * if (error instanceof RateLimitExceededError) {
20
+ * // Wait and retry
21
+ * }
22
+ * }
23
+ * ```
24
+ */
25
+ import { RateLimitExceededError } from './types.js';
26
+ const DEFAULT_CONFIG = {
27
+ windowMs: 60000,
28
+ maxRequests: 100,
29
+ skipSuccessfulRequests: false,
30
+ skipFailedRequests: false
31
+ };
32
+ /**
33
+ * Rate Limiter using token bucket algorithm
34
+ */
35
+ export class RateLimiter {
36
+ static instances = new Map();
37
+ config;
38
+ buckets = new Map();
39
+ stats = {
40
+ totalRequests: 0,
41
+ allowedRequests: 0,
42
+ rejectedRequests: 0
43
+ };
44
+ constructor(config) {
45
+ this.config = { ...DEFAULT_CONFIG, ...config };
46
+ RateLimiter.instances.set(this.config.name, this);
47
+ }
48
+ /**
49
+ * Acquire a token (throws if rate limit exceeded)
50
+ */
51
+ async acquire(context) {
52
+ const key = this.getKey(context);
53
+ this.stats.totalRequests++;
54
+ const bucket = this.getOrCreateBucket(key);
55
+ this.refillBucket(bucket);
56
+ if (bucket.tokens < 1) {
57
+ this.stats.rejectedRequests++;
58
+ this.config.onLimitReached?.(key);
59
+ // Calculate retry after
60
+ const retryAfter = this.calculateRetryAfter(bucket);
61
+ throw new RateLimitExceededError(key, retryAfter);
62
+ }
63
+ bucket.tokens -= 1;
64
+ this.stats.allowedRequests++;
65
+ }
66
+ /**
67
+ * Try to acquire a token (returns false if rate limit exceeded)
68
+ */
69
+ tryAcquire(context) {
70
+ try {
71
+ const key = this.getKey(context);
72
+ const bucket = this.getOrCreateBucket(key);
73
+ this.refillBucket(bucket);
74
+ if (bucket.tokens < 1) {
75
+ return false;
76
+ }
77
+ bucket.tokens -= 1;
78
+ this.stats.allowedRequests++;
79
+ return true;
80
+ }
81
+ catch {
82
+ return false;
83
+ }
84
+ }
85
+ /**
86
+ * Execute a function with rate limiting
87
+ */
88
+ async execute(fn, context) {
89
+ await this.acquire(context);
90
+ try {
91
+ const result = await fn();
92
+ return result;
93
+ }
94
+ catch (error) {
95
+ throw error;
96
+ }
97
+ }
98
+ /**
99
+ * Get remaining tokens for a key
100
+ */
101
+ getRemainingTokens(context) {
102
+ const key = this.getKey(context);
103
+ const bucket = this.buckets.get(key);
104
+ if (!bucket)
105
+ return this.config.maxRequests;
106
+ this.refillBucket(bucket);
107
+ return Math.floor(bucket.tokens);
108
+ }
109
+ /**
110
+ * Get rate limiter statistics
111
+ */
112
+ getStats() {
113
+ return { ...this.stats };
114
+ }
115
+ /**
116
+ * Reset rate limiter
117
+ */
118
+ reset(context) {
119
+ if (context) {
120
+ const key = this.getKey(context);
121
+ this.buckets.delete(key);
122
+ }
123
+ else {
124
+ this.buckets.clear();
125
+ }
126
+ }
127
+ /**
128
+ * Get or create bucket for a key
129
+ */
130
+ getOrCreateBucket(key) {
131
+ let bucket = this.buckets.get(key);
132
+ if (!bucket) {
133
+ bucket = {
134
+ tokens: this.config.maxRequests,
135
+ lastRefill: Date.now()
136
+ };
137
+ this.buckets.set(key, bucket);
138
+ }
139
+ return bucket;
140
+ }
141
+ /**
142
+ * Refill bucket based on elapsed time
143
+ */
144
+ refillBucket(bucket) {
145
+ const now = Date.now();
146
+ const elapsed = now - bucket.lastRefill;
147
+ // Calculate tokens to add based on elapsed time
148
+ const tokensToAdd = (elapsed / this.config.windowMs) * this.config.maxRequests;
149
+ bucket.tokens = Math.min(this.config.maxRequests, bucket.tokens + tokensToAdd);
150
+ bucket.lastRefill = now;
151
+ }
152
+ /**
153
+ * Calculate when next token will be available
154
+ */
155
+ calculateRetryAfter(bucket) {
156
+ // Time until one token is refilled
157
+ const tokenRefillTime = this.config.windowMs / this.config.maxRequests;
158
+ const tokenDeficit = 1 - bucket.tokens;
159
+ return Math.ceil(tokenDeficit * tokenRefillTime);
160
+ }
161
+ /**
162
+ * Get key for rate limiting
163
+ */
164
+ getKey(context) {
165
+ if (!context)
166
+ return 'default';
167
+ if (this.config.keyGenerator) {
168
+ return this.config.keyGenerator(context);
169
+ }
170
+ if (typeof context === 'string')
171
+ return context;
172
+ if (typeof context === 'number')
173
+ return String(context);
174
+ // For objects, try to extract common identifiers
175
+ if (typeof context === 'object' && context !== null) {
176
+ // Try common identifier properties
177
+ if (context.id)
178
+ return String(context.id);
179
+ if (context.userId)
180
+ return String(context.userId);
181
+ if (context.key)
182
+ return String(context.key);
183
+ // Safely stringify, handling circular references
184
+ try {
185
+ return JSON.stringify(context);
186
+ }
187
+ catch {
188
+ // Fallback to object hash if stringify fails
189
+ return `object_${Object.keys(context).sort().join('_')}`;
190
+ }
191
+ }
192
+ return 'default';
193
+ }
194
+ /**
195
+ * Get stats for all rate limiters
196
+ */
197
+ static getAllStats() {
198
+ const stats = new Map();
199
+ for (const [name, limiter] of this.instances) {
200
+ stats.set(name, limiter.getStats());
201
+ }
202
+ return stats;
203
+ }
204
+ /**
205
+ * Get a rate limiter by name
206
+ */
207
+ static get(name) {
208
+ return this.instances.get(name);
209
+ }
210
+ }
211
+ /**
212
+ * Decorator to apply rate limiting to a method
213
+ */
214
+ export function withRateLimit(config) {
215
+ const limiter = new RateLimiter(config);
216
+ return function (target, propertyKey, descriptor) {
217
+ const originalMethod = descriptor.value;
218
+ descriptor.value = async function (...args) {
219
+ return limiter.execute(() => originalMethod.apply(this, args));
220
+ };
221
+ return descriptor;
222
+ };
223
+ }
224
+ /**
225
+ * Create a rate limiter wrapper function
226
+ */
227
+ export function createRateLimiter(config) {
228
+ const limiter = new RateLimiter(config);
229
+ return (fn, context) => limiter.execute(fn, context);
230
+ }
231
+ /**
232
+ * Rate limiter presets for common use cases
233
+ */
234
+ export const rateLimiterPresets = {
235
+ /** Standard API rate limiting */
236
+ standard: {
237
+ windowMs: 60000,
238
+ maxRequests: 100
239
+ },
240
+ /** Strict rate limiting for trading operations */
241
+ trading: {
242
+ windowMs: 1000,
243
+ maxRequests: 10
244
+ },
245
+ /** Relaxed rate limiting for read operations */
246
+ relaxed: {
247
+ windowMs: 60000,
248
+ maxRequests: 1000
249
+ },
250
+ /** Per-user rate limiting */
251
+ perUser: {
252
+ windowMs: 60000,
253
+ maxRequests: 60,
254
+ keyGenerator: (context) => context.userId || 'anonymous'
255
+ }
256
+ };
257
+ //# sourceMappingURL=rate-limiter.js.map