@unrdf/knowledge-engine 5.0.1
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/LICENSE +21 -0
- package/README.md +84 -0
- package/package.json +64 -0
- package/src/browser-shims.mjs +343 -0
- package/src/browser.mjs +910 -0
- package/src/canonicalize.mjs +414 -0
- package/src/condition-cache.mjs +109 -0
- package/src/condition-evaluator.mjs +722 -0
- package/src/dark-matter-core.mjs +742 -0
- package/src/define-hook.mjs +213 -0
- package/src/effect-sandbox-browser.mjs +283 -0
- package/src/effect-sandbox-worker.mjs +170 -0
- package/src/effect-sandbox.mjs +517 -0
- package/src/engines/index.mjs +11 -0
- package/src/engines/rdf-engine.mjs +299 -0
- package/src/file-resolver.mjs +387 -0
- package/src/hook-executor-batching.mjs +277 -0
- package/src/hook-executor.mjs +870 -0
- package/src/hook-management.mjs +150 -0
- package/src/index.mjs +93 -0
- package/src/ken-parliment.mjs +119 -0
- package/src/ken.mjs +149 -0
- package/src/knowledge-engine/builtin-rules.mjs +190 -0
- package/src/knowledge-engine/inference-engine.mjs +418 -0
- package/src/knowledge-engine/knowledge-engine.mjs +317 -0
- package/src/knowledge-engine/pattern-dsl.mjs +142 -0
- package/src/knowledge-engine/pattern-matcher.mjs +215 -0
- package/src/knowledge-engine/rules.mjs +184 -0
- package/src/knowledge-engine.mjs +319 -0
- package/src/knowledge-hook-engine.mjs +360 -0
- package/src/knowledge-hook-manager.mjs +469 -0
- package/src/knowledge-substrate-core.mjs +927 -0
- package/src/lite.mjs +222 -0
- package/src/lockchain-writer-browser.mjs +414 -0
- package/src/lockchain-writer.mjs +602 -0
- package/src/monitoring/andon-signals.mjs +775 -0
- package/src/observability.mjs +531 -0
- package/src/parse.mjs +290 -0
- package/src/performance-optimizer.mjs +678 -0
- package/src/policy-pack.mjs +572 -0
- package/src/query-cache.mjs +116 -0
- package/src/query-optimizer.mjs +1051 -0
- package/src/query.mjs +306 -0
- package/src/reason.mjs +350 -0
- package/src/resolution-layer.mjs +506 -0
- package/src/schemas.mjs +1063 -0
- package/src/security/error-sanitizer.mjs +257 -0
- package/src/security/path-validator.mjs +194 -0
- package/src/security/sandbox-restrictions.mjs +331 -0
- package/src/security-validator.mjs +389 -0
- package/src/store-cache.mjs +137 -0
- package/src/telemetry.mjs +167 -0
- package/src/transaction.mjs +810 -0
- package/src/utils/adaptive-monitor.mjs +746 -0
- package/src/utils/circuit-breaker.mjs +513 -0
- package/src/utils/edge-case-handler.mjs +503 -0
- package/src/utils/memory-manager.mjs +498 -0
- package/src/utils/ring-buffer.mjs +282 -0
- package/src/validate.mjs +319 -0
- package/src/validators/index.mjs +338 -0
|
@@ -0,0 +1,513 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Circuit Breaker Pattern for Fault Tolerance
|
|
3
|
+
* @module knowledge-engine/utils/circuit-breaker
|
|
4
|
+
*
|
|
5
|
+
* @description
|
|
6
|
+
* Implements the circuit breaker pattern for fault tolerance in distributed
|
|
7
|
+
* operations like federation, remote SPARQL endpoints, and external services.
|
|
8
|
+
*
|
|
9
|
+
* States:
|
|
10
|
+
* - CLOSED: Normal operation, requests pass through
|
|
11
|
+
* - OPEN: Circuit tripped, requests fail immediately
|
|
12
|
+
* - HALF_OPEN: Testing if service recovered
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { trace, SpanStatusCode } from '@opentelemetry/api';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Circuit breaker states
|
|
19
|
+
* @readonly
|
|
20
|
+
* @enum {string}
|
|
21
|
+
*/
|
|
22
|
+
export const CircuitState = Object.freeze({
|
|
23
|
+
CLOSED: 'closed',
|
|
24
|
+
OPEN: 'open',
|
|
25
|
+
HALF_OPEN: 'half_open',
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Circuit breaker configuration schema
|
|
30
|
+
* @typedef {Object} CircuitBreakerConfig
|
|
31
|
+
* @property {number} [failureThreshold=5] - Failures before opening circuit
|
|
32
|
+
* @property {number} [resetTimeout=30000] - Time in ms before trying half-open
|
|
33
|
+
* @property {number} [halfOpenMaxCalls=3] - Max calls in half-open state
|
|
34
|
+
* @property {number} [successThreshold=2] - Successes in half-open to close
|
|
35
|
+
* @property {string} [name='circuit-breaker'] - Name for tracing
|
|
36
|
+
* @property {Function} [onStateChange] - Callback on state transitions
|
|
37
|
+
* @property {Function} [isFailure] - Custom failure detection function
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Circuit breaker for fault tolerance
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* const breaker = new CircuitBreaker({
|
|
45
|
+
* failureThreshold: 5,
|
|
46
|
+
* resetTimeout: 30000,
|
|
47
|
+
* name: 'sparql-endpoint'
|
|
48
|
+
* });
|
|
49
|
+
*
|
|
50
|
+
* try {
|
|
51
|
+
* const result = await breaker.execute(() => fetch(sparqlEndpoint));
|
|
52
|
+
* } catch (error) {
|
|
53
|
+
* if (error.name === 'CircuitOpenError') {
|
|
54
|
+
* // Circuit is open, use cached data
|
|
55
|
+
* }
|
|
56
|
+
* }
|
|
57
|
+
*/
|
|
58
|
+
export class CircuitBreaker {
|
|
59
|
+
/**
|
|
60
|
+
* Create a new circuit breaker
|
|
61
|
+
* @param {CircuitBreakerConfig} [config={}] - Configuration options
|
|
62
|
+
*/
|
|
63
|
+
constructor(config = {}) {
|
|
64
|
+
this.state = CircuitState.CLOSED;
|
|
65
|
+
this.failureCount = 0;
|
|
66
|
+
this.successCount = 0;
|
|
67
|
+
this.halfOpenCalls = 0;
|
|
68
|
+
this.lastFailureTime = null;
|
|
69
|
+
this.lastStateChange = Date.now();
|
|
70
|
+
|
|
71
|
+
// Configuration with defaults
|
|
72
|
+
this.failureThreshold = config.failureThreshold ?? 5;
|
|
73
|
+
this.resetTimeout = config.resetTimeout ?? 30000;
|
|
74
|
+
this.halfOpenMaxCalls = config.halfOpenMaxCalls ?? 3;
|
|
75
|
+
this.successThreshold = config.successThreshold ?? 2;
|
|
76
|
+
this.name = config.name ?? 'circuit-breaker';
|
|
77
|
+
this.onStateChange = config.onStateChange ?? null;
|
|
78
|
+
this.isFailure = config.isFailure ?? (_error => true);
|
|
79
|
+
|
|
80
|
+
// Metrics
|
|
81
|
+
this.metrics = {
|
|
82
|
+
totalCalls: 0,
|
|
83
|
+
successfulCalls: 0,
|
|
84
|
+
failedCalls: 0,
|
|
85
|
+
rejectedCalls: 0,
|
|
86
|
+
stateChanges: 0,
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
// OTEL tracer
|
|
90
|
+
this.tracer = trace.getTracer('unrdf-circuit-breaker');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Execute a function with circuit breaker protection
|
|
95
|
+
* @param {Function} fn - Async function to execute
|
|
96
|
+
* @param {Object} [context={}] - Execution context for tracing
|
|
97
|
+
* @returns {Promise<any>} Result of the function
|
|
98
|
+
* @throws {CircuitOpenError} If circuit is open
|
|
99
|
+
* @throws {Error} If function execution fails
|
|
100
|
+
*/
|
|
101
|
+
async execute(fn, context = {}) {
|
|
102
|
+
return await this.tracer.startActiveSpan(`circuit-breaker.${this.name}`, async span => {
|
|
103
|
+
span.setAttributes({
|
|
104
|
+
'circuit.name': this.name,
|
|
105
|
+
'circuit.state': this.state,
|
|
106
|
+
'circuit.failure_count': this.failureCount,
|
|
107
|
+
...context,
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
this.metrics.totalCalls++;
|
|
111
|
+
|
|
112
|
+
try {
|
|
113
|
+
// Check if circuit should transition
|
|
114
|
+
this._checkStateTransition();
|
|
115
|
+
|
|
116
|
+
// Handle based on current state
|
|
117
|
+
if (this.state === CircuitState.OPEN) {
|
|
118
|
+
this.metrics.rejectedCalls++;
|
|
119
|
+
span.setAttributes({
|
|
120
|
+
'circuit.rejected': true,
|
|
121
|
+
'circuit.reason': 'circuit_open',
|
|
122
|
+
});
|
|
123
|
+
span.setStatus({
|
|
124
|
+
code: SpanStatusCode.ERROR,
|
|
125
|
+
message: 'Circuit is open',
|
|
126
|
+
});
|
|
127
|
+
throw new CircuitOpenError(
|
|
128
|
+
`Circuit breaker '${this.name}' is open`,
|
|
129
|
+
this.name,
|
|
130
|
+
this.lastFailureTime
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (this.state === CircuitState.HALF_OPEN) {
|
|
135
|
+
if (this.halfOpenCalls >= this.halfOpenMaxCalls) {
|
|
136
|
+
this.metrics.rejectedCalls++;
|
|
137
|
+
span.setAttributes({
|
|
138
|
+
'circuit.rejected': true,
|
|
139
|
+
'circuit.reason': 'half_open_limit',
|
|
140
|
+
});
|
|
141
|
+
span.setStatus({
|
|
142
|
+
code: SpanStatusCode.ERROR,
|
|
143
|
+
message: 'Half-open call limit reached',
|
|
144
|
+
});
|
|
145
|
+
throw new CircuitOpenError(
|
|
146
|
+
`Circuit breaker '${this.name}' half-open limit reached`,
|
|
147
|
+
this.name,
|
|
148
|
+
this.lastFailureTime
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
this.halfOpenCalls++;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Execute the function
|
|
155
|
+
const result = await fn();
|
|
156
|
+
|
|
157
|
+
// Record success
|
|
158
|
+
this.recordSuccess();
|
|
159
|
+
this.metrics.successfulCalls++;
|
|
160
|
+
span.setAttributes({
|
|
161
|
+
'circuit.success': true,
|
|
162
|
+
'circuit.final_state': this.state,
|
|
163
|
+
});
|
|
164
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
165
|
+
|
|
166
|
+
return result;
|
|
167
|
+
} catch (error) {
|
|
168
|
+
// Check if this is a circuit error (pass through)
|
|
169
|
+
if (error instanceof CircuitOpenError) {
|
|
170
|
+
span.end();
|
|
171
|
+
throw error;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Determine if this error counts as a failure
|
|
175
|
+
if (this.isFailure(error)) {
|
|
176
|
+
this.recordFailure();
|
|
177
|
+
this.metrics.failedCalls++;
|
|
178
|
+
span.setAttributes({
|
|
179
|
+
'circuit.failure': true,
|
|
180
|
+
'circuit.final_state': this.state,
|
|
181
|
+
'circuit.error': error.message,
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
span.recordException(error);
|
|
186
|
+
span.setStatus({
|
|
187
|
+
code: SpanStatusCode.ERROR,
|
|
188
|
+
message: error.message,
|
|
189
|
+
});
|
|
190
|
+
throw error;
|
|
191
|
+
} finally {
|
|
192
|
+
span.end();
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Record a successful operation
|
|
199
|
+
*/
|
|
200
|
+
recordSuccess() {
|
|
201
|
+
if (this.state === CircuitState.HALF_OPEN) {
|
|
202
|
+
this.successCount++;
|
|
203
|
+
if (this.successCount >= this.successThreshold) {
|
|
204
|
+
this.reset();
|
|
205
|
+
}
|
|
206
|
+
} else if (this.state === CircuitState.CLOSED) {
|
|
207
|
+
// Reset failure count on success in closed state
|
|
208
|
+
this.failureCount = 0;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Record a failed operation
|
|
214
|
+
*/
|
|
215
|
+
recordFailure() {
|
|
216
|
+
this.failureCount++;
|
|
217
|
+
this.lastFailureTime = Date.now();
|
|
218
|
+
|
|
219
|
+
if (this.state === CircuitState.HALF_OPEN) {
|
|
220
|
+
// Any failure in half-open trips the circuit again
|
|
221
|
+
this.trip();
|
|
222
|
+
} else if (this.state === CircuitState.CLOSED) {
|
|
223
|
+
if (this.failureCount >= this.failureThreshold) {
|
|
224
|
+
this.trip();
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Trip the circuit breaker (open the circuit)
|
|
231
|
+
*/
|
|
232
|
+
trip() {
|
|
233
|
+
if (this.state !== CircuitState.OPEN) {
|
|
234
|
+
const previousState = this.state;
|
|
235
|
+
this.state = CircuitState.OPEN;
|
|
236
|
+
this.lastStateChange = Date.now();
|
|
237
|
+
this.metrics.stateChanges++;
|
|
238
|
+
|
|
239
|
+
this._notifyStateChange(previousState, CircuitState.OPEN);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Reset the circuit breaker (close the circuit)
|
|
245
|
+
*/
|
|
246
|
+
reset() {
|
|
247
|
+
const previousState = this.state;
|
|
248
|
+
this.state = CircuitState.CLOSED;
|
|
249
|
+
this.failureCount = 0;
|
|
250
|
+
this.successCount = 0;
|
|
251
|
+
this.halfOpenCalls = 0;
|
|
252
|
+
this.lastStateChange = Date.now();
|
|
253
|
+
this.metrics.stateChanges++;
|
|
254
|
+
|
|
255
|
+
this._notifyStateChange(previousState, CircuitState.CLOSED);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Force the circuit into half-open state
|
|
260
|
+
*/
|
|
261
|
+
halfOpen() {
|
|
262
|
+
if (this.state !== CircuitState.HALF_OPEN) {
|
|
263
|
+
const previousState = this.state;
|
|
264
|
+
this.state = CircuitState.HALF_OPEN;
|
|
265
|
+
this.successCount = 0;
|
|
266
|
+
this.halfOpenCalls = 0;
|
|
267
|
+
this.lastStateChange = Date.now();
|
|
268
|
+
this.metrics.stateChanges++;
|
|
269
|
+
|
|
270
|
+
this._notifyStateChange(previousState, CircuitState.HALF_OPEN);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Check if state should transition based on timeout
|
|
276
|
+
* @private
|
|
277
|
+
*/
|
|
278
|
+
_checkStateTransition() {
|
|
279
|
+
if (this.state === CircuitState.OPEN) {
|
|
280
|
+
const timeSinceOpen = Date.now() - this.lastStateChange;
|
|
281
|
+
if (timeSinceOpen >= this.resetTimeout) {
|
|
282
|
+
this.halfOpen();
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Notify state change callback
|
|
289
|
+
* @param {string} from - Previous state
|
|
290
|
+
* @param {string} to - New state
|
|
291
|
+
* @private
|
|
292
|
+
*/
|
|
293
|
+
_notifyStateChange(from, to) {
|
|
294
|
+
if (this.onStateChange) {
|
|
295
|
+
try {
|
|
296
|
+
this.onStateChange({
|
|
297
|
+
name: this.name,
|
|
298
|
+
from,
|
|
299
|
+
to,
|
|
300
|
+
timestamp: Date.now(),
|
|
301
|
+
failureCount: this.failureCount,
|
|
302
|
+
metrics: { ...this.metrics },
|
|
303
|
+
});
|
|
304
|
+
} catch (error) {
|
|
305
|
+
// Ignore callback errors
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Get current circuit breaker status
|
|
312
|
+
* @returns {Object} Status object
|
|
313
|
+
*/
|
|
314
|
+
getStatus() {
|
|
315
|
+
return {
|
|
316
|
+
name: this.name,
|
|
317
|
+
state: this.state,
|
|
318
|
+
failureCount: this.failureCount,
|
|
319
|
+
successCount: this.successCount,
|
|
320
|
+
halfOpenCalls: this.halfOpenCalls,
|
|
321
|
+
lastFailureTime: this.lastFailureTime,
|
|
322
|
+
lastStateChange: this.lastStateChange,
|
|
323
|
+
config: {
|
|
324
|
+
failureThreshold: this.failureThreshold,
|
|
325
|
+
resetTimeout: this.resetTimeout,
|
|
326
|
+
halfOpenMaxCalls: this.halfOpenMaxCalls,
|
|
327
|
+
successThreshold: this.successThreshold,
|
|
328
|
+
},
|
|
329
|
+
metrics: { ...this.metrics },
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Check if circuit is healthy (closed)
|
|
335
|
+
* @returns {boolean} True if circuit is closed
|
|
336
|
+
*/
|
|
337
|
+
isHealthy() {
|
|
338
|
+
return this.state === CircuitState.CLOSED;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Check if circuit is open
|
|
343
|
+
* @returns {boolean} True if circuit is open
|
|
344
|
+
*/
|
|
345
|
+
isOpen() {
|
|
346
|
+
this._checkStateTransition();
|
|
347
|
+
return this.state === CircuitState.OPEN;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Check if circuit is half-open
|
|
352
|
+
* @returns {boolean} True if circuit is half-open
|
|
353
|
+
*/
|
|
354
|
+
isHalfOpen() {
|
|
355
|
+
this._checkStateTransition();
|
|
356
|
+
return this.state === CircuitState.HALF_OPEN;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Reset metrics counters
|
|
361
|
+
*/
|
|
362
|
+
resetMetrics() {
|
|
363
|
+
this.metrics = {
|
|
364
|
+
totalCalls: 0,
|
|
365
|
+
successfulCalls: 0,
|
|
366
|
+
failedCalls: 0,
|
|
367
|
+
rejectedCalls: 0,
|
|
368
|
+
stateChanges: 0,
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Error thrown when circuit is open
|
|
375
|
+
*/
|
|
376
|
+
export class CircuitOpenError extends Error {
|
|
377
|
+
/**
|
|
378
|
+
* Create a circuit open error
|
|
379
|
+
* @param {string} message - Error message
|
|
380
|
+
* @param {string} circuitName - Name of the circuit
|
|
381
|
+
* @param {number} lastFailureTime - Timestamp of last failure
|
|
382
|
+
*/
|
|
383
|
+
constructor(message, circuitName, lastFailureTime) {
|
|
384
|
+
super(message);
|
|
385
|
+
this.name = 'CircuitOpenError';
|
|
386
|
+
this.circuitName = circuitName;
|
|
387
|
+
this.lastFailureTime = lastFailureTime;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Circuit breaker registry for managing multiple breakers
|
|
393
|
+
*/
|
|
394
|
+
export class CircuitBreakerRegistry {
|
|
395
|
+
/**
|
|
396
|
+
*
|
|
397
|
+
*/
|
|
398
|
+
constructor() {
|
|
399
|
+
this.breakers = new Map();
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Get or create a circuit breaker
|
|
404
|
+
* @param {string} name - Breaker name
|
|
405
|
+
* @param {CircuitBreakerConfig} [config] - Config for new breaker
|
|
406
|
+
* @returns {CircuitBreaker} Circuit breaker instance
|
|
407
|
+
*/
|
|
408
|
+
getOrCreate(name, config = {}) {
|
|
409
|
+
if (!this.breakers.has(name)) {
|
|
410
|
+
this.breakers.set(name, new CircuitBreaker({ ...config, name }));
|
|
411
|
+
}
|
|
412
|
+
return this.breakers.get(name);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Get an existing circuit breaker
|
|
417
|
+
* @param {string} name - Breaker name
|
|
418
|
+
* @returns {CircuitBreaker|undefined} Circuit breaker or undefined
|
|
419
|
+
*/
|
|
420
|
+
get(name) {
|
|
421
|
+
return this.breakers.get(name);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Check if a breaker exists
|
|
426
|
+
* @param {string} name - Breaker name
|
|
427
|
+
* @returns {boolean} True if exists
|
|
428
|
+
*/
|
|
429
|
+
has(name) {
|
|
430
|
+
return this.breakers.has(name);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* Remove a circuit breaker
|
|
435
|
+
* @param {string} name - Breaker name
|
|
436
|
+
* @returns {boolean} True if removed
|
|
437
|
+
*/
|
|
438
|
+
remove(name) {
|
|
439
|
+
return this.breakers.delete(name);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Get all circuit breaker statuses
|
|
444
|
+
* @returns {Object} Map of name to status
|
|
445
|
+
*/
|
|
446
|
+
getAllStatuses() {
|
|
447
|
+
const statuses = {};
|
|
448
|
+
for (const [name, breaker] of this.breakers) {
|
|
449
|
+
statuses[name] = breaker.getStatus();
|
|
450
|
+
}
|
|
451
|
+
return statuses;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* Reset all circuit breakers
|
|
456
|
+
*/
|
|
457
|
+
resetAll() {
|
|
458
|
+
for (const breaker of this.breakers.values()) {
|
|
459
|
+
breaker.reset();
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* Get health summary of all breakers
|
|
465
|
+
* @returns {Object} Health summary
|
|
466
|
+
*/
|
|
467
|
+
getHealthSummary() {
|
|
468
|
+
let healthy = 0;
|
|
469
|
+
let open = 0;
|
|
470
|
+
let halfOpen = 0;
|
|
471
|
+
|
|
472
|
+
for (const breaker of this.breakers.values()) {
|
|
473
|
+
if (breaker.isHealthy()) healthy++;
|
|
474
|
+
else if (breaker.isOpen()) open++;
|
|
475
|
+
else if (breaker.isHalfOpen()) halfOpen++;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
return {
|
|
479
|
+
total: this.breakers.size,
|
|
480
|
+
healthy,
|
|
481
|
+
open,
|
|
482
|
+
halfOpen,
|
|
483
|
+
healthPercent: this.breakers.size > 0 ? (healthy / this.breakers.size) * 100 : 100,
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* Default global registry
|
|
490
|
+
*/
|
|
491
|
+
export const defaultRegistry = new CircuitBreakerRegistry();
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* Create a circuit breaker with default configuration
|
|
495
|
+
* @param {string} name - Breaker name
|
|
496
|
+
* @param {CircuitBreakerConfig} [config] - Configuration
|
|
497
|
+
* @returns {CircuitBreaker} Circuit breaker instance
|
|
498
|
+
*/
|
|
499
|
+
export function createCircuitBreaker(name, config = {}) {
|
|
500
|
+
return new CircuitBreaker({ ...config, name });
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* Decorator/wrapper function for adding circuit breaker to any async function
|
|
505
|
+
* @param {Function} fn - Async function to wrap
|
|
506
|
+
* @param {CircuitBreaker} breaker - Circuit breaker instance
|
|
507
|
+
* @returns {Function} Wrapped function
|
|
508
|
+
*/
|
|
509
|
+
export function withCircuitBreaker(fn, breaker) {
|
|
510
|
+
return async function (...args) {
|
|
511
|
+
return await breaker.execute(() => fn.apply(this, args));
|
|
512
|
+
};
|
|
513
|
+
}
|