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
@@ -0,0 +1,718 @@
1
+ import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
2
+ import { Event, getEvents, resetEvents } from './event';
3
+ import { type Logger } from './logger';
4
+ import { init } from './init';
5
+ import { shutdown } from './shutdown';
6
+ import { trace } from './functional';
7
+
8
+ describe('Events', () => {
9
+ let mockLogger: Logger;
10
+
11
+ beforeEach(() => {
12
+ resetEvents();
13
+ mockLogger = {
14
+ info: vi.fn(),
15
+ warn: vi.fn(),
16
+ error: vi.fn(),
17
+ debug: vi.fn(),
18
+ };
19
+ });
20
+
21
+ describe('trackEvent', () => {
22
+ it('should track business events', () => {
23
+ const event = new Event('test-service', { logger: mockLogger });
24
+
25
+ event.trackEvent('application.submitted', {
26
+ jobId: '123',
27
+ userId: '456',
28
+ });
29
+
30
+ expect(mockLogger.info).toHaveBeenCalledWith('Event tracked', {
31
+ event: 'application.submitted',
32
+ attributes: { service: 'test-service', jobId: '123', userId: '456' },
33
+ });
34
+ });
35
+
36
+ it('should track events without attributes', () => {
37
+ const event = new Event('test-service', { logger: mockLogger });
38
+
39
+ event.trackEvent('user.login');
40
+
41
+ expect(mockLogger.info).toHaveBeenCalledWith('Event tracked', {
42
+ event: 'user.login',
43
+ attributes: { service: 'test-service' },
44
+ });
45
+ });
46
+ });
47
+
48
+ describe('trackFunnelStep', () => {
49
+ it('should track funnel progression', () => {
50
+ const event = new Event('test-service', { logger: mockLogger });
51
+
52
+ event.trackFunnelStep('checkout', 'started', { cartValue: 99.99 });
53
+ event.trackFunnelStep('checkout', 'completed', { cartValue: 99.99 });
54
+
55
+ expect(mockLogger.info).toHaveBeenCalledTimes(2);
56
+ expect(mockLogger.info).toHaveBeenCalledWith('Funnel step tracked', {
57
+ funnel: 'checkout',
58
+ status: 'started',
59
+ attributes: { service: 'test-service', cartValue: 99.99 },
60
+ });
61
+ });
62
+
63
+ it('should track funnel abandonment', () => {
64
+ const event = new Event('test-service', { logger: mockLogger });
65
+
66
+ event.trackFunnelStep('checkout', 'abandoned', { reason: 'timeout' });
67
+
68
+ expect(mockLogger.info).toHaveBeenCalledWith('Funnel step tracked', {
69
+ funnel: 'checkout',
70
+ status: 'abandoned',
71
+ attributes: { service: 'test-service', reason: 'timeout' },
72
+ });
73
+ });
74
+ });
75
+
76
+ describe('trackOutcome', () => {
77
+ it('should track successful outcomes', () => {
78
+ const event = new Event('test-service', { logger: mockLogger });
79
+
80
+ event.trackOutcome('email.delivery', 'success', {
81
+ recipientType: 'school',
82
+ });
83
+
84
+ expect(mockLogger.info).toHaveBeenCalledWith('Outcome tracked', {
85
+ operation: 'email.delivery',
86
+ status: 'success',
87
+ attributes: { service: 'test-service', recipientType: 'school' },
88
+ });
89
+ });
90
+
91
+ it('should track failed outcomes', () => {
92
+ const event = new Event('test-service', { logger: mockLogger });
93
+
94
+ event.trackOutcome('email.delivery', 'failure', {
95
+ error: 'invalid_email',
96
+ });
97
+
98
+ expect(mockLogger.info).toHaveBeenCalledWith('Outcome tracked', {
99
+ operation: 'email.delivery',
100
+ status: 'failure',
101
+ attributes: { service: 'test-service', error: 'invalid_email' },
102
+ });
103
+ });
104
+
105
+ it('should track partial outcomes', () => {
106
+ const event = new Event('test-service', { logger: mockLogger });
107
+
108
+ event.trackOutcome('batch.process', 'partial', {
109
+ successCount: 8,
110
+ failureCount: 2,
111
+ });
112
+
113
+ expect(mockLogger.info).toHaveBeenCalledWith('Outcome tracked', {
114
+ operation: 'batch.process',
115
+ status: 'partial',
116
+ attributes: {
117
+ service: 'test-service',
118
+ successCount: 8,
119
+ failureCount: 2,
120
+ },
121
+ });
122
+ });
123
+ });
124
+
125
+ describe('trackValue', () => {
126
+ it('should track revenue metrics', () => {
127
+ const event = new Event('test-service', { logger: mockLogger });
128
+
129
+ event.trackValue('order.revenue', 149.99, {
130
+ currency: 'USD',
131
+ productCategory: 'electronics',
132
+ });
133
+
134
+ expect(mockLogger.debug).toHaveBeenCalledWith('Value tracked', {
135
+ metric: 'order.revenue',
136
+ value: 149.99,
137
+ attributes: {
138
+ service: 'test-service',
139
+ metric: 'order.revenue',
140
+ currency: 'USD',
141
+ productCategory: 'electronics',
142
+ },
143
+ });
144
+ });
145
+
146
+ it('should track processing time', () => {
147
+ const event = new Event('test-service', { logger: mockLogger });
148
+
149
+ event.trackValue('application.processing_time', 2500, {
150
+ unit: 'ms',
151
+ });
152
+
153
+ expect(mockLogger.debug).toHaveBeenCalledWith('Value tracked', {
154
+ metric: 'application.processing_time',
155
+ value: 2500,
156
+ attributes: {
157
+ service: 'test-service',
158
+ metric: 'application.processing_time',
159
+ unit: 'ms',
160
+ },
161
+ });
162
+ });
163
+ });
164
+
165
+ describe('getEvents', () => {
166
+ it('should return singleton instance', () => {
167
+ const events1 = getEvents('test-service');
168
+ const events2 = getEvents('test-service');
169
+
170
+ expect(events1).toBe(events2);
171
+ });
172
+
173
+ it('should return different instances for different services', () => {
174
+ const events1 = getEvents('service-1');
175
+ const events2 = getEvents('service-2');
176
+
177
+ expect(events1).not.toBe(events2);
178
+ });
179
+
180
+ it('should reset instances', () => {
181
+ const events1 = getEvents('test-service');
182
+ resetEvents();
183
+ const events2 = getEvents('test-service');
184
+
185
+ expect(events1).not.toBe(events2);
186
+ });
187
+ });
188
+
189
+ describe('real-world usage example', () => {
190
+ it('should track job application flow', () => {
191
+ const event = new Event('job-application', {
192
+ logger: mockLogger,
193
+ });
194
+
195
+ // User starts application
196
+ event.trackFunnelStep('application', 'started', { jobId: '123' });
197
+
198
+ // User submits application
199
+ event.trackEvent('application.submitted', {
200
+ jobId: '123',
201
+ userId: '456',
202
+ });
203
+ event.trackFunnelStep('application', 'completed', { jobId: '123' });
204
+
205
+ // Email sent successfully
206
+ event.trackOutcome('email.sent', 'success', {
207
+ recipientType: 'school',
208
+ jobId: '123',
209
+ });
210
+
211
+ expect(mockLogger.info).toHaveBeenCalledTimes(4);
212
+ });
213
+
214
+ it('should track email delivery failures', () => {
215
+ const event = new Event('email-service', { logger: mockLogger });
216
+
217
+ // Failed email delivery
218
+ event.trackOutcome('email.delivery', 'failure', {
219
+ error: 'invalid_email',
220
+ recipientEmail: 'redacted',
221
+ });
222
+
223
+ // Track event for alerting
224
+ event.trackEvent('email.bounce', {
225
+ bounceType: 'permanent',
226
+ });
227
+
228
+ expect(mockLogger.info).toHaveBeenCalledTimes(2);
229
+ });
230
+ });
231
+
232
+ describe('automatic telemetry context enrichment', () => {
233
+ beforeEach(() => {
234
+ resetEvents();
235
+ });
236
+
237
+ afterEach(async () => {
238
+ await shutdown();
239
+ });
240
+
241
+ // Test without config first (before any init() is called)
242
+ it('should still work without config (graceful degradation)', () => {
243
+ // Don't initialize - no config available
244
+ const event = new Event('test-service', { logger: mockLogger });
245
+
246
+ event.trackEvent('user.login');
247
+
248
+ expect(mockLogger.info).toHaveBeenCalledWith(
249
+ 'Event tracked',
250
+ expect.objectContaining({
251
+ attributes: {
252
+ service: 'test-service',
253
+ // No version/environment - gracefully omitted
254
+ },
255
+ }),
256
+ );
257
+ });
258
+
259
+ it('should auto-capture resource attributes (service.version, deployment.environment)', () => {
260
+ // Initialize with config
261
+ init({
262
+ service: 'test-service',
263
+ version: '2.1.0',
264
+ environment: 'production',
265
+ });
266
+
267
+ const event = new Event('test-service', { logger: mockLogger });
268
+
269
+ event.trackEvent('user.signup', { userId: '123' });
270
+
271
+ expect(mockLogger.info).toHaveBeenCalledWith(
272
+ 'Event tracked',
273
+ expect.objectContaining({
274
+ attributes: expect.objectContaining({
275
+ service: 'test-service',
276
+ 'service.version': '2.1.0',
277
+ 'deployment.environment': 'production',
278
+ userId: '123',
279
+ }),
280
+ }),
281
+ );
282
+ });
283
+
284
+ it('should auto-capture trace context (traceId, spanId, correlationId) when inside a trace', async () => {
285
+ init({ service: 'test-service' });
286
+
287
+ const event = new Event('test-service', { logger: mockLogger });
288
+
289
+ const tracedOperation = trace('test.operation', async () => {
290
+ event.trackEvent('operation.started', { step: 1 });
291
+ });
292
+
293
+ await tracedOperation();
294
+
295
+ const capturedCall = (mockLogger.info as ReturnType<typeof vi.fn>).mock
296
+ .calls[0];
297
+ const attributes = capturedCall[1].attributes;
298
+
299
+ expect(attributes).toHaveProperty('traceId');
300
+ expect(attributes).toHaveProperty('spanId');
301
+ expect(attributes).toHaveProperty('correlationId');
302
+ expect(typeof attributes.traceId).toBe('string');
303
+ expect(typeof attributes.spanId).toBe('string');
304
+ expect(typeof attributes.correlationId).toBe('string');
305
+ // Correlation ID should be first 16 chars of traceId
306
+ expect(attributes.correlationId).toBe(attributes.traceId.slice(0, 16));
307
+ });
308
+
309
+ it('should enrich trackFunnelStep with telemetry context', async () => {
310
+ init({
311
+ service: 'test-service',
312
+ version: '1.5.0',
313
+ environment: 'staging',
314
+ });
315
+
316
+ const event = new Event('test-service', { logger: mockLogger });
317
+
318
+ const tracedOperation = trace('checkout.flow', async () => {
319
+ event.trackFunnelStep('checkout', 'started', { cartValue: 99.99 });
320
+ });
321
+
322
+ await tracedOperation();
323
+
324
+ expect(mockLogger.info).toHaveBeenCalledWith(
325
+ 'Funnel step tracked',
326
+ expect.objectContaining({
327
+ attributes: expect.objectContaining({
328
+ service: 'test-service',
329
+ 'service.version': '1.5.0',
330
+ 'deployment.environment': 'staging',
331
+ cartValue: 99.99,
332
+ traceId: expect.any(String),
333
+ spanId: expect.any(String),
334
+ correlationId: expect.any(String),
335
+ }),
336
+ }),
337
+ );
338
+ });
339
+
340
+ it('should enrich trackOutcome with telemetry context', async () => {
341
+ init({
342
+ service: 'test-service',
343
+ version: '3.0.0',
344
+ environment: 'development',
345
+ });
346
+
347
+ const event = new Event('test-service', { logger: mockLogger });
348
+
349
+ const tracedOperation = trace('email.send', async () => {
350
+ event.trackOutcome('email.delivery', 'success', {
351
+ recipientType: 'user',
352
+ });
353
+ });
354
+
355
+ await tracedOperation();
356
+
357
+ expect(mockLogger.info).toHaveBeenCalledWith(
358
+ 'Outcome tracked',
359
+ expect.objectContaining({
360
+ attributes: expect.objectContaining({
361
+ service: 'test-service',
362
+ 'service.version': '3.0.0',
363
+ 'deployment.environment': 'development',
364
+ recipientType: 'user',
365
+ traceId: expect.any(String),
366
+ spanId: expect.any(String),
367
+ correlationId: expect.any(String),
368
+ }),
369
+ }),
370
+ );
371
+ });
372
+
373
+ it('should enrich trackValue with telemetry context', async () => {
374
+ init({
375
+ service: 'test-service',
376
+ version: '4.2.1',
377
+ environment: 'production',
378
+ });
379
+
380
+ const event = new Event('test-service', { logger: mockLogger });
381
+
382
+ const tracedOperation = trace('order.process', async () => {
383
+ event.trackValue('order.revenue', 149.99, { currency: 'USD' });
384
+ });
385
+
386
+ await tracedOperation();
387
+
388
+ expect(mockLogger.debug).toHaveBeenCalledWith(
389
+ 'Value tracked',
390
+ expect.objectContaining({
391
+ attributes: expect.objectContaining({
392
+ service: 'test-service',
393
+ metric: 'order.revenue',
394
+ 'service.version': '4.2.1',
395
+ 'deployment.environment': 'production',
396
+ currency: 'USD',
397
+ traceId: expect.any(String),
398
+ spanId: expect.any(String),
399
+ correlationId: expect.any(String),
400
+ }),
401
+ }),
402
+ );
403
+ });
404
+
405
+ it('should still work outside a trace (no trace context)', () => {
406
+ init({
407
+ service: 'test-service',
408
+ version: '1.0.0',
409
+ environment: 'test',
410
+ });
411
+
412
+ const event = new Event('test-service', { logger: mockLogger });
413
+
414
+ // Call outside a trace
415
+ event.trackEvent('background.job.completed', { jobId: 'job-123' });
416
+
417
+ expect(mockLogger.info).toHaveBeenCalledWith(
418
+ 'Event tracked',
419
+ expect.objectContaining({
420
+ attributes: {
421
+ service: 'test-service',
422
+ 'service.version': '1.0.0',
423
+ 'deployment.environment': 'test',
424
+ jobId: 'job-123',
425
+ // No traceId/spanId/correlationId - gracefully omitted
426
+ },
427
+ }),
428
+ );
429
+ });
430
+ });
431
+
432
+ describe('automatic operation context enrichment', () => {
433
+ beforeEach(() => {
434
+ resetEvents();
435
+ });
436
+
437
+ afterEach(async () => {
438
+ await shutdown();
439
+ });
440
+
441
+ it('should auto-capture operation.name when inside trace() with string name', async () => {
442
+ init({ service: 'test-service' });
443
+
444
+ const event = new Event('test-service', { logger: mockLogger });
445
+
446
+ const operation = trace('user.create', async () => {
447
+ event.trackEvent('user.created', { userId: '123' });
448
+ });
449
+
450
+ await operation();
451
+
452
+ expect(mockLogger.info).toHaveBeenCalledWith(
453
+ 'Event tracked',
454
+ expect.objectContaining({
455
+ attributes: expect.objectContaining({
456
+ 'operation.name': 'user.create',
457
+ userId: '123',
458
+ }),
459
+ }),
460
+ );
461
+ });
462
+
463
+ it('should auto-capture operation.name when inside trace() with named function', async () => {
464
+ init({ service: 'test-service' });
465
+
466
+ const event = new Event('test-service', { logger: mockLogger });
467
+
468
+ const createUser = trace(async function createUser() {
469
+ event.trackEvent('user.created', { userId: '456' });
470
+ });
471
+
472
+ await createUser();
473
+
474
+ expect(mockLogger.info).toHaveBeenCalledWith(
475
+ 'Event tracked',
476
+ expect.objectContaining({
477
+ attributes: expect.objectContaining({
478
+ // Function name might be inferred with slight variations (e.g., 'createUser2')
479
+ // The important thing is that operation.name is auto-captured
480
+ 'operation.name': expect.stringMatching(/createUser/),
481
+ userId: '456',
482
+ }),
483
+ }),
484
+ );
485
+ });
486
+
487
+ it('should reliably infer function names in both factory and non-factory patterns', async () => {
488
+ init({ service: 'test-service' });
489
+
490
+ const event = new Event('test-service', { logger: mockLogger });
491
+
492
+ // Test 1: Named function declaration (non-factory pattern)
493
+ // Should infer name from function declaration
494
+ const updateUser = trace(async function updateUser(userId: string) {
495
+ event.trackEvent('user.updated', { userId });
496
+ });
497
+ await updateUser('user-123');
498
+
499
+ // Test 2: Named function with factory pattern (ctx parameter)
500
+ // Explicit name should take precedence
501
+ const deleteUser = trace(
502
+ 'user.delete',
503
+ (ctx) =>
504
+ async function deleteUser(userId: string) {
505
+ ctx.setAttribute('user.id', userId);
506
+ event.trackEvent('user.deleted', { userId });
507
+ },
508
+ );
509
+ await deleteUser('user-456');
510
+
511
+ // Test 3: Named function in factory pattern (should infer inner function name)
512
+ const createOrder = trace(
513
+ (ctx) =>
514
+ async function createOrder(orderId: string) {
515
+ ctx.setAttribute('order.id', orderId);
516
+ event.trackEvent('order.created', { orderId });
517
+ },
518
+ );
519
+ await createOrder('order-789');
520
+
521
+ // Verify all operations captured correct names
522
+ const calls = (mockLogger.info as ReturnType<typeof vi.fn>).mock.calls;
523
+
524
+ // First call: updateUser - should infer from named function declaration
525
+ expect(calls[0][1].attributes['operation.name']).toMatch(/updateUser/);
526
+
527
+ // Second call: user.delete - explicit name takes precedence
528
+ expect(calls[1][1].attributes['operation.name']).toBe('user.delete');
529
+
530
+ // Third call: createOrder - should infer from inner named function in factory pattern
531
+ expect(calls[2][1].attributes['operation.name']).toMatch(/createOrder/);
532
+ });
533
+
534
+ it('should auto-capture operation.name in nested spans', async () => {
535
+ init({ service: 'test-service' });
536
+
537
+ const event = new Event('test-service', { logger: mockLogger });
538
+ const { span } = await import('./functional');
539
+
540
+ const operation = trace('order.process', async () => {
541
+ span({ name: 'order.validate' }, () => {
542
+ // Should capture the innermost operation name
543
+ event.trackEvent('order.validated', { orderId: 'ord_123' });
544
+ });
545
+ });
546
+
547
+ await operation();
548
+
549
+ expect(mockLogger.info).toHaveBeenCalledWith(
550
+ 'Event tracked',
551
+ expect.objectContaining({
552
+ attributes: expect.objectContaining({
553
+ 'operation.name': 'order.validate',
554
+ orderId: 'ord_123',
555
+ }),
556
+ }),
557
+ );
558
+ });
559
+
560
+ it('should auto-capture operation.name in trackFunnelStep', async () => {
561
+ init({ service: 'test-service' });
562
+
563
+ const event = new Event('test-service', { logger: mockLogger });
564
+
565
+ const checkout = trace('checkout.flow', async () => {
566
+ event.trackFunnelStep('checkout', 'started', {
567
+ cartValue: 99.99,
568
+ });
569
+ });
570
+
571
+ await checkout();
572
+
573
+ expect(mockLogger.info).toHaveBeenCalledWith(
574
+ 'Funnel step tracked',
575
+ expect.objectContaining({
576
+ attributes: expect.objectContaining({
577
+ 'operation.name': 'checkout.flow',
578
+ cartValue: 99.99,
579
+ }),
580
+ }),
581
+ );
582
+ });
583
+
584
+ it('should auto-capture operation.name in trackOutcome', async () => {
585
+ init({ service: 'test-service' });
586
+
587
+ const event = new Event('test-service', { logger: mockLogger });
588
+
589
+ const sendEmail = trace('email.send', async () => {
590
+ event.trackOutcome('email.delivery', 'success', {
591
+ recipientType: 'user',
592
+ });
593
+ });
594
+
595
+ await sendEmail();
596
+
597
+ expect(mockLogger.info).toHaveBeenCalledWith(
598
+ 'Outcome tracked',
599
+ expect.objectContaining({
600
+ attributes: expect.objectContaining({
601
+ 'operation.name': 'email.send',
602
+ recipientType: 'user',
603
+ }),
604
+ }),
605
+ );
606
+ });
607
+
608
+ it('should auto-capture operation.name in trackValue', async () => {
609
+ init({ service: 'test-service' });
610
+
611
+ const event = new Event('test-service', { logger: mockLogger });
612
+
613
+ const processOrder = trace('order.process', async () => {
614
+ event.trackValue('order.revenue', 149.99, { currency: 'USD' });
615
+ });
616
+
617
+ await processOrder();
618
+
619
+ expect(mockLogger.debug).toHaveBeenCalledWith(
620
+ 'Value tracked',
621
+ expect.objectContaining({
622
+ attributes: expect.objectContaining({
623
+ 'operation.name': 'order.process',
624
+ currency: 'USD',
625
+ }),
626
+ }),
627
+ );
628
+ });
629
+
630
+ it('should handle missing operation.name gracefully (outside trace)', () => {
631
+ init({ service: 'test-service' });
632
+
633
+ const event = new Event('test-service', { logger: mockLogger });
634
+
635
+ // Call outside any trace
636
+ event.trackEvent('background.job', { jobId: 'job-123' });
637
+
638
+ expect(mockLogger.info).toHaveBeenCalledWith(
639
+ 'Event tracked',
640
+ expect.objectContaining({
641
+ attributes: {
642
+ service: 'test-service',
643
+ 'service.version': undefined,
644
+ 'deployment.environment': undefined,
645
+ jobId: 'job-123',
646
+ // No operation.name - gracefully omitted
647
+ },
648
+ }),
649
+ );
650
+ });
651
+
652
+ it('should capture parent operation.name when not in nested span', async () => {
653
+ init({ service: 'test-service' });
654
+
655
+ const event = new Event('test-service', { logger: mockLogger });
656
+
657
+ const parentOperation = trace('parent.operation', async () => {
658
+ // Track event in parent context (not in a nested span)
659
+ event.trackEvent('parent.event', { step: 1 });
660
+
661
+ // Then create a nested span
662
+ const { span } = await import('./functional');
663
+ span({ name: 'child.operation' }, () => {
664
+ event.trackEvent('child.event', { step: 2 });
665
+ });
666
+ });
667
+
668
+ await parentOperation();
669
+
670
+ // Check parent event has parent operation name
671
+ expect(mockLogger.info).toHaveBeenNthCalledWith(
672
+ 1,
673
+ 'Event tracked',
674
+ expect.objectContaining({
675
+ attributes: expect.objectContaining({
676
+ 'operation.name': 'parent.operation',
677
+ step: 1,
678
+ }),
679
+ }),
680
+ );
681
+
682
+ // Check child event has child operation name
683
+ expect(mockLogger.info).toHaveBeenNthCalledWith(
684
+ 2,
685
+ 'Event tracked',
686
+ expect.objectContaining({
687
+ attributes: expect.objectContaining({
688
+ 'operation.name': 'child.operation',
689
+ step: 2,
690
+ }),
691
+ }),
692
+ );
693
+ });
694
+
695
+ it('should work with trace() factory pattern', async () => {
696
+ init({ service: 'test-service' });
697
+
698
+ const event = new Event('test-service', { logger: mockLogger });
699
+
700
+ const operation = trace('factory.operation', (ctx) => async () => {
701
+ ctx.setAttribute('custom', 'attribute');
702
+ event.trackEvent('factory.event', { data: 'test' });
703
+ });
704
+
705
+ await operation();
706
+
707
+ expect(mockLogger.info).toHaveBeenCalledWith(
708
+ 'Event tracked',
709
+ expect.objectContaining({
710
+ attributes: expect.objectContaining({
711
+ 'operation.name': 'factory.operation',
712
+ data: 'test',
713
+ }),
714
+ }),
715
+ );
716
+ });
717
+ });
718
+ });