@push.rocks/smartregistry 2.2.3 → 2.4.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 (110) hide show
  1. package/dist_ts/00_commitinfo_data.js +1 -1
  2. package/dist_ts/cargo/classes.cargoregistry.d.ts +7 -1
  3. package/dist_ts/cargo/classes.cargoregistry.js +42 -4
  4. package/dist_ts/cargo/classes.cargoupstream.d.ts +44 -0
  5. package/dist_ts/cargo/classes.cargoupstream.js +129 -0
  6. package/dist_ts/cargo/index.d.ts +1 -0
  7. package/dist_ts/cargo/index.js +2 -1
  8. package/dist_ts/classes.smartregistry.d.ts +33 -2
  9. package/dist_ts/classes.smartregistry.js +45 -12
  10. package/dist_ts/composer/classes.composerregistry.d.ts +7 -1
  11. package/dist_ts/composer/classes.composerregistry.js +34 -3
  12. package/dist_ts/composer/classes.composerupstream.d.ts +40 -0
  13. package/dist_ts/composer/classes.composerupstream.js +159 -0
  14. package/dist_ts/composer/index.d.ts +1 -0
  15. package/dist_ts/composer/index.js +2 -1
  16. package/dist_ts/core/classes.authmanager.d.ts +30 -80
  17. package/dist_ts/core/classes.authmanager.js +63 -337
  18. package/dist_ts/core/classes.defaultauthprovider.d.ts +78 -0
  19. package/dist_ts/core/classes.defaultauthprovider.js +311 -0
  20. package/dist_ts/core/classes.registrystorage.d.ts +70 -4
  21. package/dist_ts/core/classes.registrystorage.js +165 -5
  22. package/dist_ts/core/index.d.ts +3 -0
  23. package/dist_ts/core/index.js +7 -2
  24. package/dist_ts/core/interfaces.auth.d.ts +83 -0
  25. package/dist_ts/core/interfaces.auth.js +2 -0
  26. package/dist_ts/core/interfaces.core.d.ts +38 -0
  27. package/dist_ts/core/interfaces.storage.d.ts +120 -0
  28. package/dist_ts/core/interfaces.storage.js +2 -0
  29. package/dist_ts/index.d.ts +1 -0
  30. package/dist_ts/index.js +3 -1
  31. package/dist_ts/maven/classes.mavenregistry.d.ts +12 -1
  32. package/dist_ts/maven/classes.mavenregistry.js +69 -4
  33. package/dist_ts/maven/classes.mavenupstream.d.ts +45 -0
  34. package/dist_ts/maven/classes.mavenupstream.js +153 -0
  35. package/dist_ts/maven/index.d.ts +1 -0
  36. package/dist_ts/maven/index.js +2 -1
  37. package/dist_ts/npm/classes.npmregistry.d.ts +3 -1
  38. package/dist_ts/npm/classes.npmregistry.js +55 -6
  39. package/dist_ts/npm/classes.npmupstream.d.ts +51 -0
  40. package/dist_ts/npm/classes.npmupstream.js +206 -0
  41. package/dist_ts/npm/index.d.ts +1 -0
  42. package/dist_ts/npm/index.js +2 -1
  43. package/dist_ts/oci/classes.ociregistry.d.ts +4 -1
  44. package/dist_ts/oci/classes.ociregistry.js +78 -17
  45. package/dist_ts/oci/classes.ociupstream.d.ts +62 -0
  46. package/dist_ts/oci/classes.ociupstream.js +206 -0
  47. package/dist_ts/oci/index.d.ts +1 -0
  48. package/dist_ts/oci/index.js +2 -1
  49. package/dist_ts/plugins.d.ts +4 -1
  50. package/dist_ts/plugins.js +6 -2
  51. package/dist_ts/pypi/classes.pypiregistry.d.ts +7 -1
  52. package/dist_ts/pypi/classes.pypiregistry.js +60 -4
  53. package/dist_ts/pypi/classes.pypiupstream.d.ts +48 -0
  54. package/dist_ts/pypi/classes.pypiupstream.js +165 -0
  55. package/dist_ts/pypi/index.d.ts +1 -0
  56. package/dist_ts/pypi/index.js +2 -1
  57. package/dist_ts/rubygems/classes.rubygemsregistry.d.ts +7 -1
  58. package/dist_ts/rubygems/classes.rubygemsregistry.js +35 -4
  59. package/dist_ts/rubygems/classes.rubygemsupstream.d.ts +47 -0
  60. package/dist_ts/rubygems/classes.rubygemsupstream.js +184 -0
  61. package/dist_ts/rubygems/index.d.ts +1 -0
  62. package/dist_ts/rubygems/index.js +2 -1
  63. package/dist_ts/upstream/classes.baseupstream.d.ts +112 -0
  64. package/dist_ts/upstream/classes.baseupstream.js +411 -0
  65. package/dist_ts/upstream/classes.circuitbreaker.d.ts +111 -0
  66. package/dist_ts/upstream/classes.circuitbreaker.js +192 -0
  67. package/dist_ts/upstream/classes.upstreamcache.d.ts +170 -0
  68. package/dist_ts/upstream/classes.upstreamcache.js +485 -0
  69. package/dist_ts/upstream/index.d.ts +6 -0
  70. package/dist_ts/upstream/index.js +7 -0
  71. package/dist_ts/upstream/interfaces.upstream.d.ts +169 -0
  72. package/dist_ts/upstream/interfaces.upstream.js +23 -0
  73. package/package.json +4 -2
  74. package/ts/00_commitinfo_data.ts +1 -1
  75. package/ts/cargo/classes.cargoregistry.ts +48 -3
  76. package/ts/cargo/classes.cargoupstream.ts +159 -0
  77. package/ts/cargo/index.ts +1 -0
  78. package/ts/classes.smartregistry.ts +88 -11
  79. package/ts/composer/classes.composerregistry.ts +39 -2
  80. package/ts/composer/classes.composerupstream.ts +200 -0
  81. package/ts/composer/index.ts +1 -0
  82. package/ts/core/classes.authmanager.ts +74 -412
  83. package/ts/core/classes.defaultauthprovider.ts +393 -0
  84. package/ts/core/classes.registrystorage.ts +199 -5
  85. package/ts/core/index.ts +8 -1
  86. package/ts/core/interfaces.auth.ts +91 -0
  87. package/ts/core/interfaces.core.ts +42 -0
  88. package/ts/core/interfaces.storage.ts +130 -0
  89. package/ts/index.ts +3 -0
  90. package/ts/maven/classes.mavenregistry.ts +84 -3
  91. package/ts/maven/classes.mavenupstream.ts +220 -0
  92. package/ts/maven/index.ts +1 -0
  93. package/ts/npm/classes.npmregistry.ts +61 -5
  94. package/ts/npm/classes.npmupstream.ts +260 -0
  95. package/ts/npm/index.ts +1 -0
  96. package/ts/oci/classes.ociregistry.ts +89 -17
  97. package/ts/oci/classes.ociupstream.ts +263 -0
  98. package/ts/oci/index.ts +1 -0
  99. package/ts/plugins.ts +7 -1
  100. package/ts/pypi/classes.pypiregistry.ts +68 -3
  101. package/ts/pypi/classes.pypiupstream.ts +211 -0
  102. package/ts/pypi/index.ts +1 -0
  103. package/ts/rubygems/classes.rubygemsregistry.ts +40 -3
  104. package/ts/rubygems/classes.rubygemsupstream.ts +230 -0
  105. package/ts/rubygems/index.ts +1 -0
  106. package/ts/upstream/classes.baseupstream.ts +526 -0
  107. package/ts/upstream/classes.circuitbreaker.ts +238 -0
  108. package/ts/upstream/classes.upstreamcache.ts +626 -0
  109. package/ts/upstream/index.ts +11 -0
  110. package/ts/upstream/interfaces.upstream.ts +195 -0
@@ -0,0 +1,411 @@
1
+ import * as plugins from '../plugins.js';
2
+ import { DEFAULT_CACHE_CONFIG, DEFAULT_RESILIENCE_CONFIG, } from './interfaces.upstream.js';
3
+ import { CircuitBreaker, CircuitOpenError, withCircuitBreaker } from './classes.circuitbreaker.js';
4
+ import { UpstreamCache } from './classes.upstreamcache.js';
5
+ /**
6
+ * Base class for protocol-specific upstream implementations.
7
+ *
8
+ * Provides:
9
+ * - Multi-upstream routing with priority
10
+ * - Scope-based filtering (glob patterns)
11
+ * - Authentication handling
12
+ * - Circuit breaker per upstream
13
+ * - Caching with TTL
14
+ * - Retry with exponential backoff
15
+ * - 429 rate limit handling
16
+ */
17
+ export class BaseUpstream {
18
+ /** Upstream configuration */
19
+ config;
20
+ /** Resolved cache configuration */
21
+ cacheConfig;
22
+ /** Resolved resilience configuration */
23
+ resilienceConfig;
24
+ /** Circuit breakers per upstream */
25
+ circuitBreakers = new Map();
26
+ /** Upstream cache */
27
+ cache;
28
+ /** Logger instance */
29
+ logger;
30
+ constructor(config, logger) {
31
+ this.config = config;
32
+ this.cacheConfig = { ...DEFAULT_CACHE_CONFIG, ...config.cache };
33
+ this.resilienceConfig = { ...DEFAULT_RESILIENCE_CONFIG, ...config.resilience };
34
+ this.cache = new UpstreamCache(this.cacheConfig);
35
+ this.logger = logger || new plugins.smartlog.Smartlog({
36
+ logContext: {
37
+ company: 'smartregistry',
38
+ companyunit: 'upstream',
39
+ environment: 'production',
40
+ runtime: 'node',
41
+ }
42
+ });
43
+ // Initialize circuit breakers for each upstream
44
+ for (const upstream of config.upstreams) {
45
+ const upstreamResilience = { ...this.resilienceConfig, ...upstream.resilience };
46
+ this.circuitBreakers.set(upstream.id, new CircuitBreaker(upstream.id, upstreamResilience));
47
+ }
48
+ }
49
+ /**
50
+ * Check if upstream is enabled.
51
+ */
52
+ isEnabled() {
53
+ return this.config.enabled;
54
+ }
55
+ /**
56
+ * Get all configured upstreams.
57
+ */
58
+ getUpstreams() {
59
+ return this.config.upstreams;
60
+ }
61
+ /**
62
+ * Get circuit breaker state for an upstream.
63
+ */
64
+ getCircuitState(upstreamId) {
65
+ const breaker = this.circuitBreakers.get(upstreamId);
66
+ return breaker ? breaker.getState() : null;
67
+ }
68
+ /**
69
+ * Get cache statistics.
70
+ */
71
+ getCacheStats() {
72
+ return this.cache.getStats();
73
+ }
74
+ /**
75
+ * Fetch a resource from upstreams.
76
+ * Tries upstreams in priority order, respecting circuit breakers and scope rules.
77
+ */
78
+ async fetch(context) {
79
+ if (!this.config.enabled) {
80
+ return null;
81
+ }
82
+ // Get applicable upstreams sorted by priority
83
+ const applicableUpstreams = this.getApplicableUpstreams(context.resource);
84
+ if (applicableUpstreams.length === 0) {
85
+ return null;
86
+ }
87
+ // Use the first applicable upstream's URL for cache key
88
+ const primaryUpstreamUrl = applicableUpstreams[0]?.url;
89
+ // Check cache first
90
+ const cached = await this.cache.get(context, primaryUpstreamUrl);
91
+ if (cached && !cached.stale) {
92
+ return {
93
+ success: true,
94
+ status: 200,
95
+ headers: cached.headers,
96
+ body: cached.data,
97
+ upstreamId: cached.upstreamId,
98
+ fromCache: true,
99
+ latencyMs: 0,
100
+ };
101
+ }
102
+ // Check for negative cache (recent 404)
103
+ if (await this.cache.hasNegative(context, primaryUpstreamUrl)) {
104
+ return {
105
+ success: false,
106
+ status: 404,
107
+ headers: {},
108
+ upstreamId: 'cache',
109
+ fromCache: true,
110
+ latencyMs: 0,
111
+ };
112
+ }
113
+ // If we have stale cache, return it immediately and revalidate in background
114
+ if (cached?.stale && this.cacheConfig.staleWhileRevalidate) {
115
+ // Fire and forget revalidation
116
+ this.revalidateInBackground(context, applicableUpstreams);
117
+ return {
118
+ success: true,
119
+ status: 200,
120
+ headers: cached.headers,
121
+ body: cached.data,
122
+ upstreamId: cached.upstreamId,
123
+ fromCache: true,
124
+ latencyMs: 0,
125
+ };
126
+ }
127
+ // Try each upstream in order
128
+ let lastError = null;
129
+ for (const upstream of applicableUpstreams) {
130
+ const breaker = this.circuitBreakers.get(upstream.id);
131
+ if (!breaker)
132
+ continue;
133
+ try {
134
+ const result = await withCircuitBreaker(breaker, () => this.fetchFromUpstream(upstream, context));
135
+ // Cache successful responses
136
+ if (result.success && result.body) {
137
+ await this.cache.set(context, Buffer.isBuffer(result.body) ? result.body : Buffer.from(JSON.stringify(result.body)), result.headers['content-type'] || 'application/octet-stream', result.headers, upstream.id, upstream.url);
138
+ }
139
+ // Cache 404 responses
140
+ if (result.status === 404) {
141
+ await this.cache.setNegative(context, upstream.id, upstream.url);
142
+ }
143
+ return result;
144
+ }
145
+ catch (error) {
146
+ if (error instanceof CircuitOpenError) {
147
+ this.logger.log('debug', `Circuit open for upstream ${upstream.id}, trying next`);
148
+ }
149
+ else {
150
+ this.logger.log('warn', `Upstream ${upstream.id} failed: ${error.message}`);
151
+ }
152
+ lastError = error;
153
+ // Continue to next upstream
154
+ }
155
+ }
156
+ // All upstreams failed
157
+ if (lastError) {
158
+ this.logger.log('error', `All upstreams failed for ${context.resource}: ${lastError.message}`);
159
+ }
160
+ return null;
161
+ }
162
+ /**
163
+ * Invalidate cache for a resource pattern.
164
+ */
165
+ async invalidateCache(pattern) {
166
+ return this.cache.invalidatePattern(pattern);
167
+ }
168
+ /**
169
+ * Clear all cache entries.
170
+ */
171
+ async clearCache() {
172
+ await this.cache.clear();
173
+ }
174
+ /**
175
+ * Stop the upstream (cleanup resources).
176
+ */
177
+ stop() {
178
+ this.cache.stop();
179
+ }
180
+ /**
181
+ * Get upstreams that apply to a resource, sorted by priority.
182
+ */
183
+ getApplicableUpstreams(resource) {
184
+ return this.config.upstreams
185
+ .filter(upstream => {
186
+ if (!upstream.enabled)
187
+ return false;
188
+ // Check circuit breaker
189
+ const breaker = this.circuitBreakers.get(upstream.id);
190
+ if (breaker && !breaker.canRequest())
191
+ return false;
192
+ // Check scope rules
193
+ return this.matchesScopeRules(resource, upstream.scopeRules);
194
+ })
195
+ .sort((a, b) => a.priority - b.priority);
196
+ }
197
+ /**
198
+ * Check if a resource matches scope rules.
199
+ * Empty rules = match all.
200
+ */
201
+ matchesScopeRules(resource, rules) {
202
+ if (!rules || rules.length === 0) {
203
+ return true;
204
+ }
205
+ // Process rules in order
206
+ // Start with default exclude (nothing matches)
207
+ let matched = false;
208
+ for (const rule of rules) {
209
+ const isMatch = plugins.minimatch(resource, rule.pattern);
210
+ if (isMatch) {
211
+ matched = rule.action === 'include';
212
+ }
213
+ }
214
+ return matched;
215
+ }
216
+ /**
217
+ * Fetch from a specific upstream with retry logic.
218
+ */
219
+ async fetchFromUpstream(upstream, context) {
220
+ const upstreamResilience = { ...this.resilienceConfig, ...upstream.resilience };
221
+ const startTime = Date.now();
222
+ let lastError = null;
223
+ for (let attempt = 0; attempt <= upstreamResilience.maxRetries; attempt++) {
224
+ try {
225
+ const result = await this.executeRequest(upstream, context, upstreamResilience.timeoutMs);
226
+ return {
227
+ ...result,
228
+ upstreamId: upstream.id,
229
+ fromCache: false,
230
+ latencyMs: Date.now() - startTime,
231
+ };
232
+ }
233
+ catch (error) {
234
+ lastError = error;
235
+ // Don't retry on 4xx errors (except 429)
236
+ if (this.isNonRetryableError(error)) {
237
+ break;
238
+ }
239
+ // Calculate delay with exponential backoff and jitter
240
+ if (attempt < upstreamResilience.maxRetries) {
241
+ const delay = this.calculateBackoffDelay(attempt, upstreamResilience.retryDelayMs, upstreamResilience.retryMaxDelayMs);
242
+ await this.sleep(delay);
243
+ }
244
+ }
245
+ }
246
+ throw lastError || new Error('Request failed');
247
+ }
248
+ /**
249
+ * Execute a single HTTP request to an upstream.
250
+ */
251
+ async executeRequest(upstream, context, timeoutMs) {
252
+ // Build the full URL
253
+ const url = this.buildUpstreamUrl(upstream, context);
254
+ // Build headers with auth
255
+ const headers = this.buildHeaders(upstream, context);
256
+ // Make the request using SmartRequest
257
+ const request = plugins.smartrequest.SmartRequest.create()
258
+ .url(url)
259
+ .method(context.method)
260
+ .headers(headers)
261
+ .timeout(timeoutMs)
262
+ .handle429Backoff({ maxRetries: 3, fallbackDelay: 1000, maxWaitTime: 30000 });
263
+ // Add query params if present
264
+ if (Object.keys(context.query).length > 0) {
265
+ request.query(context.query);
266
+ }
267
+ let response;
268
+ switch (context.method.toUpperCase()) {
269
+ case 'GET':
270
+ response = await request.get();
271
+ break;
272
+ case 'HEAD':
273
+ // SmartRequest doesn't have head(), use options
274
+ response = await request.method('HEAD').get();
275
+ break;
276
+ default:
277
+ response = await request.get();
278
+ }
279
+ // Parse response
280
+ const responseHeaders = {};
281
+ for (const [key, value] of Object.entries(response.headers)) {
282
+ responseHeaders[key.toLowerCase()] = Array.isArray(value) ? value[0] : value;
283
+ }
284
+ let body;
285
+ const contentType = responseHeaders['content-type'] || '';
286
+ if (response.ok) {
287
+ if (contentType.includes('application/json')) {
288
+ body = await response.json();
289
+ }
290
+ else {
291
+ const arrayBuffer = await response.arrayBuffer();
292
+ body = Buffer.from(arrayBuffer);
293
+ }
294
+ }
295
+ return {
296
+ success: response.ok,
297
+ status: response.status,
298
+ headers: responseHeaders,
299
+ body,
300
+ };
301
+ }
302
+ /**
303
+ * Build the full URL for an upstream request.
304
+ * Subclasses can override for protocol-specific URL building.
305
+ */
306
+ buildUpstreamUrl(upstream, context) {
307
+ // Remove leading slash if URL already has trailing slash
308
+ let path = context.path;
309
+ if (upstream.url.endsWith('/') && path.startsWith('/')) {
310
+ path = path.slice(1);
311
+ }
312
+ return `${upstream.url}${path}`;
313
+ }
314
+ /**
315
+ * Build headers including authentication.
316
+ */
317
+ buildHeaders(upstream, context) {
318
+ const headers = { ...context.headers };
319
+ // Remove host header (will be set by HTTP client)
320
+ delete headers['host'];
321
+ // Add authentication
322
+ this.addAuthHeaders(headers, upstream.auth);
323
+ return headers;
324
+ }
325
+ /**
326
+ * Add authentication headers based on auth config.
327
+ */
328
+ addAuthHeaders(headers, auth) {
329
+ switch (auth.type) {
330
+ case 'basic':
331
+ if (auth.username && auth.password) {
332
+ const credentials = Buffer.from(`${auth.username}:${auth.password}`).toString('base64');
333
+ headers['authorization'] = `Basic ${credentials}`;
334
+ }
335
+ break;
336
+ case 'bearer':
337
+ if (auth.token) {
338
+ headers['authorization'] = `Bearer ${auth.token}`;
339
+ }
340
+ break;
341
+ case 'api-key':
342
+ if (auth.token) {
343
+ const headerName = auth.headerName || 'authorization';
344
+ headers[headerName.toLowerCase()] = auth.token;
345
+ }
346
+ break;
347
+ case 'none':
348
+ default:
349
+ // No authentication
350
+ break;
351
+ }
352
+ }
353
+ /**
354
+ * Check if an error should not be retried.
355
+ */
356
+ isNonRetryableError(error) {
357
+ // Check for HTTP status errors
358
+ if (error && typeof error === 'object' && 'status' in error) {
359
+ const status = error.status;
360
+ // Don't retry 4xx errors except 429 (rate limited)
361
+ if (status >= 400 && status < 500 && status !== 429) {
362
+ return true;
363
+ }
364
+ }
365
+ return false;
366
+ }
367
+ /**
368
+ * Calculate backoff delay with exponential backoff and jitter.
369
+ */
370
+ calculateBackoffDelay(attempt, baseDelayMs, maxDelayMs) {
371
+ // Exponential backoff: delay = base * 2^attempt
372
+ const exponentialDelay = baseDelayMs * Math.pow(2, attempt);
373
+ // Cap at max delay
374
+ const cappedDelay = Math.min(exponentialDelay, maxDelayMs);
375
+ // Add jitter (±25%)
376
+ const jitter = cappedDelay * 0.25 * (Math.random() * 2 - 1);
377
+ return Math.floor(cappedDelay + jitter);
378
+ }
379
+ /**
380
+ * Sleep for a specified duration.
381
+ */
382
+ sleep(ms) {
383
+ return new Promise(resolve => setTimeout(resolve, ms));
384
+ }
385
+ /**
386
+ * Revalidate cache in background.
387
+ */
388
+ async revalidateInBackground(context, upstreams) {
389
+ try {
390
+ for (const upstream of upstreams) {
391
+ const breaker = this.circuitBreakers.get(upstream.id);
392
+ if (!breaker || !breaker.canRequest())
393
+ continue;
394
+ try {
395
+ const result = await withCircuitBreaker(breaker, () => this.fetchFromUpstream(upstream, context));
396
+ if (result.success && result.body) {
397
+ await this.cache.set(context, Buffer.isBuffer(result.body) ? result.body : Buffer.from(JSON.stringify(result.body)), result.headers['content-type'] || 'application/octet-stream', result.headers, upstream.id, upstream.url);
398
+ return; // Successfully revalidated
399
+ }
400
+ }
401
+ catch {
402
+ // Continue to next upstream
403
+ }
404
+ }
405
+ }
406
+ catch (error) {
407
+ this.logger.log('debug', `Background revalidation failed: ${error.message}`);
408
+ }
409
+ }
410
+ }
411
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5iYXNldXBzdHJlYW0uanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90cy91cHN0cmVhbS9jbGFzc2VzLmJhc2V1cHN0cmVhbS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLGVBQWUsQ0FBQztBQVl6QyxPQUFPLEVBQ0wsb0JBQW9CLEVBQ3BCLHlCQUF5QixHQUMxQixNQUFNLDBCQUEwQixDQUFDO0FBQ2xDLE9BQU8sRUFBRSxjQUFjLEVBQUUsZ0JBQWdCLEVBQUUsa0JBQWtCLEVBQUUsTUFBTSw2QkFBNkIsQ0FBQztBQUNuRyxPQUFPLEVBQUUsYUFBYSxFQUFFLE1BQU0sNEJBQTRCLENBQUM7QUFFM0Q7Ozs7Ozs7Ozs7O0dBV0c7QUFDSCxNQUFNLE9BQWdCLFlBQVk7SUFJaEMsNkJBQTZCO0lBQ1YsTUFBTSxDQUEwQjtJQUVuRCxtQ0FBbUM7SUFDaEIsV0FBVyxDQUF1QjtJQUVyRCx3Q0FBd0M7SUFDckIsZ0JBQWdCLENBQTRCO0lBRS9ELG9DQUFvQztJQUNqQixlQUFlLEdBQWdDLElBQUksR0FBRyxFQUFFLENBQUM7SUFFNUUscUJBQXFCO0lBQ0YsS0FBSyxDQUFnQjtJQUV4QyxzQkFBc0I7SUFDSCxNQUFNLENBQTRCO0lBRXJELFlBQVksTUFBK0IsRUFBRSxNQUFrQztRQUM3RSxJQUFJLENBQUMsTUFBTSxHQUFHLE1BQU0sQ0FBQztRQUNyQixJQUFJLENBQUMsV0FBVyxHQUFHLEVBQUUsR0FBRyxvQkFBb0IsRUFBRSxHQUFHLE1BQU0sQ0FBQyxLQUFLLEVBQUUsQ0FBQztRQUNoRSxJQUFJLENBQUMsZ0JBQWdCLEdBQUcsRUFBRSxHQUFHLHlCQUF5QixFQUFFLEdBQUcsTUFBTSxDQUFDLFVBQVUsRUFBRSxDQUFDO1FBQy9FLElBQUksQ0FBQyxLQUFLLEdBQUcsSUFBSSxhQUFhLENBQUMsSUFBSSxDQUFDLFdBQVcsQ0FBQyxDQUFDO1FBQ2pELElBQUksQ0FBQyxNQUFNLEdBQUcs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
@@ -0,0 +1,111 @@
1
+ import type { TCircuitState, IUpstreamResilienceConfig } from './interfaces.upstream.js';
2
+ /**
3
+ * Circuit breaker implementation for upstream resilience.
4
+ *
5
+ * States:
6
+ * - CLOSED: Normal operation, requests pass through
7
+ * - OPEN: Circuit is tripped, requests fail fast
8
+ * - HALF_OPEN: Testing if upstream has recovered
9
+ *
10
+ * Transitions:
11
+ * - CLOSED → OPEN: When failure count exceeds threshold
12
+ * - OPEN → HALF_OPEN: After reset timeout expires
13
+ * - HALF_OPEN → CLOSED: On successful request
14
+ * - HALF_OPEN → OPEN: On failed request
15
+ */
16
+ export declare class CircuitBreaker {
17
+ /** Unique identifier for logging and metrics */
18
+ readonly id: string;
19
+ /** Current circuit state */
20
+ private state;
21
+ /** Count of consecutive failures */
22
+ private failureCount;
23
+ /** Timestamp when circuit was opened */
24
+ private openedAt;
25
+ /** Number of successful requests in half-open state */
26
+ private halfOpenSuccesses;
27
+ /** Configuration */
28
+ private readonly config;
29
+ /** Number of successes required to close circuit from half-open */
30
+ private readonly halfOpenThreshold;
31
+ constructor(id: string, config?: Partial<IUpstreamResilienceConfig>);
32
+ /**
33
+ * Get current circuit state.
34
+ */
35
+ getState(): TCircuitState;
36
+ /**
37
+ * Check if circuit allows requests.
38
+ * Returns true if requests should be allowed.
39
+ */
40
+ canRequest(): boolean;
41
+ /**
42
+ * Record a successful request.
43
+ * May transition circuit from HALF_OPEN to CLOSED.
44
+ */
45
+ recordSuccess(): void;
46
+ /**
47
+ * Record a failed request.
48
+ * May transition circuit from CLOSED/HALF_OPEN to OPEN.
49
+ */
50
+ recordFailure(): void;
51
+ /**
52
+ * Force circuit to open state.
53
+ * Useful for manual intervention or external health checks.
54
+ */
55
+ forceOpen(): void;
56
+ /**
57
+ * Force circuit to closed state.
58
+ * Useful for manual intervention after fixing upstream issues.
59
+ */
60
+ forceClose(): void;
61
+ /**
62
+ * Reset circuit to initial state.
63
+ */
64
+ reset(): void;
65
+ /**
66
+ * Get circuit metrics for monitoring.
67
+ */
68
+ getMetrics(): ICircuitBreakerMetrics;
69
+ /**
70
+ * Transition to a new state with proper cleanup.
71
+ */
72
+ private transitionTo;
73
+ }
74
+ /**
75
+ * Metrics for circuit breaker monitoring.
76
+ */
77
+ export interface ICircuitBreakerMetrics {
78
+ /** Circuit breaker identifier */
79
+ id: string;
80
+ /** Current state */
81
+ state: TCircuitState;
82
+ /** Number of consecutive failures */
83
+ failureCount: number;
84
+ /** When circuit was opened (null if never opened) */
85
+ openedAt: Date | null;
86
+ /** Milliseconds until circuit transitions to half-open (0 if not open) */
87
+ timeUntilHalfOpen: number;
88
+ /** Number of successes in half-open state */
89
+ halfOpenSuccesses: number;
90
+ /** Failure threshold for opening circuit */
91
+ threshold: number;
92
+ /** Reset timeout in milliseconds */
93
+ resetMs: number;
94
+ }
95
+ /**
96
+ * Execute a function with circuit breaker protection.
97
+ *
98
+ * @param breaker The circuit breaker to use
99
+ * @param fn The async function to execute
100
+ * @param fallback Optional fallback function when circuit is open
101
+ * @returns The result of fn or fallback
102
+ * @throws CircuitOpenError if circuit is open and no fallback provided
103
+ */
104
+ export declare function withCircuitBreaker<T>(breaker: CircuitBreaker, fn: () => Promise<T>, fallback?: () => Promise<T>): Promise<T>;
105
+ /**
106
+ * Error thrown when circuit is open and no fallback is provided.
107
+ */
108
+ export declare class CircuitOpenError extends Error {
109
+ readonly circuitId: string;
110
+ constructor(circuitId: string);
111
+ }