@teneo-protocol/sdk 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.
- package/.dockerignore +14 -0
- package/.env.test.example +14 -0
- package/.eslintrc.json +26 -0
- package/.github/workflows/claude-code-review.yml +78 -0
- package/.github/workflows/claude-reviewer.yml +64 -0
- package/.github/workflows/publish-npm.yml +38 -0
- package/.github/workflows/push-to-main.yml +23 -0
- package/.node-version +1 -0
- package/.prettierrc +11 -0
- package/Dockerfile +25 -0
- package/LICENCE +661 -0
- package/README.md +709 -0
- package/dist/constants.d.ts +42 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +45 -0
- package/dist/constants.js.map +1 -0
- package/dist/core/websocket-client.d.ts +261 -0
- package/dist/core/websocket-client.d.ts.map +1 -0
- package/dist/core/websocket-client.js +875 -0
- package/dist/core/websocket-client.js.map +1 -0
- package/dist/formatters/response-formatter.d.ts +354 -0
- package/dist/formatters/response-formatter.d.ts.map +1 -0
- package/dist/formatters/response-formatter.js +575 -0
- package/dist/formatters/response-formatter.js.map +1 -0
- package/dist/handlers/message-handler-registry.d.ts +155 -0
- package/dist/handlers/message-handler-registry.d.ts.map +1 -0
- package/dist/handlers/message-handler-registry.js +216 -0
- package/dist/handlers/message-handler-registry.js.map +1 -0
- package/dist/handlers/message-handlers/agent-selected-handler.d.ts +112 -0
- package/dist/handlers/message-handlers/agent-selected-handler.d.ts.map +1 -0
- package/dist/handlers/message-handlers/agent-selected-handler.js +40 -0
- package/dist/handlers/message-handlers/agent-selected-handler.js.map +1 -0
- package/dist/handlers/message-handlers/agents-list-handler.d.ts +14 -0
- package/dist/handlers/message-handlers/agents-list-handler.d.ts.map +1 -0
- package/dist/handlers/message-handlers/agents-list-handler.js +25 -0
- package/dist/handlers/message-handlers/agents-list-handler.js.map +1 -0
- package/dist/handlers/message-handlers/auth-error-handler.d.ts +71 -0
- package/dist/handlers/message-handlers/auth-error-handler.d.ts.map +1 -0
- package/dist/handlers/message-handlers/auth-error-handler.js +30 -0
- package/dist/handlers/message-handlers/auth-error-handler.js.map +1 -0
- package/dist/handlers/message-handlers/auth-message-handler.d.ts +18 -0
- package/dist/handlers/message-handlers/auth-message-handler.d.ts.map +1 -0
- package/dist/handlers/message-handlers/auth-message-handler.js +60 -0
- package/dist/handlers/message-handlers/auth-message-handler.js.map +1 -0
- package/dist/handlers/message-handlers/auth-required-handler.d.ts +76 -0
- package/dist/handlers/message-handlers/auth-required-handler.d.ts.map +1 -0
- package/dist/handlers/message-handlers/auth-required-handler.js +23 -0
- package/dist/handlers/message-handlers/auth-required-handler.js.map +1 -0
- package/dist/handlers/message-handlers/auth-success-handler.d.ts +18 -0
- package/dist/handlers/message-handlers/auth-success-handler.d.ts.map +1 -0
- package/dist/handlers/message-handlers/auth-success-handler.js +51 -0
- package/dist/handlers/message-handlers/auth-success-handler.js.map +1 -0
- package/dist/handlers/message-handlers/base-handler.d.ts +55 -0
- package/dist/handlers/message-handlers/base-handler.d.ts.map +1 -0
- package/dist/handlers/message-handlers/base-handler.js +83 -0
- package/dist/handlers/message-handlers/base-handler.js.map +1 -0
- package/dist/handlers/message-handlers/challenge-handler.d.ts +73 -0
- package/dist/handlers/message-handlers/challenge-handler.d.ts.map +1 -0
- package/dist/handlers/message-handlers/challenge-handler.js +47 -0
- package/dist/handlers/message-handlers/challenge-handler.js.map +1 -0
- package/dist/handlers/message-handlers/error-message-handler.d.ts +76 -0
- package/dist/handlers/message-handlers/error-message-handler.d.ts.map +1 -0
- package/dist/handlers/message-handlers/error-message-handler.js +29 -0
- package/dist/handlers/message-handlers/error-message-handler.js.map +1 -0
- package/dist/handlers/message-handlers/index.d.ts +28 -0
- package/dist/handlers/message-handlers/index.d.ts.map +1 -0
- package/dist/handlers/message-handlers/index.js +100 -0
- package/dist/handlers/message-handlers/index.js.map +1 -0
- package/dist/handlers/message-handlers/list-rooms-response-handler.d.ts +122 -0
- package/dist/handlers/message-handlers/list-rooms-response-handler.d.ts.map +1 -0
- package/dist/handlers/message-handlers/list-rooms-response-handler.js +30 -0
- package/dist/handlers/message-handlers/list-rooms-response-handler.js.map +1 -0
- package/dist/handlers/message-handlers/ping-pong-handler.d.ts +104 -0
- package/dist/handlers/message-handlers/ping-pong-handler.d.ts.map +1 -0
- package/dist/handlers/message-handlers/ping-pong-handler.js +36 -0
- package/dist/handlers/message-handlers/ping-pong-handler.js.map +1 -0
- package/dist/handlers/message-handlers/regular-message-handler.d.ts +56 -0
- package/dist/handlers/message-handlers/regular-message-handler.d.ts.map +1 -0
- package/dist/handlers/message-handlers/regular-message-handler.js +59 -0
- package/dist/handlers/message-handlers/regular-message-handler.js.map +1 -0
- package/dist/handlers/message-handlers/subscribe-response-handler.d.ts +81 -0
- package/dist/handlers/message-handlers/subscribe-response-handler.d.ts.map +1 -0
- package/dist/handlers/message-handlers/subscribe-response-handler.js +48 -0
- package/dist/handlers/message-handlers/subscribe-response-handler.js.map +1 -0
- package/dist/handlers/message-handlers/task-response-handler.d.ts +14 -0
- package/dist/handlers/message-handlers/task-response-handler.d.ts.map +1 -0
- package/dist/handlers/message-handlers/task-response-handler.js +44 -0
- package/dist/handlers/message-handlers/task-response-handler.js.map +1 -0
- package/dist/handlers/message-handlers/types.d.ts +51 -0
- package/dist/handlers/message-handlers/types.d.ts.map +1 -0
- package/dist/handlers/message-handlers/types.js +7 -0
- package/dist/handlers/message-handlers/types.js.map +1 -0
- package/dist/handlers/message-handlers/unsubscribe-response-handler.d.ts +81 -0
- package/dist/handlers/message-handlers/unsubscribe-response-handler.d.ts.map +1 -0
- package/dist/handlers/message-handlers/unsubscribe-response-handler.js +48 -0
- package/dist/handlers/message-handlers/unsubscribe-response-handler.js.map +1 -0
- package/dist/handlers/webhook-handler.d.ts +202 -0
- package/dist/handlers/webhook-handler.d.ts.map +1 -0
- package/dist/handlers/webhook-handler.js +511 -0
- package/dist/handlers/webhook-handler.js.map +1 -0
- package/dist/index.d.ts +71 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +217 -0
- package/dist/index.js.map +1 -0
- package/dist/managers/agent-registry.d.ts +173 -0
- package/dist/managers/agent-registry.d.ts.map +1 -0
- package/dist/managers/agent-registry.js +310 -0
- package/dist/managers/agent-registry.js.map +1 -0
- package/dist/managers/connection-manager.d.ts +134 -0
- package/dist/managers/connection-manager.d.ts.map +1 -0
- package/dist/managers/connection-manager.js +176 -0
- package/dist/managers/connection-manager.js.map +1 -0
- package/dist/managers/index.d.ts +9 -0
- package/dist/managers/index.d.ts.map +1 -0
- package/dist/managers/index.js +16 -0
- package/dist/managers/index.js.map +1 -0
- package/dist/managers/message-router.d.ts +112 -0
- package/dist/managers/message-router.d.ts.map +1 -0
- package/dist/managers/message-router.js +260 -0
- package/dist/managers/message-router.js.map +1 -0
- package/dist/managers/room-manager.d.ts +165 -0
- package/dist/managers/room-manager.d.ts.map +1 -0
- package/dist/managers/room-manager.js +227 -0
- package/dist/managers/room-manager.js.map +1 -0
- package/dist/teneo-sdk.d.ts +703 -0
- package/dist/teneo-sdk.d.ts.map +1 -0
- package/dist/teneo-sdk.js +907 -0
- package/dist/teneo-sdk.js.map +1 -0
- package/dist/types/config.d.ts +1047 -0
- package/dist/types/config.d.ts.map +1 -0
- package/dist/types/config.js +720 -0
- package/dist/types/config.js.map +1 -0
- package/dist/types/error-codes.d.ts +29 -0
- package/dist/types/error-codes.d.ts.map +1 -0
- package/dist/types/error-codes.js +41 -0
- package/dist/types/error-codes.js.map +1 -0
- package/dist/types/events.d.ts +616 -0
- package/dist/types/events.d.ts.map +1 -0
- package/dist/types/events.js +261 -0
- package/dist/types/events.js.map +1 -0
- package/dist/types/health.d.ts +40 -0
- package/dist/types/health.d.ts.map +1 -0
- package/dist/types/health.js +6 -0
- package/dist/types/health.js.map +1 -0
- package/dist/types/index.d.ts +10 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +123 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/messages.d.ts +3734 -0
- package/dist/types/messages.d.ts.map +1 -0
- package/dist/types/messages.js +482 -0
- package/dist/types/messages.js.map +1 -0
- package/dist/types/validation.d.ts +81 -0
- package/dist/types/validation.d.ts.map +1 -0
- package/dist/types/validation.js +115 -0
- package/dist/types/validation.js.map +1 -0
- package/dist/utils/bounded-queue.d.ts +127 -0
- package/dist/utils/bounded-queue.d.ts.map +1 -0
- package/dist/utils/bounded-queue.js +181 -0
- package/dist/utils/bounded-queue.js.map +1 -0
- package/dist/utils/circuit-breaker.d.ts +141 -0
- package/dist/utils/circuit-breaker.d.ts.map +1 -0
- package/dist/utils/circuit-breaker.js +215 -0
- package/dist/utils/circuit-breaker.js.map +1 -0
- package/dist/utils/deduplication-cache.d.ts +110 -0
- package/dist/utils/deduplication-cache.d.ts.map +1 -0
- package/dist/utils/deduplication-cache.js +177 -0
- package/dist/utils/deduplication-cache.js.map +1 -0
- package/dist/utils/event-waiter.d.ts +101 -0
- package/dist/utils/event-waiter.d.ts.map +1 -0
- package/dist/utils/event-waiter.js +118 -0
- package/dist/utils/event-waiter.js.map +1 -0
- package/dist/utils/index.d.ts +51 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +72 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/logger.d.ts +22 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +91 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/rate-limiter.d.ts +122 -0
- package/dist/utils/rate-limiter.d.ts.map +1 -0
- package/dist/utils/rate-limiter.js +190 -0
- package/dist/utils/rate-limiter.js.map +1 -0
- package/dist/utils/retry-policy.d.ts +191 -0
- package/dist/utils/retry-policy.d.ts.map +1 -0
- package/dist/utils/retry-policy.js +225 -0
- package/dist/utils/retry-policy.js.map +1 -0
- package/dist/utils/secure-private-key.d.ts +113 -0
- package/dist/utils/secure-private-key.d.ts.map +1 -0
- package/dist/utils/secure-private-key.js +188 -0
- package/dist/utils/secure-private-key.js.map +1 -0
- package/dist/utils/signature-verifier.d.ts +143 -0
- package/dist/utils/signature-verifier.d.ts.map +1 -0
- package/dist/utils/signature-verifier.js +238 -0
- package/dist/utils/signature-verifier.js.map +1 -0
- package/dist/utils/ssrf-validator.d.ts +36 -0
- package/dist/utils/ssrf-validator.d.ts.map +1 -0
- package/dist/utils/ssrf-validator.js +195 -0
- package/dist/utils/ssrf-validator.js.map +1 -0
- package/examples/.env.example +17 -0
- package/examples/basic-usage.ts +211 -0
- package/examples/production-dashboard/.env.example +153 -0
- package/examples/production-dashboard/package.json +39 -0
- package/examples/production-dashboard/public/dashboard.html +642 -0
- package/examples/production-dashboard/server.ts +753 -0
- package/examples/webhook-integration.ts +239 -0
- package/examples/x-influencer-battle-redesign.html +1065 -0
- package/examples/x-influencer-battle-server.ts +217 -0
- package/examples/x-influencer-battle.html +787 -0
- package/package.json +65 -0
- package/src/constants.ts +43 -0
- package/src/core/websocket-client.test.ts +512 -0
- package/src/core/websocket-client.ts +1056 -0
- package/src/formatters/response-formatter.test.ts +571 -0
- package/src/formatters/response-formatter.ts +677 -0
- package/src/handlers/message-handler-registry.ts +239 -0
- package/src/handlers/message-handlers/agent-selected-handler.ts +40 -0
- package/src/handlers/message-handlers/agents-list-handler.ts +26 -0
- package/src/handlers/message-handlers/auth-error-handler.ts +31 -0
- package/src/handlers/message-handlers/auth-message-handler.ts +66 -0
- package/src/handlers/message-handlers/auth-required-handler.ts +23 -0
- package/src/handlers/message-handlers/auth-success-handler.ts +57 -0
- package/src/handlers/message-handlers/base-handler.ts +101 -0
- package/src/handlers/message-handlers/challenge-handler.ts +57 -0
- package/src/handlers/message-handlers/error-message-handler.ts +27 -0
- package/src/handlers/message-handlers/index.ts +77 -0
- package/src/handlers/message-handlers/list-rooms-response-handler.ts +28 -0
- package/src/handlers/message-handlers/ping-pong-handler.ts +30 -0
- package/src/handlers/message-handlers/regular-message-handler.ts +65 -0
- package/src/handlers/message-handlers/subscribe-response-handler.ts +47 -0
- package/src/handlers/message-handlers/task-response-handler.ts +45 -0
- package/src/handlers/message-handlers/types.ts +77 -0
- package/src/handlers/message-handlers/unsubscribe-response-handler.ts +47 -0
- package/src/handlers/webhook-handler.test.ts +789 -0
- package/src/handlers/webhook-handler.ts +576 -0
- package/src/index.ts +269 -0
- package/src/managers/agent-registry.test.ts +466 -0
- package/src/managers/agent-registry.ts +347 -0
- package/src/managers/connection-manager.ts +195 -0
- package/src/managers/index.ts +9 -0
- package/src/managers/message-router.ts +349 -0
- package/src/managers/room-manager.ts +248 -0
- package/src/teneo-sdk.ts +1022 -0
- package/src/types/config.test.ts +325 -0
- package/src/types/config.ts +799 -0
- package/src/types/error-codes.ts +44 -0
- package/src/types/events.test.ts +302 -0
- package/src/types/events.ts +382 -0
- package/src/types/health.ts +46 -0
- package/src/types/index.ts +199 -0
- package/src/types/messages.test.ts +660 -0
- package/src/types/messages.ts +570 -0
- package/src/types/validation.ts +123 -0
- package/src/utils/bounded-queue.test.ts +356 -0
- package/src/utils/bounded-queue.ts +205 -0
- package/src/utils/circuit-breaker.test.ts +394 -0
- package/src/utils/circuit-breaker.ts +262 -0
- package/src/utils/deduplication-cache.test.ts +380 -0
- package/src/utils/deduplication-cache.ts +198 -0
- package/src/utils/event-waiter.test.ts +381 -0
- package/src/utils/event-waiter.ts +172 -0
- package/src/utils/index.ts +74 -0
- package/src/utils/logger.ts +87 -0
- package/src/utils/rate-limiter.test.ts +341 -0
- package/src/utils/rate-limiter.ts +211 -0
- package/src/utils/retry-policy.test.ts +558 -0
- package/src/utils/retry-policy.ts +272 -0
- package/src/utils/secure-private-key.test.ts +356 -0
- package/src/utils/secure-private-key.ts +205 -0
- package/src/utils/signature-verifier.test.ts +464 -0
- package/src/utils/signature-verifier.ts +298 -0
- package/src/utils/ssrf-validator.test.ts +372 -0
- package/src/utils/ssrf-validator.ts +224 -0
- package/tests/integration/real-server.test.ts +740 -0
- package/tests/integration/websocket.test.ts +381 -0
- package/tests/integration-setup.ts +16 -0
- package/tests/setup.ts +34 -0
- package/tsconfig.json +32 -0
- package/vitest.config.ts +42 -0
- package/vitest.integration.config.ts +23 -0
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for Circuit Breaker
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect, beforeEach, vi } from "vitest";
|
|
6
|
+
import { CircuitBreaker, CircuitBreakerError, type CircuitState } from "./circuit-breaker";
|
|
7
|
+
|
|
8
|
+
describe("CircuitBreaker", () => {
|
|
9
|
+
describe("constructor", () => {
|
|
10
|
+
it("should create circuit breaker with default options", () => {
|
|
11
|
+
const breaker = new CircuitBreaker();
|
|
12
|
+
const config = breaker.getConfig();
|
|
13
|
+
|
|
14
|
+
expect(config.failureThreshold).toBe(5);
|
|
15
|
+
expect(config.successThreshold).toBe(2);
|
|
16
|
+
expect(config.timeout).toBe(60000);
|
|
17
|
+
expect(config.windowSize).toBe(60000);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("should create circuit breaker with custom options", () => {
|
|
21
|
+
const breaker = new CircuitBreaker({
|
|
22
|
+
failureThreshold: 3,
|
|
23
|
+
successThreshold: 1,
|
|
24
|
+
timeout: 30000,
|
|
25
|
+
windowSize: 30000
|
|
26
|
+
});
|
|
27
|
+
const config = breaker.getConfig();
|
|
28
|
+
|
|
29
|
+
expect(config.failureThreshold).toBe(3);
|
|
30
|
+
expect(config.successThreshold).toBe(1);
|
|
31
|
+
expect(config.timeout).toBe(30000);
|
|
32
|
+
expect(config.windowSize).toBe(30000);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("should throw error if failureThreshold is less than 1", () => {
|
|
36
|
+
expect(() => new CircuitBreaker({ failureThreshold: 0 })).toThrow(
|
|
37
|
+
"failureThreshold must be at least 1"
|
|
38
|
+
);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("should throw error if successThreshold is less than 1", () => {
|
|
42
|
+
expect(() => new CircuitBreaker({ successThreshold: 0 })).toThrow(
|
|
43
|
+
"successThreshold must be at least 1"
|
|
44
|
+
);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
describe("CLOSED state", () => {
|
|
49
|
+
let breaker: CircuitBreaker;
|
|
50
|
+
|
|
51
|
+
beforeEach(() => {
|
|
52
|
+
breaker = new CircuitBreaker({ failureThreshold: 3 });
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("should start in CLOSED state", () => {
|
|
56
|
+
const state = breaker.getState();
|
|
57
|
+
expect(state.state).toBe("CLOSED");
|
|
58
|
+
expect(state.failureCount).toBe(0);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("should execute operations successfully in CLOSED state", async () => {
|
|
62
|
+
const operation = vi.fn(async () => "success");
|
|
63
|
+
|
|
64
|
+
const result = await breaker.execute(operation);
|
|
65
|
+
|
|
66
|
+
expect(result).toBe("success");
|
|
67
|
+
expect(operation).toHaveBeenCalledTimes(1);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("should track failures in CLOSED state", async () => {
|
|
71
|
+
const operation = vi.fn(async () => {
|
|
72
|
+
throw new Error("operation failed");
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
await expect(breaker.execute(operation)).rejects.toThrow("operation failed");
|
|
76
|
+
|
|
77
|
+
const state = breaker.getState();
|
|
78
|
+
expect(state.failureCount).toBe(1);
|
|
79
|
+
expect(state.state).toBe("CLOSED");
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("should open circuit after reaching failure threshold", async () => {
|
|
83
|
+
const operation = vi.fn(async () => {
|
|
84
|
+
throw new Error("operation failed");
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// Fail 3 times (threshold = 3)
|
|
88
|
+
for (let i = 0; i < 3; i++) {
|
|
89
|
+
await expect(breaker.execute(operation)).rejects.toThrow("operation failed");
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const state = breaker.getState();
|
|
93
|
+
expect(state.state).toBe("OPEN");
|
|
94
|
+
expect(state.failureCount).toBe(3);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("should only count failures within the time window", async () => {
|
|
98
|
+
const breaker = new CircuitBreaker({
|
|
99
|
+
failureThreshold: 3,
|
|
100
|
+
windowSize: 100 // 100ms window
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
const operation = vi.fn(async () => {
|
|
104
|
+
throw new Error("failed");
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// First failure
|
|
108
|
+
await expect(breaker.execute(operation)).rejects.toThrow("failed");
|
|
109
|
+
expect(breaker.getState().failureCount).toBe(1);
|
|
110
|
+
|
|
111
|
+
// Wait for window to expire
|
|
112
|
+
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
113
|
+
|
|
114
|
+
// Second failure (first should be outside window now)
|
|
115
|
+
await expect(breaker.execute(operation)).rejects.toThrow("failed");
|
|
116
|
+
expect(breaker.getState().failureCount).toBe(1);
|
|
117
|
+
expect(breaker.getState().state).toBe("CLOSED");
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
describe("OPEN state", () => {
|
|
122
|
+
let breaker: CircuitBreaker;
|
|
123
|
+
|
|
124
|
+
beforeEach(async () => {
|
|
125
|
+
breaker = new CircuitBreaker({
|
|
126
|
+
failureThreshold: 2,
|
|
127
|
+
timeout: 1000
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// Trigger circuit to open
|
|
131
|
+
const failOp = vi.fn(async () => {
|
|
132
|
+
throw new Error("fail");
|
|
133
|
+
});
|
|
134
|
+
for (let i = 0; i < 2; i++) {
|
|
135
|
+
await expect(breaker.execute(failOp)).rejects.toThrow("fail");
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it("should be in OPEN state after threshold failures", () => {
|
|
140
|
+
const state = breaker.getState();
|
|
141
|
+
expect(state.state).toBe("OPEN");
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it("should fail fast with CircuitBreakerError in OPEN state", async () => {
|
|
145
|
+
const operation = vi.fn(async () => "success");
|
|
146
|
+
|
|
147
|
+
await expect(breaker.execute(operation)).rejects.toThrow(CircuitBreakerError);
|
|
148
|
+
await expect(breaker.execute(operation)).rejects.toThrow("Circuit breaker is OPEN");
|
|
149
|
+
|
|
150
|
+
// Operation should not be called
|
|
151
|
+
expect(operation).not.toHaveBeenCalled();
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it("should transition to HALF_OPEN after timeout", async () => {
|
|
155
|
+
expect(breaker.getState().state).toBe("OPEN");
|
|
156
|
+
|
|
157
|
+
// Wait for timeout
|
|
158
|
+
await new Promise((resolve) => setTimeout(resolve, 1100));
|
|
159
|
+
|
|
160
|
+
// Try operation - should be in HALF_OPEN now
|
|
161
|
+
const operation = vi.fn(async () => "success");
|
|
162
|
+
await breaker.execute(operation);
|
|
163
|
+
|
|
164
|
+
expect(operation).toHaveBeenCalled();
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it("should set nextAttemptTime when opening", () => {
|
|
168
|
+
const state = breaker.getState();
|
|
169
|
+
expect(state.nextAttemptTime).toBeDefined();
|
|
170
|
+
expect(state.nextAttemptTime! > Date.now()).toBe(true);
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
describe("HALF_OPEN state", () => {
|
|
175
|
+
let breaker: CircuitBreaker;
|
|
176
|
+
|
|
177
|
+
beforeEach(async () => {
|
|
178
|
+
breaker = new CircuitBreaker({
|
|
179
|
+
failureThreshold: 2,
|
|
180
|
+
successThreshold: 2,
|
|
181
|
+
timeout: 100 // Short timeout for testing
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
// Open the circuit
|
|
185
|
+
const failOp = vi.fn(async () => {
|
|
186
|
+
throw new Error("fail");
|
|
187
|
+
});
|
|
188
|
+
for (let i = 0; i < 2; i++) {
|
|
189
|
+
await expect(breaker.execute(failOp)).rejects.toThrow("fail");
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Wait for timeout to move to HALF_OPEN
|
|
193
|
+
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it("should allow operations in HALF_OPEN state", async () => {
|
|
197
|
+
const operation = vi.fn(async () => "success");
|
|
198
|
+
|
|
199
|
+
const result = await breaker.execute(operation);
|
|
200
|
+
|
|
201
|
+
expect(result).toBe("success");
|
|
202
|
+
expect(operation).toHaveBeenCalled();
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it("should close circuit after enough successes", async () => {
|
|
206
|
+
const operation = vi.fn(async () => "success");
|
|
207
|
+
|
|
208
|
+
// Need 2 successes (successThreshold = 2)
|
|
209
|
+
await breaker.execute(operation);
|
|
210
|
+
expect(breaker.getState().state).toBe("HALF_OPEN");
|
|
211
|
+
|
|
212
|
+
await breaker.execute(operation);
|
|
213
|
+
expect(breaker.getState().state).toBe("CLOSED");
|
|
214
|
+
expect(breaker.getState().failureCount).toBe(0);
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it("should reopen circuit on failure in HALF_OPEN", async () => {
|
|
218
|
+
const operation = vi.fn(async () => {
|
|
219
|
+
throw new Error("failed again");
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
await expect(breaker.execute(operation)).rejects.toThrow("failed again");
|
|
223
|
+
|
|
224
|
+
const state = breaker.getState();
|
|
225
|
+
expect(state.state).toBe("OPEN");
|
|
226
|
+
expect(state.nextAttemptTime).toBeDefined();
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
it("should reset success count when reopening", async () => {
|
|
230
|
+
const successOp = vi.fn(async () => "success");
|
|
231
|
+
const failOp = vi.fn(async () => {
|
|
232
|
+
throw new Error("fail");
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
// One success
|
|
236
|
+
await breaker.execute(successOp);
|
|
237
|
+
expect(breaker.getState().successCount).toBe(1);
|
|
238
|
+
|
|
239
|
+
// Then fail - should reopen and reset success count
|
|
240
|
+
await expect(breaker.execute(failOp)).rejects.toThrow("fail");
|
|
241
|
+
expect(breaker.getState().state).toBe("OPEN");
|
|
242
|
+
expect(breaker.getState().successCount).toBe(0);
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
describe("reset", () => {
|
|
247
|
+
it("should reset circuit to CLOSED state", async () => {
|
|
248
|
+
const breaker = new CircuitBreaker({ failureThreshold: 1 });
|
|
249
|
+
|
|
250
|
+
// Open circuit
|
|
251
|
+
const failOp = vi.fn(async () => {
|
|
252
|
+
throw new Error("fail");
|
|
253
|
+
});
|
|
254
|
+
await expect(breaker.execute(failOp)).rejects.toThrow("fail");
|
|
255
|
+
|
|
256
|
+
expect(breaker.getState().state).toBe("OPEN");
|
|
257
|
+
|
|
258
|
+
// Reset
|
|
259
|
+
breaker.reset();
|
|
260
|
+
|
|
261
|
+
const state = breaker.getState();
|
|
262
|
+
expect(state.state).toBe("CLOSED");
|
|
263
|
+
expect(state.failureCount).toBe(0);
|
|
264
|
+
expect(state.successCount).toBe(0);
|
|
265
|
+
expect(state.lastFailureTime).toBeUndefined();
|
|
266
|
+
expect(state.nextAttemptTime).toBeUndefined();
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
it("should allow operations after reset", async () => {
|
|
270
|
+
const breaker = new CircuitBreaker({ failureThreshold: 1 });
|
|
271
|
+
|
|
272
|
+
// Open circuit
|
|
273
|
+
await expect(
|
|
274
|
+
breaker.execute(async () => {
|
|
275
|
+
throw new Error("fail");
|
|
276
|
+
})
|
|
277
|
+
).rejects.toThrow("fail");
|
|
278
|
+
|
|
279
|
+
breaker.reset();
|
|
280
|
+
|
|
281
|
+
// Should work now
|
|
282
|
+
const result = await breaker.execute(async () => "success");
|
|
283
|
+
expect(result).toBe("success");
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
describe("getState", () => {
|
|
288
|
+
it("should return current state and metrics", async () => {
|
|
289
|
+
const breaker = new CircuitBreaker();
|
|
290
|
+
|
|
291
|
+
const state1 = breaker.getState();
|
|
292
|
+
expect(state1).toMatchObject({
|
|
293
|
+
state: "CLOSED",
|
|
294
|
+
failureCount: 0,
|
|
295
|
+
successCount: 0
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
// Cause a failure
|
|
299
|
+
await expect(
|
|
300
|
+
breaker.execute(async () => {
|
|
301
|
+
throw new Error("fail");
|
|
302
|
+
})
|
|
303
|
+
).rejects.toThrow("fail");
|
|
304
|
+
|
|
305
|
+
const state2 = breaker.getState();
|
|
306
|
+
expect(state2.failureCount).toBe(1);
|
|
307
|
+
expect(state2.lastFailureTime).toBeDefined();
|
|
308
|
+
});
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
describe("edge cases", () => {
|
|
312
|
+
it("should handle rapid failures", async () => {
|
|
313
|
+
const breaker = new CircuitBreaker({ failureThreshold: 10 });
|
|
314
|
+
|
|
315
|
+
const operation = vi.fn(async () => {
|
|
316
|
+
throw new Error("fail");
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
// Rapid failures
|
|
320
|
+
const promises = Array(10)
|
|
321
|
+
.fill(0)
|
|
322
|
+
.map(() => breaker.execute(operation).catch(() => {}));
|
|
323
|
+
|
|
324
|
+
await Promise.all(promises);
|
|
325
|
+
|
|
326
|
+
expect(breaker.getState().state).toBe("OPEN");
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
it("should handle mixed success/failure in CLOSED", async () => {
|
|
330
|
+
const breaker = new CircuitBreaker({ failureThreshold: 3 });
|
|
331
|
+
|
|
332
|
+
let shouldFail = true;
|
|
333
|
+
const operation = vi.fn(async () => {
|
|
334
|
+
if (shouldFail) {
|
|
335
|
+
throw new Error("fail");
|
|
336
|
+
}
|
|
337
|
+
return "success";
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
// Fail
|
|
341
|
+
await expect(breaker.execute(operation)).rejects.toThrow("fail");
|
|
342
|
+
expect(breaker.getState().failureCount).toBe(1);
|
|
343
|
+
|
|
344
|
+
// Succeed
|
|
345
|
+
shouldFail = false;
|
|
346
|
+
await breaker.execute(operation);
|
|
347
|
+
expect(breaker.getState().failureCount).toBe(0);
|
|
348
|
+
|
|
349
|
+
// Should still be CLOSED
|
|
350
|
+
expect(breaker.getState().state).toBe("CLOSED");
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
it("should handle synchronous errors", async () => {
|
|
354
|
+
const breaker = new CircuitBreaker({ failureThreshold: 1 });
|
|
355
|
+
|
|
356
|
+
await expect(
|
|
357
|
+
breaker.execute(async () => {
|
|
358
|
+
throw new Error("sync error");
|
|
359
|
+
})
|
|
360
|
+
).rejects.toThrow("sync error");
|
|
361
|
+
|
|
362
|
+
expect(breaker.getState().state).toBe("OPEN");
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
it("should handle operations that return undefined", async () => {
|
|
366
|
+
const breaker = new CircuitBreaker();
|
|
367
|
+
|
|
368
|
+
const result = await breaker.execute(async () => undefined);
|
|
369
|
+
|
|
370
|
+
expect(result).toBeUndefined();
|
|
371
|
+
expect(breaker.getState().state).toBe("CLOSED");
|
|
372
|
+
});
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
describe("CircuitBreakerError", () => {
|
|
376
|
+
it("should be instanceof Error", () => {
|
|
377
|
+
const error = new CircuitBreakerError("Test", "OPEN");
|
|
378
|
+
expect(error).toBeInstanceOf(Error);
|
|
379
|
+
expect(error).toBeInstanceOf(CircuitBreakerError);
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
it("should have correct name and state", () => {
|
|
383
|
+
const error = new CircuitBreakerError("Test", "OPEN");
|
|
384
|
+
expect(error.name).toBe("CircuitBreakerError");
|
|
385
|
+
expect(error.state).toBe("OPEN");
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
it("should preserve error message", () => {
|
|
389
|
+
const message = "Custom message";
|
|
390
|
+
const error = new CircuitBreakerError(message, "OPEN");
|
|
391
|
+
expect(error.message).toBe(message);
|
|
392
|
+
});
|
|
393
|
+
});
|
|
394
|
+
});
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Circuit Breaker Pattern Implementation
|
|
3
|
+
* Provides fault tolerance by preventing cascading failures
|
|
4
|
+
*
|
|
5
|
+
* States:
|
|
6
|
+
* - CLOSED: Normal operation, requests pass through
|
|
7
|
+
* - OPEN: Failure threshold exceeded, requests fail fast
|
|
8
|
+
* - HALF_OPEN: Testing if service recovered, limited requests allowed
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Circuit breaker states
|
|
13
|
+
*/
|
|
14
|
+
export type CircuitState = "CLOSED" | "OPEN" | "HALF_OPEN";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Circuit breaker configuration options
|
|
18
|
+
*/
|
|
19
|
+
export interface CircuitBreakerOptions {
|
|
20
|
+
/** Number of failures before opening circuit (default: 5) */
|
|
21
|
+
failureThreshold?: number;
|
|
22
|
+
/** Success count needed to close from half-open (default: 2) */
|
|
23
|
+
successThreshold?: number;
|
|
24
|
+
/** Time in ms to wait before trying half-open (default: 60000) */
|
|
25
|
+
timeout?: number;
|
|
26
|
+
/** Window size in ms for tracking failures (default: 60000) */
|
|
27
|
+
windowSize?: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Error thrown when circuit is open
|
|
32
|
+
*/
|
|
33
|
+
export class CircuitBreakerError extends Error {
|
|
34
|
+
constructor(message: string, public readonly state: CircuitState) {
|
|
35
|
+
super(message);
|
|
36
|
+
this.name = "CircuitBreakerError";
|
|
37
|
+
|
|
38
|
+
if (Error.captureStackTrace) {
|
|
39
|
+
Error.captureStackTrace(this, CircuitBreakerError);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Circuit breaker for fault tolerance
|
|
46
|
+
* Prevents cascading failures by failing fast when errors are detected
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```typescript
|
|
50
|
+
* const breaker = new CircuitBreaker({
|
|
51
|
+
* failureThreshold: 5,
|
|
52
|
+
* timeout: 60000
|
|
53
|
+
* });
|
|
54
|
+
*
|
|
55
|
+
* try {
|
|
56
|
+
* await breaker.execute(async () => {
|
|
57
|
+
* return await fetch('https://api.example.com/data');
|
|
58
|
+
* });
|
|
59
|
+
* } catch (error) {
|
|
60
|
+
* if (error instanceof CircuitBreakerError) {
|
|
61
|
+
* console.log('Circuit is open, failing fast');
|
|
62
|
+
* }
|
|
63
|
+
* }
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
export class CircuitBreaker {
|
|
67
|
+
private state: CircuitState = "CLOSED";
|
|
68
|
+
private failureCount = 0;
|
|
69
|
+
private successCount = 0;
|
|
70
|
+
private lastFailureTime?: number;
|
|
71
|
+
private nextAttemptTime?: number;
|
|
72
|
+
private readonly failures: number[] = []; // Timestamps of failures
|
|
73
|
+
|
|
74
|
+
private readonly failureThreshold: number;
|
|
75
|
+
private readonly successThreshold: number;
|
|
76
|
+
private readonly timeout: number;
|
|
77
|
+
private readonly windowSize: number;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Creates a new circuit breaker
|
|
81
|
+
*
|
|
82
|
+
* @param options - Configuration options
|
|
83
|
+
* @throws {Error} If thresholds are less than 1
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* ```typescript
|
|
87
|
+
* const breaker = new CircuitBreaker({
|
|
88
|
+
* failureThreshold: 5, // Open after 5 failures
|
|
89
|
+
* successThreshold: 2, // Close after 2 successes
|
|
90
|
+
* timeout: 60000, // Wait 60s before trying again
|
|
91
|
+
* windowSize: 60000 // Track failures in 60s window
|
|
92
|
+
* });
|
|
93
|
+
* ```
|
|
94
|
+
*/
|
|
95
|
+
constructor(options: CircuitBreakerOptions = {}) {
|
|
96
|
+
this.failureThreshold = options.failureThreshold ?? 5;
|
|
97
|
+
this.successThreshold = options.successThreshold ?? 2;
|
|
98
|
+
this.timeout = options.timeout ?? 60000;
|
|
99
|
+
this.windowSize = options.windowSize ?? 60000;
|
|
100
|
+
|
|
101
|
+
if (this.failureThreshold < 1) {
|
|
102
|
+
throw new Error("CircuitBreaker failureThreshold must be at least 1");
|
|
103
|
+
}
|
|
104
|
+
if (this.successThreshold < 1) {
|
|
105
|
+
throw new Error("CircuitBreaker successThreshold must be at least 1");
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Execute an operation through the circuit breaker
|
|
111
|
+
*
|
|
112
|
+
* @template T - Return type of the operation
|
|
113
|
+
* @param operation - Async operation to execute
|
|
114
|
+
* @returns Promise resolving to operation result
|
|
115
|
+
* @throws {CircuitBreakerError} If circuit is open
|
|
116
|
+
* @throws Operation error if operation fails
|
|
117
|
+
*
|
|
118
|
+
* @example
|
|
119
|
+
* ```typescript
|
|
120
|
+
* const result = await breaker.execute(async () => {
|
|
121
|
+
* const response = await fetch(url);
|
|
122
|
+
* return await response.json();
|
|
123
|
+
* });
|
|
124
|
+
* ```
|
|
125
|
+
*/
|
|
126
|
+
public async execute<T>(operation: () => Promise<T>): Promise<T> {
|
|
127
|
+
// Check if we should try to close the circuit
|
|
128
|
+
this.tryTransitionToHalfOpen();
|
|
129
|
+
|
|
130
|
+
// If circuit is open, fail fast
|
|
131
|
+
if (this.state === "OPEN") {
|
|
132
|
+
throw new CircuitBreakerError("Circuit breaker is OPEN", this.state);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
try {
|
|
136
|
+
const result = await operation();
|
|
137
|
+
this.onSuccess();
|
|
138
|
+
return result;
|
|
139
|
+
} catch (error) {
|
|
140
|
+
this.onFailure();
|
|
141
|
+
throw error;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Get current circuit breaker state and metrics
|
|
147
|
+
*
|
|
148
|
+
* @returns State object with status and counters
|
|
149
|
+
*/
|
|
150
|
+
public getState(): {
|
|
151
|
+
state: CircuitState;
|
|
152
|
+
failureCount: number;
|
|
153
|
+
successCount: number;
|
|
154
|
+
lastFailureTime?: number;
|
|
155
|
+
nextAttemptTime?: number;
|
|
156
|
+
} {
|
|
157
|
+
return {
|
|
158
|
+
state: this.state,
|
|
159
|
+
failureCount: this.failureCount,
|
|
160
|
+
successCount: this.successCount,
|
|
161
|
+
lastFailureTime: this.lastFailureTime,
|
|
162
|
+
nextAttemptTime: this.nextAttemptTime
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Get circuit breaker configuration
|
|
168
|
+
*/
|
|
169
|
+
public getConfig(): {
|
|
170
|
+
failureThreshold: number;
|
|
171
|
+
successThreshold: number;
|
|
172
|
+
timeout: number;
|
|
173
|
+
windowSize: number;
|
|
174
|
+
} {
|
|
175
|
+
return {
|
|
176
|
+
failureThreshold: this.failureThreshold,
|
|
177
|
+
successThreshold: this.successThreshold,
|
|
178
|
+
timeout: this.timeout,
|
|
179
|
+
windowSize: this.windowSize
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Manually reset the circuit breaker to CLOSED state
|
|
185
|
+
* Useful for recovery scenarios or testing
|
|
186
|
+
*/
|
|
187
|
+
public reset(): void {
|
|
188
|
+
this.state = "CLOSED";
|
|
189
|
+
this.failureCount = 0;
|
|
190
|
+
this.successCount = 0;
|
|
191
|
+
this.lastFailureTime = undefined;
|
|
192
|
+
this.nextAttemptTime = undefined;
|
|
193
|
+
this.failures.length = 0;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Try to transition from OPEN to HALF_OPEN if timeout has passed
|
|
198
|
+
*/
|
|
199
|
+
private tryTransitionToHalfOpen(): void {
|
|
200
|
+
if (this.state === "OPEN" && this.nextAttemptTime) {
|
|
201
|
+
if (Date.now() >= this.nextAttemptTime) {
|
|
202
|
+
this.state = "HALF_OPEN";
|
|
203
|
+
this.successCount = 0;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Handle successful operation
|
|
210
|
+
*/
|
|
211
|
+
private onSuccess(): void {
|
|
212
|
+
if (this.state === "HALF_OPEN") {
|
|
213
|
+
this.successCount++;
|
|
214
|
+
if (this.successCount >= this.successThreshold) {
|
|
215
|
+
// Close the circuit - service has recovered
|
|
216
|
+
this.state = "CLOSED";
|
|
217
|
+
this.failureCount = 0;
|
|
218
|
+
this.successCount = 0;
|
|
219
|
+
this.failures.length = 0;
|
|
220
|
+
this.lastFailureTime = undefined;
|
|
221
|
+
this.nextAttemptTime = undefined;
|
|
222
|
+
}
|
|
223
|
+
} else if (this.state === "CLOSED") {
|
|
224
|
+
// Reset failure count on success in CLOSED state
|
|
225
|
+
this.failureCount = Math.max(0, this.failureCount - 1);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Handle failed operation
|
|
231
|
+
*/
|
|
232
|
+
private onFailure(): void {
|
|
233
|
+
const now = Date.now();
|
|
234
|
+
this.lastFailureTime = now;
|
|
235
|
+
|
|
236
|
+
if (this.state === "HALF_OPEN") {
|
|
237
|
+
// Failed while testing - reopen circuit
|
|
238
|
+
this.state = "OPEN";
|
|
239
|
+
this.nextAttemptTime = now + this.timeout;
|
|
240
|
+
this.successCount = 0;
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Track failure with timestamp
|
|
245
|
+
this.failures.push(now);
|
|
246
|
+
|
|
247
|
+
// Remove failures outside the window
|
|
248
|
+
const windowStart = now - this.windowSize;
|
|
249
|
+
while (this.failures.length > 0 && this.failures[0]! < windowStart) {
|
|
250
|
+
this.failures.shift();
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Count failures in current window
|
|
254
|
+
this.failureCount = this.failures.length;
|
|
255
|
+
|
|
256
|
+
// Open circuit if threshold exceeded
|
|
257
|
+
if (this.failureCount >= this.failureThreshold) {
|
|
258
|
+
this.state = "OPEN";
|
|
259
|
+
this.nextAttemptTime = now + this.timeout;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|