autotel 2.1.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 (272) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1946 -0
  3. package/dist/chunk-2LNRY4QK.js +273 -0
  4. package/dist/chunk-2LNRY4QK.js.map +1 -0
  5. package/dist/chunk-3HENGDW2.js +587 -0
  6. package/dist/chunk-3HENGDW2.js.map +1 -0
  7. package/dist/chunk-4OAT42CA.cjs +73 -0
  8. package/dist/chunk-4OAT42CA.cjs.map +1 -0
  9. package/dist/chunk-5GWX5LFW.js +70 -0
  10. package/dist/chunk-5GWX5LFW.js.map +1 -0
  11. package/dist/chunk-5R2M36QB.js +195 -0
  12. package/dist/chunk-5R2M36QB.js.map +1 -0
  13. package/dist/chunk-5ZN622AO.js +73 -0
  14. package/dist/chunk-5ZN622AO.js.map +1 -0
  15. package/dist/chunk-77MSMAUQ.cjs +498 -0
  16. package/dist/chunk-77MSMAUQ.cjs.map +1 -0
  17. package/dist/chunk-ABPEQ6RK.cjs +596 -0
  18. package/dist/chunk-ABPEQ6RK.cjs.map +1 -0
  19. package/dist/chunk-BWYGJKRB.js +95 -0
  20. package/dist/chunk-BWYGJKRB.js.map +1 -0
  21. package/dist/chunk-BZHG5IZ4.js +73 -0
  22. package/dist/chunk-BZHG5IZ4.js.map +1 -0
  23. package/dist/chunk-G7VZBCD6.cjs +35 -0
  24. package/dist/chunk-G7VZBCD6.cjs.map +1 -0
  25. package/dist/chunk-GVLK7YUU.cjs +30 -0
  26. package/dist/chunk-GVLK7YUU.cjs.map +1 -0
  27. package/dist/chunk-HCCXC7XG.js +205 -0
  28. package/dist/chunk-HCCXC7XG.js.map +1 -0
  29. package/dist/chunk-HE6T6FIX.cjs +203 -0
  30. package/dist/chunk-HE6T6FIX.cjs.map +1 -0
  31. package/dist/chunk-KIXWPOCO.cjs +100 -0
  32. package/dist/chunk-KIXWPOCO.cjs.map +1 -0
  33. package/dist/chunk-KVGNW3FC.js +87 -0
  34. package/dist/chunk-KVGNW3FC.js.map +1 -0
  35. package/dist/chunk-LITNXTTT.js +3 -0
  36. package/dist/chunk-LITNXTTT.js.map +1 -0
  37. package/dist/chunk-M4ANN7RL.js +114 -0
  38. package/dist/chunk-M4ANN7RL.js.map +1 -0
  39. package/dist/chunk-NC52UBR2.cjs +32 -0
  40. package/dist/chunk-NC52UBR2.cjs.map +1 -0
  41. package/dist/chunk-NHCNRQD3.cjs +212 -0
  42. package/dist/chunk-NHCNRQD3.cjs.map +1 -0
  43. package/dist/chunk-NZ72VDNY.cjs +4 -0
  44. package/dist/chunk-NZ72VDNY.cjs.map +1 -0
  45. package/dist/chunk-P6JUDYNO.js +57 -0
  46. package/dist/chunk-P6JUDYNO.js.map +1 -0
  47. package/dist/chunk-RJYY7BWX.js +1349 -0
  48. package/dist/chunk-RJYY7BWX.js.map +1 -0
  49. package/dist/chunk-TRI4V5BF.cjs +126 -0
  50. package/dist/chunk-TRI4V5BF.cjs.map +1 -0
  51. package/dist/chunk-UL33I6IS.js +139 -0
  52. package/dist/chunk-UL33I6IS.js.map +1 -0
  53. package/dist/chunk-URRW6M2C.cjs +61 -0
  54. package/dist/chunk-URRW6M2C.cjs.map +1 -0
  55. package/dist/chunk-UY3UYPBZ.cjs +77 -0
  56. package/dist/chunk-UY3UYPBZ.cjs.map +1 -0
  57. package/dist/chunk-W3253FGB.cjs +277 -0
  58. package/dist/chunk-W3253FGB.cjs.map +1 -0
  59. package/dist/chunk-W7LHZVQF.js +26 -0
  60. package/dist/chunk-W7LHZVQF.js.map +1 -0
  61. package/dist/chunk-WBWNM6LB.cjs +1360 -0
  62. package/dist/chunk-WBWNM6LB.cjs.map +1 -0
  63. package/dist/chunk-WFJ7L2RV.js +494 -0
  64. package/dist/chunk-WFJ7L2RV.js.map +1 -0
  65. package/dist/chunk-X4RMFFMR.js +28 -0
  66. package/dist/chunk-X4RMFFMR.js.map +1 -0
  67. package/dist/chunk-Y4Y2S7BM.cjs +92 -0
  68. package/dist/chunk-Y4Y2S7BM.cjs.map +1 -0
  69. package/dist/chunk-YLPNXZFI.cjs +143 -0
  70. package/dist/chunk-YLPNXZFI.cjs.map +1 -0
  71. package/dist/chunk-YTXEZ4SD.cjs +77 -0
  72. package/dist/chunk-YTXEZ4SD.cjs.map +1 -0
  73. package/dist/chunk-Z6ZWNWWR.js +30 -0
  74. package/dist/chunk-Z6ZWNWWR.js.map +1 -0
  75. package/dist/config.cjs +26 -0
  76. package/dist/config.cjs.map +1 -0
  77. package/dist/config.d.cts +75 -0
  78. package/dist/config.d.ts +75 -0
  79. package/dist/config.js +5 -0
  80. package/dist/config.js.map +1 -0
  81. package/dist/db.cjs +233 -0
  82. package/dist/db.cjs.map +1 -0
  83. package/dist/db.d.cts +123 -0
  84. package/dist/db.d.ts +123 -0
  85. package/dist/db.js +228 -0
  86. package/dist/db.js.map +1 -0
  87. package/dist/decorators.cjs +67 -0
  88. package/dist/decorators.cjs.map +1 -0
  89. package/dist/decorators.d.cts +91 -0
  90. package/dist/decorators.d.ts +91 -0
  91. package/dist/decorators.js +65 -0
  92. package/dist/decorators.js.map +1 -0
  93. package/dist/event-subscriber.cjs +6 -0
  94. package/dist/event-subscriber.cjs.map +1 -0
  95. package/dist/event-subscriber.d.cts +116 -0
  96. package/dist/event-subscriber.d.ts +116 -0
  97. package/dist/event-subscriber.js +3 -0
  98. package/dist/event-subscriber.js.map +1 -0
  99. package/dist/event-testing.cjs +21 -0
  100. package/dist/event-testing.cjs.map +1 -0
  101. package/dist/event-testing.d.cts +110 -0
  102. package/dist/event-testing.d.ts +110 -0
  103. package/dist/event-testing.js +4 -0
  104. package/dist/event-testing.js.map +1 -0
  105. package/dist/event.cjs +30 -0
  106. package/dist/event.cjs.map +1 -0
  107. package/dist/event.d.cts +282 -0
  108. package/dist/event.d.ts +282 -0
  109. package/dist/event.js +13 -0
  110. package/dist/event.js.map +1 -0
  111. package/dist/exporters.cjs +17 -0
  112. package/dist/exporters.cjs.map +1 -0
  113. package/dist/exporters.d.cts +1 -0
  114. package/dist/exporters.d.ts +1 -0
  115. package/dist/exporters.js +4 -0
  116. package/dist/exporters.js.map +1 -0
  117. package/dist/functional.cjs +46 -0
  118. package/dist/functional.cjs.map +1 -0
  119. package/dist/functional.d.cts +478 -0
  120. package/dist/functional.d.ts +478 -0
  121. package/dist/functional.js +13 -0
  122. package/dist/functional.js.map +1 -0
  123. package/dist/http.cjs +189 -0
  124. package/dist/http.cjs.map +1 -0
  125. package/dist/http.d.cts +169 -0
  126. package/dist/http.d.ts +169 -0
  127. package/dist/http.js +184 -0
  128. package/dist/http.js.map +1 -0
  129. package/dist/index.cjs +333 -0
  130. package/dist/index.cjs.map +1 -0
  131. package/dist/index.d.cts +758 -0
  132. package/dist/index.d.ts +758 -0
  133. package/dist/index.js +143 -0
  134. package/dist/index.js.map +1 -0
  135. package/dist/instrumentation.cjs +182 -0
  136. package/dist/instrumentation.cjs.map +1 -0
  137. package/dist/instrumentation.d.cts +49 -0
  138. package/dist/instrumentation.d.ts +49 -0
  139. package/dist/instrumentation.js +179 -0
  140. package/dist/instrumentation.js.map +1 -0
  141. package/dist/logger.cjs +19 -0
  142. package/dist/logger.cjs.map +1 -0
  143. package/dist/logger.d.cts +146 -0
  144. package/dist/logger.d.ts +146 -0
  145. package/dist/logger.js +6 -0
  146. package/dist/logger.js.map +1 -0
  147. package/dist/metric-helpers.cjs +31 -0
  148. package/dist/metric-helpers.cjs.map +1 -0
  149. package/dist/metric-helpers.d.cts +13 -0
  150. package/dist/metric-helpers.d.ts +13 -0
  151. package/dist/metric-helpers.js +6 -0
  152. package/dist/metric-helpers.js.map +1 -0
  153. package/dist/metric-testing.cjs +21 -0
  154. package/dist/metric-testing.cjs.map +1 -0
  155. package/dist/metric-testing.d.cts +110 -0
  156. package/dist/metric-testing.d.ts +110 -0
  157. package/dist/metric-testing.js +4 -0
  158. package/dist/metric-testing.js.map +1 -0
  159. package/dist/metric.cjs +26 -0
  160. package/dist/metric.cjs.map +1 -0
  161. package/dist/metric.d.cts +240 -0
  162. package/dist/metric.d.ts +240 -0
  163. package/dist/metric.js +9 -0
  164. package/dist/metric.js.map +1 -0
  165. package/dist/processors.cjs +17 -0
  166. package/dist/processors.cjs.map +1 -0
  167. package/dist/processors.d.cts +1 -0
  168. package/dist/processors.d.ts +1 -0
  169. package/dist/processors.js +4 -0
  170. package/dist/processors.js.map +1 -0
  171. package/dist/sampling.cjs +40 -0
  172. package/dist/sampling.cjs.map +1 -0
  173. package/dist/sampling.d.cts +260 -0
  174. package/dist/sampling.d.ts +260 -0
  175. package/dist/sampling.js +7 -0
  176. package/dist/sampling.js.map +1 -0
  177. package/dist/semantic-helpers.cjs +35 -0
  178. package/dist/semantic-helpers.cjs.map +1 -0
  179. package/dist/semantic-helpers.d.cts +442 -0
  180. package/dist/semantic-helpers.d.ts +442 -0
  181. package/dist/semantic-helpers.js +14 -0
  182. package/dist/semantic-helpers.js.map +1 -0
  183. package/dist/tail-sampling-processor.cjs +13 -0
  184. package/dist/tail-sampling-processor.cjs.map +1 -0
  185. package/dist/tail-sampling-processor.d.cts +27 -0
  186. package/dist/tail-sampling-processor.d.ts +27 -0
  187. package/dist/tail-sampling-processor.js +4 -0
  188. package/dist/tail-sampling-processor.js.map +1 -0
  189. package/dist/testing.cjs +286 -0
  190. package/dist/testing.cjs.map +1 -0
  191. package/dist/testing.d.cts +291 -0
  192. package/dist/testing.d.ts +291 -0
  193. package/dist/testing.js +263 -0
  194. package/dist/testing.js.map +1 -0
  195. package/dist/trace-context-DRZdUvVY.d.cts +181 -0
  196. package/dist/trace-context-DRZdUvVY.d.ts +181 -0
  197. package/dist/trace-helpers.cjs +54 -0
  198. package/dist/trace-helpers.cjs.map +1 -0
  199. package/dist/trace-helpers.d.cts +524 -0
  200. package/dist/trace-helpers.d.ts +524 -0
  201. package/dist/trace-helpers.js +5 -0
  202. package/dist/trace-helpers.js.map +1 -0
  203. package/dist/tracer-provider.cjs +21 -0
  204. package/dist/tracer-provider.cjs.map +1 -0
  205. package/dist/tracer-provider.d.cts +169 -0
  206. package/dist/tracer-provider.d.ts +169 -0
  207. package/dist/tracer-provider.js +4 -0
  208. package/dist/tracer-provider.js.map +1 -0
  209. package/package.json +280 -0
  210. package/src/baggage-span-processor.test.ts +202 -0
  211. package/src/baggage-span-processor.ts +98 -0
  212. package/src/circuit-breaker.test.ts +341 -0
  213. package/src/circuit-breaker.ts +184 -0
  214. package/src/config.test.ts +94 -0
  215. package/src/config.ts +169 -0
  216. package/src/db.test.ts +252 -0
  217. package/src/db.ts +447 -0
  218. package/src/decorators.test.ts +203 -0
  219. package/src/decorators.ts +188 -0
  220. package/src/env-config.test.ts +246 -0
  221. package/src/env-config.ts +158 -0
  222. package/src/event-queue.test.ts +222 -0
  223. package/src/event-queue.ts +203 -0
  224. package/src/event-subscriber.ts +136 -0
  225. package/src/event-testing.ts +197 -0
  226. package/src/event.test.ts +718 -0
  227. package/src/event.ts +556 -0
  228. package/src/exporters.ts +96 -0
  229. package/src/functional.test.ts +1059 -0
  230. package/src/functional.ts +2295 -0
  231. package/src/http.test.ts +487 -0
  232. package/src/http.ts +424 -0
  233. package/src/index.ts +158 -0
  234. package/src/init.customization.test.ts +210 -0
  235. package/src/init.integrations.test.ts +366 -0
  236. package/src/init.openllmetry.test.ts +282 -0
  237. package/src/init.protocol.test.ts +215 -0
  238. package/src/init.ts +1426 -0
  239. package/src/instrumentation.test.ts +108 -0
  240. package/src/instrumentation.ts +308 -0
  241. package/src/logger.test.ts +117 -0
  242. package/src/logger.ts +246 -0
  243. package/src/metric-helpers.ts +47 -0
  244. package/src/metric-testing.ts +197 -0
  245. package/src/metric.ts +434 -0
  246. package/src/metrics.test.ts +205 -0
  247. package/src/operation-context.ts +93 -0
  248. package/src/processors.ts +106 -0
  249. package/src/rate-limiter.test.ts +199 -0
  250. package/src/rate-limiter.ts +98 -0
  251. package/src/sampling.test.ts +513 -0
  252. package/src/sampling.ts +428 -0
  253. package/src/semantic-helpers.test.ts +311 -0
  254. package/src/semantic-helpers.ts +584 -0
  255. package/src/shutdown.test.ts +311 -0
  256. package/src/shutdown.ts +222 -0
  257. package/src/stub.integration.test.ts +361 -0
  258. package/src/tail-sampling-processor.test.ts +226 -0
  259. package/src/tail-sampling-processor.ts +51 -0
  260. package/src/testing.ts +670 -0
  261. package/src/trace-context.ts +470 -0
  262. package/src/trace-helpers.new.test.ts +278 -0
  263. package/src/trace-helpers.test.ts +242 -0
  264. package/src/trace-helpers.ts +690 -0
  265. package/src/tracer-provider.test.ts +183 -0
  266. package/src/tracer-provider.ts +266 -0
  267. package/src/track.test.ts +153 -0
  268. package/src/track.ts +120 -0
  269. package/src/validation.test.ts +306 -0
  270. package/src/validation.ts +239 -0
  271. package/src/variable-name-inference.test.ts +178 -0
  272. package/src/variable-name-inference.ts +242 -0
package/src/event.ts ADDED
@@ -0,0 +1,556 @@
1
+ /**
2
+ * Events API for product events platforms
3
+ *
4
+ * Track user behavior, business events, and critical actions.
5
+ * Sends to product events platforms (PostHog, Mixpanel, Amplitude) via subscribers.
6
+ * For business people who think in events/funnels.
7
+ *
8
+ * For OpenTelemetry metrics (Prometheus/Grafana), use the Metrics class instead.
9
+ *
10
+ * @example Recommended: Configure subscribers in init(), use track() function
11
+ * ```typescript
12
+ * import { init, track } from 'autotel';
13
+ * import { PostHogSubscriber } from 'autotel-subscribers/posthog';
14
+ *
15
+ * init({
16
+ * service: 'my-app',
17
+ * subscribers: [new PostHogSubscriber({ apiKey: 'phc_...' })]
18
+ * });
19
+ *
20
+ * // Track events - uses subscribers from init()
21
+ * track('application.submitted', { jobId: '123', userId: '456' });
22
+ * ```
23
+ *
24
+ * @example Create Event instance (inherits subscribers from init)
25
+ * ```typescript
26
+ * import { Event } from 'autotel/event';
27
+ *
28
+ * // Uses subscribers configured in init()
29
+ * const event = new Event('job-application');
30
+ * event.trackEvent('application.submitted', { jobId: '123' });
31
+ * ```
32
+ *
33
+ * @example Override subscribers for specific Event instance
34
+ * ```typescript
35
+ * import { Event } from 'autotel/event';
36
+ * import { PostHogSubscriber } from 'autotel-subscribers/posthog';
37
+ *
38
+ * // Override: use different subscribers for this instance
39
+ * const event = new Event('job-application', {
40
+ * subscribers: [new PostHogSubscriber({ apiKey: 'phc_different_project' })]
41
+ * });
42
+ *
43
+ * event.trackEvent('application.submitted', { jobId: '123' });
44
+ * ```
45
+ */
46
+
47
+ import { trace } from '@opentelemetry/api';
48
+ import { type Logger } from './logger';
49
+ import { getLogger, getValidationConfig, getConfig } from './init';
50
+ import {
51
+ type EventSubscriber,
52
+ type EventAttributes,
53
+ type FunnelStatus,
54
+ type OutcomeStatus,
55
+ } from './event-subscriber';
56
+ import { type EventCollector } from './event-testing';
57
+ import { CircuitBreaker, CircuitOpenError } from './circuit-breaker';
58
+ import { validateEvent } from './validation';
59
+ import { getOperationContext } from './operation-context';
60
+
61
+ // Re-export types for convenience
62
+ export type {
63
+ EventAttributes,
64
+ FunnelStatus,
65
+ OutcomeStatus,
66
+ } from './event-subscriber';
67
+
68
+ /**
69
+ * Events class for tracking user behavior and product events
70
+ *
71
+ * Track critical indicators such as:
72
+ * - User events (signups, purchases, feature usage)
73
+ * - Conversion funnels (signup → activation → purchase)
74
+ * - Business outcomes (success/failure rates)
75
+ * - Product metrics (revenue, engagement, retention)
76
+ *
77
+ * All events are sent to events platforms via subscribers (PostHog, Mixpanel, etc.).
78
+ * For OpenTelemetry metrics, use the Metrics class instead.
79
+ */
80
+ /**
81
+ * Events options
82
+ */
83
+ export interface EventsOptions {
84
+ /** Optional logger for audit trail */
85
+ logger?: Logger;
86
+ /** Optional collector for testing (captures events in memory) */
87
+ collector?: EventCollector;
88
+ /**
89
+ * Optional subscribers to send events to other platforms
90
+ * (e.g., PostHog, Mixpanel, Amplitude)
91
+ *
92
+ * **Subscriber Resolution**:
93
+ * - If provided → uses these subscribers (instance override)
94
+ * - If not provided → falls back to subscribers from `init()` (global config)
95
+ * - If neither → no subscribers (events logged only)
96
+ *
97
+ * Install `autotel-subscribers` package for ready-made subscribers
98
+ */
99
+ subscribers?: EventSubscriber[];
100
+ }
101
+
102
+ export class Event {
103
+ private serviceName: string;
104
+ private logger?: Logger;
105
+ private collector?: EventCollector;
106
+ private subscribers: EventSubscriber[];
107
+ private hasSubscribers: boolean; // Cached for performance
108
+ private circuitBreakers: Map<EventSubscriber, CircuitBreaker>; // One per subscriber
109
+
110
+ /**
111
+ * Create a new Event instance
112
+ *
113
+ * **Note**: Most users should use `init()` + `track()` instead of creating Event instances directly.
114
+ *
115
+ * **Subscriber Resolution**:
116
+ * - If `subscribers` provided in options → uses those (instance override)
117
+ * - If `subscribers` not provided → falls back to subscribers from `init()` (global config)
118
+ * - If neither → no subscribers (events logged only)
119
+ *
120
+ * @param serviceName - Service name for identifying events
121
+ * @param options - Optional configuration (logger, collector, subscribers)
122
+ *
123
+ * @example Recommended: Use track() with init()
124
+ * ```typescript
125
+ * import { init, track } from 'autotel';
126
+ * import { PostHogSubscriber } from 'autotel-subscribers/posthog';
127
+ *
128
+ * init({
129
+ * service: 'checkout',
130
+ * subscribers: [new PostHogSubscriber({ apiKey: 'phc_...' })]
131
+ * });
132
+ *
133
+ * track('purchase.completed', { amount: 99.99 });
134
+ * ```
135
+ *
136
+ * @example Inherit subscribers from init()
137
+ * ```typescript
138
+ * // Uses subscribers configured in init()
139
+ * const event = new Event('checkout');
140
+ * event.trackEvent('purchase.completed', { amount: 99.99 });
141
+ * ```
142
+ *
143
+ * @example Override subscribers for this instance
144
+ * ```typescript
145
+ * import { Event } from 'autotel/event';
146
+ * import { PostHogSubscriber } from 'autotel-subscribers/posthog';
147
+ *
148
+ * // Override: use different subscribers for this instance only
149
+ * const event = new Event('checkout', {
150
+ * subscribers: [new PostHogSubscriber({ apiKey: 'phc_different_project' })]
151
+ * });
152
+ * ```
153
+ */
154
+ constructor(serviceName: string, options: EventsOptions = {}) {
155
+ this.serviceName = serviceName;
156
+ this.logger = options.logger;
157
+ this.collector = options.collector;
158
+
159
+ // Subscriber resolution: instance-level overrides global init() config
160
+ // If subscribers provided to constructor, use those
161
+ // Otherwise, fall back to subscribers from init()
162
+ this.subscribers =
163
+ options.subscribers === undefined
164
+ ? getConfig()?.subscribers || []
165
+ : options.subscribers;
166
+
167
+ this.hasSubscribers = this.subscribers.length > 0; // Cache for hot path
168
+
169
+ // Create circuit breaker for each subscriber
170
+ this.circuitBreakers = new Map();
171
+ for (const subscriber of this.subscribers) {
172
+ const subscriberName = subscriber.name || 'Unknown';
173
+ this.circuitBreakers.set(
174
+ subscriber,
175
+ new CircuitBreaker(subscriberName, {
176
+ failureThreshold: 5,
177
+ resetTimeout: 30_000, // 30s
178
+ windowSize: 60_000, // 1min
179
+ }),
180
+ );
181
+ }
182
+ }
183
+
184
+ /**
185
+ * Automatically enrich attributes with all available telemetry context
186
+ *
187
+ * Auto-captures:
188
+ * - Resource attributes: service.version, deployment.environment
189
+ * - Trace context: traceId, spanId, correlationId
190
+ * - Operation context: operation.name
191
+ */
192
+ private enrichWithTelemetryContext(
193
+ attributes: EventAttributes = {},
194
+ ): EventAttributes {
195
+ const enriched: EventAttributes = {
196
+ service: this.serviceName,
197
+ ...attributes,
198
+ };
199
+
200
+ // 1. Resource attributes (service-level context)
201
+ const config = getConfig();
202
+ if (config) {
203
+ if (config.version) {
204
+ enriched['service.version'] = config.version;
205
+ }
206
+ if (config.environment) {
207
+ enriched['deployment.environment'] = config.environment;
208
+ }
209
+ }
210
+
211
+ // 2. Trace context (if inside a traced operation)
212
+ const span = trace.getActiveSpan();
213
+ const spanContext = span?.spanContext();
214
+ if (spanContext) {
215
+ enriched.traceId = spanContext.traceId;
216
+ enriched.spanId = spanContext.spanId;
217
+ // Add correlation ID (first 16 chars of trace ID) for easier log grouping
218
+ enriched.correlationId = spanContext.traceId.slice(0, 16);
219
+ }
220
+
221
+ // 3. Operation context (if inside a trace/span)
222
+ const operationContext = getOperationContext();
223
+ if (operationContext) {
224
+ enriched['operation.name'] = operationContext.name;
225
+ }
226
+
227
+ return enriched;
228
+ }
229
+
230
+ /**
231
+ * Track a business event
232
+ *
233
+ * Use this for tracking user actions, business events, product usage:
234
+ * - "user.signup"
235
+ * - "order.completed"
236
+ * - "feature.used"
237
+ *
238
+ * Events are sent to configured subscribers (PostHog, Mixpanel, etc.).
239
+ *
240
+ * @example
241
+ * ```typescript
242
+ * // Track user signup
243
+ * events.trackEvent('user.signup', {
244
+ * userId: '123',
245
+ * plan: 'pro'
246
+ * })
247
+ *
248
+ * // Track order
249
+ * events.trackEvent('order.completed', {
250
+ * orderId: 'ord_123',
251
+ * amount: 99.99
252
+ * })
253
+ * ```
254
+ */
255
+ trackEvent(eventName: string, attributes?: EventAttributes): void {
256
+ // Validate and sanitize input (with custom config if provided)
257
+ const validationConfig = getValidationConfig();
258
+ const validated = validateEvent(
259
+ eventName,
260
+ attributes,
261
+ validationConfig || undefined,
262
+ );
263
+
264
+ // Auto-attach all available telemetry context
265
+ const enrichedAttributes = this.enrichWithTelemetryContext(
266
+ validated.attributes,
267
+ );
268
+
269
+ this.logger?.info('Event tracked', {
270
+ event: validated.eventName,
271
+ attributes: enrichedAttributes,
272
+ });
273
+
274
+ // Record for testing
275
+ this.collector?.recordEvent({
276
+ event: validated.eventName,
277
+ attributes: enrichedAttributes,
278
+ service: this.serviceName,
279
+ timestamp: Date.now(),
280
+ });
281
+
282
+ // Notify subscribers (zero overhead if no subscribers)
283
+ // Run in background - don't block event recording
284
+ if (this.hasSubscribers) {
285
+ void this.notifySubscribers((subscriber) =>
286
+ subscriber.trackEvent(validated.eventName, enrichedAttributes),
287
+ );
288
+ }
289
+ }
290
+
291
+ /**
292
+ * Notify all subscribers concurrently without blocking
293
+ * Uses circuit breakers to protect against failing subscribers
294
+ * Uses Promise.allSettled to prevent subscriber errors from affecting other subscribers
295
+ */
296
+ private async notifySubscribers(
297
+ fn: (subscriber: EventSubscriber) => Promise<void>,
298
+ ): Promise<void> {
299
+ const promises = this.subscribers.map(async (subscriber) => {
300
+ const circuitBreaker = this.circuitBreakers.get(subscriber);
301
+ if (!circuitBreaker) return; // Should never happen
302
+
303
+ try {
304
+ // Execute with circuit breaker protection
305
+ await circuitBreaker.execute(() => fn(subscriber));
306
+ } catch (error) {
307
+ // Handle circuit open errors (expected behavior when subscriber is down)
308
+ if (error instanceof CircuitOpenError) {
309
+ // Circuit is open - subscriber is down, log at warn level for visibility (same behavior in all environments)
310
+ getLogger().warn(`[Events] ${error.message}`, {
311
+ subscriberName: subscriber.name || 'Unknown',
312
+ });
313
+ return;
314
+ }
315
+
316
+ // Log other subscriber errors but don't throw - event failures shouldn't break business logic
317
+ getLogger().error(
318
+ `[Events] Subscriber ${subscriber.name || 'Unknown'} failed`,
319
+ error instanceof Error ? error : undefined,
320
+ { subscriberName: subscriber.name || 'Unknown' },
321
+ );
322
+ }
323
+ });
324
+
325
+ // Wait for all subscribers (success or failure)
326
+ await Promise.allSettled(promises);
327
+ }
328
+
329
+ /**
330
+ * Track conversion funnel steps
331
+ *
332
+ * Monitor where users drop off in multi-step processes.
333
+ *
334
+ * @example
335
+ * ```typescript
336
+ * // Track signup funnel
337
+ * events.trackFunnelStep('signup', 'started', { userId: '123' })
338
+ * events.trackFunnelStep('signup', 'email_verified', { userId: '123' })
339
+ * events.trackFunnelStep('signup', 'completed', { userId: '123' })
340
+ *
341
+ * // Track checkout flow
342
+ * events.trackFunnelStep('checkout', 'started', { cartValue: 99.99 })
343
+ * events.trackFunnelStep('checkout', 'payment_info', { cartValue: 99.99 })
344
+ * events.trackFunnelStep('checkout', 'completed', { cartValue: 99.99 })
345
+ * ```
346
+ */
347
+ trackFunnelStep(
348
+ funnelName: string,
349
+ status: FunnelStatus,
350
+ attributes?: EventAttributes,
351
+ ): void {
352
+ // Auto-attach all available telemetry context
353
+ const enrichedAttributes = this.enrichWithTelemetryContext(attributes);
354
+
355
+ this.logger?.info('Funnel step tracked', {
356
+ funnel: funnelName,
357
+ status,
358
+ attributes: enrichedAttributes,
359
+ });
360
+
361
+ // Record for testing
362
+ this.collector?.recordFunnelStep({
363
+ funnel: funnelName,
364
+ status,
365
+ attributes: enrichedAttributes,
366
+ service: this.serviceName,
367
+ timestamp: Date.now(),
368
+ });
369
+
370
+ // Notify subscribers
371
+ if (this.hasSubscribers) {
372
+ void this.notifySubscribers((subscriber) =>
373
+ subscriber.trackFunnelStep(funnelName, status, enrichedAttributes),
374
+ );
375
+ }
376
+ }
377
+
378
+ /**
379
+ * Track outcomes (success/failure/partial)
380
+ *
381
+ * Monitor success rates of critical operations.
382
+ *
383
+ * @example
384
+ * ```typescript
385
+ * // Track email delivery
386
+ * events.trackOutcome('email.delivery', 'success', {
387
+ * recipientType: 'user',
388
+ * emailType: 'welcome'
389
+ * })
390
+ *
391
+ * events.trackOutcome('email.delivery', 'failure', {
392
+ * recipientType: 'user',
393
+ * errorCode: 'invalid_email'
394
+ * })
395
+ *
396
+ * // Track payment processing
397
+ * events.trackOutcome('payment.process', 'success', { amount: 99.99 })
398
+ * events.trackOutcome('payment.process', 'failure', { error: 'insufficient_funds' })
399
+ * ```
400
+ */
401
+ trackOutcome(
402
+ operationName: string,
403
+ status: OutcomeStatus,
404
+ attributes?: EventAttributes,
405
+ ): void {
406
+ // Auto-attach all available telemetry context
407
+ const enrichedAttributes = this.enrichWithTelemetryContext(attributes);
408
+
409
+ this.logger?.info('Outcome tracked', {
410
+ operation: operationName,
411
+ status,
412
+ attributes: enrichedAttributes,
413
+ });
414
+
415
+ // Record for testing
416
+ this.collector?.recordOutcome({
417
+ operation: operationName,
418
+ status,
419
+ attributes: enrichedAttributes,
420
+ service: this.serviceName,
421
+ timestamp: Date.now(),
422
+ });
423
+
424
+ // Notify subscribers
425
+ if (this.hasSubscribers) {
426
+ void this.notifySubscribers((subscriber) =>
427
+ subscriber.trackOutcome(operationName, status, enrichedAttributes),
428
+ );
429
+ }
430
+ }
431
+
432
+ /**
433
+ * Track value metrics
434
+ *
435
+ * Record numerical values like revenue, transaction amounts,
436
+ * item counts, processing times, engagement scores, etc.
437
+ *
438
+ * @example
439
+ * ```typescript
440
+ * // Track revenue
441
+ * events.trackValue('order.revenue', 149.99, {
442
+ * currency: 'USD',
443
+ * productCategory: 'electronics'
444
+ * })
445
+ *
446
+ * // Track items per cart
447
+ * events.trackValue('cart.item_count', 5, {
448
+ * userId: '123'
449
+ * })
450
+ *
451
+ * // Track processing time
452
+ * events.trackValue('api.response_time', 250, {
453
+ * unit: 'ms',
454
+ * endpoint: '/api/checkout'
455
+ * })
456
+ * ```
457
+ */
458
+ trackValue(
459
+ metricName: string,
460
+ value: number,
461
+ attributes?: EventAttributes,
462
+ ): void {
463
+ // Auto-attach all available telemetry context
464
+ const enrichedAttributes = this.enrichWithTelemetryContext({
465
+ metric: metricName,
466
+ ...attributes,
467
+ });
468
+
469
+ this.logger?.debug('Value tracked', {
470
+ metric: metricName,
471
+ value,
472
+ attributes: enrichedAttributes,
473
+ });
474
+
475
+ // Record for testing
476
+ this.collector?.recordValue({
477
+ metric: metricName,
478
+ value,
479
+ attributes: enrichedAttributes,
480
+ service: this.serviceName,
481
+ timestamp: Date.now(),
482
+ });
483
+
484
+ // Notify subscribers
485
+ if (this.hasSubscribers) {
486
+ void this.notifySubscribers((subscriber) =>
487
+ subscriber.trackValue(metricName, value, enrichedAttributes),
488
+ );
489
+ }
490
+ }
491
+
492
+ /**
493
+ * Flush all subscribers and wait for pending events
494
+ *
495
+ * Call this before shutdown to ensure all events are delivered.
496
+ *
497
+ * @example
498
+ * ```typescript
499
+ * const event =new Event('app', { subscribers: [...] });
500
+ *
501
+ * // Before shutdown
502
+ * await events.flush();
503
+ * ```
504
+ */
505
+ async flush(): Promise<void> {
506
+ if (!this.hasSubscribers) return;
507
+
508
+ const shutdownPromises = this.subscribers.map(async (subscriber) => {
509
+ if (subscriber.shutdown) {
510
+ try {
511
+ await subscriber.shutdown();
512
+ } catch (error) {
513
+ getLogger().error(
514
+ `[Events] Failed to shutdown subscriber ${subscriber.name || 'Unknown'}`,
515
+ error instanceof Error ? error : undefined,
516
+ { subscriberName: subscriber.name || 'Unknown' },
517
+ );
518
+ }
519
+ }
520
+ });
521
+
522
+ await Promise.allSettled(shutdownPromises);
523
+ }
524
+ }
525
+
526
+ /**
527
+ * Global events instances (singleton pattern)
528
+ */
529
+ const eventsInstances = new Map<string, Event>();
530
+
531
+ /**
532
+ * Get or create an Events instance for a service
533
+ *
534
+ * @param serviceName - Service name for identifying events
535
+ * @param logger - Optional logger
536
+ * @returns Events instance
537
+ *
538
+ * @example
539
+ * ```typescript
540
+ * const event =getEvents('job-application')
541
+ * events.trackEvent('application.submitted', { jobId: '123' })
542
+ * ```
543
+ */
544
+ export function getEvents(serviceName: string, logger?: Logger): Event {
545
+ if (!eventsInstances.has(serviceName)) {
546
+ eventsInstances.set(serviceName, new Event(serviceName, { logger }));
547
+ }
548
+ return eventsInstances.get(serviceName)!;
549
+ }
550
+
551
+ /**
552
+ * Reset all events instances (mainly for testing)
553
+ */
554
+ export function resetEvents(): void {
555
+ eventsInstances.clear();
556
+ }
@@ -0,0 +1,96 @@
1
+ /**
2
+ * OpenTelemetry Exporters
3
+ *
4
+ * Re-exports commonly-needed OpenTelemetry exporters for development and debugging.
5
+ *
6
+ * These exporters are already included in autotel's dependencies, so re-exporting
7
+ * them provides a "one install is all you need" developer experience without any
8
+ * bundle size impact.
9
+ *
10
+ * Use these for:
11
+ * - Development debugging (see spans in console)
12
+ * - Progressive development (verify instrumentation works)
13
+ * - Example applications (demonstrate tracing)
14
+ * - Testing (capture spans for assertions)
15
+ *
16
+ * @example Console debugging (development)
17
+ * ```typescript
18
+ * import { init } from 'autotel'
19
+ * import { ConsoleSpanExporter } from 'autotel/exporters'
20
+ *
21
+ * init({
22
+ * service: 'my-app',
23
+ * spanExporter: new ConsoleSpanExporter(),
24
+ * })
25
+ * ```
26
+ *
27
+ * @example In-memory testing
28
+ * ```typescript
29
+ * import { init } from 'autotel'
30
+ * import { InMemorySpanExporter } from 'autotel/exporters'
31
+ * import { SimpleSpanProcessor } from 'autotel/processors'
32
+ *
33
+ * const exporter = new InMemorySpanExporter()
34
+ * init({
35
+ * service: 'test',
36
+ * spanProcessor: new SimpleSpanProcessor(exporter),
37
+ * })
38
+ *
39
+ * // Run code under test
40
+ * await myFunction()
41
+ *
42
+ * // Assert on collected spans
43
+ * const spans = exporter.getFinishedSpans()
44
+ * expect(spans).toHaveLength(1)
45
+ * ```
46
+ *
47
+ * Note: For high-level testing utilities with assertion helpers, see `autotel/testing`.
48
+ * For custom span processing, see `autotel/processors`.
49
+ *
50
+ * @module autotel/exporters
51
+ * @see {@link https://opentelemetry.io/docs/specs/otel/trace/sdk/#span-exporter | OTel Span Exporter Spec}
52
+ */
53
+
54
+ export {
55
+ /**
56
+ * Console exporter - prints spans to stdout.
57
+ *
58
+ * Perfect for:
59
+ * - Local development (see what's being traced)
60
+ * - Example applications (demonstrate tracing)
61
+ * - Quick debugging (no backend setup required)
62
+ * - Progressive development (verify spans are created)
63
+ *
64
+ * Note: Not recommended for production use.
65
+ *
66
+ * @example
67
+ * ```typescript
68
+ * import { ConsoleSpanExporter } from 'autotel/exporters'
69
+ *
70
+ * const exporter = new ConsoleSpanExporter()
71
+ * ```
72
+ */
73
+ ConsoleSpanExporter,
74
+
75
+ /**
76
+ * In-memory exporter - stores spans in memory.
77
+ *
78
+ * Perfect for:
79
+ * - Unit testing (capture and assert on spans)
80
+ * - Integration testing (verify trace structure)
81
+ * - Development (inspect spans programmatically)
82
+ *
83
+ * Note: Memory will grow unbounded - clear spans periodically or use for testing only.
84
+ *
85
+ * @example
86
+ * ```typescript
87
+ * import { InMemorySpanExporter } from 'autotel/exporters'
88
+ *
89
+ * const exporter = new InMemorySpanExporter()
90
+ * // ... run code
91
+ * const spans = exporter.getFinishedSpans()
92
+ * exporter.reset() // Clear memory
93
+ * ```
94
+ */
95
+ InMemorySpanExporter,
96
+ } from '@opentelemetry/sdk-trace-base';