@stacksjs/bun-router 0.0.17 → 0.0.19

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 (153) hide show
  1. package/dist/chunk-2mx7fq49.js +4 -0
  2. package/dist/chunk-a8p83068.js +103 -0
  3. package/dist/{chunk-cgptvjdf.js → chunk-y3xq6wq6.js} +1 -1
  4. package/dist/cli.js +4 -3
  5. package/dist/container/index.js +1149 -211
  6. package/dist/container/service-provider.d.ts +3 -3
  7. package/dist/development/performance-profiler.d.ts +3 -3
  8. package/dist/development/route-debugger.d.ts +1 -1
  9. package/dist/development/route-inspector.d.ts +2 -2
  10. package/dist/index.js +369 -2209
  11. package/dist/observability/integration.d.ts +1 -1
  12. package/dist/response/macros.d.ts +1 -1
  13. package/dist/router/index.d.ts +7 -7
  14. package/dist/router/router.d.ts +0 -32
  15. package/dist/testing/performance-testing.d.ts +2 -2
  16. package/dist/testing/test-client.d.ts +1 -1
  17. package/dist/testing/websocket-testing.d.ts +1 -1
  18. package/package.json +7 -7
  19. package/dist/chunk-1ahs68ys.js +0 -18
  20. package/dist/chunk-g3ybefhg.js +0 -1923
  21. package/src/auth.ts +0 -489
  22. package/src/cache/lru-cache.ts +0 -457
  23. package/src/cache/middleware-memoization.ts +0 -531
  24. package/src/cache/route-cache-warmer.ts +0 -486
  25. package/src/cache/sqlite-cache.ts +0 -783
  26. package/src/cache/streaming-cache.ts +0 -572
  27. package/src/cli/colors.ts +0 -29
  28. package/src/cli/index.ts +0 -287
  29. package/src/cli/middleware.ts +0 -291
  30. package/src/cli/openapi.ts +0 -407
  31. package/src/cli/router.ts +0 -188
  32. package/src/cli/routes.ts +0 -265
  33. package/src/cli/utils.ts +0 -531
  34. package/src/cli.ts +0 -5
  35. package/src/config.ts +0 -322
  36. package/src/container/container.ts +0 -786
  37. package/src/container/contextual-binding.ts +0 -606
  38. package/src/container/decorators.ts +0 -368
  39. package/src/container/index.ts +0 -49
  40. package/src/container/service-provider.ts +0 -596
  41. package/src/development/hot-reload.ts +0 -673
  42. package/src/development/index.ts +0 -499
  43. package/src/development/performance-profiler.ts +0 -717
  44. package/src/development/route-debugger.ts +0 -527
  45. package/src/development/route-inspector.ts +0 -749
  46. package/src/development/typescript-utilities.ts +0 -682
  47. package/src/docs.ts +0 -397
  48. package/src/errors/circuit-breaker.ts +0 -733
  49. package/src/errors/error-handler.ts +0 -569
  50. package/src/errors/error-reporting.ts +0 -672
  51. package/src/errors/exceptions.ts +0 -536
  52. package/src/errors/graceful-degradation.ts +0 -623
  53. package/src/errors/index.ts +0 -21
  54. package/src/errors/router-errors.ts +0 -632
  55. package/src/file-serving/static-files.ts +0 -718
  56. package/src/index.ts +0 -66
  57. package/src/middleware/auth.ts +0 -220
  58. package/src/middleware/content_security_policy.ts +0 -215
  59. package/src/middleware/cors.ts +0 -142
  60. package/src/middleware/csrf.ts +0 -166
  61. package/src/middleware/ddos_protection.ts +0 -279
  62. package/src/middleware/file_security.ts +0 -191
  63. package/src/middleware/file_upload.ts +0 -275
  64. package/src/middleware/helmet.ts +0 -268
  65. package/src/middleware/index.ts +0 -117
  66. package/src/middleware/input_validation.ts +0 -453
  67. package/src/middleware/json_body.ts +0 -37
  68. package/src/middleware/performance_alerting.ts +0 -540
  69. package/src/middleware/performance_dashboard.ts +0 -661
  70. package/src/middleware/performance_monitor.ts +0 -945
  71. package/src/middleware/pipeline.ts +0 -489
  72. package/src/middleware/rate_limit.ts +0 -251
  73. package/src/middleware/request_id.ts +0 -36
  74. package/src/middleware/request_signing.ts +0 -638
  75. package/src/middleware/request_tracer.ts +0 -653
  76. package/src/middleware/response_cache.ts +0 -778
  77. package/src/middleware/security.ts +0 -482
  78. package/src/middleware/security_suite.ts +0 -257
  79. package/src/middleware/session.ts +0 -91
  80. package/src/model-binding/index.ts +0 -18
  81. package/src/model-binding/model-middleware.ts +0 -425
  82. package/src/model-binding/model-registry.ts +0 -550
  83. package/src/model-binding.ts +0 -370
  84. package/src/model-resolver-factory.ts +0 -106
  85. package/src/observability/correlation.ts +0 -691
  86. package/src/observability/health-checks.ts +0 -729
  87. package/src/observability/index.ts +0 -184
  88. package/src/observability/integration.ts +0 -548
  89. package/src/observability/metrics.ts +0 -753
  90. package/src/observability/tracing.ts +0 -638
  91. package/src/optimization/bun-utilities.ts +0 -778
  92. package/src/query-builder-integration.ts +0 -137
  93. package/src/request/context.ts +0 -109
  94. package/src/request/enhanced-request.ts +0 -876
  95. package/src/request/macros.ts +0 -822
  96. package/src/response/macros.ts +0 -665
  97. package/src/response/response-factory.ts +0 -596
  98. package/src/router/api-routes.ts +0 -243
  99. package/src/router/file-based-routing.ts +0 -697
  100. package/src/router/file-streaming.ts +0 -383
  101. package/src/router/fluent-routing.ts +0 -866
  102. package/src/router/group-organization.ts +0 -229
  103. package/src/router/handler-resolver.ts +0 -327
  104. package/src/router/http-methods.ts +0 -407
  105. package/src/router/index.ts +0 -193
  106. package/src/router/middleware-groups.ts +0 -222
  107. package/src/router/middleware-integration.ts +0 -399
  108. package/src/router/middleware.ts +0 -234
  109. package/src/router/model-binding.ts +0 -215
  110. package/src/router/optimized-route-matching.ts +0 -302
  111. package/src/router/route-building.ts +0 -221
  112. package/src/router/route-compiler.ts +0 -691
  113. package/src/router/route-matching.ts +0 -368
  114. package/src/router/route-trie.ts +0 -450
  115. package/src/router/router.ts +0 -1616
  116. package/src/router/server.ts +0 -684
  117. package/src/router/validation-integration.ts +0 -449
  118. package/src/router/view-rendering.ts +0 -233
  119. package/src/router/websocket.ts +0 -100
  120. package/src/routing/route-caching.ts +0 -402
  121. package/src/routing/route-throttling.ts +0 -469
  122. package/src/routing/subdomain-routing.ts +0 -492
  123. package/src/session/database-store.ts +0 -109
  124. package/src/session/file-store.ts +0 -148
  125. package/src/session/index.ts +0 -244
  126. package/src/session/memory-store.ts +0 -88
  127. package/src/session/redis-store.ts +0 -93
  128. package/src/streaming/index.ts +0 -17
  129. package/src/streaming/sse-handler.ts +0 -482
  130. package/src/streaming/stream-handler.ts +0 -552
  131. package/src/testing/auth-testing.ts +0 -448
  132. package/src/testing/file-upload-testing.ts +0 -543
  133. package/src/testing/index.ts +0 -10
  134. package/src/testing/middleware-testing.ts +0 -322
  135. package/src/testing/model-binding-testing.ts +0 -645
  136. package/src/testing/performance-testing.ts +0 -738
  137. package/src/testing/test-client.ts +0 -310
  138. package/src/testing/test-request.ts +0 -315
  139. package/src/testing/test-response.ts +0 -332
  140. package/src/testing/types.ts +0 -224
  141. package/src/testing/websocket-testing.ts +0 -590
  142. package/src/types/controller-types.ts +0 -385
  143. package/src/types/core.ts +0 -683
  144. package/src/types/middleware-types.ts +0 -422
  145. package/src/types/request-response-augmentation.ts +0 -489
  146. package/src/types/route-inference.ts +0 -372
  147. package/src/types.ts +0 -2123
  148. package/src/url.ts +0 -131
  149. package/src/utils/index.ts +0 -1
  150. package/src/utils/query-preservation.ts +0 -201
  151. package/src/utils.ts +0 -395
  152. package/src/validation/validator.ts +0 -688
  153. 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
- }