@push.rocks/smartproxy 19.5.3 → 19.5.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.
Files changed (42) hide show
  1. package/dist_ts/core/utils/async-utils.d.ts +81 -0
  2. package/dist_ts/core/utils/async-utils.js +216 -0
  3. package/dist_ts/core/utils/binary-heap.d.ts +73 -0
  4. package/dist_ts/core/utils/binary-heap.js +193 -0
  5. package/dist_ts/core/utils/enhanced-connection-pool.d.ts +110 -0
  6. package/dist_ts/core/utils/enhanced-connection-pool.js +320 -0
  7. package/dist_ts/core/utils/fs-utils.d.ts +144 -0
  8. package/dist_ts/core/utils/fs-utils.js +252 -0
  9. package/dist_ts/core/utils/index.d.ts +5 -2
  10. package/dist_ts/core/utils/index.js +6 -3
  11. package/dist_ts/core/utils/lifecycle-component.d.ts +59 -0
  12. package/dist_ts/core/utils/lifecycle-component.js +195 -0
  13. package/dist_ts/plugins.d.ts +2 -1
  14. package/dist_ts/plugins.js +3 -2
  15. package/dist_ts/proxies/http-proxy/certificate-manager.d.ts +15 -0
  16. package/dist_ts/proxies/http-proxy/certificate-manager.js +49 -2
  17. package/dist_ts/proxies/nftables-proxy/nftables-proxy.d.ts +10 -0
  18. package/dist_ts/proxies/nftables-proxy/nftables-proxy.js +53 -43
  19. package/dist_ts/proxies/smart-proxy/cert-store.js +22 -20
  20. package/dist_ts/proxies/smart-proxy/connection-manager.d.ts +37 -7
  21. package/dist_ts/proxies/smart-proxy/connection-manager.js +257 -180
  22. package/package.json +2 -2
  23. package/readme.hints.md +96 -1
  24. package/readme.md +515 -301
  25. package/readme.plan.md +1135 -221
  26. package/readme.problems.md +167 -83
  27. package/ts/core/utils/async-utils.ts +275 -0
  28. package/ts/core/utils/binary-heap.ts +225 -0
  29. package/ts/core/utils/enhanced-connection-pool.ts +420 -0
  30. package/ts/core/utils/fs-utils.ts +270 -0
  31. package/ts/core/utils/index.ts +5 -2
  32. package/ts/core/utils/lifecycle-component.ts +231 -0
  33. package/ts/plugins.ts +2 -1
  34. package/ts/proxies/http-proxy/certificate-manager.ts +52 -1
  35. package/ts/proxies/nftables-proxy/nftables-proxy.ts +64 -79
  36. package/ts/proxies/smart-proxy/cert-store.ts +26 -20
  37. package/ts/proxies/smart-proxy/connection-manager.ts +291 -189
  38. package/readme.plan2.md +0 -764
  39. package/ts/common/eventUtils.ts +0 -34
  40. package/ts/common/types.ts +0 -91
  41. package/ts/core/utils/event-system.ts +0 -376
  42. package/ts/core/utils/event-utils.ts +0 -25
@@ -1,86 +1,170 @@
1
- # SmartProxy Module Problems
2
-
3
- Based on test analysis, the following potential issues have been identified in the SmartProxy module:
4
-
5
- ## 1. HttpProxy Route Configuration Issue
6
- **Location**: `ts/proxies/http-proxy/http-proxy.ts:380`
7
- **Problem**: The HttpProxy is trying to read the 'type' property of an undefined object when updating route configurations.
8
- **Evidence**: `test.http-forwarding-fix.ts` fails with:
9
- ```
10
- TypeError: Cannot read properties of undefined (reading 'type')
11
- at HttpProxy.updateRouteConfigs (/mnt/data/lossless/push.rocks/smartproxy/ts/proxies/http-proxy/http-proxy.ts:380:24)
12
- ```
13
- **Impact**: Routes with `useHttpProxy` configuration may not work properly.
14
-
15
- ## 2. Connection Forwarding Issues
16
- **Problem**: Basic TCP forwarding appears to not be working correctly after the simplification to just 'forward' and 'socket-handler' action types.
17
- **Evidence**: Multiple forwarding tests timeout waiting for data to be forwarded:
18
- - `test.forwarding-fix-verification.ts` - times out waiting for forwarded data
19
- - `test.connection-forwarding.ts` - times out on SNI-based forwarding
20
- **Impact**: The 'forward' action type may not be properly forwarding connections to target servers.
21
-
22
- ## 3. Missing Certificate Manager Methods
23
- **Problem**: Tests expect `provisionAllCertificates` method on certificate manager but it may not exist or may not be properly initialized.
24
- **Evidence**: Multiple tests fail with "this.certManager.provisionAllCertificates is not a function"
25
- **Impact**: Certificate provisioning may not work as expected.
26
-
27
- ## 4. Route Update Mechanism
28
- **Problem**: The route update mechanism may have issues preserving certificate manager callbacks and other state.
29
- **Evidence**: Tests specifically designed to verify callback preservation after route updates.
30
- **Impact**: Dynamic route updates might break certificate management functionality.
31
-
32
- ## 5. Route-Specific Security Not Fully Implemented
33
- **Problem**: While the route definitions support security configurations (ipAllowList, ipBlockList, authentication), these are not being enforced at the route level.
34
- **Evidence**:
35
- - SecurityManager has methods like `isIPAuthorized` for route-specific security
36
- - Route connection handler only checks global IP validation, not route-specific security rules
37
- - No evidence of route.action.security being checked when handling connections
38
- **Impact**: Route-specific security rules defined in configuration are not enforced, potentially allowing unauthorized access.
39
- **Status**: FIXED - Route-specific IP allow/block lists are now enforced when a route is matched. Authentication is logged as not enforceable for non-terminated connections.
40
- **Additional Fix**: Removed security checks from route matching logic - security is now properly enforced AFTER a route is matched, not during matching.
41
-
42
- ## 6. Security Property Location Consolidation
43
- **Problem**: Security was defined in two places - route.security and route.action.security - causing confusion.
44
- **Status**: ✅ FIXED - Consolidated to only route.security. Removed action.security from types and updated all references.
1
+ # SmartProxy Performance Issues Report
2
+
3
+ ## Executive Summary
4
+ This report identifies performance issues and blocking operations in the SmartProxy codebase that could impact scalability and responsiveness under high load.
5
+
6
+ ## Critical Issues
7
+
8
+ ### 1. **Synchronous Filesystem Operations**
9
+ These operations block the event loop and should be replaced with async alternatives:
10
+
11
+ #### Certificate Management
12
+ - `ts/proxies/http-proxy/certificate-manager.ts:29`: `fs.existsSync()`
13
+ - `ts/proxies/http-proxy/certificate-manager.ts:30`: `fs.mkdirSync()`
14
+ - `ts/proxies/http-proxy/certificate-manager.ts:49-50`: `fs.readFileSync()` for loading certificates
15
+
16
+ #### NFTables Proxy
17
+ - `ts/proxies/nftables-proxy/nftables-proxy.ts`: Multiple uses of `execSync()` for system commands
18
+ - `ts/proxies/nftables-proxy/nftables-proxy.ts`: Multiple `fs.writeFileSync()` and `fs.unlinkSync()` operations
19
+
20
+ #### Certificate Store
21
+ - `ts/proxies/smart-proxy/cert-store.ts:8`: `ensureDirSync()`
22
+ - `ts/proxies/smart-proxy/cert-store.ts:15,31,76`: `fileExistsSync()`
23
+ - `ts/proxies/smart-proxy/cert-store.ts:77`: `removeManySync()`
24
+
25
+ ### 2. **Event Loop Blocking Operations**
26
+
27
+ #### Busy Wait Loop
28
+ - `ts/proxies/nftables-proxy/nftables-proxy.ts:235-238`:
29
+ ```typescript
30
+ const waitUntil = Date.now() + retryDelayMs;
31
+ while (Date.now() < waitUntil) {
32
+ // busy wait - blocks event loop completely
33
+ }
34
+ ```
35
+ This is extremely problematic as it blocks the entire Node.js event loop.
36
+
37
+ ### 3. **Potential Memory Leaks**
38
+
39
+ #### Timer Management Issues
40
+ Several timers are created without proper cleanup:
41
+ - `ts/proxies/http-proxy/function-cache.ts`: `setInterval()` without storing reference for cleanup
42
+ - `ts/proxies/http-proxy/request-handler.ts`: `setInterval()` for rate limit cleanup without cleanup
43
+ - `ts/core/utils/shared-security-manager.ts`: `cleanupInterval` stored but no cleanup method
44
+
45
+ #### Event Listener Accumulation
46
+ - Multiple instances of event listeners being added without corresponding cleanup
47
+ - Connection handlers add listeners without always removing them on connection close
48
+
49
+ ### 4. **Connection Pool Management**
50
+
51
+ #### ConnectionPool (ts/proxies/http-proxy/connection-pool.ts)
52
+ **Good practices observed:**
53
+ - Proper connection lifecycle management
54
+ - Periodic cleanup of idle connections
55
+ - Connection limits enforcement
56
+
57
+ **Potential issues:**
58
+ - No backpressure mechanism when pool is full
59
+ - Synchronous sorting operation in `cleanupConnectionPool()` could be slow with many connections
60
+
61
+ ### 5. **Resource Management Issues**
62
+
63
+ #### Socket Cleanup
64
+ - Some error paths don't properly clean up sockets
65
+ - Missing `removeAllListeners()` in some error scenarios could lead to memory leaks
66
+
67
+ #### Timeout Management
68
+ - Inconsistent timeout handling across different components
69
+ - Some sockets created without timeout settings
70
+
71
+ ### 6. **JSON Operations on Large Objects**
72
+ - `ts/proxies/smart-proxy/cert-store.ts:21`: `JSON.parse()` on certificate metadata
73
+ - `ts/proxies/smart-proxy/cert-store.ts:71`: `JSON.stringify()` with pretty printing
74
+ - `ts/proxies/http-proxy/function-cache.ts:76`: `JSON.stringify()` for cache keys (called frequently)
45
75
 
46
76
  ## Recommendations
47
77
 
48
- 1. **Verify Forward Action Implementation**: Check that the 'forward' action type properly establishes bidirectional data flow between client and target server. ✅ FIXED - Basic forwarding now works correctly.
49
-
50
- 2. **Fix HttpProxy Route Handling**: Ensure that route objects passed to HttpProxy.updateRouteConfigs have the expected structure with all required properties. ✅ FIXED - Routes now preserve their structure.
51
-
52
- 3. **Review Certificate Manager API**: Ensure all expected methods exist and are properly documented.
53
-
54
- 4. **Add Integration Tests**: Many unit tests are testing internal implementation details. Consider adding more integration tests that test the public API.
55
-
56
- 5. **Implement Route-Specific Security**: Add security checks when a route is matched to enforce route-specific IP allow/block lists and authentication rules. ✅ FIXED - IP allow/block lists are now enforced at the route level.
57
-
58
- 6. **Fix TLS Detection Logic**: The connection handler was treating all connections as TLS. This has been partially fixed but needs proper testing for all TLS modes.
59
-
60
- ## 7. HTTP Domain Matching Issue
61
- **Problem**: Routes with domain restrictions fail to match HTTP connections because domain information is only available after HTTP headers are received, but route matching happens immediately upon connection.
62
- **Evidence**:
63
- - `test.http-port8080-forwarding.ts` - "No route found for connection on port 8080" despite having a matching route
64
- - HTTP connections provide domain info via the Host header, which arrives after the initial TCP connection
65
- - Route matching in `handleInitialData` happens before HTTP headers are parsed
66
- **Impact**: HTTP routes with domain restrictions cannot be matched, forcing users to remove domain restrictions for HTTP routes.
67
- **Root Cause**: For non-TLS connections, SmartProxy attempts to match routes immediately, but the domain information needed for matching is only available after parsing HTTP headers.
68
- **Status**: ✅ FIXED - Added skipDomainCheck parameter to route matching for HTTP proxy ports. When a port is configured with useHttpProxy and the connection is not TLS, domain validation is skipped at the initial route matching stage, allowing the HttpProxy to handle domain-based routing after headers are received.
69
-
70
- ## 8. HttpProxy Plain HTTP Forwarding Issue
71
- **Problem**: HttpProxy is an HTTPS server but SmartProxy forwards plain HTTP connections to it via `useHttpProxy` configuration.
72
- **Evidence**:
73
- - `test.http-port8080-forwarding.ts` - Connection immediately closed after forwarding to HttpProxy
74
- - HttpProxy is created with `http2.createSecureServer` expecting TLS connections
75
- - SmartProxy forwards raw HTTP data to HttpProxy's HTTPS port
76
- **Impact**: Plain HTTP connections cannot be handled by HttpProxy, despite `useHttpProxy` configuration suggesting this should work.
77
- **Root Cause**: Design mismatch - HttpProxy is designed for HTTPS/TLS termination, not plain HTTP forwarding.
78
- **Status**: Documented. The `useHttpProxy` configuration should only be used for ports that receive TLS connections requiring termination. For plain HTTP forwarding, use direct forwarding without HttpProxy.
79
-
80
- ## 9. Route Security Configuration Location Issue
81
- **Problem**: Tests were placing security configuration in `route.action.security` instead of `route.security`.
82
- **Evidence**:
83
- - `test.route-security.ts` - IP block list test failing because security was in wrong location
84
- - IRouteConfig interface defines security at route level, not inside action
85
- **Impact**: Security rules defined in action.security were ignored, causing tests to fail.
86
- **Status**: ✅ FIXED - Updated tests to place security configuration at the correct location (route.security).
78
+ ### Immediate Actions (High Priority)
79
+
80
+ 1. **Replace Synchronous Operations**
81
+ ```typescript
82
+ // Instead of:
83
+ if (fs.existsSync(path)) { ... }
84
+
85
+ // Use:
86
+ try {
87
+ await fs.promises.access(path);
88
+ // file exists
89
+ } catch {
90
+ // file doesn't exist
91
+ }
92
+ ```
93
+
94
+ 2. **Fix Busy Wait Loop**
95
+ ```typescript
96
+ // Instead of:
97
+ while (Date.now() < waitUntil) { }
98
+
99
+ // Use:
100
+ await new Promise(resolve => setTimeout(resolve, retryDelayMs));
101
+ ```
102
+
103
+ 3. **Add Timer Cleanup**
104
+ ```typescript
105
+ class Component {
106
+ private cleanupTimer?: NodeJS.Timeout;
107
+
108
+ start() {
109
+ this.cleanupTimer = setInterval(() => { ... }, 60000);
110
+ }
111
+
112
+ stop() {
113
+ if (this.cleanupTimer) {
114
+ clearInterval(this.cleanupTimer);
115
+ this.cleanupTimer = undefined;
116
+ }
117
+ }
118
+ }
119
+ ```
120
+
121
+ ### Medium Priority
122
+
123
+ 1. **Optimize JSON Operations**
124
+ - Cache JSON.stringify results for frequently used objects
125
+ - Consider using faster hashing for cache keys (e.g., crypto.createHash)
126
+ - Use streaming JSON parsers for large objects
127
+
128
+ 2. **Improve Connection Pool**
129
+ - Implement backpressure/queueing when pool is full
130
+ - Use a heap or priority queue for connection management instead of sorting
131
+
132
+ 3. **Standardize Resource Cleanup**
133
+ - Create a base class for components with lifecycle management
134
+ - Ensure all event listeners are removed on cleanup
135
+ - Add abort controllers for better cancellation support
136
+
137
+ ### Long-term Improvements
138
+
139
+ 1. **Worker Threads**
140
+ - Move CPU-intensive operations to worker threads
141
+ - Consider using worker pools for NFTables operations
142
+
143
+ 2. **Monitoring and Metrics**
144
+ - Add performance monitoring for event loop lag
145
+ - Track connection pool utilization
146
+ - Monitor memory usage patterns
147
+
148
+ 3. **Graceful Degradation**
149
+ - Implement circuit breakers for backend connections
150
+ - Add request queuing with overflow protection
151
+ - Implement adaptive timeout strategies
152
+
153
+ ## Impact Assessment
154
+
155
+ These issues primarily affect:
156
+ - **Scalability**: Blocking operations limit concurrent connection handling
157
+ - **Responsiveness**: Event loop blocking causes latency spikes
158
+ - **Stability**: Memory leaks could cause crashes under sustained load
159
+ - **Resource Usage**: Inefficient resource management increases memory/CPU usage
160
+
161
+ ## Testing Recommendations
162
+
163
+ 1. Load test with high connection counts (10k+ concurrent)
164
+ 2. Monitor event loop lag under stress
165
+ 3. Test long-running scenarios to detect memory leaks
166
+ 4. Benchmark with async vs sync operations to measure improvement
167
+
168
+ ## Conclusion
169
+
170
+ While SmartProxy has good architectural design and many best practices, the identified blocking operations and resource management issues could significantly impact performance under high load. The most critical issues (busy wait loop and synchronous filesystem operations) should be addressed immediately.
@@ -0,0 +1,275 @@
1
+ /**
2
+ * Async utility functions for SmartProxy
3
+ * Provides non-blocking alternatives to synchronous operations
4
+ */
5
+
6
+ /**
7
+ * Delays execution for the specified number of milliseconds
8
+ * Non-blocking alternative to busy wait loops
9
+ * @param ms - Number of milliseconds to delay
10
+ * @returns Promise that resolves after the delay
11
+ */
12
+ export async function delay(ms: number): Promise<void> {
13
+ return new Promise(resolve => setTimeout(resolve, ms));
14
+ }
15
+
16
+ /**
17
+ * Retry an async operation with exponential backoff
18
+ * @param fn - The async function to retry
19
+ * @param options - Retry options
20
+ * @returns The result of the function or throws the last error
21
+ */
22
+ export async function retryWithBackoff<T>(
23
+ fn: () => Promise<T>,
24
+ options: {
25
+ maxAttempts?: number;
26
+ initialDelay?: number;
27
+ maxDelay?: number;
28
+ factor?: number;
29
+ onRetry?: (attempt: number, error: Error) => void;
30
+ } = {}
31
+ ): Promise<T> {
32
+ const {
33
+ maxAttempts = 3,
34
+ initialDelay = 100,
35
+ maxDelay = 10000,
36
+ factor = 2,
37
+ onRetry
38
+ } = options;
39
+
40
+ let lastError: Error | null = null;
41
+ let currentDelay = initialDelay;
42
+
43
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
44
+ try {
45
+ return await fn();
46
+ } catch (error: any) {
47
+ lastError = error;
48
+
49
+ if (attempt === maxAttempts) {
50
+ throw error;
51
+ }
52
+
53
+ if (onRetry) {
54
+ onRetry(attempt, error);
55
+ }
56
+
57
+ await delay(currentDelay);
58
+ currentDelay = Math.min(currentDelay * factor, maxDelay);
59
+ }
60
+ }
61
+
62
+ throw lastError || new Error('Retry failed');
63
+ }
64
+
65
+ /**
66
+ * Execute an async operation with a timeout
67
+ * @param fn - The async function to execute
68
+ * @param timeoutMs - Timeout in milliseconds
69
+ * @param timeoutError - Optional custom timeout error
70
+ * @returns The result of the function or throws timeout error
71
+ */
72
+ export async function withTimeout<T>(
73
+ fn: () => Promise<T>,
74
+ timeoutMs: number,
75
+ timeoutError?: Error
76
+ ): Promise<T> {
77
+ const timeoutPromise = new Promise<never>((_, reject) => {
78
+ setTimeout(() => {
79
+ reject(timeoutError || new Error(`Operation timed out after ${timeoutMs}ms`));
80
+ }, timeoutMs);
81
+ });
82
+
83
+ return Promise.race([fn(), timeoutPromise]);
84
+ }
85
+
86
+ /**
87
+ * Run multiple async operations in parallel with a concurrency limit
88
+ * @param items - Array of items to process
89
+ * @param fn - Async function to run for each item
90
+ * @param concurrency - Maximum number of concurrent operations
91
+ * @returns Array of results in the same order as input
92
+ */
93
+ export async function parallelLimit<T, R>(
94
+ items: T[],
95
+ fn: (item: T, index: number) => Promise<R>,
96
+ concurrency: number
97
+ ): Promise<R[]> {
98
+ const results: R[] = new Array(items.length);
99
+ const executing: Set<Promise<void>> = new Set();
100
+
101
+ for (let i = 0; i < items.length; i++) {
102
+ const promise = fn(items[i], i).then(result => {
103
+ results[i] = result;
104
+ executing.delete(promise);
105
+ });
106
+
107
+ executing.add(promise);
108
+
109
+ if (executing.size >= concurrency) {
110
+ await Promise.race(executing);
111
+ }
112
+ }
113
+
114
+ await Promise.all(executing);
115
+ return results;
116
+ }
117
+
118
+ /**
119
+ * Debounce an async function
120
+ * @param fn - The async function to debounce
121
+ * @param delayMs - Delay in milliseconds
122
+ * @returns Debounced function with cancel method
123
+ */
124
+ export function debounceAsync<T extends (...args: any[]) => Promise<any>>(
125
+ fn: T,
126
+ delayMs: number
127
+ ): T & { cancel: () => void } {
128
+ let timeoutId: NodeJS.Timeout | null = null;
129
+ let lastPromise: Promise<any> | null = null;
130
+
131
+ const debounced = ((...args: Parameters<T>) => {
132
+ if (timeoutId) {
133
+ clearTimeout(timeoutId);
134
+ }
135
+
136
+ lastPromise = new Promise((resolve, reject) => {
137
+ timeoutId = setTimeout(async () => {
138
+ timeoutId = null;
139
+ try {
140
+ const result = await fn(...args);
141
+ resolve(result);
142
+ } catch (error) {
143
+ reject(error);
144
+ }
145
+ }, delayMs);
146
+ });
147
+
148
+ return lastPromise;
149
+ }) as any;
150
+
151
+ debounced.cancel = () => {
152
+ if (timeoutId) {
153
+ clearTimeout(timeoutId);
154
+ timeoutId = null;
155
+ }
156
+ };
157
+
158
+ return debounced as T & { cancel: () => void };
159
+ }
160
+
161
+ /**
162
+ * Create a mutex for ensuring exclusive access to a resource
163
+ */
164
+ export class AsyncMutex {
165
+ private queue: Array<() => void> = [];
166
+ private locked = false;
167
+
168
+ async acquire(): Promise<() => void> {
169
+ if (!this.locked) {
170
+ this.locked = true;
171
+ return () => this.release();
172
+ }
173
+
174
+ return new Promise<() => void>(resolve => {
175
+ this.queue.push(() => {
176
+ resolve(() => this.release());
177
+ });
178
+ });
179
+ }
180
+
181
+ private release(): void {
182
+ const next = this.queue.shift();
183
+ if (next) {
184
+ next();
185
+ } else {
186
+ this.locked = false;
187
+ }
188
+ }
189
+
190
+ async runExclusive<T>(fn: () => Promise<T>): Promise<T> {
191
+ const release = await this.acquire();
192
+ try {
193
+ return await fn();
194
+ } finally {
195
+ release();
196
+ }
197
+ }
198
+ }
199
+
200
+ /**
201
+ * Circuit breaker for protecting against cascading failures
202
+ */
203
+ export class CircuitBreaker {
204
+ private failureCount = 0;
205
+ private lastFailureTime = 0;
206
+ private state: 'closed' | 'open' | 'half-open' = 'closed';
207
+
208
+ constructor(
209
+ private options: {
210
+ failureThreshold: number;
211
+ resetTimeout: number;
212
+ onStateChange?: (state: 'closed' | 'open' | 'half-open') => void;
213
+ }
214
+ ) {}
215
+
216
+ async execute<T>(fn: () => Promise<T>): Promise<T> {
217
+ if (this.state === 'open') {
218
+ if (Date.now() - this.lastFailureTime > this.options.resetTimeout) {
219
+ this.setState('half-open');
220
+ } else {
221
+ throw new Error('Circuit breaker is open');
222
+ }
223
+ }
224
+
225
+ try {
226
+ const result = await fn();
227
+ this.onSuccess();
228
+ return result;
229
+ } catch (error) {
230
+ this.onFailure();
231
+ throw error;
232
+ }
233
+ }
234
+
235
+ private onSuccess(): void {
236
+ this.failureCount = 0;
237
+ if (this.state !== 'closed') {
238
+ this.setState('closed');
239
+ }
240
+ }
241
+
242
+ private onFailure(): void {
243
+ this.failureCount++;
244
+ this.lastFailureTime = Date.now();
245
+
246
+ if (this.failureCount >= this.options.failureThreshold) {
247
+ this.setState('open');
248
+ }
249
+ }
250
+
251
+ private setState(state: 'closed' | 'open' | 'half-open'): void {
252
+ if (this.state !== state) {
253
+ this.state = state;
254
+ if (this.options.onStateChange) {
255
+ this.options.onStateChange(state);
256
+ }
257
+ }
258
+ }
259
+
260
+ isOpen(): boolean {
261
+ return this.state === 'open';
262
+ }
263
+
264
+ getState(): 'closed' | 'open' | 'half-open' {
265
+ return this.state;
266
+ }
267
+
268
+ recordSuccess(): void {
269
+ this.onSuccess();
270
+ }
271
+
272
+ recordFailure(): void {
273
+ this.onFailure();
274
+ }
275
+ }