@sip-protocol/sdk 0.7.3 → 0.7.4
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 +267 -0
- package/dist/{TransportWebUSB-TQ7WZ4LE.mjs → TransportWebUSB-YQMAGJAJ.mjs} +12 -9
- package/dist/browser.d.mts +10 -4
- package/dist/browser.d.ts +10 -4
- package/dist/browser.js +47556 -19603
- package/dist/browser.mjs +628 -48
- package/dist/chunk-4GRJ5MAW.mjs +152 -0
- package/dist/chunk-5D7A3L3W.mjs +717 -0
- package/dist/chunk-64AYA5F5.mjs +7834 -0
- package/dist/chunk-GMDGB22A.mjs +379 -0
- package/dist/chunk-I534WKN7.mjs +328 -0
- package/dist/chunk-IBZVA5Y7.mjs +1003 -0
- package/dist/chunk-PRRZAWJE.mjs +223 -0
- package/dist/{chunk-UJCSKKID.mjs → chunk-XGB3TDIC.mjs} +13 -1
- package/dist/{chunk-3M3HNQCW.mjs → chunk-YWGJ77A2.mjs} +28656 -13103
- package/dist/{chunk-6WGN57S2.mjs → chunk-Z3K7W5S3.mjs} +48 -0
- package/dist/constants-LHAAUC2T.mjs +51 -0
- package/dist/dist-2OGQ7FED.mjs +3957 -0
- package/dist/dist-IFHPYLDX.mjs +254 -0
- package/dist/fulfillment_proof-ANHVPKTB.mjs +21 -0
- package/dist/funding_proof-ICFZ5LHY.mjs +21 -0
- package/dist/{index-DIBZHOOQ.d.ts → index-DXh2IGkz.d.ts} +21239 -10304
- package/dist/{index-8MQz13eJ.d.mts → index-DeE1ZzA4.d.mts} +21239 -10304
- package/dist/index.d.mts +9 -3
- package/dist/index.d.ts +9 -3
- package/dist/index.js +48396 -19623
- package/dist/index.mjs +537 -19
- package/dist/interface-Bf7w1PLW.d.mts +679 -0
- package/dist/interface-Bf7w1PLW.d.ts +679 -0
- package/dist/{noir-DKfEzWy9.d.mts → noir-kzbLVTei.d.mts} +31 -21
- package/dist/{noir-DKfEzWy9.d.ts → noir-kzbLVTei.d.ts} +31 -21
- package/dist/proofs/halo2.d.mts +151 -0
- package/dist/proofs/halo2.d.ts +151 -0
- package/dist/proofs/halo2.js +350 -0
- package/dist/proofs/halo2.mjs +11 -0
- package/dist/proofs/kimchi.d.mts +160 -0
- package/dist/proofs/kimchi.d.ts +160 -0
- package/dist/proofs/kimchi.js +431 -0
- package/dist/proofs/kimchi.mjs +13 -0
- package/dist/proofs/noir.d.mts +1 -1
- package/dist/proofs/noir.d.ts +1 -1
- package/dist/proofs/noir.js +74 -18
- package/dist/proofs/noir.mjs +84 -24
- package/dist/solana-U3MEGU7W.mjs +280 -0
- package/dist/validity_proof-3POXLPNY.mjs +21 -0
- package/package.json +54 -21
- package/src/adapters/index.ts +41 -0
- package/src/adapters/jupiter.ts +571 -0
- package/src/adapters/near-intents.ts +135 -0
- package/src/advisor/advisor.ts +653 -0
- package/src/advisor/index.ts +54 -0
- package/src/advisor/tools.ts +303 -0
- package/src/advisor/types.ts +164 -0
- package/src/chains/ethereum/announcement.ts +536 -0
- package/src/chains/ethereum/bnb-optimizations.ts +474 -0
- package/src/chains/ethereum/commitment.ts +522 -0
- package/src/chains/ethereum/constants.ts +462 -0
- package/src/chains/ethereum/deployment.ts +596 -0
- package/src/chains/ethereum/gas-estimation.ts +538 -0
- package/src/chains/ethereum/index.ts +268 -0
- package/src/chains/ethereum/optimizations.ts +614 -0
- package/src/chains/ethereum/privacy-adapter.ts +855 -0
- package/src/chains/ethereum/registry.ts +584 -0
- package/src/chains/ethereum/rpc.ts +905 -0
- package/src/chains/ethereum/stealth.ts +491 -0
- package/src/chains/ethereum/token.ts +790 -0
- package/src/chains/ethereum/transfer.ts +637 -0
- package/src/chains/ethereum/types.ts +456 -0
- package/src/chains/ethereum/viewing-key.ts +455 -0
- package/src/chains/near/commitment.ts +608 -0
- package/src/chains/near/constants.ts +284 -0
- package/src/chains/near/function-call.ts +871 -0
- package/src/chains/near/history.ts +654 -0
- package/src/chains/near/implicit-account.ts +840 -0
- package/src/chains/near/index.ts +393 -0
- package/src/chains/near/native-transfer.ts +658 -0
- package/src/chains/near/nep141.ts +775 -0
- package/src/chains/near/privacy-adapter.ts +889 -0
- package/src/chains/near/resolver.ts +971 -0
- package/src/chains/near/rpc.ts +1016 -0
- package/src/chains/near/stealth.ts +419 -0
- package/src/chains/near/types.ts +317 -0
- package/src/chains/near/viewing-key.ts +876 -0
- package/src/chains/solana/anchor-transfer.ts +386 -0
- package/src/chains/solana/commitment.ts +577 -0
- package/src/chains/solana/constants.ts +126 -12
- package/src/chains/solana/ephemeral-keys.ts +543 -0
- package/src/chains/solana/index.ts +252 -1
- package/src/chains/solana/key-derivation.ts +418 -0
- package/src/chains/solana/kit-compat.ts +334 -0
- package/src/chains/solana/optimizations.ts +560 -0
- package/src/chains/solana/privacy-adapter.ts +605 -0
- package/src/chains/solana/providers/generic.ts +47 -6
- package/src/chains/solana/providers/helius-enhanced-types.ts +336 -0
- package/src/chains/solana/providers/helius-enhanced.ts +623 -0
- package/src/chains/solana/providers/helius.ts +186 -33
- package/src/chains/solana/providers/index.ts +31 -0
- package/src/chains/solana/providers/interface.ts +61 -18
- package/src/chains/solana/providers/quicknode.ts +409 -0
- package/src/chains/solana/providers/triton.ts +426 -0
- package/src/chains/solana/providers/webhook.ts +338 -67
- package/src/chains/solana/rpc-client.ts +1150 -0
- package/src/chains/solana/scan.ts +83 -66
- package/src/chains/solana/sol-transfer.ts +732 -0
- package/src/chains/solana/spl-transfer.ts +886 -0
- package/src/chains/solana/stealth-scanner.ts +703 -0
- package/src/chains/solana/sunspot-verifier.ts +453 -0
- package/src/chains/solana/transaction-builder.ts +755 -0
- package/src/chains/solana/transfer.ts +74 -5
- package/src/chains/solana/types.ts +57 -6
- package/src/chains/solana/utils.ts +110 -0
- package/src/chains/solana/viewing-key.ts +807 -0
- package/src/compliance/fireblocks.ts +921 -0
- package/src/compliance/index.ts +23 -0
- package/src/compliance/range-sas.ts +398 -33
- package/src/config/endpoints.ts +100 -0
- package/src/crypto.ts +11 -8
- package/src/errors.ts +82 -0
- package/src/evm/erc4337-relayer.ts +830 -0
- package/src/evm/index.ts +47 -0
- package/src/fees/calculator.ts +396 -0
- package/src/fees/index.ts +87 -0
- package/src/fees/near-contract.ts +429 -0
- package/src/fees/types.ts +268 -0
- package/src/index.ts +686 -1
- package/src/intent.ts +6 -3
- package/src/logger.ts +324 -0
- package/src/network/index.ts +80 -0
- package/src/network/proxy.ts +691 -0
- package/src/optimizations/index.ts +541 -0
- package/src/oracle/types.ts +1 -0
- package/src/privacy-backends/arcium-types.ts +727 -0
- package/src/privacy-backends/arcium.ts +719 -0
- package/src/privacy-backends/combined-privacy.ts +866 -0
- package/src/privacy-backends/cspl-token.ts +595 -0
- package/src/privacy-backends/cspl-types.ts +512 -0
- package/src/privacy-backends/cspl.ts +907 -0
- package/src/privacy-backends/health.ts +488 -0
- package/src/privacy-backends/inco-types.ts +323 -0
- package/src/privacy-backends/inco.ts +616 -0
- package/src/privacy-backends/index.ts +254 -4
- package/src/privacy-backends/interface.ts +649 -6
- package/src/privacy-backends/lru-cache.ts +343 -0
- package/src/privacy-backends/magicblock.ts +458 -0
- package/src/privacy-backends/mock.ts +258 -0
- package/src/privacy-backends/privacycash.ts +13 -17
- package/src/privacy-backends/private-swap.ts +570 -0
- package/src/privacy-backends/rate-limiter.ts +683 -0
- package/src/privacy-backends/registry.ts +414 -2
- package/src/privacy-backends/router.ts +283 -3
- package/src/privacy-backends/shadowwire.ts +449 -0
- package/src/privacy-backends/sip-native.ts +3 -0
- package/src/privacy-logger.ts +191 -0
- package/src/production-safety.ts +373 -0
- package/src/proofs/aggregator.ts +1029 -0
- package/src/proofs/browser-composer.ts +1150 -0
- package/src/proofs/browser.ts +113 -25
- package/src/proofs/cache/index.ts +127 -0
- package/src/proofs/cache/interface.ts +545 -0
- package/src/proofs/cache/key-generator.ts +188 -0
- package/src/proofs/cache/lru-cache.ts +481 -0
- package/src/proofs/cache/multi-tier-cache.ts +575 -0
- package/src/proofs/cache/persistent-cache.ts +788 -0
- package/src/proofs/compliance-proof.ts +872 -0
- package/src/proofs/composer/base.ts +923 -0
- package/src/proofs/composer/index.ts +25 -0
- package/src/proofs/composer/interface.ts +518 -0
- package/src/proofs/composer/types.ts +383 -0
- package/src/proofs/converters/halo2.ts +452 -0
- package/src/proofs/converters/index.ts +208 -0
- package/src/proofs/converters/interface.ts +363 -0
- package/src/proofs/converters/kimchi.ts +462 -0
- package/src/proofs/converters/noir.ts +451 -0
- package/src/proofs/fallback.ts +888 -0
- package/src/proofs/halo2.ts +42 -0
- package/src/proofs/index.ts +471 -0
- package/src/proofs/interface.ts +13 -0
- package/src/proofs/kimchi.ts +42 -0
- package/src/proofs/lazy.ts +1004 -0
- package/src/proofs/mock.ts +25 -1
- package/src/proofs/noir.ts +110 -29
- package/src/proofs/orchestrator.ts +960 -0
- package/src/proofs/parallel/concurrency.ts +297 -0
- package/src/proofs/parallel/dependency-graph.ts +602 -0
- package/src/proofs/parallel/executor.ts +420 -0
- package/src/proofs/parallel/index.ts +131 -0
- package/src/proofs/parallel/interface.ts +685 -0
- package/src/proofs/parallel/worker-pool.ts +644 -0
- package/src/proofs/providers/halo2.ts +560 -0
- package/src/proofs/providers/index.ts +34 -0
- package/src/proofs/providers/kimchi.ts +641 -0
- package/src/proofs/validator.ts +881 -0
- package/src/proofs/verifier.ts +867 -0
- package/src/quantum/index.ts +112 -0
- package/src/quantum/winternitz-vault.ts +639 -0
- package/src/quantum/wots.ts +611 -0
- package/src/settlement/backends/direct-chain.ts +1 -0
- package/src/settlement/index.ts +9 -0
- package/src/settlement/router.ts +732 -46
- package/src/solana/index.ts +72 -0
- package/src/solana/jito-relayer.ts +687 -0
- package/src/solana/noir-verifier-types.ts +430 -0
- package/src/solana/noir-verifier.ts +816 -0
- package/src/stealth/address-derivation.ts +193 -0
- package/src/stealth/ed25519.ts +431 -0
- package/src/stealth/index.ts +233 -0
- package/src/stealth/meta-address.ts +221 -0
- package/src/stealth/secp256k1.ts +368 -0
- package/src/stealth/utils.ts +194 -0
- package/src/stealth.ts +50 -1504
- package/src/sync/index.ts +106 -0
- package/src/sync/manager.ts +504 -0
- package/src/sync/mock-provider.ts +318 -0
- package/src/sync/oblivious.ts +625 -0
- package/src/tokens/index.ts +15 -0
- package/src/tokens/registry.ts +301 -0
- package/src/utils/deprecation.ts +94 -0
- package/src/utils/index.ts +9 -0
- package/src/wallet/ethereum/index.ts +68 -0
- package/src/wallet/ethereum/metamask-privacy.ts +420 -0
- package/src/wallet/ethereum/multi-wallet.ts +646 -0
- package/src/wallet/ethereum/privacy-adapter.ts +700 -0
- package/src/wallet/ethereum/types.ts +3 -1
- package/src/wallet/ethereum/walletconnect-adapter.ts +675 -0
- package/src/wallet/hardware/index.ts +10 -0
- package/src/wallet/hardware/ledger-privacy.ts +414 -0
- package/src/wallet/index.ts +71 -0
- package/src/wallet/near/adapter.ts +626 -0
- package/src/wallet/near/index.ts +86 -0
- package/src/wallet/near/meteor-wallet.ts +1153 -0
- package/src/wallet/near/my-near-wallet.ts +790 -0
- package/src/wallet/near/wallet-selector.ts +702 -0
- package/src/wallet/solana/adapter.ts +6 -4
- package/src/wallet/solana/index.ts +13 -0
- package/src/wallet/solana/privacy-adapter.ts +567 -0
- package/src/wallet/sui/types.ts +6 -4
- package/src/zcash/rpc-client.ts +13 -6
- package/dist/chunk-2XIVXWHA.mjs +0 -1930
- package/dist/chunk-3INS3PR5.mjs +0 -884
- package/dist/chunk-3OVABDRH.mjs +0 -17096
- package/dist/chunk-7RFRWDCW.mjs +0 -1504
- package/dist/chunk-DLDWZFYC.mjs +0 -1495
- package/dist/chunk-E6SZWREQ.mjs +0 -57
- package/dist/chunk-F6F73W35.mjs +0 -16166
- package/dist/chunk-G33LB27A.mjs +0 -16166
- package/dist/chunk-HGU6HZRC.mjs +0 -231
- package/dist/chunk-L2K34JCU.mjs +0 -1496
- package/dist/chunk-OFDBEIEK.mjs +0 -16166
- package/dist/chunk-SF7YSLF5.mjs +0 -1515
- package/dist/chunk-SN4ZDTVW.mjs +0 -16166
- package/dist/chunk-WWUSGOXE.mjs +0 -17129
- package/dist/constants-VOI7BSLK.mjs +0 -27
- package/dist/index-B71aXVzk.d.ts +0 -13264
- package/dist/index-BYZbDjal.d.ts +0 -11390
- package/dist/index-CHB3KuOB.d.mts +0 -11859
- package/dist/index-CzWPI6Le.d.ts +0 -11859
- package/dist/index-pOIIuwfV.d.mts +0 -13264
- package/dist/index-xbWjohNq.d.mts +0 -11390
- package/dist/solana-4O4K45VU.mjs +0 -46
- package/dist/solana-5EMCTPTS.mjs +0 -46
- package/dist/solana-NDABAZ6P.mjs +0 -56
- package/dist/solana-Q4NAVBTS.mjs +0 -46
- package/dist/solana-ZYO63LY5.mjs +0 -46
|
@@ -0,0 +1,488 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Backend Health Tracker
|
|
3
|
+
*
|
|
4
|
+
* Implements circuit breaker pattern and metrics tracking for privacy backends.
|
|
5
|
+
*
|
|
6
|
+
* ## Circuit Breaker States
|
|
7
|
+
*
|
|
8
|
+
* ```
|
|
9
|
+
* CLOSED (healthy) ──[N failures]──► OPEN (disabled)
|
|
10
|
+
* ▲ │
|
|
11
|
+
* │ [timeout]
|
|
12
|
+
* │ ▼
|
|
13
|
+
* └──[M successes]──── HALF_OPEN (testing)
|
|
14
|
+
* │
|
|
15
|
+
* [failure]
|
|
16
|
+
* │
|
|
17
|
+
* └──────► OPEN
|
|
18
|
+
* ```
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```typescript
|
|
22
|
+
* const tracker = new BackendHealthTracker({
|
|
23
|
+
* failureThreshold: 3,
|
|
24
|
+
* resetTimeoutMs: 30000,
|
|
25
|
+
* })
|
|
26
|
+
*
|
|
27
|
+
* // Record results
|
|
28
|
+
* tracker.recordSuccess('sip-native', 150)
|
|
29
|
+
* tracker.recordFailure('privacycash', 'Connection timeout')
|
|
30
|
+
*
|
|
31
|
+
* // Check health
|
|
32
|
+
* if (tracker.shouldAttempt('sip-native')) {
|
|
33
|
+
* // Safe to make request
|
|
34
|
+
* }
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
import type {
|
|
39
|
+
BackendHealthState,
|
|
40
|
+
BackendMetrics,
|
|
41
|
+
CircuitBreakerConfig,
|
|
42
|
+
CircuitState,
|
|
43
|
+
} from './interface'
|
|
44
|
+
import { DEFAULT_CIRCUIT_BREAKER_CONFIG, deepFreeze } from './interface'
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Create initial health state for a backend
|
|
48
|
+
*/
|
|
49
|
+
function createInitialHealthState(): BackendHealthState {
|
|
50
|
+
return {
|
|
51
|
+
circuitState: 'closed',
|
|
52
|
+
isHealthy: true,
|
|
53
|
+
lastChecked: Date.now(),
|
|
54
|
+
consecutiveFailures: 0,
|
|
55
|
+
consecutiveSuccesses: 0,
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Create initial metrics for a backend
|
|
61
|
+
*/
|
|
62
|
+
function createInitialMetrics(): BackendMetrics {
|
|
63
|
+
return {
|
|
64
|
+
totalRequests: 0,
|
|
65
|
+
successfulRequests: 0,
|
|
66
|
+
failedRequests: 0,
|
|
67
|
+
totalLatencyMs: 0,
|
|
68
|
+
averageLatencyMs: 0,
|
|
69
|
+
lastRequestTime: 0,
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Backend Health Tracker
|
|
75
|
+
*
|
|
76
|
+
* Centralized health and metrics tracking for all privacy backends.
|
|
77
|
+
* Implements the circuit breaker pattern to automatically disable
|
|
78
|
+
* failing backends and re-enable them after recovery.
|
|
79
|
+
*/
|
|
80
|
+
export class BackendHealthTracker {
|
|
81
|
+
private health: Map<string, BackendHealthState> = new Map()
|
|
82
|
+
private metrics: Map<string, BackendMetrics> = new Map()
|
|
83
|
+
private config: CircuitBreakerConfig
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Create a new health tracker
|
|
87
|
+
*
|
|
88
|
+
* @param config - Circuit breaker configuration
|
|
89
|
+
*/
|
|
90
|
+
constructor(config: Partial<CircuitBreakerConfig> = {}) {
|
|
91
|
+
this.config = { ...DEFAULT_CIRCUIT_BREAKER_CONFIG, ...config }
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Initialize tracking for a backend
|
|
96
|
+
*
|
|
97
|
+
* Called automatically on first access, but can be called explicitly
|
|
98
|
+
* to pre-register backends.
|
|
99
|
+
*
|
|
100
|
+
* @param name - Backend name
|
|
101
|
+
*/
|
|
102
|
+
register(name: string): void {
|
|
103
|
+
if (!this.health.has(name)) {
|
|
104
|
+
this.health.set(name, createInitialHealthState())
|
|
105
|
+
}
|
|
106
|
+
if (this.config.enableMetrics && !this.metrics.has(name)) {
|
|
107
|
+
this.metrics.set(name, createInitialMetrics())
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Remove tracking for a backend
|
|
113
|
+
*
|
|
114
|
+
* @param name - Backend name
|
|
115
|
+
* @returns true if backend was tracked, false if not found
|
|
116
|
+
*/
|
|
117
|
+
unregister(name: string): boolean {
|
|
118
|
+
const healthRemoved = this.health.delete(name)
|
|
119
|
+
this.metrics.delete(name)
|
|
120
|
+
return healthRemoved
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Record a successful request
|
|
125
|
+
*
|
|
126
|
+
* Updates health state and metrics based on success.
|
|
127
|
+
*
|
|
128
|
+
* @param name - Backend name
|
|
129
|
+
* @param latencyMs - Request latency in milliseconds
|
|
130
|
+
*/
|
|
131
|
+
recordSuccess(name: string, latencyMs: number): void {
|
|
132
|
+
this.register(name)
|
|
133
|
+
const now = Date.now()
|
|
134
|
+
|
|
135
|
+
// Update health state
|
|
136
|
+
const health = this.health.get(name)!
|
|
137
|
+
health.lastChecked = now
|
|
138
|
+
health.consecutiveFailures = 0
|
|
139
|
+
health.consecutiveSuccesses++
|
|
140
|
+
|
|
141
|
+
// Handle circuit state transitions
|
|
142
|
+
if (health.circuitState === 'half-open') {
|
|
143
|
+
if (health.consecutiveSuccesses >= this.config.successThreshold) {
|
|
144
|
+
// Recovery confirmed, close circuit
|
|
145
|
+
health.circuitState = 'closed'
|
|
146
|
+
health.isHealthy = true
|
|
147
|
+
health.circuitOpenedAt = undefined
|
|
148
|
+
}
|
|
149
|
+
} else if (health.circuitState === 'open') {
|
|
150
|
+
// Shouldn't happen, but handle gracefully
|
|
151
|
+
health.circuitState = 'half-open'
|
|
152
|
+
health.consecutiveSuccesses = 1
|
|
153
|
+
} else {
|
|
154
|
+
// Already closed, stay healthy
|
|
155
|
+
health.isHealthy = true
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Update metrics
|
|
159
|
+
if (this.config.enableMetrics) {
|
|
160
|
+
const metrics = this.metrics.get(name)!
|
|
161
|
+
metrics.totalRequests++
|
|
162
|
+
metrics.successfulRequests++
|
|
163
|
+
metrics.totalLatencyMs += latencyMs
|
|
164
|
+
metrics.averageLatencyMs = metrics.totalLatencyMs / metrics.successfulRequests
|
|
165
|
+
metrics.lastRequestTime = now
|
|
166
|
+
metrics.lastSuccessTime = now
|
|
167
|
+
|
|
168
|
+
// Track min/max latency
|
|
169
|
+
if (metrics.minLatencyMs === undefined || latencyMs < metrics.minLatencyMs) {
|
|
170
|
+
metrics.minLatencyMs = latencyMs
|
|
171
|
+
}
|
|
172
|
+
if (metrics.maxLatencyMs === undefined || latencyMs > metrics.maxLatencyMs) {
|
|
173
|
+
metrics.maxLatencyMs = latencyMs
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Record a failed request
|
|
180
|
+
*
|
|
181
|
+
* Updates health state and may open circuit if threshold reached.
|
|
182
|
+
*
|
|
183
|
+
* @param name - Backend name
|
|
184
|
+
* @param reason - Failure reason
|
|
185
|
+
*/
|
|
186
|
+
recordFailure(name: string, reason: string): void {
|
|
187
|
+
this.register(name)
|
|
188
|
+
const now = Date.now()
|
|
189
|
+
|
|
190
|
+
// Update health state
|
|
191
|
+
const health = this.health.get(name)!
|
|
192
|
+
health.lastChecked = now
|
|
193
|
+
health.consecutiveSuccesses = 0
|
|
194
|
+
health.consecutiveFailures++
|
|
195
|
+
health.lastFailureReason = reason
|
|
196
|
+
health.lastFailureTime = now
|
|
197
|
+
|
|
198
|
+
// Handle circuit state transitions
|
|
199
|
+
if (health.circuitState === 'half-open') {
|
|
200
|
+
// Failed during recovery test, reopen circuit
|
|
201
|
+
health.circuitState = 'open'
|
|
202
|
+
health.isHealthy = false
|
|
203
|
+
health.circuitOpenedAt = now
|
|
204
|
+
} else if (health.circuitState === 'closed') {
|
|
205
|
+
// Check if we should open circuit
|
|
206
|
+
if (health.consecutiveFailures >= this.config.failureThreshold) {
|
|
207
|
+
health.circuitState = 'open'
|
|
208
|
+
health.isHealthy = false
|
|
209
|
+
health.circuitOpenedAt = now
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
// If already open, stay open (circuitOpenedAt already set)
|
|
213
|
+
|
|
214
|
+
// Update metrics
|
|
215
|
+
if (this.config.enableMetrics) {
|
|
216
|
+
const metrics = this.metrics.get(name)!
|
|
217
|
+
metrics.totalRequests++
|
|
218
|
+
metrics.failedRequests++
|
|
219
|
+
metrics.lastRequestTime = now
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Check if a request should be attempted
|
|
225
|
+
*
|
|
226
|
+
* Returns true if:
|
|
227
|
+
* - Circuit is closed (healthy)
|
|
228
|
+
* - Circuit is half-open (testing recovery)
|
|
229
|
+
* - Circuit is open but timeout has elapsed (transition to half-open)
|
|
230
|
+
*
|
|
231
|
+
* @param name - Backend name
|
|
232
|
+
* @returns true if request should be attempted
|
|
233
|
+
*/
|
|
234
|
+
shouldAttempt(name: string): boolean {
|
|
235
|
+
this.register(name)
|
|
236
|
+
const health = this.health.get(name)!
|
|
237
|
+
const now = Date.now()
|
|
238
|
+
|
|
239
|
+
switch (health.circuitState) {
|
|
240
|
+
case 'closed':
|
|
241
|
+
return true
|
|
242
|
+
|
|
243
|
+
case 'half-open':
|
|
244
|
+
// Allow request for recovery testing
|
|
245
|
+
return true
|
|
246
|
+
|
|
247
|
+
case 'open':
|
|
248
|
+
// Check if timeout has elapsed
|
|
249
|
+
if (health.circuitOpenedAt) {
|
|
250
|
+
const elapsed = now - health.circuitOpenedAt
|
|
251
|
+
if (elapsed >= this.config.resetTimeoutMs) {
|
|
252
|
+
// Transition to half-open
|
|
253
|
+
health.circuitState = 'half-open'
|
|
254
|
+
health.consecutiveSuccesses = 0
|
|
255
|
+
health.consecutiveFailures = 0
|
|
256
|
+
health.lastChecked = now
|
|
257
|
+
return true
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
return false
|
|
261
|
+
|
|
262
|
+
default:
|
|
263
|
+
return true
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Check if backend is healthy
|
|
269
|
+
*
|
|
270
|
+
* @param name - Backend name
|
|
271
|
+
* @returns true if backend is healthy (circuit closed or half-open)
|
|
272
|
+
*/
|
|
273
|
+
isHealthy(name: string): boolean {
|
|
274
|
+
if (!this.health.has(name)) {
|
|
275
|
+
return true // Unknown backends assumed healthy
|
|
276
|
+
}
|
|
277
|
+
const health = this.health.get(name)!
|
|
278
|
+
return health.circuitState !== 'open'
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Check if circuit is open (backend disabled)
|
|
283
|
+
*
|
|
284
|
+
* @param name - Backend name
|
|
285
|
+
* @returns true if circuit is open
|
|
286
|
+
*/
|
|
287
|
+
isCircuitOpen(name: string): boolean {
|
|
288
|
+
if (!this.health.has(name)) {
|
|
289
|
+
return false
|
|
290
|
+
}
|
|
291
|
+
return this.health.get(name)!.circuitState === 'open'
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Get current circuit state
|
|
296
|
+
*
|
|
297
|
+
* @param name - Backend name
|
|
298
|
+
* @returns Circuit state
|
|
299
|
+
*/
|
|
300
|
+
getCircuitState(name: string): CircuitState {
|
|
301
|
+
if (!this.health.has(name)) {
|
|
302
|
+
return 'closed'
|
|
303
|
+
}
|
|
304
|
+
return this.health.get(name)!.circuitState
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Get health state for a backend
|
|
309
|
+
*
|
|
310
|
+
* @param name - Backend name
|
|
311
|
+
* @returns Health state or undefined if not tracked
|
|
312
|
+
*/
|
|
313
|
+
getHealth(name: string): BackendHealthState | undefined {
|
|
314
|
+
return this.health.get(name)
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Get metrics for a backend
|
|
319
|
+
*
|
|
320
|
+
* @param name - Backend name
|
|
321
|
+
* @returns Metrics or undefined if not tracked
|
|
322
|
+
*/
|
|
323
|
+
getMetrics(name: string): BackendMetrics | undefined {
|
|
324
|
+
return this.metrics.get(name)
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Get all health states
|
|
329
|
+
*
|
|
330
|
+
* @returns Map of backend name to health state
|
|
331
|
+
*/
|
|
332
|
+
getAllHealth(): Map<string, BackendHealthState> {
|
|
333
|
+
return new Map(this.health)
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Get all metrics
|
|
338
|
+
*
|
|
339
|
+
* @returns Map of backend name to metrics
|
|
340
|
+
*/
|
|
341
|
+
getAllMetrics(): Map<string, BackendMetrics> {
|
|
342
|
+
return new Map(this.metrics)
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Get summary of all backend health
|
|
347
|
+
*
|
|
348
|
+
* @returns Object with backend names as keys
|
|
349
|
+
*/
|
|
350
|
+
getHealthSummary(): Record<string, {
|
|
351
|
+
healthy: boolean
|
|
352
|
+
state: CircuitState
|
|
353
|
+
failures: number
|
|
354
|
+
lastError?: string
|
|
355
|
+
}> {
|
|
356
|
+
const summary: Record<string, {
|
|
357
|
+
healthy: boolean
|
|
358
|
+
state: CircuitState
|
|
359
|
+
failures: number
|
|
360
|
+
lastError?: string
|
|
361
|
+
}> = {}
|
|
362
|
+
|
|
363
|
+
for (const [name, health] of this.health) {
|
|
364
|
+
summary[name] = {
|
|
365
|
+
healthy: health.isHealthy,
|
|
366
|
+
state: health.circuitState,
|
|
367
|
+
failures: health.consecutiveFailures,
|
|
368
|
+
lastError: health.lastFailureReason,
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
return summary
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Manually reset a backend's health state
|
|
377
|
+
*
|
|
378
|
+
* Clears failure count and closes circuit.
|
|
379
|
+
*
|
|
380
|
+
* @param name - Backend name
|
|
381
|
+
*/
|
|
382
|
+
reset(name: string): void {
|
|
383
|
+
if (this.health.has(name)) {
|
|
384
|
+
this.health.set(name, createInitialHealthState())
|
|
385
|
+
}
|
|
386
|
+
if (this.metrics.has(name)) {
|
|
387
|
+
this.metrics.set(name, createInitialMetrics())
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Manually force circuit open
|
|
393
|
+
*
|
|
394
|
+
* Useful for maintenance or known issues.
|
|
395
|
+
*
|
|
396
|
+
* @param name - Backend name
|
|
397
|
+
*/
|
|
398
|
+
forceOpen(name: string): void {
|
|
399
|
+
this.register(name)
|
|
400
|
+
const health = this.health.get(name)!
|
|
401
|
+
health.circuitState = 'open'
|
|
402
|
+
health.isHealthy = false
|
|
403
|
+
health.circuitOpenedAt = Date.now()
|
|
404
|
+
health.lastFailureReason = 'Manually opened'
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Manually force circuit closed
|
|
409
|
+
*
|
|
410
|
+
* Use with caution - may route requests to failing backend.
|
|
411
|
+
*
|
|
412
|
+
* @param name - Backend name
|
|
413
|
+
*/
|
|
414
|
+
forceClose(name: string): void {
|
|
415
|
+
this.register(name)
|
|
416
|
+
const health = this.health.get(name)!
|
|
417
|
+
health.circuitState = 'closed'
|
|
418
|
+
health.isHealthy = true
|
|
419
|
+
health.consecutiveFailures = 0
|
|
420
|
+
health.consecutiveSuccesses = 0
|
|
421
|
+
health.circuitOpenedAt = undefined
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Clear all health and metrics data
|
|
426
|
+
*/
|
|
427
|
+
clear(): void {
|
|
428
|
+
this.health.clear()
|
|
429
|
+
this.metrics.clear()
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Get current configuration (deeply frozen copy)
|
|
434
|
+
*/
|
|
435
|
+
getConfig(): Readonly<CircuitBreakerConfig> {
|
|
436
|
+
return deepFreeze({ ...this.config })
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Update configuration
|
|
441
|
+
*
|
|
442
|
+
* Note: Changes apply to future operations, not existing state.
|
|
443
|
+
*
|
|
444
|
+
* @param config - Partial configuration to merge
|
|
445
|
+
*/
|
|
446
|
+
updateConfig(config: Partial<CircuitBreakerConfig>): void {
|
|
447
|
+
this.config = { ...this.config, ...config }
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* Get time until circuit reset for a backend
|
|
452
|
+
*
|
|
453
|
+
* @param name - Backend name
|
|
454
|
+
* @returns Milliseconds until reset, or 0 if not open
|
|
455
|
+
*/
|
|
456
|
+
getTimeUntilReset(name: string): number {
|
|
457
|
+
if (!this.health.has(name)) {
|
|
458
|
+
return 0
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
const health = this.health.get(name)!
|
|
462
|
+
if (health.circuitState !== 'open' || !health.circuitOpenedAt) {
|
|
463
|
+
return 0
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
const elapsed = Date.now() - health.circuitOpenedAt
|
|
467
|
+
return Math.max(0, this.config.resetTimeoutMs - elapsed)
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/**
|
|
471
|
+
* Get names of all tracked backends
|
|
472
|
+
*
|
|
473
|
+
* @returns Array of backend names
|
|
474
|
+
*/
|
|
475
|
+
getTrackedBackends(): string[] {
|
|
476
|
+
return Array.from(this.health.keys())
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/**
|
|
480
|
+
* Check if a backend is being tracked
|
|
481
|
+
*
|
|
482
|
+
* @param name - Backend name
|
|
483
|
+
* @returns true if backend is tracked
|
|
484
|
+
*/
|
|
485
|
+
isTracked(name: string): boolean {
|
|
486
|
+
return this.health.has(name)
|
|
487
|
+
}
|
|
488
|
+
}
|