@sip-protocol/sdk 0.7.3 → 0.8.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.
Files changed (263) hide show
  1. package/README.md +267 -0
  2. package/dist/{TransportWebUSB-TQ7WZ4LE.mjs → TransportWebUSB-YQMAGJAJ.mjs} +12 -9
  3. package/dist/browser.d.mts +10 -4
  4. package/dist/browser.d.ts +10 -4
  5. package/dist/browser.js +47556 -19603
  6. package/dist/browser.mjs +628 -48
  7. package/dist/chunk-4GRJ5MAW.mjs +152 -0
  8. package/dist/chunk-5D7A3L3W.mjs +717 -0
  9. package/dist/chunk-64AYA5F5.mjs +7834 -0
  10. package/dist/chunk-GMDGB22A.mjs +379 -0
  11. package/dist/chunk-I534WKN7.mjs +328 -0
  12. package/dist/chunk-IBZVA5Y7.mjs +1003 -0
  13. package/dist/chunk-PRRZAWJE.mjs +223 -0
  14. package/dist/{chunk-UJCSKKID.mjs → chunk-XGB3TDIC.mjs} +13 -1
  15. package/dist/{chunk-3M3HNQCW.mjs → chunk-YWGJ77A2.mjs} +28656 -13103
  16. package/dist/{chunk-6WGN57S2.mjs → chunk-Z3K7W5S3.mjs} +48 -0
  17. package/dist/constants-LHAAUC2T.mjs +51 -0
  18. package/dist/dist-2OGQ7FED.mjs +3957 -0
  19. package/dist/dist-IFHPYLDX.mjs +254 -0
  20. package/dist/fulfillment_proof-ANHVPKTB.mjs +21 -0
  21. package/dist/funding_proof-ICFZ5LHY.mjs +21 -0
  22. package/dist/{index-DIBZHOOQ.d.ts → index-DXh2IGkz.d.ts} +21239 -10304
  23. package/dist/{index-8MQz13eJ.d.mts → index-DeE1ZzA4.d.mts} +21239 -10304
  24. package/dist/index.d.mts +9 -3
  25. package/dist/index.d.ts +9 -3
  26. package/dist/index.js +48396 -19623
  27. package/dist/index.mjs +537 -19
  28. package/dist/interface-Bf7w1PLW.d.mts +679 -0
  29. package/dist/interface-Bf7w1PLW.d.ts +679 -0
  30. package/dist/{noir-DKfEzWy9.d.mts → noir-kzbLVTei.d.mts} +31 -21
  31. package/dist/{noir-DKfEzWy9.d.ts → noir-kzbLVTei.d.ts} +31 -21
  32. package/dist/proofs/halo2.d.mts +151 -0
  33. package/dist/proofs/halo2.d.ts +151 -0
  34. package/dist/proofs/halo2.js +350 -0
  35. package/dist/proofs/halo2.mjs +11 -0
  36. package/dist/proofs/kimchi.d.mts +160 -0
  37. package/dist/proofs/kimchi.d.ts +160 -0
  38. package/dist/proofs/kimchi.js +431 -0
  39. package/dist/proofs/kimchi.mjs +13 -0
  40. package/dist/proofs/noir.d.mts +1 -1
  41. package/dist/proofs/noir.d.ts +1 -1
  42. package/dist/proofs/noir.js +74 -18
  43. package/dist/proofs/noir.mjs +84 -24
  44. package/dist/solana-U3MEGU7W.mjs +280 -0
  45. package/dist/validity_proof-3POXLPNY.mjs +21 -0
  46. package/package.json +44 -11
  47. package/src/adapters/index.ts +41 -0
  48. package/src/adapters/jupiter.ts +571 -0
  49. package/src/adapters/near-intents.ts +135 -0
  50. package/src/advisor/advisor.ts +653 -0
  51. package/src/advisor/index.ts +54 -0
  52. package/src/advisor/tools.ts +303 -0
  53. package/src/advisor/types.ts +164 -0
  54. package/src/chains/ethereum/announcement.ts +536 -0
  55. package/src/chains/ethereum/bnb-optimizations.ts +474 -0
  56. package/src/chains/ethereum/commitment.ts +522 -0
  57. package/src/chains/ethereum/constants.ts +462 -0
  58. package/src/chains/ethereum/deployment.ts +596 -0
  59. package/src/chains/ethereum/gas-estimation.ts +538 -0
  60. package/src/chains/ethereum/index.ts +268 -0
  61. package/src/chains/ethereum/optimizations.ts +614 -0
  62. package/src/chains/ethereum/privacy-adapter.ts +855 -0
  63. package/src/chains/ethereum/registry.ts +584 -0
  64. package/src/chains/ethereum/rpc.ts +905 -0
  65. package/src/chains/ethereum/stealth.ts +491 -0
  66. package/src/chains/ethereum/token.ts +790 -0
  67. package/src/chains/ethereum/transfer.ts +637 -0
  68. package/src/chains/ethereum/types.ts +456 -0
  69. package/src/chains/ethereum/viewing-key.ts +455 -0
  70. package/src/chains/near/commitment.ts +608 -0
  71. package/src/chains/near/constants.ts +284 -0
  72. package/src/chains/near/function-call.ts +871 -0
  73. package/src/chains/near/history.ts +654 -0
  74. package/src/chains/near/implicit-account.ts +840 -0
  75. package/src/chains/near/index.ts +393 -0
  76. package/src/chains/near/native-transfer.ts +658 -0
  77. package/src/chains/near/nep141.ts +775 -0
  78. package/src/chains/near/privacy-adapter.ts +889 -0
  79. package/src/chains/near/resolver.ts +971 -0
  80. package/src/chains/near/rpc.ts +1016 -0
  81. package/src/chains/near/stealth.ts +419 -0
  82. package/src/chains/near/types.ts +317 -0
  83. package/src/chains/near/viewing-key.ts +876 -0
  84. package/src/chains/solana/anchor-transfer.ts +386 -0
  85. package/src/chains/solana/commitment.ts +577 -0
  86. package/src/chains/solana/constants.ts +126 -12
  87. package/src/chains/solana/ephemeral-keys.ts +543 -0
  88. package/src/chains/solana/index.ts +252 -1
  89. package/src/chains/solana/key-derivation.ts +418 -0
  90. package/src/chains/solana/kit-compat.ts +334 -0
  91. package/src/chains/solana/optimizations.ts +560 -0
  92. package/src/chains/solana/privacy-adapter.ts +605 -0
  93. package/src/chains/solana/providers/generic.ts +47 -6
  94. package/src/chains/solana/providers/helius-enhanced-types.ts +336 -0
  95. package/src/chains/solana/providers/helius-enhanced.ts +623 -0
  96. package/src/chains/solana/providers/helius.ts +186 -33
  97. package/src/chains/solana/providers/index.ts +31 -0
  98. package/src/chains/solana/providers/interface.ts +61 -18
  99. package/src/chains/solana/providers/quicknode.ts +409 -0
  100. package/src/chains/solana/providers/triton.ts +426 -0
  101. package/src/chains/solana/providers/webhook.ts +338 -67
  102. package/src/chains/solana/rpc-client.ts +1150 -0
  103. package/src/chains/solana/scan.ts +83 -66
  104. package/src/chains/solana/sol-transfer.ts +732 -0
  105. package/src/chains/solana/spl-transfer.ts +886 -0
  106. package/src/chains/solana/stealth-scanner.ts +703 -0
  107. package/src/chains/solana/sunspot-verifier.ts +453 -0
  108. package/src/chains/solana/transaction-builder.ts +755 -0
  109. package/src/chains/solana/transfer.ts +74 -5
  110. package/src/chains/solana/types.ts +57 -6
  111. package/src/chains/solana/utils.ts +110 -0
  112. package/src/chains/solana/viewing-key.ts +807 -0
  113. package/src/compliance/fireblocks.ts +921 -0
  114. package/src/compliance/index.ts +23 -0
  115. package/src/compliance/range-sas.ts +398 -33
  116. package/src/config/endpoints.ts +100 -0
  117. package/src/crypto.ts +11 -8
  118. package/src/errors.ts +82 -0
  119. package/src/evm/erc4337-relayer.ts +830 -0
  120. package/src/evm/index.ts +47 -0
  121. package/src/fees/calculator.ts +396 -0
  122. package/src/fees/index.ts +87 -0
  123. package/src/fees/near-contract.ts +429 -0
  124. package/src/fees/types.ts +268 -0
  125. package/src/index.ts +686 -1
  126. package/src/intent.ts +6 -3
  127. package/src/logger.ts +324 -0
  128. package/src/network/index.ts +80 -0
  129. package/src/network/proxy.ts +691 -0
  130. package/src/optimizations/index.ts +541 -0
  131. package/src/oracle/types.ts +1 -0
  132. package/src/privacy-backends/arcium-types.ts +727 -0
  133. package/src/privacy-backends/arcium.ts +719 -0
  134. package/src/privacy-backends/combined-privacy.ts +866 -0
  135. package/src/privacy-backends/cspl-token.ts +595 -0
  136. package/src/privacy-backends/cspl-types.ts +512 -0
  137. package/src/privacy-backends/cspl.ts +907 -0
  138. package/src/privacy-backends/health.ts +488 -0
  139. package/src/privacy-backends/inco-types.ts +323 -0
  140. package/src/privacy-backends/inco.ts +616 -0
  141. package/src/privacy-backends/index.ts +254 -4
  142. package/src/privacy-backends/interface.ts +649 -6
  143. package/src/privacy-backends/lru-cache.ts +343 -0
  144. package/src/privacy-backends/magicblock.ts +458 -0
  145. package/src/privacy-backends/mock.ts +258 -0
  146. package/src/privacy-backends/privacycash.ts +13 -17
  147. package/src/privacy-backends/private-swap.ts +570 -0
  148. package/src/privacy-backends/rate-limiter.ts +683 -0
  149. package/src/privacy-backends/registry.ts +414 -2
  150. package/src/privacy-backends/router.ts +283 -3
  151. package/src/privacy-backends/shadowwire.ts +449 -0
  152. package/src/privacy-backends/sip-native.ts +3 -0
  153. package/src/privacy-logger.ts +191 -0
  154. package/src/production-safety.ts +373 -0
  155. package/src/proofs/aggregator.ts +1029 -0
  156. package/src/proofs/browser-composer.ts +1150 -0
  157. package/src/proofs/browser.ts +113 -25
  158. package/src/proofs/cache/index.ts +127 -0
  159. package/src/proofs/cache/interface.ts +545 -0
  160. package/src/proofs/cache/key-generator.ts +188 -0
  161. package/src/proofs/cache/lru-cache.ts +481 -0
  162. package/src/proofs/cache/multi-tier-cache.ts +575 -0
  163. package/src/proofs/cache/persistent-cache.ts +788 -0
  164. package/src/proofs/compliance-proof.ts +872 -0
  165. package/src/proofs/composer/base.ts +923 -0
  166. package/src/proofs/composer/index.ts +25 -0
  167. package/src/proofs/composer/interface.ts +518 -0
  168. package/src/proofs/composer/types.ts +383 -0
  169. package/src/proofs/converters/halo2.ts +452 -0
  170. package/src/proofs/converters/index.ts +208 -0
  171. package/src/proofs/converters/interface.ts +363 -0
  172. package/src/proofs/converters/kimchi.ts +462 -0
  173. package/src/proofs/converters/noir.ts +451 -0
  174. package/src/proofs/fallback.ts +888 -0
  175. package/src/proofs/halo2.ts +42 -0
  176. package/src/proofs/index.ts +471 -0
  177. package/src/proofs/interface.ts +13 -0
  178. package/src/proofs/kimchi.ts +42 -0
  179. package/src/proofs/lazy.ts +1004 -0
  180. package/src/proofs/mock.ts +25 -1
  181. package/src/proofs/noir.ts +110 -29
  182. package/src/proofs/orchestrator.ts +960 -0
  183. package/src/proofs/parallel/concurrency.ts +297 -0
  184. package/src/proofs/parallel/dependency-graph.ts +602 -0
  185. package/src/proofs/parallel/executor.ts +420 -0
  186. package/src/proofs/parallel/index.ts +131 -0
  187. package/src/proofs/parallel/interface.ts +685 -0
  188. package/src/proofs/parallel/worker-pool.ts +644 -0
  189. package/src/proofs/providers/halo2.ts +560 -0
  190. package/src/proofs/providers/index.ts +34 -0
  191. package/src/proofs/providers/kimchi.ts +641 -0
  192. package/src/proofs/validator.ts +881 -0
  193. package/src/proofs/verifier.ts +867 -0
  194. package/src/quantum/index.ts +112 -0
  195. package/src/quantum/winternitz-vault.ts +639 -0
  196. package/src/quantum/wots.ts +611 -0
  197. package/src/settlement/backends/direct-chain.ts +1 -0
  198. package/src/settlement/index.ts +9 -0
  199. package/src/settlement/router.ts +732 -46
  200. package/src/solana/index.ts +72 -0
  201. package/src/solana/jito-relayer.ts +687 -0
  202. package/src/solana/noir-verifier-types.ts +430 -0
  203. package/src/solana/noir-verifier.ts +816 -0
  204. package/src/stealth/address-derivation.ts +193 -0
  205. package/src/stealth/ed25519.ts +431 -0
  206. package/src/stealth/index.ts +233 -0
  207. package/src/stealth/meta-address.ts +221 -0
  208. package/src/stealth/secp256k1.ts +368 -0
  209. package/src/stealth/utils.ts +194 -0
  210. package/src/stealth.ts +50 -1504
  211. package/src/sync/index.ts +106 -0
  212. package/src/sync/manager.ts +504 -0
  213. package/src/sync/mock-provider.ts +318 -0
  214. package/src/sync/oblivious.ts +625 -0
  215. package/src/tokens/index.ts +15 -0
  216. package/src/tokens/registry.ts +301 -0
  217. package/src/utils/deprecation.ts +94 -0
  218. package/src/utils/index.ts +9 -0
  219. package/src/wallet/ethereum/index.ts +68 -0
  220. package/src/wallet/ethereum/metamask-privacy.ts +420 -0
  221. package/src/wallet/ethereum/multi-wallet.ts +646 -0
  222. package/src/wallet/ethereum/privacy-adapter.ts +700 -0
  223. package/src/wallet/ethereum/types.ts +3 -1
  224. package/src/wallet/ethereum/walletconnect-adapter.ts +675 -0
  225. package/src/wallet/hardware/index.ts +10 -0
  226. package/src/wallet/hardware/ledger-privacy.ts +414 -0
  227. package/src/wallet/index.ts +71 -0
  228. package/src/wallet/near/adapter.ts +626 -0
  229. package/src/wallet/near/index.ts +86 -0
  230. package/src/wallet/near/meteor-wallet.ts +1153 -0
  231. package/src/wallet/near/my-near-wallet.ts +790 -0
  232. package/src/wallet/near/wallet-selector.ts +702 -0
  233. package/src/wallet/solana/adapter.ts +6 -4
  234. package/src/wallet/solana/index.ts +13 -0
  235. package/src/wallet/solana/privacy-adapter.ts +567 -0
  236. package/src/wallet/sui/types.ts +6 -4
  237. package/src/zcash/rpc-client.ts +13 -6
  238. package/dist/chunk-2XIVXWHA.mjs +0 -1930
  239. package/dist/chunk-3INS3PR5.mjs +0 -884
  240. package/dist/chunk-3OVABDRH.mjs +0 -17096
  241. package/dist/chunk-7RFRWDCW.mjs +0 -1504
  242. package/dist/chunk-DLDWZFYC.mjs +0 -1495
  243. package/dist/chunk-E6SZWREQ.mjs +0 -57
  244. package/dist/chunk-F6F73W35.mjs +0 -16166
  245. package/dist/chunk-G33LB27A.mjs +0 -16166
  246. package/dist/chunk-HGU6HZRC.mjs +0 -231
  247. package/dist/chunk-L2K34JCU.mjs +0 -1496
  248. package/dist/chunk-OFDBEIEK.mjs +0 -16166
  249. package/dist/chunk-SF7YSLF5.mjs +0 -1515
  250. package/dist/chunk-SN4ZDTVW.mjs +0 -16166
  251. package/dist/chunk-WWUSGOXE.mjs +0 -17129
  252. package/dist/constants-VOI7BSLK.mjs +0 -27
  253. package/dist/index-B71aXVzk.d.ts +0 -13264
  254. package/dist/index-BYZbDjal.d.ts +0 -11390
  255. package/dist/index-CHB3KuOB.d.mts +0 -11859
  256. package/dist/index-CzWPI6Le.d.ts +0 -11859
  257. package/dist/index-pOIIuwfV.d.mts +0 -13264
  258. package/dist/index-xbWjohNq.d.mts +0 -11390
  259. package/dist/solana-4O4K45VU.mjs +0 -46
  260. package/dist/solana-5EMCTPTS.mjs +0 -46
  261. package/dist/solana-NDABAZ6P.mjs +0 -56
  262. package/dist/solana-Q4NAVBTS.mjs +0 -46
  263. 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
+ }