@stacksjs/bun-router 0.0.17 → 0.0.18

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 (146) hide show
  1. package/dist/cli.js +1 -1
  2. package/dist/container/service-provider.d.ts +3 -3
  3. package/dist/development/performance-profiler.d.ts +3 -3
  4. package/dist/development/route-debugger.d.ts +1 -1
  5. package/dist/development/route-inspector.d.ts +2 -2
  6. package/dist/observability/integration.d.ts +1 -1
  7. package/dist/response/macros.d.ts +1 -1
  8. package/dist/router/index.d.ts +7 -7
  9. package/dist/router/router.d.ts +0 -32
  10. package/dist/testing/performance-testing.d.ts +2 -2
  11. package/dist/testing/test-client.d.ts +1 -1
  12. package/dist/testing/websocket-testing.d.ts +1 -1
  13. package/package.json +2 -3
  14. package/src/auth.ts +0 -489
  15. package/src/cache/lru-cache.ts +0 -457
  16. package/src/cache/middleware-memoization.ts +0 -531
  17. package/src/cache/route-cache-warmer.ts +0 -486
  18. package/src/cache/sqlite-cache.ts +0 -783
  19. package/src/cache/streaming-cache.ts +0 -572
  20. package/src/cli/colors.ts +0 -29
  21. package/src/cli/index.ts +0 -287
  22. package/src/cli/middleware.ts +0 -291
  23. package/src/cli/openapi.ts +0 -407
  24. package/src/cli/router.ts +0 -188
  25. package/src/cli/routes.ts +0 -265
  26. package/src/cli/utils.ts +0 -531
  27. package/src/cli.ts +0 -5
  28. package/src/config.ts +0 -322
  29. package/src/container/container.ts +0 -786
  30. package/src/container/contextual-binding.ts +0 -606
  31. package/src/container/decorators.ts +0 -368
  32. package/src/container/index.ts +0 -49
  33. package/src/container/service-provider.ts +0 -596
  34. package/src/development/hot-reload.ts +0 -673
  35. package/src/development/index.ts +0 -499
  36. package/src/development/performance-profiler.ts +0 -717
  37. package/src/development/route-debugger.ts +0 -527
  38. package/src/development/route-inspector.ts +0 -749
  39. package/src/development/typescript-utilities.ts +0 -682
  40. package/src/docs.ts +0 -397
  41. package/src/errors/circuit-breaker.ts +0 -733
  42. package/src/errors/error-handler.ts +0 -569
  43. package/src/errors/error-reporting.ts +0 -672
  44. package/src/errors/exceptions.ts +0 -536
  45. package/src/errors/graceful-degradation.ts +0 -623
  46. package/src/errors/index.ts +0 -21
  47. package/src/errors/router-errors.ts +0 -632
  48. package/src/file-serving/static-files.ts +0 -718
  49. package/src/index.ts +0 -66
  50. package/src/middleware/auth.ts +0 -220
  51. package/src/middleware/content_security_policy.ts +0 -215
  52. package/src/middleware/cors.ts +0 -142
  53. package/src/middleware/csrf.ts +0 -166
  54. package/src/middleware/ddos_protection.ts +0 -279
  55. package/src/middleware/file_security.ts +0 -191
  56. package/src/middleware/file_upload.ts +0 -275
  57. package/src/middleware/helmet.ts +0 -268
  58. package/src/middleware/index.ts +0 -117
  59. package/src/middleware/input_validation.ts +0 -453
  60. package/src/middleware/json_body.ts +0 -37
  61. package/src/middleware/performance_alerting.ts +0 -540
  62. package/src/middleware/performance_dashboard.ts +0 -661
  63. package/src/middleware/performance_monitor.ts +0 -945
  64. package/src/middleware/pipeline.ts +0 -489
  65. package/src/middleware/rate_limit.ts +0 -251
  66. package/src/middleware/request_id.ts +0 -36
  67. package/src/middleware/request_signing.ts +0 -638
  68. package/src/middleware/request_tracer.ts +0 -653
  69. package/src/middleware/response_cache.ts +0 -778
  70. package/src/middleware/security.ts +0 -482
  71. package/src/middleware/security_suite.ts +0 -257
  72. package/src/middleware/session.ts +0 -91
  73. package/src/model-binding/index.ts +0 -18
  74. package/src/model-binding/model-middleware.ts +0 -425
  75. package/src/model-binding/model-registry.ts +0 -550
  76. package/src/model-binding.ts +0 -370
  77. package/src/model-resolver-factory.ts +0 -106
  78. package/src/observability/correlation.ts +0 -691
  79. package/src/observability/health-checks.ts +0 -729
  80. package/src/observability/index.ts +0 -184
  81. package/src/observability/integration.ts +0 -548
  82. package/src/observability/metrics.ts +0 -753
  83. package/src/observability/tracing.ts +0 -638
  84. package/src/optimization/bun-utilities.ts +0 -778
  85. package/src/query-builder-integration.ts +0 -137
  86. package/src/request/context.ts +0 -109
  87. package/src/request/enhanced-request.ts +0 -876
  88. package/src/request/macros.ts +0 -822
  89. package/src/response/macros.ts +0 -665
  90. package/src/response/response-factory.ts +0 -596
  91. package/src/router/api-routes.ts +0 -243
  92. package/src/router/file-based-routing.ts +0 -697
  93. package/src/router/file-streaming.ts +0 -383
  94. package/src/router/fluent-routing.ts +0 -866
  95. package/src/router/group-organization.ts +0 -229
  96. package/src/router/handler-resolver.ts +0 -327
  97. package/src/router/http-methods.ts +0 -407
  98. package/src/router/index.ts +0 -193
  99. package/src/router/middleware-groups.ts +0 -222
  100. package/src/router/middleware-integration.ts +0 -399
  101. package/src/router/middleware.ts +0 -234
  102. package/src/router/model-binding.ts +0 -215
  103. package/src/router/optimized-route-matching.ts +0 -302
  104. package/src/router/route-building.ts +0 -221
  105. package/src/router/route-compiler.ts +0 -691
  106. package/src/router/route-matching.ts +0 -368
  107. package/src/router/route-trie.ts +0 -450
  108. package/src/router/router.ts +0 -1616
  109. package/src/router/server.ts +0 -684
  110. package/src/router/validation-integration.ts +0 -449
  111. package/src/router/view-rendering.ts +0 -233
  112. package/src/router/websocket.ts +0 -100
  113. package/src/routing/route-caching.ts +0 -402
  114. package/src/routing/route-throttling.ts +0 -469
  115. package/src/routing/subdomain-routing.ts +0 -492
  116. package/src/session/database-store.ts +0 -109
  117. package/src/session/file-store.ts +0 -148
  118. package/src/session/index.ts +0 -244
  119. package/src/session/memory-store.ts +0 -88
  120. package/src/session/redis-store.ts +0 -93
  121. package/src/streaming/index.ts +0 -17
  122. package/src/streaming/sse-handler.ts +0 -482
  123. package/src/streaming/stream-handler.ts +0 -552
  124. package/src/testing/auth-testing.ts +0 -448
  125. package/src/testing/file-upload-testing.ts +0 -543
  126. package/src/testing/index.ts +0 -10
  127. package/src/testing/middleware-testing.ts +0 -322
  128. package/src/testing/model-binding-testing.ts +0 -645
  129. package/src/testing/performance-testing.ts +0 -738
  130. package/src/testing/test-client.ts +0 -310
  131. package/src/testing/test-request.ts +0 -315
  132. package/src/testing/test-response.ts +0 -332
  133. package/src/testing/types.ts +0 -224
  134. package/src/testing/websocket-testing.ts +0 -590
  135. package/src/types/controller-types.ts +0 -385
  136. package/src/types/core.ts +0 -683
  137. package/src/types/middleware-types.ts +0 -422
  138. package/src/types/request-response-augmentation.ts +0 -489
  139. package/src/types/route-inference.ts +0 -372
  140. package/src/types.ts +0 -2123
  141. package/src/url.ts +0 -131
  142. package/src/utils/index.ts +0 -1
  143. package/src/utils/query-preservation.ts +0 -201
  144. package/src/utils.ts +0 -395
  145. package/src/validation/validator.ts +0 -688
  146. package/src/websocket/clustering.ts +0 -769
@@ -1,733 +0,0 @@
1
- /**
2
- * Advanced Error Handling - Circuit Breaker Pattern
3
- *
4
- * Prevents cascading failures by monitoring external service health
5
- * and temporarily blocking requests when services are failing
6
- */
7
-
8
- import type { ErrorContext } from './exceptions'
9
- import { CircuitBreakerOpenException, ExternalServiceException, TimeoutException } from './exceptions'
10
-
11
- export interface CircuitBreakerConfig {
12
- name: string
13
- failureThreshold: number
14
- recoveryTimeout: number
15
- timeout: number
16
- monitoringPeriod: number
17
- minimumRequests: number
18
- errorThresholdPercentage: number
19
- halfOpenMaxCalls: number
20
- resetTimeout: number
21
- onStateChange?: (state: CircuitBreakerState, name: string) => void
22
- onFailure?: (error: Error, name: string) => void
23
- onSuccess?: (name: string) => void
24
- shouldTripOnError?: (error: Error) => boolean
25
- }
26
-
27
- export type CircuitBreakerState = 'CLOSED' | 'OPEN' | 'HALF_OPEN'
28
-
29
- export interface CircuitBreakerMetrics {
30
- totalRequests: number
31
- successfulRequests: number
32
- failedRequests: number
33
- timeouts: number
34
- averageResponseTime: number
35
- lastFailureTime?: Date
36
- lastSuccessTime?: Date
37
- state: CircuitBreakerState
38
- nextAttemptTime?: Date
39
- consecutiveFailures: number
40
- consecutiveSuccesses: number
41
- }
42
-
43
- export interface CircuitBreakerCall<T> {
44
- execute: () => Promise<T>
45
- fallback?: () => Promise<T>
46
- timeout?: number
47
- retries?: number
48
- context?: ErrorContext
49
- }
50
-
51
- /**
52
- * Circuit breaker implementation
53
- */
54
- export class CircuitBreaker {
55
- private config: CircuitBreakerConfig
56
- private state: CircuitBreakerState = 'CLOSED'
57
- private metrics: CircuitBreakerMetrics
58
- private nextAttemptTime?: Date
59
- private halfOpenCallCount = 0
60
- private recentRequests: Array<{ timestamp: Date, success: boolean, responseTime: number }> = []
61
-
62
- constructor(config: CircuitBreakerConfig) {
63
- const defaults = {
64
- failureThreshold: 5,
65
- recoveryTimeout: 60000,
66
- timeout: 30000,
67
- monitoringPeriod: 60000,
68
- minimumRequests: 10,
69
- errorThresholdPercentage: 50,
70
- halfOpenMaxCalls: 3,
71
- resetTimeout: 60000,
72
- }
73
-
74
- this.config = {
75
- ...defaults,
76
- ...config,
77
- }
78
-
79
- this.metrics = {
80
- totalRequests: 0,
81
- successfulRequests: 0,
82
- failedRequests: 0,
83
- timeouts: 0,
84
- averageResponseTime: 0,
85
- state: this.state,
86
- consecutiveFailures: 0,
87
- consecutiveSuccesses: 0,
88
- }
89
-
90
- // Clean up old requests periodically (unref'd so the timer never
91
- // keeps the process alive)
92
- setInterval(() => this.cleanupOldRequests(), this.config.monitoringPeriod / 4).unref?.()
93
- }
94
-
95
- /**
96
- * Execute a function with circuit breaker protection
97
- */
98
- async execute<T>(call: CircuitBreakerCall<T>): Promise<T> {
99
- // Check if circuit is open
100
- if (this.state === 'OPEN') {
101
- if (this.nextAttemptTime && new Date() < this.nextAttemptTime) {
102
- // Circuit is still open, reject immediately
103
- const error = new CircuitBreakerOpenException(
104
- this.config.name,
105
- this.nextAttemptTime,
106
- call.context,
107
- )
108
-
109
- if (call.fallback) {
110
- try {
111
- return await call.fallback()
112
- }
113
- catch (fallbackError) {
114
- console.error(fallbackError)
115
- }
116
- }
117
-
118
- throw error
119
- }
120
- else {
121
- // Time to try half-open
122
- this.transitionToHalfOpen()
123
- }
124
- }
125
-
126
- // Check half-open call limit
127
- if (this.state === 'HALF_OPEN' && this.halfOpenCallCount >= this.config.halfOpenMaxCalls) {
128
- const error = new CircuitBreakerOpenException(
129
- this.config.name,
130
- this.nextAttemptTime || new Date(Date.now() + this.config.recoveryTimeout),
131
- call.context,
132
- )
133
-
134
- if (call.fallback) {
135
- try {
136
- return await call.fallback()
137
- }
138
- catch (fallbackError) {
139
- console.error(fallbackError)
140
-
141
- throw error
142
- }
143
- }
144
-
145
- throw error
146
- }
147
-
148
- const startTime = Date.now()
149
- let result: T
150
- let error: Error | null = null
151
-
152
- try {
153
- // Increment call count for half-open state
154
- if (this.state === 'HALF_OPEN') {
155
- this.halfOpenCallCount++
156
- }
157
-
158
- // Execute with timeout
159
- const timeout = call.timeout || this.config.timeout
160
- const timeoutPromise = new Promise<never>((_, reject) => {
161
- setTimeout(() => {
162
- reject(new TimeoutException(`Circuit breaker timeout for ${this.config.name}`, timeout, call.context))
163
- }, timeout)
164
- })
165
-
166
- result = await Promise.race([call.execute(), timeoutPromise])
167
-
168
- // Success
169
- this.onSuccess(Date.now() - startTime)
170
- return result
171
- }
172
- catch (err) {
173
- error = err as Error
174
- this.onFailure(error, Date.now() - startTime)
175
-
176
- // Try fallback if available
177
- if (call.fallback) {
178
- try {
179
- return await call.fallback()
180
- }
181
- catch (fallbackError) {
182
- console.error(fallbackError)
183
- throw error
184
- }
185
- }
186
- else {
187
- throw error
188
- }
189
- }
190
- }
191
-
192
- /**
193
- * Get current circuit breaker state
194
- */
195
- getState(): CircuitBreakerState {
196
- return this.state
197
- }
198
-
199
- /**
200
- * Get circuit breaker metrics
201
- */
202
- getMetrics(): CircuitBreakerMetrics {
203
- return {
204
- ...this.metrics,
205
- state: this.state,
206
- nextAttemptTime: this.nextAttemptTime,
207
- }
208
- }
209
-
210
- /**
211
- * Force circuit breaker to open state
212
- */
213
- forceOpen(): void {
214
- this.transitionToOpen()
215
- }
216
-
217
- /**
218
- * Force circuit breaker to closed state
219
- */
220
- forceClosed(): void {
221
- this.transitionToClosed()
222
- }
223
-
224
- /**
225
- * Reset circuit breaker metrics
226
- */
227
- reset(): void {
228
- this.metrics = {
229
- totalRequests: 0,
230
- successfulRequests: 0,
231
- failedRequests: 0,
232
- timeouts: 0,
233
- averageResponseTime: 0,
234
- state: this.state,
235
- consecutiveFailures: 0,
236
- consecutiveSuccesses: 0,
237
- }
238
- this.recentRequests = []
239
- }
240
-
241
- private onSuccess(responseTime: number): void {
242
- this.metrics.totalRequests++
243
- this.metrics.successfulRequests++
244
- this.metrics.consecutiveSuccesses++
245
- this.metrics.consecutiveFailures = 0
246
- this.metrics.lastSuccessTime = new Date()
247
-
248
- // Update average response time
249
- this.updateAverageResponseTime(responseTime)
250
-
251
- // Add to recent requests
252
- this.recentRequests.push({
253
- timestamp: new Date(),
254
- success: true,
255
- responseTime,
256
- })
257
-
258
- // Handle state transitions
259
- if (this.state === 'HALF_OPEN') {
260
- if (this.metrics.consecutiveSuccesses >= this.config.halfOpenMaxCalls) {
261
- this.transitionToClosed()
262
- }
263
- }
264
-
265
- if (this.config.onSuccess) {
266
- this.config.onSuccess(this.config.name)
267
- }
268
- }
269
-
270
- private onFailure(error: Error, responseTime: number): void {
271
- this.metrics.totalRequests++
272
- this.metrics.failedRequests++
273
- this.metrics.consecutiveFailures++
274
- this.metrics.consecutiveSuccesses = 0
275
- this.metrics.lastFailureTime = new Date()
276
-
277
- if (error instanceof TimeoutException) {
278
- this.metrics.timeouts++
279
- }
280
-
281
- // Update average response time
282
- this.updateAverageResponseTime(responseTime)
283
-
284
- // Add to recent requests
285
- this.recentRequests.push({
286
- timestamp: new Date(),
287
- success: false,
288
- responseTime,
289
- })
290
-
291
- // Check if we should trip the circuit breaker
292
- if (this.shouldTrip(error)) {
293
- if (this.state === 'CLOSED') {
294
- this.transitionToOpen()
295
- }
296
- else if (this.state === 'HALF_OPEN') {
297
- this.transitionToOpen()
298
- }
299
- }
300
-
301
- if (this.config.onFailure) {
302
- this.config.onFailure(error, this.config.name)
303
- }
304
- }
305
-
306
- private shouldTrip(error: Error): boolean {
307
- // Custom error check
308
- if (this.config.shouldTripOnError && !this.config.shouldTripOnError(error)) {
309
- return false
310
- }
311
-
312
- // Check consecutive failures threshold
313
- if (this.metrics.consecutiveFailures >= this.config.failureThreshold) {
314
- return true
315
- }
316
-
317
- // Check error rate over monitoring period
318
- const recentRequests = this.getRecentRequests()
319
- if (recentRequests.length >= this.config.minimumRequests) {
320
- const failedRequests = recentRequests.filter(r => !r.success).length
321
- const errorRate = (failedRequests / recentRequests.length) * 100
322
-
323
- if (errorRate >= this.config.errorThresholdPercentage) {
324
- return true
325
- }
326
- }
327
-
328
- return false
329
- }
330
-
331
- private transitionToClosed(): void {
332
- this.state = 'CLOSED'
333
- this.nextAttemptTime = undefined
334
- this.halfOpenCallCount = 0
335
- this.metrics.state = this.state
336
-
337
- if (this.config.onStateChange) {
338
- this.config.onStateChange(this.state, this.config.name)
339
- }
340
- }
341
-
342
- private transitionToOpen(): void {
343
- this.state = 'OPEN'
344
- this.nextAttemptTime = new Date(Date.now() + this.config.recoveryTimeout)
345
- this.halfOpenCallCount = 0
346
- this.metrics.state = this.state
347
- this.metrics.nextAttemptTime = this.nextAttemptTime
348
-
349
- if (this.config.onStateChange) {
350
- this.config.onStateChange(this.state, this.config.name)
351
- }
352
- }
353
-
354
- private transitionToHalfOpen(): void {
355
- this.state = 'HALF_OPEN'
356
- this.halfOpenCallCount = 0
357
- this.metrics.state = this.state
358
-
359
- if (this.config.onStateChange) {
360
- this.config.onStateChange(this.state, this.config.name)
361
- }
362
- }
363
-
364
- private updateAverageResponseTime(responseTime: number): void {
365
- if (this.metrics.totalRequests === 1) {
366
- this.metrics.averageResponseTime = responseTime
367
- }
368
- else {
369
- // Exponential moving average
370
- const alpha = 0.1
371
- this.metrics.averageResponseTime
372
- = (alpha * responseTime) + ((1 - alpha) * this.metrics.averageResponseTime)
373
- }
374
- }
375
-
376
- private getRecentRequests(): Array<{ timestamp: Date, success: boolean, responseTime: number }> {
377
- const cutoff = new Date(Date.now() - this.config.monitoringPeriod)
378
- return this.recentRequests.filter(r => r.timestamp > cutoff)
379
- }
380
-
381
- private cleanupOldRequests(): void {
382
- const cutoff = new Date(Date.now() - this.config.monitoringPeriod)
383
- this.recentRequests = this.recentRequests.filter(r => r.timestamp > cutoff)
384
- }
385
- }
386
-
387
- /**
388
- * Circuit breaker registry for managing multiple circuit breakers
389
- */
390
- export class CircuitBreakerRegistry {
391
- private breakers = new Map<string, CircuitBreaker>()
392
- private configs = new Map<string, CircuitBreakerConfig>()
393
-
394
- /**
395
- * Register a new circuit breaker
396
- */
397
- register(config: CircuitBreakerConfig): CircuitBreaker {
398
- const breaker = new CircuitBreaker(config)
399
- this.breakers.set(config.name, breaker)
400
- this.configs.set(config.name, config)
401
- return breaker
402
- }
403
-
404
- /**
405
- * Get circuit breaker by name
406
- */
407
- get(name: string): CircuitBreaker | undefined {
408
- return this.breakers.get(name)
409
- }
410
-
411
- /**
412
- * Execute function with circuit breaker protection
413
- */
414
- async execute<T>(name: string, call: CircuitBreakerCall<T>): Promise<T> {
415
- const breaker = this.breakers.get(name)
416
- if (!breaker) {
417
- throw new Error(`Circuit breaker '${name}' not found`)
418
- }
419
- return breaker.execute(call)
420
- }
421
-
422
- /**
423
- * Get all circuit breaker metrics
424
- */
425
- getAllMetrics(): Record<string, CircuitBreakerMetrics> {
426
- const metrics: Record<string, CircuitBreakerMetrics> = {}
427
- for (const [name, breaker] of this.breakers) {
428
- metrics[name] = breaker.getMetrics()
429
- }
430
- return metrics
431
- }
432
-
433
- /**
434
- * Get circuit breakers by state
435
- */
436
- getBreakersByState(state: CircuitBreakerState): string[] {
437
- const names: string[] = []
438
- for (const [name, breaker] of this.breakers) {
439
- if (breaker.getState() === state) {
440
- names.push(name)
441
- }
442
- }
443
- return names
444
- }
445
-
446
- /**
447
- * Force all circuit breakers to closed state
448
- */
449
- forceAllClosed(): void {
450
- for (const breaker of this.breakers.values()) {
451
- breaker.forceClosed()
452
- }
453
- }
454
-
455
- /**
456
- * Reset all circuit breaker metrics
457
- */
458
- resetAll(): void {
459
- for (const breaker of this.breakers.values()) {
460
- breaker.reset()
461
- }
462
- }
463
-
464
- /**
465
- * Remove circuit breaker
466
- */
467
- remove(name: string): boolean {
468
- this.configs.delete(name)
469
- return this.breakers.delete(name)
470
- }
471
-
472
- /**
473
- * Get all registered circuit breaker names
474
- */
475
- getNames(): string[] {
476
- return Array.from(this.breakers.keys())
477
- }
478
- }
479
-
480
- /**
481
- * Decorator for circuit breaker protection
482
- */
483
- export function circuitBreaker(config: CircuitBreakerConfig): <T extends (...args: any[]) => Promise<any>>(
484
- target: any,
485
- propertyName: string,
486
- descriptor: TypedPropertyDescriptor<T>,
487
- ) => TypedPropertyDescriptor<T> {
488
- const breaker = new CircuitBreaker(config)
489
-
490
- return function <T extends (...args: any[]) => Promise<any>>(
491
- target: any,
492
- propertyName: string,
493
- descriptor: TypedPropertyDescriptor<T>,
494
- ): TypedPropertyDescriptor<T> {
495
- const method = descriptor.value!
496
-
497
- descriptor.value = async function (this: any, ...args: any[]) {
498
- return breaker.execute({
499
- execute: () => method.apply(this, args),
500
- context: {
501
- method: propertyName,
502
- timestamp: new Date(),
503
- },
504
- })
505
- } as T
506
-
507
- return descriptor
508
- }
509
- }
510
-
511
- /**
512
- * HTTP client with circuit breaker protection
513
- */
514
- export class CircuitBreakerHttpClient {
515
- private registry: CircuitBreakerRegistry
516
-
517
- constructor(registry?: CircuitBreakerRegistry) {
518
- this.registry = registry || new CircuitBreakerRegistry()
519
- }
520
-
521
- /**
522
- * Make HTTP request with circuit breaker protection
523
- */
524
- async fetch(
525
- url: string,
526
- options: RequestInit & {
527
- circuitBreaker?: string | CircuitBreakerConfig
528
- fallback?: () => Promise<Response>
529
- context?: ErrorContext
530
- } = {},
531
- ): Promise<Response> {
532
- const { circuitBreaker, fallback, context, ...fetchOptions } = options
533
-
534
- // Determine circuit breaker to use
535
- let breakerName: string
536
- if (typeof circuitBreaker === 'string') {
537
- breakerName = circuitBreaker
538
- }
539
- else if (circuitBreaker) {
540
- breakerName = circuitBreaker.name
541
- if (!this.registry.get(breakerName)) {
542
- this.registry.register(circuitBreaker)
543
- }
544
- }
545
- else {
546
- // Create default circuit breaker based on hostname
547
- const hostname = new URL(url).hostname
548
- breakerName = `http-${hostname}`
549
- if (!this.registry.get(breakerName)) {
550
- this.registry.register({
551
- name: breakerName,
552
- failureThreshold: 5,
553
- recoveryTimeout: 60000,
554
- timeout: 30000,
555
- monitoringPeriod: 60000,
556
- minimumRequests: 10,
557
- errorThresholdPercentage: 50,
558
- halfOpenMaxCalls: 3,
559
- resetTimeout: 60000,
560
- })
561
- }
562
- }
563
-
564
- return this.registry.execute(breakerName, {
565
- execute: async () => {
566
- const response = await fetch(url, fetchOptions)
567
-
568
- // Consider 5xx status codes as failures
569
- if (response.status >= 500) {
570
- throw new ExternalServiceException(
571
- `HTTP ${response.status}: ${response.statusText}`,
572
- breakerName,
573
- url,
574
- response.status,
575
- undefined,
576
- context,
577
- )
578
- }
579
-
580
- return response
581
- },
582
- fallback,
583
- context,
584
- })
585
- }
586
-
587
- /**
588
- * Get circuit breaker registry
589
- */
590
- getRegistry(): CircuitBreakerRegistry {
591
- return this.registry
592
- }
593
- }
594
-
595
- /**
596
- * Middleware for circuit breaker protection
597
- */
598
- export function createCircuitBreakerMiddleware(registry: CircuitBreakerRegistry) {
599
- return async (req: any, next: () => Promise<Response>): Promise<Response> => {
600
- const serviceName = req.context?.serviceName as string
601
- if (!serviceName) {
602
- return next()
603
- }
604
-
605
- const breaker = registry.get(serviceName)
606
- if (!breaker) {
607
- return next()
608
- }
609
-
610
- return breaker.execute({
611
- execute: next,
612
- context: {
613
- requestId: req.requestId,
614
- route: req.route,
615
- method: req.method,
616
- url: req.url,
617
- },
618
- })
619
- }
620
- }
621
-
622
- /**
623
- * Factory functions for common circuit breaker configurations
624
- */
625
- export const CircuitBreakerPresets = {
626
- /**
627
- * Fast-failing circuit breaker for critical services
628
- */
629
- critical: (name: string): CircuitBreakerConfig => ({
630
- name,
631
- failureThreshold: 3,
632
- recoveryTimeout: 30000,
633
- timeout: 5000,
634
- monitoringPeriod: 30000,
635
- minimumRequests: 5,
636
- errorThresholdPercentage: 30,
637
- halfOpenMaxCalls: 2,
638
- resetTimeout: 30000,
639
- }),
640
-
641
- /**
642
- * Standard circuit breaker for regular services
643
- */
644
- standard: (name: string): CircuitBreakerConfig => ({
645
- name,
646
- failureThreshold: 5,
647
- recoveryTimeout: 60000,
648
- timeout: 30000,
649
- monitoringPeriod: 60000,
650
- minimumRequests: 10,
651
- errorThresholdPercentage: 50,
652
- halfOpenMaxCalls: 3,
653
- resetTimeout: 60000,
654
- }),
655
-
656
- /**
657
- * Tolerant circuit breaker for non-critical services
658
- */
659
- tolerant: (name: string): CircuitBreakerConfig => ({
660
- name,
661
- failureThreshold: 10,
662
- recoveryTimeout: 120000,
663
- timeout: 60000,
664
- monitoringPeriod: 120000,
665
- minimumRequests: 20,
666
- errorThresholdPercentage: 70,
667
- halfOpenMaxCalls: 5,
668
- resetTimeout: 120000,
669
- }),
670
-
671
- /**
672
- * Database circuit breaker with longer timeouts
673
- */
674
- database: (name: string): CircuitBreakerConfig => ({
675
- name,
676
- failureThreshold: 3,
677
- recoveryTimeout: 60000,
678
- timeout: 10000,
679
- monitoringPeriod: 60000,
680
- minimumRequests: 5,
681
- errorThresholdPercentage: 40,
682
- halfOpenMaxCalls: 2,
683
- resetTimeout: 60000,
684
- shouldTripOnError: (error) => {
685
- // Don't trip on validation errors or client errors
686
- return !(error.message.includes('validation') || error.message.includes('constraint'))
687
- },
688
- }),
689
-
690
- /**
691
- * External API circuit breaker
692
- */
693
- externalApi: (name: string): CircuitBreakerConfig => ({
694
- name,
695
- failureThreshold: 5,
696
- recoveryTimeout: 90000,
697
- timeout: 15000,
698
- monitoringPeriod: 90000,
699
- minimumRequests: 8,
700
- errorThresholdPercentage: 60,
701
- halfOpenMaxCalls: 3,
702
- resetTimeout: 90000,
703
- shouldTripOnError: (error) => {
704
- // Don't trip on 4xx client errors, only 5xx server errors
705
- if (error instanceof ExternalServiceException) {
706
- return error.statusCode >= 500
707
- }
708
- return true
709
- },
710
- }),
711
- }
712
-
713
- /**
714
- * Global circuit breaker registry instance
715
- */
716
- export const globalCircuitBreakerRegistry: CircuitBreakerRegistry = new CircuitBreakerRegistry()
717
-
718
- /**
719
- * Convenience function to create and register a circuit breaker
720
- */
721
- export function createCircuitBreaker(config: CircuitBreakerConfig): CircuitBreaker {
722
- return globalCircuitBreakerRegistry.register(config)
723
- }
724
-
725
- /**
726
- * Convenience function to execute with circuit breaker protection
727
- */
728
- export async function withCircuitBreaker<T>(
729
- name: string,
730
- call: CircuitBreakerCall<T>,
731
- ): Promise<T> {
732
- return globalCircuitBreakerRegistry.execute(name, call)
733
- }