@zintrust/workers 0.1.27
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/README.md +861 -0
- package/dist/AnomalyDetection.d.ts +102 -0
- package/dist/AnomalyDetection.js +321 -0
- package/dist/AutoScaler.d.ts +127 -0
- package/dist/AutoScaler.js +425 -0
- package/dist/BroadcastWorker.d.ts +21 -0
- package/dist/BroadcastWorker.js +24 -0
- package/dist/CanaryController.d.ts +103 -0
- package/dist/CanaryController.js +380 -0
- package/dist/ChaosEngineering.d.ts +79 -0
- package/dist/ChaosEngineering.js +216 -0
- package/dist/CircuitBreaker.d.ts +106 -0
- package/dist/CircuitBreaker.js +374 -0
- package/dist/ClusterLock.d.ts +90 -0
- package/dist/ClusterLock.js +385 -0
- package/dist/ComplianceManager.d.ts +177 -0
- package/dist/ComplianceManager.js +556 -0
- package/dist/DatacenterOrchestrator.d.ts +133 -0
- package/dist/DatacenterOrchestrator.js +404 -0
- package/dist/DeadLetterQueue.d.ts +122 -0
- package/dist/DeadLetterQueue.js +539 -0
- package/dist/HealthMonitor.d.ts +42 -0
- package/dist/HealthMonitor.js +301 -0
- package/dist/MultiQueueWorker.d.ts +89 -0
- package/dist/MultiQueueWorker.js +277 -0
- package/dist/NotificationWorker.d.ts +21 -0
- package/dist/NotificationWorker.js +23 -0
- package/dist/Observability.d.ts +153 -0
- package/dist/Observability.js +530 -0
- package/dist/PluginManager.d.ts +123 -0
- package/dist/PluginManager.js +392 -0
- package/dist/PriorityQueue.d.ts +117 -0
- package/dist/PriorityQueue.js +244 -0
- package/dist/ResourceMonitor.d.ts +164 -0
- package/dist/ResourceMonitor.js +605 -0
- package/dist/SLAMonitor.d.ts +110 -0
- package/dist/SLAMonitor.js +274 -0
- package/dist/WorkerFactory.d.ts +193 -0
- package/dist/WorkerFactory.js +1507 -0
- package/dist/WorkerInit.d.ts +85 -0
- package/dist/WorkerInit.js +223 -0
- package/dist/WorkerMetrics.d.ts +114 -0
- package/dist/WorkerMetrics.js +509 -0
- package/dist/WorkerRegistry.d.ts +145 -0
- package/dist/WorkerRegistry.js +319 -0
- package/dist/WorkerShutdown.d.ts +61 -0
- package/dist/WorkerShutdown.js +159 -0
- package/dist/WorkerVersioning.d.ts +107 -0
- package/dist/WorkerVersioning.js +300 -0
- package/dist/build-manifest.json +462 -0
- package/dist/config/workerConfig.d.ts +3 -0
- package/dist/config/workerConfig.js +19 -0
- package/dist/createQueueWorker.d.ts +23 -0
- package/dist/createQueueWorker.js +113 -0
- package/dist/dashboard/index.d.ts +1 -0
- package/dist/dashboard/index.js +1 -0
- package/dist/dashboard/types.d.ts +117 -0
- package/dist/dashboard/types.js +1 -0
- package/dist/dashboard/workers-api.d.ts +4 -0
- package/dist/dashboard/workers-api.js +638 -0
- package/dist/dashboard/workers-dashboard-ui.d.ts +3 -0
- package/dist/dashboard/workers-dashboard-ui.js +1026 -0
- package/dist/dashboard/workers-dashboard.d.ts +4 -0
- package/dist/dashboard/workers-dashboard.js +904 -0
- package/dist/helper/index.d.ts +5 -0
- package/dist/helper/index.js +10 -0
- package/dist/http/WorkerApiController.d.ts +38 -0
- package/dist/http/WorkerApiController.js +312 -0
- package/dist/http/WorkerController.d.ts +374 -0
- package/dist/http/WorkerController.js +1351 -0
- package/dist/http/middleware/CustomValidation.d.ts +92 -0
- package/dist/http/middleware/CustomValidation.js +270 -0
- package/dist/http/middleware/DatacenterValidator.d.ts +3 -0
- package/dist/http/middleware/DatacenterValidator.js +94 -0
- package/dist/http/middleware/EditWorkerValidation.d.ts +7 -0
- package/dist/http/middleware/EditWorkerValidation.js +55 -0
- package/dist/http/middleware/FeaturesValidator.d.ts +3 -0
- package/dist/http/middleware/FeaturesValidator.js +60 -0
- package/dist/http/middleware/InfrastructureValidator.d.ts +31 -0
- package/dist/http/middleware/InfrastructureValidator.js +226 -0
- package/dist/http/middleware/OptionsValidator.d.ts +3 -0
- package/dist/http/middleware/OptionsValidator.js +112 -0
- package/dist/http/middleware/PayloadSanitizer.d.ts +7 -0
- package/dist/http/middleware/PayloadSanitizer.js +42 -0
- package/dist/http/middleware/ProcessorPathSanitizer.d.ts +3 -0
- package/dist/http/middleware/ProcessorPathSanitizer.js +74 -0
- package/dist/http/middleware/QueueNameSanitizer.d.ts +3 -0
- package/dist/http/middleware/QueueNameSanitizer.js +45 -0
- package/dist/http/middleware/ValidateDriver.d.ts +7 -0
- package/dist/http/middleware/ValidateDriver.js +20 -0
- package/dist/http/middleware/VersionSanitizer.d.ts +3 -0
- package/dist/http/middleware/VersionSanitizer.js +25 -0
- package/dist/http/middleware/WorkerNameSanitizer.d.ts +3 -0
- package/dist/http/middleware/WorkerNameSanitizer.js +46 -0
- package/dist/http/middleware/WorkerValidationChain.d.ts +27 -0
- package/dist/http/middleware/WorkerValidationChain.js +185 -0
- package/dist/index.d.ts +46 -0
- package/dist/index.js +48 -0
- package/dist/routes/workers.d.ts +12 -0
- package/dist/routes/workers.js +81 -0
- package/dist/storage/WorkerStore.d.ts +45 -0
- package/dist/storage/WorkerStore.js +195 -0
- package/dist/type.d.ts +76 -0
- package/dist/type.js +1 -0
- package/dist/ui/router/ui.d.ts +3 -0
- package/dist/ui/router/ui.js +83 -0
- package/dist/ui/types/worker-ui.d.ts +229 -0
- package/dist/ui/types/worker-ui.js +5 -0
- package/package.json +53 -0
- package/src/AnomalyDetection.ts +434 -0
- package/src/AutoScaler.ts +654 -0
- package/src/BroadcastWorker.ts +34 -0
- package/src/CanaryController.ts +531 -0
- package/src/ChaosEngineering.ts +301 -0
- package/src/CircuitBreaker.ts +495 -0
- package/src/ClusterLock.ts +499 -0
- package/src/ComplianceManager.ts +815 -0
- package/src/DatacenterOrchestrator.ts +561 -0
- package/src/DeadLetterQueue.ts +733 -0
- package/src/HealthMonitor.ts +390 -0
- package/src/MultiQueueWorker.ts +431 -0
- package/src/NotificationWorker.ts +33 -0
- package/src/Observability.ts +696 -0
- package/src/PluginManager.ts +551 -0
- package/src/PriorityQueue.ts +351 -0
- package/src/ResourceMonitor.ts +769 -0
- package/src/SLAMonitor.ts +408 -0
- package/src/WorkerFactory.ts +2108 -0
- package/src/WorkerInit.ts +313 -0
- package/src/WorkerMetrics.ts +709 -0
- package/src/WorkerRegistry.ts +443 -0
- package/src/WorkerShutdown.ts +210 -0
- package/src/WorkerVersioning.ts +422 -0
- package/src/config/workerConfig.ts +25 -0
- package/src/createQueueWorker.ts +174 -0
- package/src/dashboard/index.ts +6 -0
- package/src/dashboard/types.ts +141 -0
- package/src/dashboard/workers-api.ts +785 -0
- package/src/dashboard/zintrust.svg +30 -0
- package/src/helper/index.ts +11 -0
- package/src/http/WorkerApiController.ts +369 -0
- package/src/http/WorkerController.ts +1512 -0
- package/src/http/middleware/CustomValidation.ts +360 -0
- package/src/http/middleware/DatacenterValidator.ts +124 -0
- package/src/http/middleware/EditWorkerValidation.ts +74 -0
- package/src/http/middleware/FeaturesValidator.ts +82 -0
- package/src/http/middleware/InfrastructureValidator.ts +295 -0
- package/src/http/middleware/OptionsValidator.ts +144 -0
- package/src/http/middleware/PayloadSanitizer.ts +52 -0
- package/src/http/middleware/ProcessorPathSanitizer.ts +86 -0
- package/src/http/middleware/QueueNameSanitizer.ts +55 -0
- package/src/http/middleware/ValidateDriver.ts +29 -0
- package/src/http/middleware/VersionSanitizer.ts +30 -0
- package/src/http/middleware/WorkerNameSanitizer.ts +56 -0
- package/src/http/middleware/WorkerValidationChain.ts +230 -0
- package/src/index.ts +98 -0
- package/src/routes/workers.ts +154 -0
- package/src/storage/WorkerStore.ts +240 -0
- package/src/type.ts +89 -0
- package/src/types/queue-monitor.d.ts +38 -0
- package/src/types/queue-redis.d.ts +38 -0
- package/src/ui/README.md +13 -0
- package/src/ui/components/JsonEditor.js +670 -0
- package/src/ui/components/JsonViewer.js +387 -0
- package/src/ui/components/WorkerCard.js +178 -0
- package/src/ui/components/WorkerExpandPanel.js +257 -0
- package/src/ui/components/fetcher.js +42 -0
- package/src/ui/components/sla-scorecard.js +32 -0
- package/src/ui/components/styles.css +30 -0
- package/src/ui/components/table-expander.js +34 -0
- package/src/ui/integration/worker-ui-integration.js +565 -0
- package/src/ui/router/ui.ts +99 -0
- package/src/ui/services/workerApi.js +240 -0
- package/src/ui/types/worker-ui.ts +283 -0
- package/src/ui/utils/jsonValidator.js +444 -0
- package/src/ui/workers/index.html +202 -0
- package/src/ui/workers/main.js +1781 -0
- package/src/ui/workers/styles.css +1350 -0
|
@@ -0,0 +1,495 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Circuit Breaker Pattern
|
|
3
|
+
* Fault tolerance with version tracking and automatic recovery
|
|
4
|
+
* Sealed namespace for immutability
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { Logger } from '@zintrust/core';
|
|
8
|
+
|
|
9
|
+
export type CircuitState = 'closed' | 'open' | 'half-open';
|
|
10
|
+
|
|
11
|
+
export type CircuitBreakerConfig = {
|
|
12
|
+
failureThreshold: number; // Number of failures before opening
|
|
13
|
+
successThreshold: number; // Number of successes to close from half-open
|
|
14
|
+
timeout: number; // Time in ms before attempting to recover (half-open)
|
|
15
|
+
resetTimeout: number; // Time in ms to reset failure count in closed state
|
|
16
|
+
volumeThreshold: number; // Minimum requests before considering failure rate
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export type CircuitBreakerState = {
|
|
20
|
+
workerName: string;
|
|
21
|
+
version: string;
|
|
22
|
+
state: CircuitState;
|
|
23
|
+
failureCount: number;
|
|
24
|
+
successCount: number;
|
|
25
|
+
totalRequests: number;
|
|
26
|
+
lastFailureTime: Date | null;
|
|
27
|
+
lastSuccessTime: Date | null;
|
|
28
|
+
lastStateChange: Date;
|
|
29
|
+
nextRetryTime: Date | null;
|
|
30
|
+
errorRate: number;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export type CircuitBreakerEvent = {
|
|
34
|
+
workerName: string;
|
|
35
|
+
version: string;
|
|
36
|
+
event: 'opened' | 'closed' | 'half-open' | 'success' | 'failure' | 'rejected';
|
|
37
|
+
state: CircuitState;
|
|
38
|
+
timestamp: Date;
|
|
39
|
+
reason?: string;
|
|
40
|
+
error?: Error;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// Internal state
|
|
44
|
+
const circuits = new Map<string, CircuitBreakerState>();
|
|
45
|
+
const eventHistory = new Map<string, CircuitBreakerEvent[]>();
|
|
46
|
+
const defaultConfig: CircuitBreakerConfig = {
|
|
47
|
+
failureThreshold: 5,
|
|
48
|
+
successThreshold: 3,
|
|
49
|
+
timeout: 60000, // 1 minute
|
|
50
|
+
resetTimeout: 300000, // 5 minutes
|
|
51
|
+
volumeThreshold: 10,
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Helper: Get circuit key
|
|
56
|
+
*/
|
|
57
|
+
const getCircuitKey = (workerName: string, version: string): string => {
|
|
58
|
+
return `${workerName}:${version}`;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Helper: Record event
|
|
63
|
+
*/
|
|
64
|
+
const recordEvent = (event: CircuitBreakerEvent): void => {
|
|
65
|
+
const key = getCircuitKey(event.workerName, event.version);
|
|
66
|
+
let history = eventHistory.get(key);
|
|
67
|
+
|
|
68
|
+
if (!history) {
|
|
69
|
+
history = [];
|
|
70
|
+
eventHistory.set(key, history);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
history.push(event);
|
|
74
|
+
|
|
75
|
+
// Keep only last 1000 events
|
|
76
|
+
if (history.length > 1000) {
|
|
77
|
+
history.shift();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
Logger.debug(`Circuit breaker event: ${event.workerName}:${event.version} - ${event.event}`, {
|
|
81
|
+
state: event.state,
|
|
82
|
+
reason: event.reason,
|
|
83
|
+
});
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Helper: Transition to new state
|
|
88
|
+
*/
|
|
89
|
+
const transitionState = (
|
|
90
|
+
circuit: CircuitBreakerState,
|
|
91
|
+
newState: CircuitState,
|
|
92
|
+
reason: string
|
|
93
|
+
): void => {
|
|
94
|
+
const oldState = circuit.state;
|
|
95
|
+
circuit.state = newState;
|
|
96
|
+
circuit.lastStateChange = new Date();
|
|
97
|
+
|
|
98
|
+
if (newState === 'open') {
|
|
99
|
+
circuit.nextRetryTime = new Date(Date.now() + defaultConfig.timeout);
|
|
100
|
+
} else if (newState === 'closed') {
|
|
101
|
+
circuit.failureCount = 0;
|
|
102
|
+
circuit.successCount = 0;
|
|
103
|
+
circuit.nextRetryTime = null;
|
|
104
|
+
} else if (newState === 'half-open') {
|
|
105
|
+
circuit.successCount = 0;
|
|
106
|
+
circuit.nextRetryTime = null;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
Logger.info(`Circuit breaker state transition: ${circuit.workerName}:${circuit.version}`, {
|
|
110
|
+
from: oldState,
|
|
111
|
+
to: newState,
|
|
112
|
+
reason,
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
const eventType: CircuitBreakerEvent['event'] = newState === 'open' ? 'opened' : newState;
|
|
116
|
+
|
|
117
|
+
recordEvent({
|
|
118
|
+
workerName: circuit.workerName,
|
|
119
|
+
version: circuit.version,
|
|
120
|
+
event: eventType,
|
|
121
|
+
state: newState,
|
|
122
|
+
timestamp: new Date(),
|
|
123
|
+
reason,
|
|
124
|
+
});
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Helper: Calculate error rate
|
|
129
|
+
*/
|
|
130
|
+
const calculateErrorRate = (circuit: CircuitBreakerState): number => {
|
|
131
|
+
if (circuit.totalRequests === 0) {
|
|
132
|
+
return 0;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return (circuit.failureCount / circuit.totalRequests) * 100;
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Helper: Check if should reset failure count
|
|
140
|
+
*/
|
|
141
|
+
const shouldResetFailureCount = (circuit: CircuitBreakerState): boolean => {
|
|
142
|
+
if (!circuit.lastFailureTime) {
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const timeSinceLastFailure = Date.now() - circuit.lastFailureTime.getTime();
|
|
147
|
+
return timeSinceLastFailure > defaultConfig.resetTimeout;
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Circuit Breaker - Sealed namespace
|
|
152
|
+
*/
|
|
153
|
+
export const CircuitBreaker = Object.freeze({
|
|
154
|
+
/**
|
|
155
|
+
* Initialize circuit breaker for a worker version
|
|
156
|
+
*/
|
|
157
|
+
initialize(workerName: string, version: string): void {
|
|
158
|
+
const key = getCircuitKey(workerName, version);
|
|
159
|
+
|
|
160
|
+
if (circuits.has(key)) {
|
|
161
|
+
Logger.debug(`Circuit breaker already exists: ${key}`);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const circuit: CircuitBreakerState = {
|
|
166
|
+
workerName,
|
|
167
|
+
version,
|
|
168
|
+
state: 'closed',
|
|
169
|
+
failureCount: 0,
|
|
170
|
+
successCount: 0,
|
|
171
|
+
totalRequests: 0,
|
|
172
|
+
lastFailureTime: null,
|
|
173
|
+
lastSuccessTime: null,
|
|
174
|
+
lastStateChange: new Date(),
|
|
175
|
+
nextRetryTime: null,
|
|
176
|
+
errorRate: 0,
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
circuits.set(key, circuit);
|
|
180
|
+
|
|
181
|
+
Logger.info(`Circuit breaker initialized: ${key}`);
|
|
182
|
+
},
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Check if circuit allows execution
|
|
186
|
+
*/
|
|
187
|
+
canExecute(workerName: string, version: string): boolean {
|
|
188
|
+
const key = getCircuitKey(workerName, version);
|
|
189
|
+
const circuit = circuits.get(key);
|
|
190
|
+
|
|
191
|
+
if (!circuit) {
|
|
192
|
+
// No circuit exists, create and allow
|
|
193
|
+
CircuitBreaker.initialize(workerName, version);
|
|
194
|
+
return true;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const now = Date.now();
|
|
198
|
+
|
|
199
|
+
// Reset failure count if enough time has passed
|
|
200
|
+
if (circuit.state === 'closed' && shouldResetFailureCount(circuit)) {
|
|
201
|
+
circuit.failureCount = 0;
|
|
202
|
+
circuit.totalRequests = 0;
|
|
203
|
+
Logger.debug(`Reset failure count for ${key}`);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
switch (circuit.state) {
|
|
207
|
+
case 'closed':
|
|
208
|
+
return true;
|
|
209
|
+
|
|
210
|
+
case 'open':
|
|
211
|
+
// Check if timeout has passed to try half-open
|
|
212
|
+
if (circuit.nextRetryTime && now >= circuit.nextRetryTime.getTime()) {
|
|
213
|
+
transitionState(circuit, 'half-open', 'Timeout elapsed, attempting recovery');
|
|
214
|
+
return true;
|
|
215
|
+
}
|
|
216
|
+
return false;
|
|
217
|
+
|
|
218
|
+
case 'half-open':
|
|
219
|
+
// Allow limited requests to test recovery
|
|
220
|
+
return true;
|
|
221
|
+
}
|
|
222
|
+
},
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Record successful execution
|
|
226
|
+
*/
|
|
227
|
+
recordSuccess(workerName: string, version: string): void {
|
|
228
|
+
const key = getCircuitKey(workerName, version);
|
|
229
|
+
const circuit = circuits.get(key);
|
|
230
|
+
|
|
231
|
+
if (!circuit) {
|
|
232
|
+
CircuitBreaker.initialize(workerName, version);
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
circuit.successCount++;
|
|
237
|
+
circuit.totalRequests++;
|
|
238
|
+
circuit.lastSuccessTime = new Date();
|
|
239
|
+
circuit.errorRate = calculateErrorRate(circuit);
|
|
240
|
+
|
|
241
|
+
recordEvent({
|
|
242
|
+
workerName,
|
|
243
|
+
version,
|
|
244
|
+
event: 'success',
|
|
245
|
+
state: circuit.state,
|
|
246
|
+
timestamp: new Date(),
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
// Transition based on state
|
|
250
|
+
if (circuit.state === 'half-open') {
|
|
251
|
+
if (circuit.successCount >= defaultConfig.successThreshold) {
|
|
252
|
+
transitionState(circuit, 'closed', `${circuit.successCount} consecutive successes`);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
},
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Record failed execution
|
|
259
|
+
*/
|
|
260
|
+
recordFailure(workerName: string, version: string, error: Error): void {
|
|
261
|
+
const key = getCircuitKey(workerName, version);
|
|
262
|
+
const circuit = circuits.get(key);
|
|
263
|
+
|
|
264
|
+
if (!circuit) {
|
|
265
|
+
CircuitBreaker.initialize(workerName, version);
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
circuit.failureCount++;
|
|
270
|
+
circuit.totalRequests++;
|
|
271
|
+
circuit.lastFailureTime = new Date();
|
|
272
|
+
circuit.errorRate = calculateErrorRate(circuit);
|
|
273
|
+
|
|
274
|
+
recordEvent({
|
|
275
|
+
workerName,
|
|
276
|
+
version,
|
|
277
|
+
event: 'failure',
|
|
278
|
+
state: circuit.state,
|
|
279
|
+
timestamp: new Date(),
|
|
280
|
+
error,
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
// Transition based on state and thresholds
|
|
284
|
+
if (circuit.state === 'half-open') {
|
|
285
|
+
// Any failure in half-open reopens the circuit
|
|
286
|
+
transitionState(circuit, 'open', 'Failure during recovery attempt');
|
|
287
|
+
} else if (circuit.state === 'closed') {
|
|
288
|
+
// Check if should open based on failure threshold
|
|
289
|
+
if (circuit.totalRequests >= defaultConfig.volumeThreshold) {
|
|
290
|
+
if (circuit.failureCount >= defaultConfig.failureThreshold) {
|
|
291
|
+
transitionState(
|
|
292
|
+
circuit,
|
|
293
|
+
'open',
|
|
294
|
+
`Failure threshold exceeded: ${circuit.failureCount}/${defaultConfig.failureThreshold}`
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
},
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Record rejected execution (when circuit is open)
|
|
303
|
+
*/
|
|
304
|
+
recordRejection(workerName: string, version: string): void {
|
|
305
|
+
const key = getCircuitKey(workerName, version);
|
|
306
|
+
const circuit = circuits.get(key);
|
|
307
|
+
|
|
308
|
+
if (!circuit) {
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
recordEvent({
|
|
313
|
+
workerName,
|
|
314
|
+
version,
|
|
315
|
+
event: 'rejected',
|
|
316
|
+
state: circuit.state,
|
|
317
|
+
timestamp: new Date(),
|
|
318
|
+
reason: 'Circuit breaker is open',
|
|
319
|
+
});
|
|
320
|
+
},
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Get circuit state
|
|
324
|
+
*/
|
|
325
|
+
getState(workerName: string, version: string): CircuitBreakerState | null {
|
|
326
|
+
const key = getCircuitKey(workerName, version);
|
|
327
|
+
const circuit = circuits.get(key);
|
|
328
|
+
|
|
329
|
+
return circuit ? { ...circuit } : null;
|
|
330
|
+
},
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Get all circuit states
|
|
334
|
+
*/
|
|
335
|
+
getAllStates(): ReadonlyArray<CircuitBreakerState> {
|
|
336
|
+
return Array.from(circuits.values()).map((circuit) => ({ ...circuit }));
|
|
337
|
+
},
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Get circuit states by worker name (all versions)
|
|
341
|
+
*/
|
|
342
|
+
getStatesByWorker(workerName: string): ReadonlyArray<CircuitBreakerState> {
|
|
343
|
+
const states: CircuitBreakerState[] = [];
|
|
344
|
+
|
|
345
|
+
for (const circuit of circuits.values()) {
|
|
346
|
+
if (circuit.workerName === workerName) {
|
|
347
|
+
states.push({ ...circuit });
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
return states;
|
|
352
|
+
},
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Get event history
|
|
356
|
+
*/
|
|
357
|
+
getEventHistory(
|
|
358
|
+
workerName: string,
|
|
359
|
+
version: string,
|
|
360
|
+
limit = 100
|
|
361
|
+
): ReadonlyArray<CircuitBreakerEvent> {
|
|
362
|
+
const key = getCircuitKey(workerName, version);
|
|
363
|
+
const history = eventHistory.get(key) ?? [];
|
|
364
|
+
|
|
365
|
+
return history.slice(-limit).map((event) => ({ ...event }));
|
|
366
|
+
},
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Manually reset circuit to closed state
|
|
370
|
+
*/
|
|
371
|
+
reset(workerName: string, version: string): void {
|
|
372
|
+
const key = getCircuitKey(workerName, version);
|
|
373
|
+
const circuit = circuits.get(key);
|
|
374
|
+
|
|
375
|
+
if (!circuit) {
|
|
376
|
+
Logger.warn(`Circuit breaker not found: ${key}`);
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
transitionState(circuit, 'closed', 'Manual reset');
|
|
381
|
+
circuit.failureCount = 0;
|
|
382
|
+
circuit.successCount = 0;
|
|
383
|
+
circuit.totalRequests = 0;
|
|
384
|
+
circuit.errorRate = 0;
|
|
385
|
+
|
|
386
|
+
Logger.info(`Circuit breaker manually reset: ${key}`);
|
|
387
|
+
},
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Manually force circuit to open state
|
|
391
|
+
*/
|
|
392
|
+
forceOpen(workerName: string, version: string, reason: string): void {
|
|
393
|
+
const key = getCircuitKey(workerName, version);
|
|
394
|
+
const circuit = circuits.get(key);
|
|
395
|
+
|
|
396
|
+
if (!circuit) {
|
|
397
|
+
CircuitBreaker.initialize(workerName, version);
|
|
398
|
+
const newCircuit = circuits.get(key);
|
|
399
|
+
if (!newCircuit) {
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
transitionState(newCircuit, 'open', `Forced open: ${reason}`);
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
transitionState(circuit, 'open', `Forced open: ${reason}`);
|
|
407
|
+
|
|
408
|
+
Logger.warn(`Circuit breaker forced open: ${key}`, { reason });
|
|
409
|
+
},
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Delete circuit breaker
|
|
413
|
+
*/
|
|
414
|
+
delete(workerName: string, version: string): void {
|
|
415
|
+
const key = getCircuitKey(workerName, version);
|
|
416
|
+
circuits.delete(key);
|
|
417
|
+
eventHistory.delete(key);
|
|
418
|
+
|
|
419
|
+
Logger.info(`Circuit breaker deleted: ${key}`);
|
|
420
|
+
},
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* Delete all circuit breakers for a worker (all versions)
|
|
424
|
+
*/
|
|
425
|
+
deleteWorker(workerName: string): void {
|
|
426
|
+
const keysToDelete: string[] = [];
|
|
427
|
+
|
|
428
|
+
for (const [key, circuit] of circuits.entries()) {
|
|
429
|
+
if (circuit.workerName === workerName) {
|
|
430
|
+
keysToDelete.push(key);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
for (const key of keysToDelete) {
|
|
435
|
+
circuits.delete(key);
|
|
436
|
+
eventHistory.delete(key);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
Logger.info(`Deleted all circuit breakers for worker: ${workerName}`, {
|
|
440
|
+
count: keysToDelete.length,
|
|
441
|
+
});
|
|
442
|
+
},
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* Get summary statistics
|
|
446
|
+
*/
|
|
447
|
+
getSummary(): {
|
|
448
|
+
totalCircuits: number;
|
|
449
|
+
openCircuits: number;
|
|
450
|
+
halfOpenCircuits: number;
|
|
451
|
+
closedCircuits: number;
|
|
452
|
+
circuitsByWorker: Record<string, number>;
|
|
453
|
+
} {
|
|
454
|
+
const summary = {
|
|
455
|
+
totalCircuits: circuits.size,
|
|
456
|
+
openCircuits: 0,
|
|
457
|
+
halfOpenCircuits: 0,
|
|
458
|
+
closedCircuits: 0,
|
|
459
|
+
circuitsByWorker: {} as Record<string, number>,
|
|
460
|
+
};
|
|
461
|
+
|
|
462
|
+
for (const circuit of circuits.values()) {
|
|
463
|
+
switch (circuit.state) {
|
|
464
|
+
case 'open':
|
|
465
|
+
summary.openCircuits++;
|
|
466
|
+
break;
|
|
467
|
+
case 'half-open':
|
|
468
|
+
summary.halfOpenCircuits++;
|
|
469
|
+
break;
|
|
470
|
+
case 'closed':
|
|
471
|
+
summary.closedCircuits++;
|
|
472
|
+
break;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
summary.circuitsByWorker[circuit.workerName] =
|
|
476
|
+
(summary.circuitsByWorker[circuit.workerName] || 0) + 1;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
return summary;
|
|
480
|
+
},
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* Shutdown and clear all circuits
|
|
484
|
+
*/
|
|
485
|
+
shutdown(): void {
|
|
486
|
+
Logger.info('CircuitBreaker shutting down...');
|
|
487
|
+
|
|
488
|
+
circuits.clear();
|
|
489
|
+
eventHistory.clear();
|
|
490
|
+
|
|
491
|
+
Logger.info('CircuitBreaker shutdown complete');
|
|
492
|
+
},
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
// Graceful shutdown handled by WorkerShutdown
|