autotel 4.0.0 → 4.2.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 (232) hide show
  1. package/README.md +26 -1
  2. package/dist/auto.cjs +2 -2
  3. package/dist/auto.js +1 -1
  4. package/dist/correlation-id.cjs +1 -1
  5. package/dist/correlation-id.js +1 -1
  6. package/dist/decorators.cjs +1 -1
  7. package/dist/decorators.js +1 -1
  8. package/dist/{event-Dlqr4ZNL.cjs → event-BhHREDJk.cjs} +3 -3
  9. package/dist/{event-Dlqr4ZNL.cjs.map → event-BhHREDJk.cjs.map} +1 -1
  10. package/dist/{event-_58ryBjh.js → event-ByBTV9M2.js} +3 -3
  11. package/dist/{event-_58ryBjh.js.map → event-ByBTV9M2.js.map} +1 -1
  12. package/dist/event.cjs +1 -1
  13. package/dist/event.js +1 -1
  14. package/dist/{functional-BGkT8J-h.js → functional-DtI0u4vx.js} +19 -19
  15. package/dist/functional-DtI0u4vx.js.map +1 -0
  16. package/dist/{functional-C4CzoVrX.cjs → functional-zpzNLhky.cjs} +4 -4
  17. package/dist/{functional-C4CzoVrX.cjs.map → functional-zpzNLhky.cjs.map} +1 -1
  18. package/dist/functional.cjs +1 -1
  19. package/dist/functional.js +1 -1
  20. package/dist/http.cjs +1 -1
  21. package/dist/http.js +1 -1
  22. package/dist/index.cjs +5 -5
  23. package/dist/index.d.cts +1 -1
  24. package/dist/index.d.ts +1 -1
  25. package/dist/index.js +5 -5
  26. package/dist/{init-DJQOdVlN.d.ts → init-B7u-DjxM.d.ts} +57 -2
  27. package/dist/init-B7u-DjxM.d.ts.map +1 -0
  28. package/dist/{init-DvapOXCc.cjs → init-BX7AmFRl.cjs} +40 -21
  29. package/dist/init-BX7AmFRl.cjs.map +1 -0
  30. package/dist/{init-Ch6t7MNI.js → init-D-jnNMix.js} +39 -20
  31. package/dist/init-D-jnNMix.js.map +1 -0
  32. package/dist/{init-CNp-ee80.d.cts → init-DSrRmVnz.d.cts} +57 -2
  33. package/dist/init-DSrRmVnz.d.cts.map +1 -0
  34. package/dist/instrumentation.cjs +1 -1
  35. package/dist/instrumentation.js +1 -1
  36. package/dist/logger-D3Ej3DII.js +446 -0
  37. package/dist/logger-D3Ej3DII.js.map +1 -0
  38. package/dist/logger-thMPLpOG.cjs +487 -0
  39. package/dist/logger-thMPLpOG.cjs.map +1 -0
  40. package/dist/logger.cjs +8 -236
  41. package/dist/logger.js +2 -204
  42. package/dist/messaging.cjs +1 -1
  43. package/dist/messaging.js +1 -1
  44. package/dist/semantic-helpers.cjs +1 -1
  45. package/dist/semantic-helpers.js +1 -1
  46. package/dist/{track-3HY4NGV-.cjs → track-D59FfpL0.cjs} +2 -2
  47. package/dist/{track-3HY4NGV-.cjs.map → track-D59FfpL0.cjs.map} +1 -1
  48. package/dist/{track-nsKVy-pj.js → track-wc0HafS_.js} +6 -6
  49. package/dist/track-wc0HafS_.js.map +1 -0
  50. package/dist/webhook.cjs +1 -1
  51. package/dist/webhook.js +1 -1
  52. package/dist/workflow-distributed.cjs +1 -1
  53. package/dist/workflow-distributed.js +1 -1
  54. package/dist/workflow.cjs +1 -1
  55. package/dist/workflow.js +1 -1
  56. package/dist/{yaml-config-B3dQ82GR.cjs → yaml-config-Ck2uB0Dp.cjs} +2 -1
  57. package/dist/yaml-config-Ck2uB0Dp.cjs.map +1 -0
  58. package/dist/yaml-config.cjs +1 -1
  59. package/dist/yaml-config.d.cts +7 -1
  60. package/dist/yaml-config.d.cts.map +1 -1
  61. package/dist/yaml-config.d.ts +7 -1
  62. package/dist/yaml-config.d.ts.map +1 -1
  63. package/dist/yaml-config.js +1 -0
  64. package/dist/yaml-config.js.map +1 -1
  65. package/package.json +1 -2
  66. package/skills/autotel-core/SKILL.md +2 -0
  67. package/skills/autotel-instrumentation/SKILL.md +25 -0
  68. package/skills/debug-missing-spans/SKILL.md +3 -1
  69. package/skills/migrate-to-autotel/SKILL.md +24 -23
  70. package/skills/review-otel-patterns/SKILL.md +5 -4
  71. package/dist/functional-BGkT8J-h.js.map +0 -1
  72. package/dist/init-CNp-ee80.d.cts.map +0 -1
  73. package/dist/init-Ch6t7MNI.js.map +0 -1
  74. package/dist/init-DJQOdVlN.d.ts.map +0 -1
  75. package/dist/init-DvapOXCc.cjs.map +0 -1
  76. package/dist/logger.cjs.map +0 -1
  77. package/dist/logger.js.map +0 -1
  78. package/dist/track-nsKVy-pj.js.map +0 -1
  79. package/dist/yaml-config-B3dQ82GR.cjs.map +0 -1
  80. package/src/attribute-redacting-processor.test.ts +0 -763
  81. package/src/attribute-redacting-processor.ts +0 -621
  82. package/src/attributes/attachers.ts +0 -161
  83. package/src/attributes/builders.ts +0 -529
  84. package/src/attributes/domains.ts +0 -42
  85. package/src/attributes/index.ts +0 -81
  86. package/src/attributes/registry.ts +0 -323
  87. package/src/attributes/types.ts +0 -211
  88. package/src/attributes/utils.ts +0 -64
  89. package/src/attributes/validators.ts +0 -266
  90. package/src/attributes.test.ts +0 -292
  91. package/src/auto.ts +0 -67
  92. package/src/autotel-logger.test.ts +0 -548
  93. package/src/autotel-logger.ts +0 -364
  94. package/src/baggage-span-processor.test.ts +0 -202
  95. package/src/baggage-span-processor.ts +0 -100
  96. package/src/business-baggage.test.ts +0 -500
  97. package/src/business-baggage.ts +0 -669
  98. package/src/circuit-breaker.test.ts +0 -341
  99. package/src/circuit-breaker.ts +0 -184
  100. package/src/config.test.ts +0 -94
  101. package/src/config.ts +0 -172
  102. package/src/correlated-events.test.ts +0 -151
  103. package/src/correlated-events.ts +0 -47
  104. package/src/correlation-id.test.ts +0 -163
  105. package/src/correlation-id.ts +0 -206
  106. package/src/db.test.ts +0 -252
  107. package/src/db.ts +0 -447
  108. package/src/decorators.test.ts +0 -153
  109. package/src/decorators.ts +0 -188
  110. package/src/define-event.test.ts +0 -41
  111. package/src/define-event.ts +0 -58
  112. package/src/devtools.ts +0 -60
  113. package/src/drain-pipeline.test.ts +0 -68
  114. package/src/drain-pipeline.ts +0 -199
  115. package/src/drain-toolkit.test.ts +0 -113
  116. package/src/drain-toolkit.ts +0 -129
  117. package/src/enricher-toolkit.test.ts +0 -67
  118. package/src/enricher-toolkit.ts +0 -79
  119. package/src/enrichers.test.ts +0 -150
  120. package/src/enrichers.ts +0 -145
  121. package/src/env-config.test.ts +0 -323
  122. package/src/env-config.ts +0 -309
  123. package/src/error-catalog.test.ts +0 -133
  124. package/src/error-catalog.ts +0 -262
  125. package/src/event-queue.test.ts +0 -864
  126. package/src/event-queue.ts +0 -699
  127. package/src/event-subscriber.ts +0 -262
  128. package/src/event-testing.ts +0 -197
  129. package/src/event.test.ts +0 -1104
  130. package/src/event.ts +0 -988
  131. package/src/events-config.ts +0 -235
  132. package/src/exporters.ts +0 -165
  133. package/src/filtering-span-processor.test.ts +0 -281
  134. package/src/filtering-span-processor.ts +0 -111
  135. package/src/flatten-attributes.test.ts +0 -76
  136. package/src/flatten-attributes.ts +0 -80
  137. package/src/functional.strict-types.typecheck.ts +0 -53
  138. package/src/functional.test.ts +0 -1464
  139. package/src/functional.ts +0 -2539
  140. package/src/functional.types.test.ts +0 -135
  141. package/src/hook.mjs +0 -15
  142. package/src/http.test.ts +0 -485
  143. package/src/http.ts +0 -424
  144. package/src/index.ts +0 -433
  145. package/src/init-auto-redactor.test.ts +0 -53
  146. package/src/init-redactor.test.ts +0 -8
  147. package/src/init.customization.test.ts +0 -594
  148. package/src/init.integrations.test.ts +0 -399
  149. package/src/init.openllmetry.test.ts +0 -194
  150. package/src/init.protocol.test.ts +0 -215
  151. package/src/init.ts +0 -2312
  152. package/src/instrumentation.test.ts +0 -108
  153. package/src/instrumentation.ts +0 -319
  154. package/src/logger.test.ts +0 -125
  155. package/src/logger.ts +0 -341
  156. package/src/messaging-adapters.test.ts +0 -595
  157. package/src/messaging-adapters.ts +0 -583
  158. package/src/messaging-testing.test.ts +0 -573
  159. package/src/messaging-testing.ts +0 -935
  160. package/src/messaging.test.ts +0 -1646
  161. package/src/messaging.ts +0 -2245
  162. package/src/metric-helpers.ts +0 -47
  163. package/src/metric-testing.ts +0 -197
  164. package/src/metric.ts +0 -446
  165. package/src/metrics.test.ts +0 -241
  166. package/src/node-require.ts +0 -123
  167. package/src/operation-context.ts +0 -93
  168. package/src/parse-error.test.ts +0 -73
  169. package/src/parse-error.ts +0 -112
  170. package/src/posthog-logs.test.ts +0 -115
  171. package/src/posthog-logs.ts +0 -77
  172. package/src/pretty-console-exporter.test.ts +0 -545
  173. package/src/pretty-console-exporter.ts +0 -413
  174. package/src/pretty-log-formatter.test.ts +0 -123
  175. package/src/pretty-log-formatter.ts +0 -210
  176. package/src/processors/canonical-log-line-processor.test.ts +0 -523
  177. package/src/processors/canonical-log-line-processor.ts +0 -396
  178. package/src/processors.ts +0 -152
  179. package/src/rate-limiter.test.ts +0 -199
  180. package/src/rate-limiter.ts +0 -98
  181. package/src/redact-values.test.ts +0 -90
  182. package/src/redact-values.ts +0 -34
  183. package/src/register.ts +0 -37
  184. package/src/request-logger.test.ts +0 -545
  185. package/src/request-logger.ts +0 -342
  186. package/src/sampling.test.ts +0 -1060
  187. package/src/sampling.ts +0 -737
  188. package/src/security-schema.test.ts +0 -45
  189. package/src/security-schema.ts +0 -107
  190. package/src/semantic-conventions.ts +0 -15
  191. package/src/semantic-helpers.test.ts +0 -226
  192. package/src/semantic-helpers.ts +0 -438
  193. package/src/shutdown.test.ts +0 -364
  194. package/src/shutdown.ts +0 -246
  195. package/src/span-name-normalizer.test.ts +0 -377
  196. package/src/span-name-normalizer.ts +0 -213
  197. package/src/stable-hash.ts +0 -27
  198. package/src/structured-error.test.ts +0 -191
  199. package/src/structured-error.ts +0 -157
  200. package/src/stub.integration.test.ts +0 -361
  201. package/src/tail-sampling-processor.test.ts +0 -230
  202. package/src/tail-sampling-processor.ts +0 -55
  203. package/src/test-span-collector.test.ts +0 -234
  204. package/src/test-span-collector.ts +0 -150
  205. package/src/testing.ts +0 -705
  206. package/src/trace-context.test.ts +0 -73
  207. package/src/trace-context.ts +0 -567
  208. package/src/trace-helpers.new.test.ts +0 -278
  209. package/src/trace-helpers.test.ts +0 -290
  210. package/src/trace-helpers.ts +0 -710
  211. package/src/trace-hybrid.test.ts +0 -42
  212. package/src/trace-hybrid.ts +0 -37
  213. package/src/tracer-provider.test.ts +0 -183
  214. package/src/tracer-provider.ts +0 -266
  215. package/src/track.test.ts +0 -154
  216. package/src/track.ts +0 -216
  217. package/src/validate.test.ts +0 -287
  218. package/src/validate.ts +0 -307
  219. package/src/validation-attributes.ts +0 -43
  220. package/src/validation.test.ts +0 -330
  221. package/src/validation.ts +0 -246
  222. package/src/variable-name-inference.test.ts +0 -178
  223. package/src/variable-name-inference.ts +0 -242
  224. package/src/webhook.test.ts +0 -649
  225. package/src/webhook.ts +0 -637
  226. package/src/workflow-distributed.test.ts +0 -786
  227. package/src/workflow-distributed.ts +0 -916
  228. package/src/workflow.async-safety.integration.test.ts +0 -345
  229. package/src/workflow.test.ts +0 -647
  230. package/src/workflow.ts +0 -810
  231. package/src/yaml-config.test.ts +0 -337
  232. package/src/yaml-config.ts +0 -342
@@ -1,341 +0,0 @@
1
- /**
2
- * Tests for circuit breaker
3
- */
4
-
5
- import { describe, it, expect, beforeEach, vi } from 'vitest';
6
- import {
7
- CircuitBreaker,
8
- CircuitState,
9
- CircuitOpenError,
10
- } from './circuit-breaker';
11
-
12
- describe('CircuitBreaker', () => {
13
- beforeEach(() => {
14
- vi.useFakeTimers();
15
- });
16
-
17
- describe('CLOSED state (normal operation)', () => {
18
- it('should execute function successfully when closed', async () => {
19
- const cb = new CircuitBreaker('test');
20
-
21
- const result = await cb.execute(async () => 'success');
22
-
23
- expect(result).toBe('success');
24
- expect(cb.getState()).toBe(CircuitState.CLOSED);
25
- });
26
-
27
- it('should record failures but stay closed under threshold', async () => {
28
- const cb = new CircuitBreaker('test', {
29
- failureThreshold: 5,
30
- resetTimeout: 30_000,
31
- windowSize: 60_000,
32
- });
33
-
34
- // 4 failures (under threshold of 5)
35
- for (let i = 0; i < 4; i++) {
36
- try {
37
- await cb.execute(async () => {
38
- throw new Error('test error');
39
- });
40
- } catch {
41
- // Expected
42
- }
43
- }
44
-
45
- expect(cb.getState()).toBe(CircuitState.CLOSED);
46
- expect(cb.getFailureCount()).toBe(4);
47
- });
48
- });
49
-
50
- describe('OPEN state (fast-fail)', () => {
51
- it('should open circuit after threshold failures', async () => {
52
- const cb = new CircuitBreaker('test', {
53
- failureThreshold: 3,
54
- resetTimeout: 30_000,
55
- windowSize: 60_000,
56
- });
57
-
58
- // Trigger 3 failures to open circuit
59
- for (let i = 0; i < 3; i++) {
60
- try {
61
- await cb.execute(async () => {
62
- throw new Error('adapter error');
63
- });
64
- } catch {
65
- // Expected
66
- }
67
- }
68
-
69
- expect(cb.getState()).toBe(CircuitState.OPEN);
70
- expect(cb.getFailureCount()).toBe(3);
71
- });
72
-
73
- it('should fast-fail when circuit is open', async () => {
74
- const cb = new CircuitBreaker('test-adapter', {
75
- failureThreshold: 2,
76
- resetTimeout: 30_000,
77
- windowSize: 60_000,
78
- });
79
-
80
- // Open the circuit
81
- for (let i = 0; i < 2; i++) {
82
- try {
83
- await cb.execute(async () => {
84
- throw new Error('error');
85
- });
86
- } catch {
87
- // Expected
88
- }
89
- }
90
-
91
- // Should fast-fail without calling function
92
- await expect(
93
- cb.execute(async () => 'should not be called'),
94
- ).rejects.toThrow(CircuitOpenError);
95
-
96
- expect(cb.getState()).toBe(CircuitState.OPEN);
97
- });
98
-
99
- it('should transition to half-open after reset timeout', async () => {
100
- const cb = new CircuitBreaker('test', {
101
- failureThreshold: 2,
102
- resetTimeout: 30_000,
103
- windowSize: 60_000,
104
- });
105
-
106
- // Open circuit
107
- for (let i = 0; i < 2; i++) {
108
- try {
109
- await cb.execute(async () => {
110
- throw new Error('error');
111
- });
112
- } catch {
113
- // Expected
114
- }
115
- }
116
-
117
- expect(cb.getState()).toBe(CircuitState.OPEN);
118
-
119
- // Advance time past reset timeout
120
- vi.advanceTimersByTime(31_000);
121
-
122
- // Next call should transition to half-open
123
- const result = await cb.execute(async () => 'recovered');
124
-
125
- expect(result).toBe('recovered');
126
- expect(cb.getState()).toBe(CircuitState.CLOSED);
127
- });
128
- });
129
-
130
- describe('HALF_OPEN state (testing recovery)', () => {
131
- it('should close circuit on successful test request', async () => {
132
- const cb = new CircuitBreaker('test', {
133
- failureThreshold: 2,
134
- resetTimeout: 30_000,
135
- windowSize: 60_000,
136
- });
137
-
138
- // Open circuit
139
- cb.forceOpen();
140
- expect(cb.getState()).toBe(CircuitState.OPEN);
141
-
142
- // Wait for reset timeout
143
- vi.advanceTimersByTime(31_000);
144
-
145
- // Successful test request should close circuit
146
- await cb.execute(async () => 'success');
147
-
148
- expect(cb.getState()).toBe(CircuitState.CLOSED);
149
- expect(cb.getFailureCount()).toBe(0);
150
- });
151
-
152
- it('should reopen circuit if test request fails', async () => {
153
- const cb = new CircuitBreaker('test', {
154
- failureThreshold: 1,
155
- resetTimeout: 30_000,
156
- windowSize: 60_000,
157
- });
158
-
159
- // Open circuit
160
- try {
161
- await cb.execute(async () => {
162
- throw new Error('error');
163
- });
164
- } catch {
165
- // Expected
166
- }
167
-
168
- expect(cb.getState()).toBe(CircuitState.OPEN);
169
-
170
- // Wait for reset timeout
171
- vi.advanceTimersByTime(31_000);
172
-
173
- // Failed test request should reopen circuit
174
- try {
175
- await cb.execute(async () => {
176
- throw new Error('still failing');
177
- });
178
- } catch {
179
- // Expected
180
- }
181
-
182
- expect(cb.getState()).toBe(CircuitState.OPEN);
183
- });
184
- });
185
-
186
- describe('Time window management', () => {
187
- it('should only count failures within time window', async () => {
188
- const cb = new CircuitBreaker('test', {
189
- failureThreshold: 3,
190
- resetTimeout: 30_000,
191
- windowSize: 10_000, // 10 second window
192
- });
193
-
194
- // Record 2 failures
195
- for (let i = 0; i < 2; i++) {
196
- try {
197
- await cb.execute(async () => {
198
- throw new Error('error');
199
- });
200
- } catch {
201
- // Expected
202
- }
203
- }
204
-
205
- expect(cb.getFailureCount()).toBe(2);
206
-
207
- // Advance time past window
208
- vi.advanceTimersByTime(11_000);
209
-
210
- // Old failures should be cleared
211
- expect(cb.getFailureCount()).toBe(0);
212
- expect(cb.getState()).toBe(CircuitState.CLOSED);
213
- });
214
-
215
- it('should track failure timestamps correctly', async () => {
216
- const cb = new CircuitBreaker('test', {
217
- failureThreshold: 5,
218
- resetTimeout: 30_000,
219
- windowSize: 60_000,
220
- });
221
-
222
- // Record failures over time
223
- for (let i = 0; i < 3; i++) {
224
- try {
225
- await cb.execute(async () => {
226
- throw new Error('error');
227
- });
228
- } catch {
229
- // Expected
230
- }
231
- vi.advanceTimersByTime(5000); // 5 seconds between failures
232
- }
233
-
234
- const failures = cb.getRecentFailures();
235
- expect(failures).toHaveLength(3);
236
- expect(failures[0]?.error).toBe('error');
237
- });
238
- });
239
-
240
- describe('Manual control', () => {
241
- it('should allow manual reset', async () => {
242
- const cb = new CircuitBreaker('test', {
243
- failureThreshold: 2,
244
- resetTimeout: 30_000,
245
- windowSize: 60_000,
246
- });
247
-
248
- // Open circuit
249
- for (let i = 0; i < 2; i++) {
250
- try {
251
- await cb.execute(async () => {
252
- throw new Error('error');
253
- });
254
- } catch {
255
- // Expected
256
- }
257
- }
258
-
259
- expect(cb.getState()).toBe(CircuitState.OPEN);
260
-
261
- // Manual reset
262
- cb.forceReset();
263
-
264
- expect(cb.getState()).toBe(CircuitState.CLOSED);
265
- expect(cb.getFailureCount()).toBe(0);
266
- });
267
-
268
- it('should allow manual open', () => {
269
- const cb = new CircuitBreaker('test');
270
-
271
- expect(cb.getState()).toBe(CircuitState.CLOSED);
272
-
273
- cb.forceOpen();
274
-
275
- expect(cb.getState()).toBe(CircuitState.OPEN);
276
- });
277
- });
278
-
279
- describe('Configuration', () => {
280
- it('should use default config values', () => {
281
- const cb = new CircuitBreaker('test');
282
-
283
- // Should use default threshold of 5
284
- expect(cb.getFailureCount()).toBe(0);
285
- expect(cb.getState()).toBe(CircuitState.CLOSED);
286
- });
287
-
288
- it('should allow custom config', async () => {
289
- const cb = new CircuitBreaker('test', {
290
- failureThreshold: 2,
291
- resetTimeout: 5000,
292
- windowSize: 10_000,
293
- });
294
-
295
- // Should open after 2 failures (custom threshold)
296
- for (let i = 0; i < 2; i++) {
297
- try {
298
- await cb.execute(async () => {
299
- throw new Error('error');
300
- });
301
- } catch {
302
- // Expected
303
- }
304
- }
305
-
306
- expect(cb.getState()).toBe(CircuitState.OPEN);
307
- });
308
- });
309
-
310
- describe('Error handling', () => {
311
- it('should record error messages', async () => {
312
- const cb = new CircuitBreaker('test');
313
-
314
- try {
315
- await cb.execute(async () => {
316
- throw new Error('specific error message');
317
- });
318
- } catch {
319
- // Expected
320
- }
321
-
322
- const failures = cb.getRecentFailures();
323
- expect(failures[0]?.error).toBe('specific error message');
324
- });
325
-
326
- it('should handle non-Error throws', async () => {
327
- const cb = new CircuitBreaker('test');
328
-
329
- try {
330
- await cb.execute(async () => {
331
- throw 'string error';
332
- });
333
- } catch {
334
- // Expected
335
- }
336
-
337
- const failures = cb.getRecentFailures();
338
- expect(failures[0]?.error).toBe('string error');
339
- });
340
- });
341
- });
@@ -1,184 +0,0 @@
1
- /**
2
- * Circuit breaker for event subscribers
3
- *
4
- * Prevents cascading failures by fast-failing when an (subscriber) is unhealthy.
5
- * Uses the circuit breaker pattern with three states:
6
- * - CLOSED: Normal operation ((subscriber) working)
7
- * - OPEN: Fast-fail mode ((subscriber) down)
8
- * - HALF_OPEN: Testing if (subscriber) recovered
9
- */
10
-
11
- export interface CircuitBreakerConfig {
12
- /** Number of failures before opening circuit (default: 5) */
13
- failureThreshold: number;
14
- /** Time to wait before trying again in ms (default: 30000 = 30s) */
15
- resetTimeout: number;
16
- /** Time window for counting failures in ms (default: 60000 = 1min) */
17
- windowSize: number;
18
- }
19
-
20
- const DEFAULT_CONFIG: CircuitBreakerConfig = {
21
- failureThreshold: 5,
22
- resetTimeout: 30_000, // 30 seconds
23
- windowSize: 60_000, // 1 minute
24
- };
25
-
26
- export type CircuitState = 'CLOSED' | 'OPEN' | 'HALF_OPEN';
27
-
28
- export const CircuitState = {
29
- CLOSED: 'CLOSED' as const, // Normal operation
30
- OPEN: 'OPEN' as const, // Fast-fail mode
31
- HALF_OPEN: 'HALF_OPEN' as const, // Testing recovery
32
- } as const;
33
-
34
- interface FailureRecord {
35
- timestamp: number;
36
- error: string;
37
- }
38
-
39
- /**
40
- * Circuit breaker implementation
41
- *
42
- * Tracks failures and automatically opens the circuit to prevent
43
- * overwhelming failing subscribers.
44
- */
45
- export class CircuitBreaker {
46
- private state: CircuitState = CircuitState.CLOSED;
47
- private failures: FailureRecord[] = [];
48
- private lastFailureTime: number = 0;
49
- private readonly config: CircuitBreakerConfig;
50
- private readonly name: string;
51
-
52
- constructor(name: string, config?: Partial<CircuitBreakerConfig>) {
53
- this.name = name;
54
- this.config = { ...DEFAULT_CONFIG, ...config };
55
- }
56
-
57
- /**
58
- * Execute a function with circuit breaker protection
59
- * Throws CircuitOpenError if circuit is open
60
- */
61
- async execute<T>(fn: () => Promise<T>): Promise<T> {
62
- // Check if circuit is open
63
- if (this.state === CircuitState.OPEN) {
64
- // Check if we should transition to half-open
65
- const now = Date.now();
66
- if (now - this.lastFailureTime >= this.config.resetTimeout) {
67
- this.state = CircuitState.HALF_OPEN;
68
- } else {
69
- throw new CircuitOpenError(
70
- `Circuit breaker is OPEN for ${this.name}. ` +
71
- `Will retry in ${Math.ceil((this.config.resetTimeout - (now - this.lastFailureTime)) / 1000)}s`,
72
- );
73
- }
74
- }
75
-
76
- try {
77
- const result = await fn();
78
-
79
- // Success! Close circuit if it was half-open
80
- if (this.state === CircuitState.HALF_OPEN) {
81
- this.reset();
82
- }
83
-
84
- return result;
85
- } catch (error) {
86
- this.recordFailure(error);
87
- throw error;
88
- }
89
- }
90
-
91
- /**
92
- * Record a failure and potentially open the circuit
93
- */
94
- private recordFailure(error: unknown): void {
95
- const now = Date.now();
96
-
97
- // Remove old failures outside the time window
98
- this.failures = this.failures.filter(
99
- (f) => now - f.timestamp < this.config.windowSize,
100
- );
101
-
102
- // Record new failure
103
- this.failures.push({
104
- timestamp: now,
105
- error: error instanceof Error ? error.message : String(error),
106
- });
107
-
108
- this.lastFailureTime = now;
109
-
110
- // Check if we should open the circuit
111
- if (this.failures.length >= this.config.failureThreshold) {
112
- if (this.state === CircuitState.HALF_OPEN) {
113
- // Failed during test - reopen circuit
114
- this.state = CircuitState.OPEN;
115
- } else if (this.state === CircuitState.CLOSED) {
116
- // Too many failures - open circuit
117
- this.state = CircuitState.OPEN;
118
- }
119
- }
120
- }
121
-
122
- /**
123
- * Reset the circuit breaker (on success)
124
- */
125
- private reset(): void {
126
- this.state = CircuitState.CLOSED;
127
- this.failures = [];
128
- this.lastFailureTime = 0;
129
- }
130
-
131
- /**
132
- * Get current state (for monitoring)
133
- */
134
- getState(): CircuitState {
135
- return this.state;
136
- }
137
-
138
- /**
139
- * Get failure count in current window
140
- */
141
- getFailureCount(): number {
142
- const now = Date.now();
143
- // Clean up old failures
144
- this.failures = this.failures.filter(
145
- (f) => now - f.timestamp < this.config.windowSize,
146
- );
147
- return this.failures.length;
148
- }
149
-
150
- /**
151
- * Get recent failures (for debugging)
152
- */
153
- getRecentFailures(): FailureRecord[] {
154
- const now = Date.now();
155
- return this.failures.filter(
156
- (f) => now - f.timestamp < this.config.windowSize,
157
- );
158
- }
159
-
160
- /**
161
- * Manually reset the circuit breaker (for testing or manual intervention)
162
- */
163
- forceReset(): void {
164
- this.reset();
165
- }
166
-
167
- /**
168
- * Manually open the circuit (for testing or manual intervention)
169
- */
170
- forceOpen(): void {
171
- this.state = CircuitState.OPEN;
172
- this.lastFailureTime = Date.now();
173
- }
174
- }
175
-
176
- /**
177
- * Error thrown when circuit is open
178
- */
179
- export class CircuitOpenError extends Error {
180
- constructor(message: string) {
181
- super(message);
182
- this.name = 'CircuitOpenError';
183
- }
184
- }
@@ -1,94 +0,0 @@
1
- import { describe, it, expect, beforeEach, vi } from 'vitest';
2
- import { configure, getConfig, resetConfig } from './config';
3
-
4
- describe('configure()', () => {
5
- beforeEach(() => {
6
- resetConfig();
7
- });
8
-
9
- it('should set custom tracer name', () => {
10
- configure({
11
- tracerName: 'my-custom-tracer',
12
- });
13
-
14
- const config = getConfig();
15
- expect(config.tracerName).toBe('my-custom-tracer');
16
- });
17
-
18
- it('should set custom meter name', () => {
19
- configure({
20
- meterName: 'my-custom-meter',
21
- });
22
-
23
- const config = getConfig();
24
- expect(config.meterName).toBe('my-custom-meter');
25
- });
26
-
27
- it('should allow custom tracer instance', () => {
28
- const mockTracer = {
29
- startActiveSpan: vi.fn(),
30
- startSpan: vi.fn(),
31
- };
32
-
33
- configure({
34
- tracer: mockTracer as any,
35
- });
36
-
37
- const config = getConfig();
38
- expect(config.tracer).toBe(mockTracer);
39
- });
40
-
41
- it('should allow custom meter instance', () => {
42
- const mockMeter = {
43
- createCounter: vi.fn(),
44
- createHistogram: vi.fn(),
45
- createUpDownCounter: vi.fn(),
46
- createObservableGauge: vi.fn(),
47
- createObservableCounter: vi.fn(),
48
- createObservableUpDownCounter: vi.fn(),
49
- };
50
-
51
- configure({
52
- meter: mockMeter as any,
53
- });
54
-
55
- const config = getConfig();
56
- expect(config.meter).toBe(mockMeter);
57
- });
58
-
59
- it('should merge configurations', () => {
60
- configure({
61
- tracerName: 'tracer-1',
62
- });
63
-
64
- configure({
65
- meterName: 'meter-1',
66
- });
67
-
68
- const config = getConfig();
69
- expect(config.tracerName).toBe('tracer-1');
70
- expect(config.meterName).toBe('meter-1');
71
- });
72
-
73
- it('should reset to defaults', () => {
74
- configure({
75
- tracerName: 'custom-tracer',
76
- meterName: 'custom-meter',
77
- });
78
-
79
- resetConfig();
80
-
81
- const config = getConfig();
82
- expect(config.tracerName).toBe('app');
83
- expect(config.meterName).toBe('app');
84
- });
85
-
86
- it('should expose feature flags', () => {
87
- const config = getConfig();
88
- expect(config.featureFlags).toBeDefined();
89
- expect(typeof config.featureFlags.ENABLE_TRACING).toBe('boolean');
90
- expect(typeof config.featureFlags.ENABLE_METRICS_BY_DEFAULT).toBe(
91
- 'boolean',
92
- );
93
- });
94
- });