autotel 4.3.0 → 5.0.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 (381) hide show
  1. package/README.md +125 -103
  2. package/dist/analysis.cjs +87 -0
  3. package/dist/analysis.cjs.map +1 -0
  4. package/dist/analysis.d.cts +87 -0
  5. package/dist/analysis.d.cts.map +1 -0
  6. package/dist/analysis.d.ts +87 -0
  7. package/dist/analysis.d.ts.map +1 -0
  8. package/dist/analysis.js +85 -0
  9. package/dist/analysis.js.map +1 -0
  10. package/dist/{attribute-redacting-processor-DtTS9xxh.d.ts → attribute-redacting-processor-Pk7lAzWd.d.cts} +13 -8
  11. package/dist/attribute-redacting-processor-Pk7lAzWd.d.cts.map +1 -0
  12. package/dist/{attribute-redacting-processor-DtTS9xxh.d.cts → attribute-redacting-processor-Pk7lAzWd.d.ts} +13 -8
  13. package/dist/attribute-redacting-processor-Pk7lAzWd.d.ts.map +1 -0
  14. package/dist/attribute-redacting-processor.cjs +4 -4
  15. package/dist/attribute-redacting-processor.cjs.map +1 -1
  16. package/dist/attribute-redacting-processor.d.cts +1 -1
  17. package/dist/attribute-redacting-processor.d.ts +1 -1
  18. package/dist/attribute-redacting-processor.js +4 -4
  19. package/dist/attribute-redacting-processor.js.map +1 -1
  20. package/dist/attributes.d.cts +1 -1
  21. package/dist/attributes.d.ts +1 -1
  22. package/dist/auto.cjs +1 -1
  23. package/dist/auto.cjs.map +1 -1
  24. package/dist/auto.d.cts +1 -1
  25. package/dist/auto.d.ts +1 -1
  26. package/dist/auto.js +1 -1
  27. package/dist/auto.js.map +1 -1
  28. package/dist/business-baggage.d.cts +2 -3
  29. package/dist/business-baggage.d.cts.map +1 -1
  30. package/dist/business-baggage.d.ts +2 -3
  31. package/dist/business-baggage.d.ts.map +1 -1
  32. package/dist/{canonical-log-line-processor-xvjMgtpF.d.cts → canonical-log-line-processor-B4WgG-VS.d.cts} +2 -3
  33. package/dist/canonical-log-line-processor-B4WgG-VS.d.cts.map +1 -0
  34. package/dist/{canonical-log-line-processor-BcRuAdRk.d.ts → canonical-log-line-processor-CL_J7gb5.d.ts} +2 -3
  35. package/dist/canonical-log-line-processor-CL_J7gb5.d.ts.map +1 -0
  36. package/dist/{canonical-log-line-processor-CcrDi6TO.cjs → canonical-log-line-processor-DOjb6Kxy.cjs} +5 -5
  37. package/dist/canonical-log-line-processor-DOjb6Kxy.cjs.map +1 -0
  38. package/dist/{canonical-log-line-processor-FLezvFCB.js → canonical-log-line-processor-mKABvGP0.js} +5 -5
  39. package/dist/canonical-log-line-processor-mKABvGP0.js.map +1 -0
  40. package/dist/config.d.cts +12 -7
  41. package/dist/config.d.cts.map +1 -1
  42. package/dist/config.d.ts +12 -7
  43. package/dist/config.d.ts.map +1 -1
  44. package/dist/correlation-id.cjs +1 -1
  45. package/dist/correlation-id.d.cts +0 -1
  46. package/dist/correlation-id.d.cts.map +1 -1
  47. package/dist/correlation-id.d.ts +0 -1
  48. package/dist/correlation-id.d.ts.map +1 -1
  49. package/dist/correlation-id.js +1 -1
  50. package/dist/db.d.cts.map +1 -1
  51. package/dist/db.d.ts.map +1 -1
  52. package/dist/decorators.cjs +1 -1
  53. package/dist/decorators.cjs.map +1 -1
  54. package/dist/decorators.d.cts +1 -2
  55. package/dist/decorators.d.cts.map +1 -1
  56. package/dist/decorators.d.ts +1 -2
  57. package/dist/decorators.d.ts.map +1 -1
  58. package/dist/decorators.js +1 -1
  59. package/dist/decorators.js.map +1 -1
  60. package/dist/{define-event-CreknLm3.d.ts → define-event-B2z1SBe7.d.ts} +2 -3
  61. package/dist/define-event-B2z1SBe7.d.ts.map +1 -0
  62. package/dist/{define-event-HZRizPwz.d.cts → define-event-CGG048HE.d.cts} +2 -3
  63. package/dist/define-event-CGG048HE.d.cts.map +1 -0
  64. package/dist/diagnostics.cjs +10 -1
  65. package/dist/diagnostics.cjs.map +1 -1
  66. package/dist/diagnostics.d.cts +0 -1
  67. package/dist/diagnostics.d.cts.map +1 -1
  68. package/dist/diagnostics.d.ts +0 -1
  69. package/dist/diagnostics.d.ts.map +1 -1
  70. package/dist/diagnostics.js +10 -1
  71. package/dist/diagnostics.js.map +1 -1
  72. package/dist/drain-pipeline.d.cts +12 -5
  73. package/dist/drain-pipeline.d.cts.map +1 -1
  74. package/dist/drain-pipeline.d.ts +12 -5
  75. package/dist/drain-pipeline.d.ts.map +1 -1
  76. package/dist/enrichers.d.cts.map +1 -1
  77. package/dist/enrichers.d.ts.map +1 -1
  78. package/dist/{event-rzA1X5h3.cjs → event-CwIpFdoY.cjs} +3 -3
  79. package/dist/event-CwIpFdoY.cjs.map +1 -0
  80. package/dist/{event-BEm5IJ8U.js → event-pLMF5b3d.js} +4 -4
  81. package/dist/event-pLMF5b3d.js.map +1 -0
  82. package/dist/{event-subscriber-D1XLkPzi.d.ts → event-subscriber-CWh8sTGh.d.cts} +2 -2
  83. package/dist/event-subscriber-CWh8sTGh.d.cts.map +1 -0
  84. package/dist/{event-subscriber-D1XLkPzi.d.cts → event-subscriber-CWh8sTGh.d.ts} +2 -2
  85. package/dist/event-subscriber-CWh8sTGh.d.ts.map +1 -0
  86. package/dist/event-subscriber.d.cts +1 -1
  87. package/dist/event-subscriber.d.ts +1 -1
  88. package/dist/{event-testing-CfFs3to2.d.ts → event-testing-CA2mIx1E.d.ts} +2 -3
  89. package/dist/event-testing-CA2mIx1E.d.ts.map +1 -0
  90. package/dist/{event-testing-BqRnI0z4.d.cts → event-testing-D0qWfqAy.d.cts} +2 -3
  91. package/dist/event-testing-D0qWfqAy.d.cts.map +1 -0
  92. package/dist/event-testing.d.cts +1 -1
  93. package/dist/event-testing.d.ts +1 -1
  94. package/dist/event.cjs +1 -1
  95. package/dist/event.d.cts +3 -4
  96. package/dist/event.d.cts.map +1 -1
  97. package/dist/event.d.ts +3 -4
  98. package/dist/event.d.ts.map +1 -1
  99. package/dist/event.js +1 -1
  100. package/dist/exporters.d.cts +0 -4
  101. package/dist/exporters.d.cts.map +1 -1
  102. package/dist/exporters.d.ts +0 -4
  103. package/dist/exporters.d.ts.map +1 -1
  104. package/dist/{filtering-span-processor-B8R8B7Uk.d.ts → filtering-span-processor-BpDagWNa.d.cts} +1 -2
  105. package/dist/filtering-span-processor-BpDagWNa.d.cts.map +1 -0
  106. package/dist/{filtering-span-processor-B8R8B7Uk.d.cts → filtering-span-processor-BpDagWNa.d.ts} +1 -2
  107. package/dist/filtering-span-processor-BpDagWNa.d.ts.map +1 -0
  108. package/dist/filtering-span-processor.d.cts +1 -1
  109. package/dist/filtering-span-processor.d.ts +1 -1
  110. package/dist/functional-Bit6noRu.js +840 -0
  111. package/dist/functional-Bit6noRu.js.map +1 -0
  112. package/dist/functional-Cj7PSItS.cjs +890 -0
  113. package/dist/functional-Cj7PSItS.cjs.map +1 -0
  114. package/dist/functional.cjs +2 -2
  115. package/dist/functional.d.cts +79 -130
  116. package/dist/functional.d.cts.map +1 -1
  117. package/dist/functional.d.ts +79 -130
  118. package/dist/functional.d.ts.map +1 -1
  119. package/dist/functional.js +2 -2
  120. package/dist/http.cjs +5 -5
  121. package/dist/http.cjs.map +1 -1
  122. package/dist/http.d.cts +4 -5
  123. package/dist/http.d.cts.map +1 -1
  124. package/dist/http.d.ts +4 -5
  125. package/dist/http.d.ts.map +1 -1
  126. package/dist/http.js +5 -5
  127. package/dist/http.js.map +1 -1
  128. package/dist/{index-eKuioqT1.d.cts → index-BPcxVucm.d.cts} +2 -3
  129. package/dist/index-BPcxVucm.d.cts.map +1 -0
  130. package/dist/{index-Ck06vlW2.d.ts → index-DolOVX6F.d.ts} +2 -3
  131. package/dist/index-DolOVX6F.d.ts.map +1 -0
  132. package/dist/index.cjs +7 -8
  133. package/dist/index.cjs.map +1 -1
  134. package/dist/index.d.cts +14 -14
  135. package/dist/index.d.cts.map +1 -1
  136. package/dist/index.d.ts +14 -14
  137. package/dist/index.d.ts.map +1 -1
  138. package/dist/index.js +7 -8
  139. package/dist/index.js.map +1 -1
  140. package/dist/{init-BxJEJ9Tn.cjs → init-B2uvZReD.cjs} +19 -10
  141. package/dist/init-B2uvZReD.cjs.map +1 -0
  142. package/dist/{init-B7u-DjxM.d.ts → init-CTyqG9qR.d.ts} +24 -17
  143. package/dist/init-CTyqG9qR.d.ts.map +1 -0
  144. package/dist/{init-BvBB45-T.js → init-CdEWjhvl.js} +20 -11
  145. package/dist/init-CdEWjhvl.js.map +1 -0
  146. package/dist/{init-DSrRmVnz.d.cts → init-a4yPDYqC.d.cts} +24 -17
  147. package/dist/init-a4yPDYqC.d.cts.map +1 -0
  148. package/dist/instrumentation.cjs +7 -13
  149. package/dist/instrumentation.cjs.map +1 -1
  150. package/dist/instrumentation.d.cts +0 -1
  151. package/dist/instrumentation.d.cts.map +1 -1
  152. package/dist/instrumentation.d.ts +0 -1
  153. package/dist/instrumentation.d.ts.map +1 -1
  154. package/dist/instrumentation.js +8 -14
  155. package/dist/instrumentation.js.map +1 -1
  156. package/dist/{logger-BauSUhUv.d.ts → logger-CN4xpzMd.d.cts} +1 -2
  157. package/dist/logger-CN4xpzMd.d.cts.map +1 -0
  158. package/dist/{logger-BauSUhUv.d.cts → logger-CN4xpzMd.d.ts} +1 -2
  159. package/dist/logger-CN4xpzMd.d.ts.map +1 -0
  160. package/dist/logger-D3Ej3DII.js.map +1 -1
  161. package/dist/logger-thMPLpOG.cjs.map +1 -1
  162. package/dist/logger.d.cts +1 -1
  163. package/dist/logger.d.ts +1 -1
  164. package/dist/messaging-adapters.cjs.map +1 -1
  165. package/dist/messaging-adapters.d.cts +0 -1
  166. package/dist/messaging-adapters.d.cts.map +1 -1
  167. package/dist/messaging-adapters.d.ts +0 -1
  168. package/dist/messaging-adapters.d.ts.map +1 -1
  169. package/dist/messaging-adapters.js.map +1 -1
  170. package/dist/messaging-testing.d.cts +0 -1
  171. package/dist/messaging-testing.d.cts.map +1 -1
  172. package/dist/messaging-testing.d.ts +0 -1
  173. package/dist/messaging-testing.d.ts.map +1 -1
  174. package/dist/messaging.cjs +27 -10
  175. package/dist/messaging.cjs.map +1 -1
  176. package/dist/messaging.d.cts +6 -7
  177. package/dist/messaging.d.cts.map +1 -1
  178. package/dist/messaging.d.ts +6 -7
  179. package/dist/messaging.d.ts.map +1 -1
  180. package/dist/messaging.js +27 -10
  181. package/dist/messaging.js.map +1 -1
  182. package/dist/metric-helpers.d.cts +0 -1
  183. package/dist/metric-helpers.d.cts.map +1 -1
  184. package/dist/metric-helpers.d.ts +0 -1
  185. package/dist/metric-helpers.d.ts.map +1 -1
  186. package/dist/{metric-testing-DXdK3-Q3.d.ts → metric-testing-CWm6UhaR.d.ts} +2 -3
  187. package/dist/metric-testing-CWm6UhaR.d.ts.map +1 -0
  188. package/dist/{metric-testing-MxvzChDp.d.cts → metric-testing-CpvP3ZIa.d.cts} +2 -3
  189. package/dist/metric-testing-CpvP3ZIa.d.cts.map +1 -0
  190. package/dist/metric-testing.d.cts +1 -1
  191. package/dist/metric-testing.d.ts +1 -1
  192. package/dist/metric.d.cts +3 -4
  193. package/dist/metric.d.cts.map +1 -1
  194. package/dist/metric.d.ts +3 -4
  195. package/dist/metric.d.ts.map +1 -1
  196. package/dist/parse-error.d.cts.map +1 -1
  197. package/dist/parse-error.d.ts.map +1 -1
  198. package/dist/processors.cjs +1 -1
  199. package/dist/processors.d.cts +1 -1
  200. package/dist/processors.d.ts +1 -1
  201. package/dist/processors.js +1 -1
  202. package/dist/register.d.cts +1 -1
  203. package/dist/register.d.ts +1 -1
  204. package/dist/{sampling-DfYhDJij.d.cts → sampling-BR9fkSwx.d.cts} +127 -10
  205. package/dist/sampling-BR9fkSwx.d.cts.map +1 -0
  206. package/dist/{sampling-CR0Va1VB.d.ts → sampling-CP3e7-Yr.d.ts} +127 -10
  207. package/dist/sampling-CP3e7-Yr.d.ts.map +1 -0
  208. package/dist/sampling.cjs +160 -12
  209. package/dist/sampling.cjs.map +1 -1
  210. package/dist/sampling.d.cts +2 -2
  211. package/dist/sampling.d.ts +2 -2
  212. package/dist/sampling.js +157 -13
  213. package/dist/sampling.js.map +1 -1
  214. package/dist/security-schema.d.cts +9 -4
  215. package/dist/security-schema.d.cts.map +1 -1
  216. package/dist/security-schema.d.ts +9 -4
  217. package/dist/security-schema.d.ts.map +1 -1
  218. package/dist/semantic-conventions-FhSxv-bK.d.cts.map +1 -1
  219. package/dist/semantic-conventions-FhSxv-bK.d.ts.map +1 -1
  220. package/dist/semantic-conventions.cjs.map +1 -1
  221. package/dist/semantic-conventions.js.map +1 -1
  222. package/dist/semantic-helpers.cjs +85 -304
  223. package/dist/semantic-helpers.cjs.map +1 -1
  224. package/dist/semantic-helpers.d.cts +21 -12
  225. package/dist/semantic-helpers.d.cts.map +1 -1
  226. package/dist/semantic-helpers.d.ts +21 -12
  227. package/dist/semantic-helpers.d.ts.map +1 -1
  228. package/dist/semantic-helpers.js +85 -304
  229. package/dist/semantic-helpers.js.map +1 -1
  230. package/dist/slo.cjs +225 -0
  231. package/dist/slo.cjs.map +1 -0
  232. package/dist/slo.d.cts +97 -0
  233. package/dist/slo.d.cts.map +1 -0
  234. package/dist/slo.d.ts +97 -0
  235. package/dist/slo.d.ts.map +1 -0
  236. package/dist/slo.js +223 -0
  237. package/dist/slo.js.map +1 -0
  238. package/dist/{span-name-normalizer-8ZOGJuwv.d.ts → span-name-normalizer-CN1-8C76.d.cts} +1 -2
  239. package/dist/span-name-normalizer-CN1-8C76.d.cts.map +1 -0
  240. package/dist/{span-name-normalizer-8ZOGJuwv.d.cts → span-name-normalizer-CN1-8C76.d.ts} +1 -2
  241. package/dist/span-name-normalizer-CN1-8C76.d.ts.map +1 -0
  242. package/dist/span-name-normalizer.cjs.map +1 -1
  243. package/dist/span-name-normalizer.d.cts +1 -1
  244. package/dist/span-name-normalizer.d.ts +1 -1
  245. package/dist/span-name-normalizer.js.map +1 -1
  246. package/dist/tail-sampling-processor.d.cts +0 -1
  247. package/dist/tail-sampling-processor.d.cts.map +1 -1
  248. package/dist/tail-sampling-processor.d.ts +0 -1
  249. package/dist/tail-sampling-processor.d.ts.map +1 -1
  250. package/dist/test-span-collector.cjs.map +1 -1
  251. package/dist/test-span-collector.d.cts +0 -1
  252. package/dist/test-span-collector.d.cts.map +1 -1
  253. package/dist/test-span-collector.d.ts +0 -1
  254. package/dist/test-span-collector.d.ts.map +1 -1
  255. package/dist/test-span-collector.js.map +1 -1
  256. package/dist/testing.cjs +76 -11
  257. package/dist/testing.cjs.map +1 -1
  258. package/dist/testing.d.cts +30 -5
  259. package/dist/testing.d.cts.map +1 -1
  260. package/dist/testing.d.ts +30 -5
  261. package/dist/testing.d.ts.map +1 -1
  262. package/dist/testing.js +73 -12
  263. package/dist/testing.js.map +1 -1
  264. package/dist/{trace-context-Cijqoi6e.d.ts → trace-context-IuyoPyNq.d.cts} +9 -9
  265. package/dist/trace-context-IuyoPyNq.d.cts.map +1 -0
  266. package/dist/{trace-context-Cijqoi6e.d.cts → trace-context-IuyoPyNq.d.ts} +9 -9
  267. package/dist/trace-context-IuyoPyNq.d.ts.map +1 -0
  268. package/dist/trace-factory-validation-BjVAinz6.js +13 -0
  269. package/dist/trace-factory-validation-BjVAinz6.js.map +1 -0
  270. package/dist/trace-factory-validation-Dc8epn87.cjs +19 -0
  271. package/dist/trace-factory-validation-Dc8epn87.cjs.map +1 -0
  272. package/dist/trace-helpers.cjs +7 -7
  273. package/dist/trace-helpers.cjs.map +1 -1
  274. package/dist/trace-helpers.d.cts +6 -7
  275. package/dist/trace-helpers.d.cts.map +1 -1
  276. package/dist/trace-helpers.d.ts +6 -7
  277. package/dist/trace-helpers.d.ts.map +1 -1
  278. package/dist/trace-helpers.js +7 -7
  279. package/dist/trace-helpers.js.map +1 -1
  280. package/dist/tracer-provider.d.cts +0 -1
  281. package/dist/tracer-provider.d.cts.map +1 -1
  282. package/dist/tracer-provider.d.ts +0 -1
  283. package/dist/tracer-provider.d.ts.map +1 -1
  284. package/dist/{track-D7O7oIZa.js → track-BBcxEJEC.js} +7 -12
  285. package/dist/track-BBcxEJEC.js.map +1 -0
  286. package/dist/{track-eovhCJ-I.cjs → track-BI6dmZ2X.cjs} +6 -17
  287. package/dist/track-BI6dmZ2X.cjs.map +1 -0
  288. package/dist/validate.cjs.map +1 -1
  289. package/dist/validate.d.cts +1 -2
  290. package/dist/validate.d.cts.map +1 -1
  291. package/dist/validate.d.ts +1 -2
  292. package/dist/validate.d.ts.map +1 -1
  293. package/dist/validate.js.map +1 -1
  294. package/dist/validation-attributes.d.cts +17 -8
  295. package/dist/validation-attributes.d.cts.map +1 -1
  296. package/dist/validation-attributes.d.ts +17 -8
  297. package/dist/validation-attributes.d.ts.map +1 -1
  298. package/dist/webhook.cjs +5 -4
  299. package/dist/webhook.cjs.map +1 -1
  300. package/dist/webhook.d.cts +4 -4
  301. package/dist/webhook.d.cts.map +1 -1
  302. package/dist/webhook.d.ts +4 -4
  303. package/dist/webhook.d.ts.map +1 -1
  304. package/dist/webhook.js +5 -4
  305. package/dist/webhook.js.map +1 -1
  306. package/dist/workflow-distributed.cjs +5 -5
  307. package/dist/workflow-distributed.cjs.map +1 -1
  308. package/dist/workflow-distributed.d.cts +25 -15
  309. package/dist/workflow-distributed.d.cts.map +1 -1
  310. package/dist/workflow-distributed.d.ts +25 -15
  311. package/dist/workflow-distributed.d.ts.map +1 -1
  312. package/dist/workflow-distributed.js +5 -5
  313. package/dist/workflow-distributed.js.map +1 -1
  314. package/dist/workflow.cjs +24 -30
  315. package/dist/workflow.cjs.map +1 -1
  316. package/dist/workflow.d.cts +11 -6
  317. package/dist/workflow.d.cts.map +1 -1
  318. package/dist/workflow.d.ts +11 -6
  319. package/dist/workflow.d.ts.map +1 -1
  320. package/dist/workflow.js +24 -30
  321. package/dist/workflow.js.map +1 -1
  322. package/dist/yaml-config.d.cts +2 -3
  323. package/dist/yaml-config.d.cts.map +1 -1
  324. package/dist/yaml-config.d.ts +2 -3
  325. package/dist/yaml-config.d.ts.map +1 -1
  326. package/package.json +23 -14
  327. package/skills/analyze-traces/SKILL.md +42 -13
  328. package/skills/autotel-core/SKILL.md +24 -17
  329. package/skills/autotel-events/SKILL.md +1 -1
  330. package/skills/autotel-frameworks/SKILL.md +41 -6
  331. package/skills/autotel-instrumentation/SKILL.md +14 -9
  332. package/skills/autotel-request-logging/SKILL.md +19 -14
  333. package/skills/autotel-structured-errors/SKILL.md +2 -2
  334. package/skills/build-audit-trails/SKILL.md +11 -11
  335. package/skills/build-audit-trails/references/audit-queries.md +3 -3
  336. package/skills/debug-missing-spans/SKILL.md +12 -12
  337. package/skills/migrate-to-autotel/SKILL.md +20 -20
  338. package/skills/review-otel-patterns/SKILL.md +44 -42
  339. package/skills/review-otel-patterns/references/code-review.md +4 -4
  340. package/skills/review-otel-patterns/references/processor-pipeline.md +3 -3
  341. package/skills/review-otel-patterns/references/structured-errors.md +1 -1
  342. package/skills/review-otel-patterns/references/wide-spans.md +2 -2
  343. package/skills/tune-sampling/SKILL.md +91 -16
  344. package/dist/attribute-redacting-processor-DtTS9xxh.d.cts.map +0 -1
  345. package/dist/attribute-redacting-processor-DtTS9xxh.d.ts.map +0 -1
  346. package/dist/canonical-log-line-processor-BcRuAdRk.d.ts.map +0 -1
  347. package/dist/canonical-log-line-processor-CcrDi6TO.cjs.map +0 -1
  348. package/dist/canonical-log-line-processor-FLezvFCB.js.map +0 -1
  349. package/dist/canonical-log-line-processor-xvjMgtpF.d.cts.map +0 -1
  350. package/dist/define-event-CreknLm3.d.ts.map +0 -1
  351. package/dist/define-event-HZRizPwz.d.cts.map +0 -1
  352. package/dist/event-BEm5IJ8U.js.map +0 -1
  353. package/dist/event-rzA1X5h3.cjs.map +0 -1
  354. package/dist/event-subscriber-D1XLkPzi.d.cts.map +0 -1
  355. package/dist/event-subscriber-D1XLkPzi.d.ts.map +0 -1
  356. package/dist/event-testing-BqRnI0z4.d.cts.map +0 -1
  357. package/dist/event-testing-CfFs3to2.d.ts.map +0 -1
  358. package/dist/filtering-span-processor-B8R8B7Uk.d.cts.map +0 -1
  359. package/dist/filtering-span-processor-B8R8B7Uk.d.ts.map +0 -1
  360. package/dist/functional-CwsPyc4l.cjs +0 -1298
  361. package/dist/functional-CwsPyc4l.cjs.map +0 -1
  362. package/dist/functional-dAYhXQPA.js +0 -1248
  363. package/dist/functional-dAYhXQPA.js.map +0 -1
  364. package/dist/index-Ck06vlW2.d.ts.map +0 -1
  365. package/dist/index-eKuioqT1.d.cts.map +0 -1
  366. package/dist/init-B7u-DjxM.d.ts.map +0 -1
  367. package/dist/init-BvBB45-T.js.map +0 -1
  368. package/dist/init-BxJEJ9Tn.cjs.map +0 -1
  369. package/dist/init-DSrRmVnz.d.cts.map +0 -1
  370. package/dist/logger-BauSUhUv.d.cts.map +0 -1
  371. package/dist/logger-BauSUhUv.d.ts.map +0 -1
  372. package/dist/metric-testing-DXdK3-Q3.d.ts.map +0 -1
  373. package/dist/metric-testing-MxvzChDp.d.cts.map +0 -1
  374. package/dist/sampling-CR0Va1VB.d.ts.map +0 -1
  375. package/dist/sampling-DfYhDJij.d.cts.map +0 -1
  376. package/dist/span-name-normalizer-8ZOGJuwv.d.cts.map +0 -1
  377. package/dist/span-name-normalizer-8ZOGJuwv.d.ts.map +0 -1
  378. package/dist/trace-context-Cijqoi6e.d.cts.map +0 -1
  379. package/dist/trace-context-Cijqoi6e.d.ts.map +0 -1
  380. package/dist/track-D7O7oIZa.js.map +0 -1
  381. package/dist/track-eovhCJ-I.cjs.map +0 -1
package/dist/slo.js ADDED
@@ -0,0 +1,223 @@
1
+ import { getConfig } from "./config.js";
2
+ import { trace } from "@opentelemetry/api";
3
+
4
+ //#region src/slo.ts
5
+ /**
6
+ * Service level objective tracking and burn-rate evaluation.
7
+ *
8
+ * The tracker keeps a bounded rolling window in memory, records low-cardinality
9
+ * OpenTelemetry metrics, and returns snapshots that callers can feed into alert
10
+ * evaluation. Production systems can persist the emitted metrics in their
11
+ * telemetry backend while tests use the same calculations in process.
12
+ */
13
+ function validateDefinition(definition) {
14
+ if (!definition.name.trim()) throw new Error("SLO name must not be empty");
15
+ if (!Number.isFinite(definition.target) || definition.target <= 0 || definition.target >= 1) throw new Error("SLO target must be greater than 0 and less than 1");
16
+ if (!Number.isFinite(definition.windowMs) || definition.windowMs <= 0) throw new Error("SLO windowMs must be greater than 0");
17
+ }
18
+ function validateThreshold(name, threshold) {
19
+ if (!Number.isFinite(threshold) || threshold <= 0) throw new Error(`${name} must be greater than 0`);
20
+ }
21
+ function validateForecastOptions(options) {
22
+ if (!Number.isFinite(options.baselineMs) || options.baselineMs <= 0) throw new Error("baselineMs must be greater than 0");
23
+ if (!Number.isFinite(options.lookaheadMs) || options.lookaheadMs <= 0) throw new Error("lookaheadMs must be greater than 0");
24
+ if (options.lookaheadMs > options.baselineMs * 4) throw new Error("lookaheadMs must not exceed 4 times baselineMs");
25
+ if (options.expectedEventsInLookahead !== void 0 && (!Number.isFinite(options.expectedEventsInLookahead) || options.expectedEventsInLookahead < 0)) throw new Error("expectedEventsInLookahead must not be negative");
26
+ }
27
+ /**
28
+ * Track good and bad events for one rolling-window service level objective.
29
+ */
30
+ function createSloTracker(definition, options = {}) {
31
+ validateDefinition(definition);
32
+ const stableDefinition = Object.freeze({ ...definition });
33
+ const now = options.now ?? Date.now;
34
+ const observations = [];
35
+ let firstLiveIndex = 0;
36
+ let outcomeCounter;
37
+ let burnRateHistogram;
38
+ if (options.recordMetrics !== false) {
39
+ const meter = getConfig().meter;
40
+ outcomeCounter = meter.createCounter("autotel.slo.outcomes", {
41
+ description: "Good and bad events recorded against a service level objective",
42
+ unit: "1"
43
+ });
44
+ burnRateHistogram = meter.createHistogram("autotel.slo.burn_rate", {
45
+ description: "Error-budget burn rate for a service level objective",
46
+ unit: "1"
47
+ });
48
+ }
49
+ const prune = (at) => {
50
+ const cutoff = at - stableDefinition.windowMs;
51
+ while (firstLiveIndex < observations.length && observations[firstLiveIndex].timestamp < cutoff) firstLiveIndex += 1;
52
+ if (firstLiveIndex > 1024 && firstLiveIndex * 2 >= observations.length) {
53
+ observations.splice(0, firstLiveIndex);
54
+ firstLiveIndex = 0;
55
+ }
56
+ };
57
+ const calculate = (at) => {
58
+ prune(at);
59
+ let good = 0;
60
+ for (let index = firstLiveIndex; index < observations.length; index += 1) if (observations[index].outcome === "good") good += 1;
61
+ const total = observations.length - firstLiveIndex;
62
+ const bad = total - good;
63
+ const sli = total === 0 ? void 0 : good / total;
64
+ const errorBudgetFraction = 1 - stableDefinition.target;
65
+ const burnRate = (total === 0 ? 0 : bad / total) / errorBudgetFraction;
66
+ const budgetConsumed = burnRate;
67
+ return {
68
+ ...stableDefinition,
69
+ observedAt: at,
70
+ total,
71
+ good,
72
+ bad,
73
+ sli,
74
+ errorBudgetFraction,
75
+ budgetConsumed,
76
+ budgetRemaining: 1 - budgetConsumed,
77
+ burnRate,
78
+ meetsTarget: sli === void 0 || sli >= stableDefinition.target
79
+ };
80
+ };
81
+ return {
82
+ definition: stableDefinition,
83
+ record(outcome, attributes = {}) {
84
+ if (outcome !== "good" && outcome !== "bad") throw new Error("SLO outcome must be \"good\" or \"bad\"");
85
+ const timestamp = now();
86
+ observations.push({
87
+ outcome,
88
+ timestamp
89
+ });
90
+ const current = calculate(timestamp);
91
+ const metricAttributes = {
92
+ ...attributes,
93
+ "slo.name": stableDefinition.name,
94
+ "slo.outcome": outcome
95
+ };
96
+ outcomeCounter?.add(1, metricAttributes);
97
+ burnRateHistogram?.record(current.burnRate, { "slo.name": stableDefinition.name });
98
+ trace.getActiveSpan()?.setAttributes({
99
+ "slo.name": stableDefinition.name,
100
+ "slo.target": stableDefinition.target,
101
+ "slo.outcome": outcome,
102
+ "slo.burn_rate": current.burnRate
103
+ });
104
+ return current;
105
+ },
106
+ snapshot(at = now()) {
107
+ return calculate(at);
108
+ },
109
+ forecast(forecastOptions) {
110
+ validateForecastOptions(forecastOptions);
111
+ if (forecastOptions.baselineMs > stableDefinition.windowMs) throw new Error("baselineMs must not exceed the SLO windowMs");
112
+ const observedAt = now();
113
+ prune(observedAt);
114
+ const baselineCutoff = observedAt - forecastOptions.baselineMs;
115
+ const retainedCutoff = observedAt + forecastOptions.lookaheadMs - stableDefinition.windowMs;
116
+ let baselineTotal = 0;
117
+ let baselineBad = 0;
118
+ let retainedTotal = 0;
119
+ let retainedBad = 0;
120
+ for (let index = firstLiveIndex; index < observations.length; index += 1) {
121
+ const observation = observations[index];
122
+ if (observation.timestamp >= baselineCutoff) {
123
+ baselineTotal += 1;
124
+ if (observation.outcome === "bad") baselineBad += 1;
125
+ }
126
+ if (observation.timestamp >= retainedCutoff) {
127
+ retainedTotal += 1;
128
+ if (observation.outcome === "bad") retainedBad += 1;
129
+ }
130
+ }
131
+ if (baselineTotal === 0) return {
132
+ name: stableDefinition.name,
133
+ target: stableDefinition.target,
134
+ observedAt,
135
+ baselineMs: forecastOptions.baselineMs,
136
+ lookaheadMs: forecastOptions.lookaheadMs,
137
+ baselineTotal: 0,
138
+ baselineBad: 0,
139
+ baselineFailureRate: 0,
140
+ retainedTotal,
141
+ retainedBad,
142
+ projectedTotal: retainedTotal,
143
+ projectedBad: retainedBad,
144
+ projectedSli: retainedTotal === 0 ? void 0 : (retainedTotal - retainedBad) / retainedTotal,
145
+ alerting: false,
146
+ reason: "no-baseline-traffic"
147
+ };
148
+ const baselineFailureRate = baselineBad / baselineTotal;
149
+ const expectedEventsInLookahead = forecastOptions.expectedEventsInLookahead ?? baselineTotal * forecastOptions.lookaheadMs / forecastOptions.baselineMs;
150
+ const projectedFailures = baselineFailureRate * expectedEventsInLookahead;
151
+ const projectedTotal = retainedTotal + expectedEventsInLookahead;
152
+ const projectedBad = retainedBad + projectedFailures;
153
+ const projectedSli = projectedTotal === 0 ? void 0 : (projectedTotal - projectedBad) / projectedTotal;
154
+ const allowedFailureRate = 1 - stableDefinition.target;
155
+ const remainingFailures = allowedFailureRate * retainedTotal - retainedBad;
156
+ const netFailureRate = baselineFailureRate - allowedFailureRate;
157
+ const eventRate = expectedEventsInLookahead / forecastOptions.lookaheadMs;
158
+ const timeToExhaustionMs = remainingFailures <= 0 ? 0 : netFailureRate > 0 && eventRate > 0 ? remainingFailures / (netFailureRate * eventRate) : void 0;
159
+ const alerting = projectedSli !== void 0 && projectedSli < stableDefinition.target;
160
+ return {
161
+ name: stableDefinition.name,
162
+ target: stableDefinition.target,
163
+ observedAt,
164
+ baselineMs: forecastOptions.baselineMs,
165
+ lookaheadMs: forecastOptions.lookaheadMs,
166
+ baselineTotal,
167
+ baselineBad,
168
+ baselineFailureRate,
169
+ retainedTotal,
170
+ retainedBad,
171
+ projectedTotal,
172
+ projectedBad,
173
+ projectedSli,
174
+ timeToExhaustionMs,
175
+ alerting,
176
+ reason: alerting ? "projected-budget-exhaustion" : "within-budget"
177
+ };
178
+ },
179
+ reset() {
180
+ observations.length = 0;
181
+ firstLiveIndex = 0;
182
+ }
183
+ };
184
+ }
185
+ /**
186
+ * Evaluate a dual-window burn-rate alert.
187
+ *
188
+ * Both windows must exceed their threshold. The short window catches a sharp
189
+ * regression while the long window rejects brief spikes.
190
+ */
191
+ function evaluateBurnRateAlert(options) {
192
+ validateThreshold("shortThreshold", options.shortThreshold);
193
+ validateThreshold("longThreshold", options.longThreshold);
194
+ if (options.shortWindow.name !== options.longWindow.name || options.shortWindow.target !== options.longWindow.target) throw new Error("Burn-rate windows must describe the same SLO name and target");
195
+ if (options.shortWindow.total === 0 || options.longWindow.total === 0) return {
196
+ alerting: false,
197
+ reason: "no-traffic",
198
+ shortBurnRate: options.shortWindow.burnRate,
199
+ longBurnRate: options.longWindow.burnRate
200
+ };
201
+ if (options.shortWindow.burnRate < options.shortThreshold) return {
202
+ alerting: false,
203
+ reason: "short-window-below-threshold",
204
+ shortBurnRate: options.shortWindow.burnRate,
205
+ longBurnRate: options.longWindow.burnRate
206
+ };
207
+ if (options.longWindow.burnRate < options.longThreshold) return {
208
+ alerting: false,
209
+ reason: "long-window-below-threshold",
210
+ shortBurnRate: options.shortWindow.burnRate,
211
+ longBurnRate: options.longWindow.burnRate
212
+ };
213
+ return {
214
+ alerting: true,
215
+ reason: "burn-rate-thresholds-exceeded",
216
+ shortBurnRate: options.shortWindow.burnRate,
217
+ longBurnRate: options.longWindow.burnRate
218
+ };
219
+ }
220
+
221
+ //#endregion
222
+ export { createSloTracker, evaluateBurnRateAlert };
223
+ //# sourceMappingURL=slo.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slo.js","names":["otelTrace"],"sources":["../src/slo.ts"],"sourcesContent":["/**\n * Service level objective tracking and burn-rate evaluation.\n *\n * The tracker keeps a bounded rolling window in memory, records low-cardinality\n * OpenTelemetry metrics, and returns snapshots that callers can feed into alert\n * evaluation. Production systems can persist the emitted metrics in their\n * telemetry backend while tests use the same calculations in process.\n */\n\nimport {\n trace as otelTrace,\n type Attributes,\n type Counter,\n type Histogram,\n} from '@opentelemetry/api';\nimport { getConfig } from './config';\n\nexport type SloOutcome = 'good' | 'bad';\n\nexport interface SloDefinition {\n /** Stable identifier used in metric attributes and alerts. */\n name: string;\n /** Required success ratio, expressed as a value between 0 and 1. */\n target: number;\n /** Rolling observation window in milliseconds. */\n windowMs: number;\n}\n\nexport interface SloTrackerOptions {\n /** Clock override for deterministic tests. */\n now?: () => number;\n /** Disable metric recording when calculations run outside an initialized SDK. */\n recordMetrics?: boolean;\n}\n\nexport interface SloSnapshot extends SloDefinition {\n observedAt: number;\n total: number;\n good: number;\n bad: number;\n /** Observed good-event ratio. Undefined until the tracker sees an event. */\n sli?: number;\n /** Fraction of events the objective permits to fail. */\n errorBudgetFraction: number;\n /** Fraction of the available error budget consumed in this window. */\n budgetConsumed: number;\n /** Remaining budget fraction. Negative values show overspend. */\n budgetRemaining: number;\n /** Observed failure ratio divided by the permitted failure ratio. */\n burnRate: number;\n meetsTarget: boolean;\n}\n\nexport interface SloTracker {\n readonly definition: Readonly<SloDefinition>;\n record(outcome: SloOutcome, attributes?: Attributes): SloSnapshot;\n snapshot(at?: number): SloSnapshot;\n forecast(options: SloForecastOptions): SloForecast;\n reset(): void;\n}\n\nexport interface SloForecastOptions {\n /** Recent observation period used to estimate the future failure rate. */\n baselineMs: number;\n /** Future period covered by the forecast. Must not exceed 4x the baseline. */\n lookaheadMs: number;\n /**\n * Expected eligible events during the lookahead. Defaults to the event rate\n * observed during the baseline.\n */\n expectedEventsInLookahead?: number;\n}\n\nexport interface SloForecast {\n name: string;\n target: number;\n observedAt: number;\n baselineMs: number;\n lookaheadMs: number;\n baselineTotal: number;\n baselineBad: number;\n baselineFailureRate: number;\n retainedTotal: number;\n retainedBad: number;\n projectedTotal: number;\n projectedBad: number;\n projectedSli?: number;\n /** Milliseconds until the retained and projected error budget is exhausted. */\n timeToExhaustionMs?: number;\n alerting: boolean;\n reason:\n 'no-baseline-traffic' | 'within-budget' | 'projected-budget-exhaustion';\n}\n\nexport interface BurnRateAlertOptions {\n shortWindow: SloSnapshot;\n longWindow: SloSnapshot;\n shortThreshold: number;\n longThreshold: number;\n}\n\nexport interface BurnRateAlertDecision {\n alerting: boolean;\n reason:\n | 'burn-rate-thresholds-exceeded'\n | 'short-window-below-threshold'\n | 'long-window-below-threshold'\n | 'no-traffic';\n shortBurnRate: number;\n longBurnRate: number;\n}\n\ninterface Observation {\n outcome: SloOutcome;\n timestamp: number;\n}\n\nfunction validateDefinition(definition: SloDefinition): void {\n if (!definition.name.trim()) {\n throw new Error('SLO name must not be empty');\n }\n if (\n !Number.isFinite(definition.target) ||\n definition.target <= 0 ||\n definition.target >= 1\n ) {\n throw new Error('SLO target must be greater than 0 and less than 1');\n }\n if (!Number.isFinite(definition.windowMs) || definition.windowMs <= 0) {\n throw new Error('SLO windowMs must be greater than 0');\n }\n}\n\nfunction validateThreshold(name: string, threshold: number): void {\n if (!Number.isFinite(threshold) || threshold <= 0) {\n throw new Error(`${name} must be greater than 0`);\n }\n}\n\nfunction validateForecastOptions(options: SloForecastOptions): void {\n if (!Number.isFinite(options.baselineMs) || options.baselineMs <= 0) {\n throw new Error('baselineMs must be greater than 0');\n }\n if (!Number.isFinite(options.lookaheadMs) || options.lookaheadMs <= 0) {\n throw new Error('lookaheadMs must be greater than 0');\n }\n if (options.lookaheadMs > options.baselineMs * 4) {\n throw new Error('lookaheadMs must not exceed 4 times baselineMs');\n }\n if (\n options.expectedEventsInLookahead !== undefined &&\n (!Number.isFinite(options.expectedEventsInLookahead) ||\n options.expectedEventsInLookahead < 0)\n ) {\n throw new Error('expectedEventsInLookahead must not be negative');\n }\n}\n\n/**\n * Track good and bad events for one rolling-window service level objective.\n */\nexport function createSloTracker(\n definition: SloDefinition,\n options: SloTrackerOptions = {},\n): SloTracker {\n validateDefinition(definition);\n\n const stableDefinition = Object.freeze({ ...definition });\n const now = options.now ?? Date.now;\n const observations: Observation[] = [];\n let firstLiveIndex = 0;\n\n let outcomeCounter: Counter | undefined;\n let burnRateHistogram: Histogram | undefined;\n\n if (options.recordMetrics !== false) {\n const meter = getConfig().meter;\n outcomeCounter = meter.createCounter('autotel.slo.outcomes', {\n description:\n 'Good and bad events recorded against a service level objective',\n unit: '1',\n });\n burnRateHistogram = meter.createHistogram('autotel.slo.burn_rate', {\n description: 'Error-budget burn rate for a service level objective',\n unit: '1',\n });\n }\n\n const prune = (at: number): void => {\n const cutoff = at - stableDefinition.windowMs;\n while (\n firstLiveIndex < observations.length &&\n observations[firstLiveIndex]!.timestamp < cutoff\n ) {\n firstLiveIndex += 1;\n }\n\n if (firstLiveIndex > 1024 && firstLiveIndex * 2 >= observations.length) {\n observations.splice(0, firstLiveIndex);\n firstLiveIndex = 0;\n }\n };\n\n const calculate = (at: number): SloSnapshot => {\n prune(at);\n\n let good = 0;\n for (let index = firstLiveIndex; index < observations.length; index += 1) {\n if (observations[index]!.outcome === 'good') good += 1;\n }\n\n const total = observations.length - firstLiveIndex;\n const bad = total - good;\n const sli = total === 0 ? undefined : good / total;\n const errorBudgetFraction = 1 - stableDefinition.target;\n const observedBadFraction = total === 0 ? 0 : bad / total;\n const burnRate = observedBadFraction / errorBudgetFraction;\n const budgetConsumed = burnRate;\n\n return {\n ...stableDefinition,\n observedAt: at,\n total,\n good,\n bad,\n sli,\n errorBudgetFraction,\n budgetConsumed,\n budgetRemaining: 1 - budgetConsumed,\n burnRate,\n meetsTarget: sli === undefined || sli >= stableDefinition.target,\n };\n };\n\n return {\n definition: stableDefinition,\n\n record(outcome: SloOutcome, attributes: Attributes = {}): SloSnapshot {\n if (outcome !== 'good' && outcome !== 'bad') {\n throw new Error('SLO outcome must be \"good\" or \"bad\"');\n }\n\n const timestamp = now();\n observations.push({ outcome, timestamp });\n const current = calculate(timestamp);\n const metricAttributes: Attributes = {\n ...attributes,\n 'slo.name': stableDefinition.name,\n 'slo.outcome': outcome,\n };\n\n outcomeCounter?.add(1, metricAttributes);\n burnRateHistogram?.record(current.burnRate, {\n 'slo.name': stableDefinition.name,\n });\n\n const activeSpan = otelTrace.getActiveSpan();\n activeSpan?.setAttributes({\n 'slo.name': stableDefinition.name,\n 'slo.target': stableDefinition.target,\n 'slo.outcome': outcome,\n 'slo.burn_rate': current.burnRate,\n });\n\n return current;\n },\n\n snapshot(at = now()): SloSnapshot {\n return calculate(at);\n },\n\n forecast(forecastOptions: SloForecastOptions): SloForecast {\n validateForecastOptions(forecastOptions);\n if (forecastOptions.baselineMs > stableDefinition.windowMs) {\n throw new Error('baselineMs must not exceed the SLO windowMs');\n }\n const observedAt = now();\n prune(observedAt);\n\n const baselineCutoff = observedAt - forecastOptions.baselineMs;\n const retainedCutoff =\n observedAt + forecastOptions.lookaheadMs - stableDefinition.windowMs;\n let baselineTotal = 0;\n let baselineBad = 0;\n let retainedTotal = 0;\n let retainedBad = 0;\n\n for (\n let index = firstLiveIndex;\n index < observations.length;\n index += 1\n ) {\n const observation = observations[index]!;\n if (observation.timestamp >= baselineCutoff) {\n baselineTotal += 1;\n if (observation.outcome === 'bad') baselineBad += 1;\n }\n if (observation.timestamp >= retainedCutoff) {\n retainedTotal += 1;\n if (observation.outcome === 'bad') retainedBad += 1;\n }\n }\n\n if (baselineTotal === 0) {\n return {\n name: stableDefinition.name,\n target: stableDefinition.target,\n observedAt,\n baselineMs: forecastOptions.baselineMs,\n lookaheadMs: forecastOptions.lookaheadMs,\n baselineTotal: 0,\n baselineBad: 0,\n baselineFailureRate: 0,\n retainedTotal,\n retainedBad,\n projectedTotal: retainedTotal,\n projectedBad: retainedBad,\n projectedSli:\n retainedTotal === 0\n ? undefined\n : (retainedTotal - retainedBad) / retainedTotal,\n alerting: false,\n reason: 'no-baseline-traffic',\n };\n }\n\n const baselineFailureRate = baselineBad / baselineTotal;\n const expectedEventsInLookahead =\n forecastOptions.expectedEventsInLookahead ??\n (baselineTotal * forecastOptions.lookaheadMs) /\n forecastOptions.baselineMs;\n const projectedFailures = baselineFailureRate * expectedEventsInLookahead;\n const projectedTotal = retainedTotal + expectedEventsInLookahead;\n const projectedBad = retainedBad + projectedFailures;\n const projectedSli =\n projectedTotal === 0\n ? undefined\n : (projectedTotal - projectedBad) / projectedTotal;\n const allowedFailureRate = 1 - stableDefinition.target;\n const remainingFailures =\n allowedFailureRate * retainedTotal - retainedBad;\n const netFailureRate = baselineFailureRate - allowedFailureRate;\n const eventRate = expectedEventsInLookahead / forecastOptions.lookaheadMs;\n const timeToExhaustionMs =\n remainingFailures <= 0\n ? 0\n : netFailureRate > 0 && eventRate > 0\n ? remainingFailures / (netFailureRate * eventRate)\n : undefined;\n const alerting =\n projectedSli !== undefined && projectedSli < stableDefinition.target;\n\n return {\n name: stableDefinition.name,\n target: stableDefinition.target,\n observedAt,\n baselineMs: forecastOptions.baselineMs,\n lookaheadMs: forecastOptions.lookaheadMs,\n baselineTotal,\n baselineBad,\n baselineFailureRate,\n retainedTotal,\n retainedBad,\n projectedTotal,\n projectedBad,\n projectedSli,\n timeToExhaustionMs,\n alerting,\n reason: alerting ? 'projected-budget-exhaustion' : 'within-budget',\n };\n },\n\n reset(): void {\n observations.length = 0;\n firstLiveIndex = 0;\n },\n };\n}\n\n/**\n * Evaluate a dual-window burn-rate alert.\n *\n * Both windows must exceed their threshold. The short window catches a sharp\n * regression while the long window rejects brief spikes.\n */\nexport function evaluateBurnRateAlert(\n options: BurnRateAlertOptions,\n): BurnRateAlertDecision {\n validateThreshold('shortThreshold', options.shortThreshold);\n validateThreshold('longThreshold', options.longThreshold);\n\n if (\n options.shortWindow.name !== options.longWindow.name ||\n options.shortWindow.target !== options.longWindow.target\n ) {\n throw new Error(\n 'Burn-rate windows must describe the same SLO name and target',\n );\n }\n\n if (options.shortWindow.total === 0 || options.longWindow.total === 0) {\n return {\n alerting: false,\n reason: 'no-traffic',\n shortBurnRate: options.shortWindow.burnRate,\n longBurnRate: options.longWindow.burnRate,\n };\n }\n\n if (options.shortWindow.burnRate < options.shortThreshold) {\n return {\n alerting: false,\n reason: 'short-window-below-threshold',\n shortBurnRate: options.shortWindow.burnRate,\n longBurnRate: options.longWindow.burnRate,\n };\n }\n\n if (options.longWindow.burnRate < options.longThreshold) {\n return {\n alerting: false,\n reason: 'long-window-below-threshold',\n shortBurnRate: options.shortWindow.burnRate,\n longBurnRate: options.longWindow.burnRate,\n };\n }\n\n return {\n alerting: true,\n reason: 'burn-rate-thresholds-exceeded',\n shortBurnRate: options.shortWindow.burnRate,\n longBurnRate: options.longWindow.burnRate,\n };\n}\n"],"mappings":";;;;;;;;;;;;AAqHA,SAAS,mBAAmB,YAAiC;CAC3D,IAAI,CAAC,WAAW,KAAK,KAAK,GACxB,MAAM,IAAI,MAAM,4BAA4B;CAE9C,IACE,CAAC,OAAO,SAAS,WAAW,MAAM,KAClC,WAAW,UAAU,KACrB,WAAW,UAAU,GAErB,MAAM,IAAI,MAAM,mDAAmD;CAErE,IAAI,CAAC,OAAO,SAAS,WAAW,QAAQ,KAAK,WAAW,YAAY,GAClE,MAAM,IAAI,MAAM,qCAAqC;AAEzD;AAEA,SAAS,kBAAkB,MAAc,WAAyB;CAChE,IAAI,CAAC,OAAO,SAAS,SAAS,KAAK,aAAa,GAC9C,MAAM,IAAI,MAAM,GAAG,KAAK,wBAAwB;AAEpD;AAEA,SAAS,wBAAwB,SAAmC;CAClE,IAAI,CAAC,OAAO,SAAS,QAAQ,UAAU,KAAK,QAAQ,cAAc,GAChE,MAAM,IAAI,MAAM,mCAAmC;CAErD,IAAI,CAAC,OAAO,SAAS,QAAQ,WAAW,KAAK,QAAQ,eAAe,GAClE,MAAM,IAAI,MAAM,oCAAoC;CAEtD,IAAI,QAAQ,cAAc,QAAQ,aAAa,GAC7C,MAAM,IAAI,MAAM,gDAAgD;CAElE,IACE,QAAQ,8BAA8B,WACrC,CAAC,OAAO,SAAS,QAAQ,yBAAyB,KACjD,QAAQ,4BAA4B,IAEtC,MAAM,IAAI,MAAM,gDAAgD;AAEpE;;;;AAKA,SAAgB,iBACd,YACA,UAA6B,CAAC,GAClB;CACZ,mBAAmB,UAAU;CAE7B,MAAM,mBAAmB,OAAO,OAAO,EAAE,GAAG,WAAW,CAAC;CACxD,MAAM,MAAM,QAAQ,OAAO,KAAK;CAChC,MAAM,eAA8B,CAAC;CACrC,IAAI,iBAAiB;CAErB,IAAI;CACJ,IAAI;CAEJ,IAAI,QAAQ,kBAAkB,OAAO;EACnC,MAAM,QAAQ,UAAU,CAAC,CAAC;EAC1B,iBAAiB,MAAM,cAAc,wBAAwB;GAC3D,aACE;GACF,MAAM;EACR,CAAC;EACD,oBAAoB,MAAM,gBAAgB,yBAAyB;GACjE,aAAa;GACb,MAAM;EACR,CAAC;CACH;CAEA,MAAM,SAAS,OAAqB;EAClC,MAAM,SAAS,KAAK,iBAAiB;EACrC,OACE,iBAAiB,aAAa,UAC9B,aAAa,eAAe,CAAE,YAAY,QAE1C,kBAAkB;EAGpB,IAAI,iBAAiB,QAAQ,iBAAiB,KAAK,aAAa,QAAQ;GACtE,aAAa,OAAO,GAAG,cAAc;GACrC,iBAAiB;EACnB;CACF;CAEA,MAAM,aAAa,OAA4B;EAC7C,MAAM,EAAE;EAER,IAAI,OAAO;EACX,KAAK,IAAI,QAAQ,gBAAgB,QAAQ,aAAa,QAAQ,SAAS,GACrE,IAAI,aAAa,MAAM,CAAE,YAAY,QAAQ,QAAQ;EAGvD,MAAM,QAAQ,aAAa,SAAS;EACpC,MAAM,MAAM,QAAQ;EACpB,MAAM,MAAM,UAAU,IAAI,SAAY,OAAO;EAC7C,MAAM,sBAAsB,IAAI,iBAAiB;EAEjD,MAAM,YADsB,UAAU,IAAI,IAAI,MAAM,SACb;EACvC,MAAM,iBAAiB;EAEvB,OAAO;GACL,GAAG;GACH,YAAY;GACZ;GACA;GACA;GACA;GACA;GACA;GACA,iBAAiB,IAAI;GACrB;GACA,aAAa,QAAQ,UAAa,OAAO,iBAAiB;EAC5D;CACF;CAEA,OAAO;EACL,YAAY;EAEZ,OAAO,SAAqB,aAAyB,CAAC,GAAgB;GACpE,IAAI,YAAY,UAAU,YAAY,OACpC,MAAM,IAAI,MAAM,yCAAqC;GAGvD,MAAM,YAAY,IAAI;GACtB,aAAa,KAAK;IAAE;IAAS;GAAU,CAAC;GACxC,MAAM,UAAU,UAAU,SAAS;GACnC,MAAM,mBAA+B;IACnC,GAAG;IACH,YAAY,iBAAiB;IAC7B,eAAe;GACjB;GAEA,gBAAgB,IAAI,GAAG,gBAAgB;GACvC,mBAAmB,OAAO,QAAQ,UAAU,EAC1C,YAAY,iBAAiB,KAC/B,CAAC;GAGD,AADmBA,MAAU,cACpB,CAAC,EAAE,cAAc;IACxB,YAAY,iBAAiB;IAC7B,cAAc,iBAAiB;IAC/B,eAAe;IACf,iBAAiB,QAAQ;GAC3B,CAAC;GAED,OAAO;EACT;EAEA,SAAS,KAAK,IAAI,GAAgB;GAChC,OAAO,UAAU,EAAE;EACrB;EAEA,SAAS,iBAAkD;GACzD,wBAAwB,eAAe;GACvC,IAAI,gBAAgB,aAAa,iBAAiB,UAChD,MAAM,IAAI,MAAM,6CAA6C;GAE/D,MAAM,aAAa,IAAI;GACvB,MAAM,UAAU;GAEhB,MAAM,iBAAiB,aAAa,gBAAgB;GACpD,MAAM,iBACJ,aAAa,gBAAgB,cAAc,iBAAiB;GAC9D,IAAI,gBAAgB;GACpB,IAAI,cAAc;GAClB,IAAI,gBAAgB;GACpB,IAAI,cAAc;GAElB,KACE,IAAI,QAAQ,gBACZ,QAAQ,aAAa,QACrB,SAAS,GACT;IACA,MAAM,cAAc,aAAa;IACjC,IAAI,YAAY,aAAa,gBAAgB;KAC3C,iBAAiB;KACjB,IAAI,YAAY,YAAY,OAAO,eAAe;IACpD;IACA,IAAI,YAAY,aAAa,gBAAgB;KAC3C,iBAAiB;KACjB,IAAI,YAAY,YAAY,OAAO,eAAe;IACpD;GACF;GAEA,IAAI,kBAAkB,GACpB,OAAO;IACL,MAAM,iBAAiB;IACvB,QAAQ,iBAAiB;IACzB;IACA,YAAY,gBAAgB;IAC5B,aAAa,gBAAgB;IAC7B,eAAe;IACf,aAAa;IACb,qBAAqB;IACrB;IACA;IACA,gBAAgB;IAChB,cAAc;IACd,cACE,kBAAkB,IACd,UACC,gBAAgB,eAAe;IACtC,UAAU;IACV,QAAQ;GACV;GAGF,MAAM,sBAAsB,cAAc;GAC1C,MAAM,4BACJ,gBAAgB,6BACf,gBAAgB,gBAAgB,cAC/B,gBAAgB;GACpB,MAAM,oBAAoB,sBAAsB;GAChD,MAAM,iBAAiB,gBAAgB;GACvC,MAAM,eAAe,cAAc;GACnC,MAAM,eACJ,mBAAmB,IACf,UACC,iBAAiB,gBAAgB;GACxC,MAAM,qBAAqB,IAAI,iBAAiB;GAChD,MAAM,oBACJ,qBAAqB,gBAAgB;GACvC,MAAM,iBAAiB,sBAAsB;GAC7C,MAAM,YAAY,4BAA4B,gBAAgB;GAC9D,MAAM,qBACJ,qBAAqB,IACjB,IACA,iBAAiB,KAAK,YAAY,IAChC,qBAAqB,iBAAiB,aACtC;GACR,MAAM,WACJ,iBAAiB,UAAa,eAAe,iBAAiB;GAEhE,OAAO;IACL,MAAM,iBAAiB;IACvB,QAAQ,iBAAiB;IACzB;IACA,YAAY,gBAAgB;IAC5B,aAAa,gBAAgB;IAC7B;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA,QAAQ,WAAW,gCAAgC;GACrD;EACF;EAEA,QAAc;GACZ,aAAa,SAAS;GACtB,iBAAiB;EACnB;CACF;AACF;;;;;;;AAQA,SAAgB,sBACd,SACuB;CACvB,kBAAkB,kBAAkB,QAAQ,cAAc;CAC1D,kBAAkB,iBAAiB,QAAQ,aAAa;CAExD,IACE,QAAQ,YAAY,SAAS,QAAQ,WAAW,QAChD,QAAQ,YAAY,WAAW,QAAQ,WAAW,QAElD,MAAM,IAAI,MACR,8DACF;CAGF,IAAI,QAAQ,YAAY,UAAU,KAAK,QAAQ,WAAW,UAAU,GAClE,OAAO;EACL,UAAU;EACV,QAAQ;EACR,eAAe,QAAQ,YAAY;EACnC,cAAc,QAAQ,WAAW;CACnC;CAGF,IAAI,QAAQ,YAAY,WAAW,QAAQ,gBACzC,OAAO;EACL,UAAU;EACV,QAAQ;EACR,eAAe,QAAQ,YAAY;EACnC,cAAc,QAAQ,WAAW;CACnC;CAGF,IAAI,QAAQ,WAAW,WAAW,QAAQ,eACxC,OAAO;EACL,UAAU;EACV,QAAQ;EACR,eAAe,QAAQ,YAAY;EACnC,cAAc,QAAQ,WAAW;CACnC;CAGF,OAAO;EACL,UAAU;EACV,QAAQ;EACR,eAAe,QAAQ,YAAY;EACnC,cAAc,QAAQ,WAAW;CACnC;AACF"}
@@ -1,6 +1,5 @@
1
1
  import { ReadableSpan, Span, SpanProcessor } from "@opentelemetry/sdk-trace-base";
2
2
  import { Context } from "@opentelemetry/api";
3
-
4
3
  //#region src/span-name-normalizer.d.ts
5
4
  /**
6
5
  * Function to normalize a span name
@@ -67,4 +66,4 @@ declare class SpanNameNormalizingProcessor implements SpanProcessor {
67
66
  }
68
67
  //#endregion
69
68
  export { SpanNameNormalizerPreset as a, SpanNameNormalizerFn as i, NORMALIZER_PRESETS as n, SpanNameNormalizingProcessor as o, SpanNameNormalizerConfig as r, SpanNameNormalizingProcessorOptions as s, NORMALIZER_PATTERNS as t };
70
- //# sourceMappingURL=span-name-normalizer-8ZOGJuwv.d.ts.map
69
+ //# sourceMappingURL=span-name-normalizer-CN1-8C76.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"span-name-normalizer-CN1-8C76.d.cts","names":[],"sources":["../src/span-name-normalizer.ts"],"mappings":";;;;;;;;KAsCY,wBAAwB;;;;KAKxB;;;;KAKA,2BACV,uBAAuB;UAER;;;;EAIf,YAAY;;;;;cAMR;;;;;;;;;;;;;cA6BA,oBAAoB,OACxB,0BACA;;;;;;;;;;;;cAsEW,wCAAwC;mBAClC;mBACA;EAGf,YAAA,kBAAkB,eAClB,SAAS;;;;EASX,QAAQ,MAAM,MAAM,eAAe;;;;EAkBnC,MAAM,MAAM;EAIZ,cAAc;EAId,YAAY"}
@@ -1,6 +1,5 @@
1
1
  import { ReadableSpan, Span, SpanProcessor } from "@opentelemetry/sdk-trace-base";
2
2
  import { Context } from "@opentelemetry/api";
3
-
4
3
  //#region src/span-name-normalizer.d.ts
5
4
  /**
6
5
  * Function to normalize a span name
@@ -67,4 +66,4 @@ declare class SpanNameNormalizingProcessor implements SpanProcessor {
67
66
  }
68
67
  //#endregion
69
68
  export { SpanNameNormalizerPreset as a, SpanNameNormalizerFn as i, NORMALIZER_PRESETS as n, SpanNameNormalizingProcessor as o, SpanNameNormalizerConfig as r, SpanNameNormalizingProcessorOptions as s, NORMALIZER_PATTERNS as t };
70
- //# sourceMappingURL=span-name-normalizer-8ZOGJuwv.d.cts.map
69
+ //# sourceMappingURL=span-name-normalizer-CN1-8C76.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"span-name-normalizer-CN1-8C76.d.ts","names":[],"sources":["../src/span-name-normalizer.ts"],"mappings":";;;;;;;;KAsCY,wBAAwB;;;;KAKxB;;;;KAKA,2BACV,uBAAuB;UAER;;;;EAIf,YAAY;;;;;cAMR;;;;;;;;;;;;;cA6BA,oBAAoB,OACxB,0BACA;;;;;;;;;;;;cAsEW,wCAAwC;mBAClC;mBACA;EAGf,YAAA,kBAAkB,eAClB,SAAS;;;;EASX,QAAQ,MAAM,MAAM,eAAe;;;;EAkBnC,MAAM,MAAM;EAIZ,cAAc;EAId,YAAY"}
@@ -1 +1 @@
1
- {"version":3,"file":"span-name-normalizer.cjs","names":[],"sources":["../src/span-name-normalizer.ts"],"sourcesContent":["/**\n * Span Name Normalizer\n *\n * Normalizes span names to reduce cardinality from dynamic path segments.\n * This is critical for observability backends that charge by unique span names\n * or have cardinality limits.\n *\n * @example Basic usage with custom function\n * ```typescript\n * init({\n * service: 'my-app',\n * spanNameNormalizer: (name) => {\n * return name.replace(/\\/[0-9]+/g, '/:id');\n * }\n * })\n * ```\n *\n * @example Using built-in preset\n * ```typescript\n * init({\n * service: 'my-app',\n * spanNameNormalizer: 'rest-api'\n * })\n * ```\n */\n\nimport type {\n SpanProcessor,\n ReadableSpan,\n} from '@opentelemetry/sdk-trace-base';\nimport type { Context } from '@opentelemetry/api';\nimport type { Span } from '@opentelemetry/sdk-trace-base';\n\n/**\n * Function to normalize a span name\n * @param name - The original span name\n * @returns The normalized span name\n */\nexport type SpanNameNormalizerFn = (name: string) => string;\n\n/**\n * Built-in normalizer preset names\n */\nexport type SpanNameNormalizerPreset = 'rest-api' | 'graphql' | 'minimal';\n\n/**\n * Normalizer config - either a function or a preset name\n */\nexport type SpanNameNormalizerConfig =\n | SpanNameNormalizerFn\n | SpanNameNormalizerPreset;\n\nexport interface SpanNameNormalizingProcessorOptions {\n /**\n * Normalizer function or preset name\n */\n normalizer: SpanNameNormalizerConfig;\n}\n\n/**\n * Built-in normalizer patterns\n */\nconst NORMALIZER_PATTERNS = {\n // Numeric IDs: /users/123 → /users/:id\n numericId: /\\/\\d+(?=\\/|$)/g,\n\n // UUIDs: /users/550e8400-e29b-41d4-a716-446655440000 → /users/:uuid\n uuid: /\\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}(?=\\/|$)/gi,\n\n // Short UUIDs (without dashes): /users/550e8400e29b41d4a716446655440000 → /users/:uuid\n shortUuid: /\\/[0-9a-f]{32}(?=\\/|$)/gi,\n\n // MongoDB ObjectIds: /docs/507f1f77bcf86cd799439011 → /docs/:objectId\n objectId: /\\/[0-9a-f]{24}(?=\\/|$)/gi,\n\n // Hashes (6+ hex chars): /assets/abc123def.js → /assets/:hash.js\n hash: /\\/[0-9a-f]{6,}(?=\\.[a-z]+$)/gi,\n\n // ISO dates: /logs/2024-01-15 → /logs/:date\n isoDate: /\\/\\d{4}-\\d{2}-\\d{2}(?=\\/|$)/g,\n\n // Timestamps: /events/1705334400 → /events/:timestamp\n timestamp: /\\/1[0-9]{9}(?=\\/|$)/g,\n\n // Email-like segments: /users/john@example.com → /users/:email\n email: /\\/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}(?=\\/|$)/g,\n} as const;\n\n/**\n * Built-in normalizer presets\n */\nconst NORMALIZER_PRESETS: Record<\n SpanNameNormalizerPreset,\n SpanNameNormalizerFn\n> = {\n /**\n * REST API preset - normalizes common REST path patterns\n * Handles: numeric IDs, UUIDs, ObjectIds, dates, timestamps, emails\n */\n 'rest-api': (name: string): string => {\n return name\n .replaceAll(NORMALIZER_PATTERNS.uuid, '/:uuid')\n .replaceAll(NORMALIZER_PATTERNS.shortUuid, '/:uuid')\n .replaceAll(NORMALIZER_PATTERNS.objectId, '/:objectId')\n .replaceAll(NORMALIZER_PATTERNS.isoDate, '/:date')\n .replaceAll(NORMALIZER_PATTERNS.timestamp, '/:timestamp')\n .replaceAll(NORMALIZER_PATTERNS.email, '/:email')\n .replaceAll(NORMALIZER_PATTERNS.numericId, '/:id');\n },\n\n /**\n * GraphQL preset - normalizes GraphQL operation names and paths\n * Keeps query/mutation names but normalizes embedded IDs\n */\n graphql: (name: string): string => {\n // For GraphQL, normalize both path-style and embedded IDs\n return name\n .replaceAll(NORMALIZER_PATTERNS.uuid, '/:uuid')\n .replaceAll(NORMALIZER_PATTERNS.numericId, '/:id');\n },\n\n /**\n * Minimal preset - only normalizes numeric IDs and UUIDs\n */\n minimal: (name: string): string => {\n return name\n .replaceAll(NORMALIZER_PATTERNS.uuid, '/:uuid')\n .replaceAll(NORMALIZER_PATTERNS.numericId, '/:id');\n },\n};\n\n/**\n * Resolve normalizer config to a function\n */\nfunction resolveNormalizer(\n config: SpanNameNormalizerConfig,\n): SpanNameNormalizerFn {\n if (typeof config === 'function') {\n return config;\n }\n\n const preset = NORMALIZER_PRESETS[config];\n if (!preset) {\n throw new Error(\n `Unknown span name normalizer preset: \"${config}\". ` +\n `Available presets: ${Object.keys(NORMALIZER_PRESETS).join(', ')}`,\n );\n }\n\n return preset;\n}\n\n/**\n * Span processor that normalizes span names to reduce cardinality.\n *\n * Normalization happens in onStart() when we have access to the mutable Span.\n * This allows us to call span.updateName() before the span is finalized.\n *\n * Common use cases:\n * - REST APIs: /users/123/posts/456 → /users/:id/posts/:id\n * - UUIDs: /items/550e8400-e29b-41d4-a716-446655440000 → /items/:uuid\n * - Dates: /logs/2024-01-15 → /logs/:date\n */\nexport class SpanNameNormalizingProcessor implements SpanProcessor {\n private readonly wrappedProcessor: SpanProcessor;\n private readonly normalizer: SpanNameNormalizerFn;\n\n constructor(\n wrappedProcessor: SpanProcessor,\n options: SpanNameNormalizingProcessorOptions,\n ) {\n this.wrappedProcessor = wrappedProcessor;\n this.normalizer = resolveNormalizer(options.normalizer);\n }\n\n /**\n * Normalize span name on start (when Span is mutable)\n */\n onStart(span: Span, parentContext: Context): void {\n try {\n const originalName = span.name;\n const normalizedName = this.normalizer(originalName);\n\n if (normalizedName !== originalName) {\n span.updateName(normalizedName);\n }\n } catch {\n // If normalizer throws, keep original name (fail-open)\n }\n\n this.wrappedProcessor.onStart(span, parentContext);\n }\n\n /**\n * Pass through onEnd unchanged\n */\n onEnd(span: ReadableSpan): void {\n this.wrappedProcessor.onEnd(span);\n }\n\n forceFlush(): Promise<void> {\n return this.wrappedProcessor.forceFlush();\n }\n\n shutdown(): Promise<void> {\n return this.wrappedProcessor.shutdown();\n }\n}\n\n/**\n * Export built-in patterns for advanced users who want to compose their own normalizers\n */\nexport { NORMALIZER_PATTERNS, NORMALIZER_PRESETS };\n"],"mappings":";;;;;;AA8DA,MAAM,sBAAsB;CAE1B,WAAW;CAGX,MAAM;CAGN,WAAW;CAGX,UAAU;CAGV,MAAM;CAGN,SAAS;CAGT,WAAW;CAGX,OAAO;AACT;;;;AAKA,MAAM,qBAGF;;;;;CAKF,aAAa,SAAyB;EACpC,OAAO,KACJ,WAAW,oBAAoB,MAAM,QAAQ,CAAC,CAC9C,WAAW,oBAAoB,WAAW,QAAQ,CAAC,CACnD,WAAW,oBAAoB,UAAU,YAAY,CAAC,CACtD,WAAW,oBAAoB,SAAS,QAAQ,CAAC,CACjD,WAAW,oBAAoB,WAAW,aAAa,CAAC,CACxD,WAAW,oBAAoB,OAAO,SAAS,CAAC,CAChD,WAAW,oBAAoB,WAAW,MAAM;CACrD;;;;;CAMA,UAAU,SAAyB;EAEjC,OAAO,KACJ,WAAW,oBAAoB,MAAM,QAAQ,CAAC,CAC9C,WAAW,oBAAoB,WAAW,MAAM;CACrD;;;;CAKA,UAAU,SAAyB;EACjC,OAAO,KACJ,WAAW,oBAAoB,MAAM,QAAQ,CAAC,CAC9C,WAAW,oBAAoB,WAAW,MAAM;CACrD;AACF;;;;AAKA,SAAS,kBACP,QACsB;CACtB,IAAI,OAAO,WAAW,YACpB,OAAO;CAGT,MAAM,SAAS,mBAAmB;CAClC,IAAI,CAAC,QACH,MAAM,IAAI,MACR,yCAAyC,OAAO,wBACxB,OAAO,KAAK,kBAAkB,CAAC,CAAC,KAAK,IAAI,GACnE;CAGF,OAAO;AACT;;;;;;;;;;;;AAaA,IAAa,+BAAb,MAAmE;CACjE,AAAiB;CACjB,AAAiB;CAEjB,YACE,kBACA,SACA;EACA,KAAK,mBAAmB;EACxB,KAAK,aAAa,kBAAkB,QAAQ,UAAU;CACxD;;;;CAKA,QAAQ,MAAY,eAA8B;EAChD,IAAI;GACF,MAAM,eAAe,KAAK;GAC1B,MAAM,iBAAiB,KAAK,WAAW,YAAY;GAEnD,IAAI,mBAAmB,cACrB,KAAK,WAAW,cAAc;EAElC,QAAQ,CAER;EAEA,KAAK,iBAAiB,QAAQ,MAAM,aAAa;CACnD;;;;CAKA,MAAM,MAA0B;EAC9B,KAAK,iBAAiB,MAAM,IAAI;CAClC;CAEA,aAA4B;EAC1B,OAAO,KAAK,iBAAiB,WAAW;CAC1C;CAEA,WAA0B;EACxB,OAAO,KAAK,iBAAiB,SAAS;CACxC;AACF"}
1
+ {"version":3,"file":"span-name-normalizer.cjs","names":[],"sources":["../src/span-name-normalizer.ts"],"sourcesContent":["/**\n * Span Name Normalizer\n *\n * Normalizes span names to reduce cardinality from dynamic path segments.\n * This is critical for observability backends that charge by unique span names\n * or have cardinality limits.\n *\n * @example Basic usage with custom function\n * ```typescript\n * init({\n * service: 'my-app',\n * spanNameNormalizer: (name) => {\n * return name.replace(/\\/[0-9]+/g, '/:id');\n * }\n * })\n * ```\n *\n * @example Using built-in preset\n * ```typescript\n * init({\n * service: 'my-app',\n * spanNameNormalizer: 'rest-api'\n * })\n * ```\n */\n\nimport type {\n SpanProcessor,\n ReadableSpan,\n} from '@opentelemetry/sdk-trace-base';\nimport type { Context } from '@opentelemetry/api';\nimport type { Span } from '@opentelemetry/sdk-trace-base';\n\n/**\n * Function to normalize a span name\n * @param name - The original span name\n * @returns The normalized span name\n */\nexport type SpanNameNormalizerFn = (name: string) => string;\n\n/**\n * Built-in normalizer preset names\n */\nexport type SpanNameNormalizerPreset = 'rest-api' | 'graphql' | 'minimal';\n\n/**\n * Normalizer config - either a function or a preset name\n */\nexport type SpanNameNormalizerConfig =\n SpanNameNormalizerFn | SpanNameNormalizerPreset;\n\nexport interface SpanNameNormalizingProcessorOptions {\n /**\n * Normalizer function or preset name\n */\n normalizer: SpanNameNormalizerConfig;\n}\n\n/**\n * Built-in normalizer patterns\n */\nconst NORMALIZER_PATTERNS = {\n // Numeric IDs: /users/123 → /users/:id\n numericId: /\\/\\d+(?=\\/|$)/g,\n\n // UUIDs: /users/550e8400-e29b-41d4-a716-446655440000 → /users/:uuid\n uuid: /\\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}(?=\\/|$)/gi,\n\n // Short UUIDs (without dashes): /users/550e8400e29b41d4a716446655440000 → /users/:uuid\n shortUuid: /\\/[0-9a-f]{32}(?=\\/|$)/gi,\n\n // MongoDB ObjectIds: /docs/507f1f77bcf86cd799439011 → /docs/:objectId\n objectId: /\\/[0-9a-f]{24}(?=\\/|$)/gi,\n\n // Hashes (6+ hex chars): /assets/abc123def.js → /assets/:hash.js\n hash: /\\/[0-9a-f]{6,}(?=\\.[a-z]+$)/gi,\n\n // ISO dates: /logs/2024-01-15 → /logs/:date\n isoDate: /\\/\\d{4}-\\d{2}-\\d{2}(?=\\/|$)/g,\n\n // Timestamps: /events/1705334400 → /events/:timestamp\n timestamp: /\\/1[0-9]{9}(?=\\/|$)/g,\n\n // Email-like segments: /users/john@example.com → /users/:email\n email: /\\/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}(?=\\/|$)/g,\n} as const;\n\n/**\n * Built-in normalizer presets\n */\nconst NORMALIZER_PRESETS: Record<\n SpanNameNormalizerPreset,\n SpanNameNormalizerFn\n> = {\n /**\n * REST API preset - normalizes common REST path patterns\n * Handles: numeric IDs, UUIDs, ObjectIds, dates, timestamps, emails\n */\n 'rest-api': (name: string): string => {\n return name\n .replaceAll(NORMALIZER_PATTERNS.uuid, '/:uuid')\n .replaceAll(NORMALIZER_PATTERNS.shortUuid, '/:uuid')\n .replaceAll(NORMALIZER_PATTERNS.objectId, '/:objectId')\n .replaceAll(NORMALIZER_PATTERNS.isoDate, '/:date')\n .replaceAll(NORMALIZER_PATTERNS.timestamp, '/:timestamp')\n .replaceAll(NORMALIZER_PATTERNS.email, '/:email')\n .replaceAll(NORMALIZER_PATTERNS.numericId, '/:id');\n },\n\n /**\n * GraphQL preset - normalizes GraphQL operation names and paths\n * Keeps query/mutation names but normalizes embedded IDs\n */\n graphql: (name: string): string => {\n // For GraphQL, normalize both path-style and embedded IDs\n return name\n .replaceAll(NORMALIZER_PATTERNS.uuid, '/:uuid')\n .replaceAll(NORMALIZER_PATTERNS.numericId, '/:id');\n },\n\n /**\n * Minimal preset - only normalizes numeric IDs and UUIDs\n */\n minimal: (name: string): string => {\n return name\n .replaceAll(NORMALIZER_PATTERNS.uuid, '/:uuid')\n .replaceAll(NORMALIZER_PATTERNS.numericId, '/:id');\n },\n};\n\n/**\n * Resolve normalizer config to a function\n */\nfunction resolveNormalizer(\n config: SpanNameNormalizerConfig,\n): SpanNameNormalizerFn {\n if (typeof config === 'function') {\n return config;\n }\n\n const preset = NORMALIZER_PRESETS[config];\n if (!preset) {\n throw new Error(\n `Unknown span name normalizer preset: \"${config}\". ` +\n `Available presets: ${Object.keys(NORMALIZER_PRESETS).join(', ')}`,\n );\n }\n\n return preset;\n}\n\n/**\n * Span processor that normalizes span names to reduce cardinality.\n *\n * Normalization happens in onStart() when we have access to the mutable Span.\n * This allows us to call span.updateName() before the span is finalized.\n *\n * Common use cases:\n * - REST APIs: /users/123/posts/456 → /users/:id/posts/:id\n * - UUIDs: /items/550e8400-e29b-41d4-a716-446655440000 → /items/:uuid\n * - Dates: /logs/2024-01-15 → /logs/:date\n */\nexport class SpanNameNormalizingProcessor implements SpanProcessor {\n private readonly wrappedProcessor: SpanProcessor;\n private readonly normalizer: SpanNameNormalizerFn;\n\n constructor(\n wrappedProcessor: SpanProcessor,\n options: SpanNameNormalizingProcessorOptions,\n ) {\n this.wrappedProcessor = wrappedProcessor;\n this.normalizer = resolveNormalizer(options.normalizer);\n }\n\n /**\n * Normalize span name on start (when Span is mutable)\n */\n onStart(span: Span, parentContext: Context): void {\n try {\n const originalName = span.name;\n const normalizedName = this.normalizer(originalName);\n\n if (normalizedName !== originalName) {\n span.updateName(normalizedName);\n }\n } catch {\n // If normalizer throws, keep original name (fail-open)\n }\n\n this.wrappedProcessor.onStart(span, parentContext);\n }\n\n /**\n * Pass through onEnd unchanged\n */\n onEnd(span: ReadableSpan): void {\n this.wrappedProcessor.onEnd(span);\n }\n\n forceFlush(): Promise<void> {\n return this.wrappedProcessor.forceFlush();\n }\n\n shutdown(): Promise<void> {\n return this.wrappedProcessor.shutdown();\n }\n}\n\n/**\n * Export built-in patterns for advanced users who want to compose their own normalizers\n */\nexport { NORMALIZER_PATTERNS, NORMALIZER_PRESETS };\n"],"mappings":";;;;;;AA6DA,MAAM,sBAAsB;CAE1B,WAAW;CAGX,MAAM;CAGN,WAAW;CAGX,UAAU;CAGV,MAAM;CAGN,SAAS;CAGT,WAAW;CAGX,OAAO;AACT;;;;AAKA,MAAM,qBAGF;;;;;CAKF,aAAa,SAAyB;EACpC,OAAO,KACJ,WAAW,oBAAoB,MAAM,QAAQ,CAAC,CAC9C,WAAW,oBAAoB,WAAW,QAAQ,CAAC,CACnD,WAAW,oBAAoB,UAAU,YAAY,CAAC,CACtD,WAAW,oBAAoB,SAAS,QAAQ,CAAC,CACjD,WAAW,oBAAoB,WAAW,aAAa,CAAC,CACxD,WAAW,oBAAoB,OAAO,SAAS,CAAC,CAChD,WAAW,oBAAoB,WAAW,MAAM;CACrD;;;;;CAMA,UAAU,SAAyB;EAEjC,OAAO,KACJ,WAAW,oBAAoB,MAAM,QAAQ,CAAC,CAC9C,WAAW,oBAAoB,WAAW,MAAM;CACrD;;;;CAKA,UAAU,SAAyB;EACjC,OAAO,KACJ,WAAW,oBAAoB,MAAM,QAAQ,CAAC,CAC9C,WAAW,oBAAoB,WAAW,MAAM;CACrD;AACF;;;;AAKA,SAAS,kBACP,QACsB;CACtB,IAAI,OAAO,WAAW,YACpB,OAAO;CAGT,MAAM,SAAS,mBAAmB;CAClC,IAAI,CAAC,QACH,MAAM,IAAI,MACR,yCAAyC,OAAO,wBACxB,OAAO,KAAK,kBAAkB,CAAC,CAAC,KAAK,IAAI,GACnE;CAGF,OAAO;AACT;;;;;;;;;;;;AAaA,IAAa,+BAAb,MAAmE;CACjE,AAAiB;CACjB,AAAiB;CAEjB,YACE,kBACA,SACA;EACA,KAAK,mBAAmB;EACxB,KAAK,aAAa,kBAAkB,QAAQ,UAAU;CACxD;;;;CAKA,QAAQ,MAAY,eAA8B;EAChD,IAAI;GACF,MAAM,eAAe,KAAK;GAC1B,MAAM,iBAAiB,KAAK,WAAW,YAAY;GAEnD,IAAI,mBAAmB,cACrB,KAAK,WAAW,cAAc;EAElC,QAAQ,CAER;EAEA,KAAK,iBAAiB,QAAQ,MAAM,aAAa;CACnD;;;;CAKA,MAAM,MAA0B;EAC9B,KAAK,iBAAiB,MAAM,IAAI;CAClC;CAEA,aAA4B;EAC1B,OAAO,KAAK,iBAAiB,WAAW;CAC1C;CAEA,WAA0B;EACxB,OAAO,KAAK,iBAAiB,SAAS;CACxC;AACF"}
@@ -1,2 +1,2 @@
1
- import { a as SpanNameNormalizerPreset, i as SpanNameNormalizerFn, n as NORMALIZER_PRESETS, o as SpanNameNormalizingProcessor, r as SpanNameNormalizerConfig, s as SpanNameNormalizingProcessorOptions, t as NORMALIZER_PATTERNS } from "./span-name-normalizer-8ZOGJuwv.cjs";
1
+ import { a as SpanNameNormalizerPreset, i as SpanNameNormalizerFn, n as NORMALIZER_PRESETS, o as SpanNameNormalizingProcessor, r as SpanNameNormalizerConfig, s as SpanNameNormalizingProcessorOptions, t as NORMALIZER_PATTERNS } from "./span-name-normalizer-CN1-8C76.cjs";
2
2
  export { NORMALIZER_PATTERNS, NORMALIZER_PRESETS, SpanNameNormalizerConfig, SpanNameNormalizerFn, SpanNameNormalizerPreset, SpanNameNormalizingProcessor, SpanNameNormalizingProcessorOptions };
@@ -1,2 +1,2 @@
1
- import { a as SpanNameNormalizerPreset, i as SpanNameNormalizerFn, n as NORMALIZER_PRESETS, o as SpanNameNormalizingProcessor, r as SpanNameNormalizerConfig, s as SpanNameNormalizingProcessorOptions, t as NORMALIZER_PATTERNS } from "./span-name-normalizer-8ZOGJuwv.js";
1
+ import { a as SpanNameNormalizerPreset, i as SpanNameNormalizerFn, n as NORMALIZER_PRESETS, o as SpanNameNormalizingProcessor, r as SpanNameNormalizerConfig, s as SpanNameNormalizingProcessorOptions, t as NORMALIZER_PATTERNS } from "./span-name-normalizer-CN1-8C76.js";
2
2
  export { NORMALIZER_PATTERNS, NORMALIZER_PRESETS, SpanNameNormalizerConfig, SpanNameNormalizerFn, SpanNameNormalizerPreset, SpanNameNormalizingProcessor, SpanNameNormalizingProcessorOptions };
@@ -1 +1 @@
1
- {"version":3,"file":"span-name-normalizer.js","names":[],"sources":["../src/span-name-normalizer.ts"],"sourcesContent":["/**\n * Span Name Normalizer\n *\n * Normalizes span names to reduce cardinality from dynamic path segments.\n * This is critical for observability backends that charge by unique span names\n * or have cardinality limits.\n *\n * @example Basic usage with custom function\n * ```typescript\n * init({\n * service: 'my-app',\n * spanNameNormalizer: (name) => {\n * return name.replace(/\\/[0-9]+/g, '/:id');\n * }\n * })\n * ```\n *\n * @example Using built-in preset\n * ```typescript\n * init({\n * service: 'my-app',\n * spanNameNormalizer: 'rest-api'\n * })\n * ```\n */\n\nimport type {\n SpanProcessor,\n ReadableSpan,\n} from '@opentelemetry/sdk-trace-base';\nimport type { Context } from '@opentelemetry/api';\nimport type { Span } from '@opentelemetry/sdk-trace-base';\n\n/**\n * Function to normalize a span name\n * @param name - The original span name\n * @returns The normalized span name\n */\nexport type SpanNameNormalizerFn = (name: string) => string;\n\n/**\n * Built-in normalizer preset names\n */\nexport type SpanNameNormalizerPreset = 'rest-api' | 'graphql' | 'minimal';\n\n/**\n * Normalizer config - either a function or a preset name\n */\nexport type SpanNameNormalizerConfig =\n | SpanNameNormalizerFn\n | SpanNameNormalizerPreset;\n\nexport interface SpanNameNormalizingProcessorOptions {\n /**\n * Normalizer function or preset name\n */\n normalizer: SpanNameNormalizerConfig;\n}\n\n/**\n * Built-in normalizer patterns\n */\nconst NORMALIZER_PATTERNS = {\n // Numeric IDs: /users/123 → /users/:id\n numericId: /\\/\\d+(?=\\/|$)/g,\n\n // UUIDs: /users/550e8400-e29b-41d4-a716-446655440000 → /users/:uuid\n uuid: /\\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}(?=\\/|$)/gi,\n\n // Short UUIDs (without dashes): /users/550e8400e29b41d4a716446655440000 → /users/:uuid\n shortUuid: /\\/[0-9a-f]{32}(?=\\/|$)/gi,\n\n // MongoDB ObjectIds: /docs/507f1f77bcf86cd799439011 → /docs/:objectId\n objectId: /\\/[0-9a-f]{24}(?=\\/|$)/gi,\n\n // Hashes (6+ hex chars): /assets/abc123def.js → /assets/:hash.js\n hash: /\\/[0-9a-f]{6,}(?=\\.[a-z]+$)/gi,\n\n // ISO dates: /logs/2024-01-15 → /logs/:date\n isoDate: /\\/\\d{4}-\\d{2}-\\d{2}(?=\\/|$)/g,\n\n // Timestamps: /events/1705334400 → /events/:timestamp\n timestamp: /\\/1[0-9]{9}(?=\\/|$)/g,\n\n // Email-like segments: /users/john@example.com → /users/:email\n email: /\\/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}(?=\\/|$)/g,\n} as const;\n\n/**\n * Built-in normalizer presets\n */\nconst NORMALIZER_PRESETS: Record<\n SpanNameNormalizerPreset,\n SpanNameNormalizerFn\n> = {\n /**\n * REST API preset - normalizes common REST path patterns\n * Handles: numeric IDs, UUIDs, ObjectIds, dates, timestamps, emails\n */\n 'rest-api': (name: string): string => {\n return name\n .replaceAll(NORMALIZER_PATTERNS.uuid, '/:uuid')\n .replaceAll(NORMALIZER_PATTERNS.shortUuid, '/:uuid')\n .replaceAll(NORMALIZER_PATTERNS.objectId, '/:objectId')\n .replaceAll(NORMALIZER_PATTERNS.isoDate, '/:date')\n .replaceAll(NORMALIZER_PATTERNS.timestamp, '/:timestamp')\n .replaceAll(NORMALIZER_PATTERNS.email, '/:email')\n .replaceAll(NORMALIZER_PATTERNS.numericId, '/:id');\n },\n\n /**\n * GraphQL preset - normalizes GraphQL operation names and paths\n * Keeps query/mutation names but normalizes embedded IDs\n */\n graphql: (name: string): string => {\n // For GraphQL, normalize both path-style and embedded IDs\n return name\n .replaceAll(NORMALIZER_PATTERNS.uuid, '/:uuid')\n .replaceAll(NORMALIZER_PATTERNS.numericId, '/:id');\n },\n\n /**\n * Minimal preset - only normalizes numeric IDs and UUIDs\n */\n minimal: (name: string): string => {\n return name\n .replaceAll(NORMALIZER_PATTERNS.uuid, '/:uuid')\n .replaceAll(NORMALIZER_PATTERNS.numericId, '/:id');\n },\n};\n\n/**\n * Resolve normalizer config to a function\n */\nfunction resolveNormalizer(\n config: SpanNameNormalizerConfig,\n): SpanNameNormalizerFn {\n if (typeof config === 'function') {\n return config;\n }\n\n const preset = NORMALIZER_PRESETS[config];\n if (!preset) {\n throw new Error(\n `Unknown span name normalizer preset: \"${config}\". ` +\n `Available presets: ${Object.keys(NORMALIZER_PRESETS).join(', ')}`,\n );\n }\n\n return preset;\n}\n\n/**\n * Span processor that normalizes span names to reduce cardinality.\n *\n * Normalization happens in onStart() when we have access to the mutable Span.\n * This allows us to call span.updateName() before the span is finalized.\n *\n * Common use cases:\n * - REST APIs: /users/123/posts/456 → /users/:id/posts/:id\n * - UUIDs: /items/550e8400-e29b-41d4-a716-446655440000 → /items/:uuid\n * - Dates: /logs/2024-01-15 → /logs/:date\n */\nexport class SpanNameNormalizingProcessor implements SpanProcessor {\n private readonly wrappedProcessor: SpanProcessor;\n private readonly normalizer: SpanNameNormalizerFn;\n\n constructor(\n wrappedProcessor: SpanProcessor,\n options: SpanNameNormalizingProcessorOptions,\n ) {\n this.wrappedProcessor = wrappedProcessor;\n this.normalizer = resolveNormalizer(options.normalizer);\n }\n\n /**\n * Normalize span name on start (when Span is mutable)\n */\n onStart(span: Span, parentContext: Context): void {\n try {\n const originalName = span.name;\n const normalizedName = this.normalizer(originalName);\n\n if (normalizedName !== originalName) {\n span.updateName(normalizedName);\n }\n } catch {\n // If normalizer throws, keep original name (fail-open)\n }\n\n this.wrappedProcessor.onStart(span, parentContext);\n }\n\n /**\n * Pass through onEnd unchanged\n */\n onEnd(span: ReadableSpan): void {\n this.wrappedProcessor.onEnd(span);\n }\n\n forceFlush(): Promise<void> {\n return this.wrappedProcessor.forceFlush();\n }\n\n shutdown(): Promise<void> {\n return this.wrappedProcessor.shutdown();\n }\n}\n\n/**\n * Export built-in patterns for advanced users who want to compose their own normalizers\n */\nexport { NORMALIZER_PATTERNS, NORMALIZER_PRESETS };\n"],"mappings":";;;;AA8DA,MAAM,sBAAsB;CAE1B,WAAW;CAGX,MAAM;CAGN,WAAW;CAGX,UAAU;CAGV,MAAM;CAGN,SAAS;CAGT,WAAW;CAGX,OAAO;AACT;;;;AAKA,MAAM,qBAGF;;;;;CAKF,aAAa,SAAyB;EACpC,OAAO,KACJ,WAAW,oBAAoB,MAAM,QAAQ,CAAC,CAC9C,WAAW,oBAAoB,WAAW,QAAQ,CAAC,CACnD,WAAW,oBAAoB,UAAU,YAAY,CAAC,CACtD,WAAW,oBAAoB,SAAS,QAAQ,CAAC,CACjD,WAAW,oBAAoB,WAAW,aAAa,CAAC,CACxD,WAAW,oBAAoB,OAAO,SAAS,CAAC,CAChD,WAAW,oBAAoB,WAAW,MAAM;CACrD;;;;;CAMA,UAAU,SAAyB;EAEjC,OAAO,KACJ,WAAW,oBAAoB,MAAM,QAAQ,CAAC,CAC9C,WAAW,oBAAoB,WAAW,MAAM;CACrD;;;;CAKA,UAAU,SAAyB;EACjC,OAAO,KACJ,WAAW,oBAAoB,MAAM,QAAQ,CAAC,CAC9C,WAAW,oBAAoB,WAAW,MAAM;CACrD;AACF;;;;AAKA,SAAS,kBACP,QACsB;CACtB,IAAI,OAAO,WAAW,YACpB,OAAO;CAGT,MAAM,SAAS,mBAAmB;CAClC,IAAI,CAAC,QACH,MAAM,IAAI,MACR,yCAAyC,OAAO,wBACxB,OAAO,KAAK,kBAAkB,CAAC,CAAC,KAAK,IAAI,GACnE;CAGF,OAAO;AACT;;;;;;;;;;;;AAaA,IAAa,+BAAb,MAAmE;CACjE,AAAiB;CACjB,AAAiB;CAEjB,YACE,kBACA,SACA;EACA,KAAK,mBAAmB;EACxB,KAAK,aAAa,kBAAkB,QAAQ,UAAU;CACxD;;;;CAKA,QAAQ,MAAY,eAA8B;EAChD,IAAI;GACF,MAAM,eAAe,KAAK;GAC1B,MAAM,iBAAiB,KAAK,WAAW,YAAY;GAEnD,IAAI,mBAAmB,cACrB,KAAK,WAAW,cAAc;EAElC,QAAQ,CAER;EAEA,KAAK,iBAAiB,QAAQ,MAAM,aAAa;CACnD;;;;CAKA,MAAM,MAA0B;EAC9B,KAAK,iBAAiB,MAAM,IAAI;CAClC;CAEA,aAA4B;EAC1B,OAAO,KAAK,iBAAiB,WAAW;CAC1C;CAEA,WAA0B;EACxB,OAAO,KAAK,iBAAiB,SAAS;CACxC;AACF"}
1
+ {"version":3,"file":"span-name-normalizer.js","names":[],"sources":["../src/span-name-normalizer.ts"],"sourcesContent":["/**\n * Span Name Normalizer\n *\n * Normalizes span names to reduce cardinality from dynamic path segments.\n * This is critical for observability backends that charge by unique span names\n * or have cardinality limits.\n *\n * @example Basic usage with custom function\n * ```typescript\n * init({\n * service: 'my-app',\n * spanNameNormalizer: (name) => {\n * return name.replace(/\\/[0-9]+/g, '/:id');\n * }\n * })\n * ```\n *\n * @example Using built-in preset\n * ```typescript\n * init({\n * service: 'my-app',\n * spanNameNormalizer: 'rest-api'\n * })\n * ```\n */\n\nimport type {\n SpanProcessor,\n ReadableSpan,\n} from '@opentelemetry/sdk-trace-base';\nimport type { Context } from '@opentelemetry/api';\nimport type { Span } from '@opentelemetry/sdk-trace-base';\n\n/**\n * Function to normalize a span name\n * @param name - The original span name\n * @returns The normalized span name\n */\nexport type SpanNameNormalizerFn = (name: string) => string;\n\n/**\n * Built-in normalizer preset names\n */\nexport type SpanNameNormalizerPreset = 'rest-api' | 'graphql' | 'minimal';\n\n/**\n * Normalizer config - either a function or a preset name\n */\nexport type SpanNameNormalizerConfig =\n SpanNameNormalizerFn | SpanNameNormalizerPreset;\n\nexport interface SpanNameNormalizingProcessorOptions {\n /**\n * Normalizer function or preset name\n */\n normalizer: SpanNameNormalizerConfig;\n}\n\n/**\n * Built-in normalizer patterns\n */\nconst NORMALIZER_PATTERNS = {\n // Numeric IDs: /users/123 → /users/:id\n numericId: /\\/\\d+(?=\\/|$)/g,\n\n // UUIDs: /users/550e8400-e29b-41d4-a716-446655440000 → /users/:uuid\n uuid: /\\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}(?=\\/|$)/gi,\n\n // Short UUIDs (without dashes): /users/550e8400e29b41d4a716446655440000 → /users/:uuid\n shortUuid: /\\/[0-9a-f]{32}(?=\\/|$)/gi,\n\n // MongoDB ObjectIds: /docs/507f1f77bcf86cd799439011 → /docs/:objectId\n objectId: /\\/[0-9a-f]{24}(?=\\/|$)/gi,\n\n // Hashes (6+ hex chars): /assets/abc123def.js → /assets/:hash.js\n hash: /\\/[0-9a-f]{6,}(?=\\.[a-z]+$)/gi,\n\n // ISO dates: /logs/2024-01-15 → /logs/:date\n isoDate: /\\/\\d{4}-\\d{2}-\\d{2}(?=\\/|$)/g,\n\n // Timestamps: /events/1705334400 → /events/:timestamp\n timestamp: /\\/1[0-9]{9}(?=\\/|$)/g,\n\n // Email-like segments: /users/john@example.com → /users/:email\n email: /\\/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}(?=\\/|$)/g,\n} as const;\n\n/**\n * Built-in normalizer presets\n */\nconst NORMALIZER_PRESETS: Record<\n SpanNameNormalizerPreset,\n SpanNameNormalizerFn\n> = {\n /**\n * REST API preset - normalizes common REST path patterns\n * Handles: numeric IDs, UUIDs, ObjectIds, dates, timestamps, emails\n */\n 'rest-api': (name: string): string => {\n return name\n .replaceAll(NORMALIZER_PATTERNS.uuid, '/:uuid')\n .replaceAll(NORMALIZER_PATTERNS.shortUuid, '/:uuid')\n .replaceAll(NORMALIZER_PATTERNS.objectId, '/:objectId')\n .replaceAll(NORMALIZER_PATTERNS.isoDate, '/:date')\n .replaceAll(NORMALIZER_PATTERNS.timestamp, '/:timestamp')\n .replaceAll(NORMALIZER_PATTERNS.email, '/:email')\n .replaceAll(NORMALIZER_PATTERNS.numericId, '/:id');\n },\n\n /**\n * GraphQL preset - normalizes GraphQL operation names and paths\n * Keeps query/mutation names but normalizes embedded IDs\n */\n graphql: (name: string): string => {\n // For GraphQL, normalize both path-style and embedded IDs\n return name\n .replaceAll(NORMALIZER_PATTERNS.uuid, '/:uuid')\n .replaceAll(NORMALIZER_PATTERNS.numericId, '/:id');\n },\n\n /**\n * Minimal preset - only normalizes numeric IDs and UUIDs\n */\n minimal: (name: string): string => {\n return name\n .replaceAll(NORMALIZER_PATTERNS.uuid, '/:uuid')\n .replaceAll(NORMALIZER_PATTERNS.numericId, '/:id');\n },\n};\n\n/**\n * Resolve normalizer config to a function\n */\nfunction resolveNormalizer(\n config: SpanNameNormalizerConfig,\n): SpanNameNormalizerFn {\n if (typeof config === 'function') {\n return config;\n }\n\n const preset = NORMALIZER_PRESETS[config];\n if (!preset) {\n throw new Error(\n `Unknown span name normalizer preset: \"${config}\". ` +\n `Available presets: ${Object.keys(NORMALIZER_PRESETS).join(', ')}`,\n );\n }\n\n return preset;\n}\n\n/**\n * Span processor that normalizes span names to reduce cardinality.\n *\n * Normalization happens in onStart() when we have access to the mutable Span.\n * This allows us to call span.updateName() before the span is finalized.\n *\n * Common use cases:\n * - REST APIs: /users/123/posts/456 → /users/:id/posts/:id\n * - UUIDs: /items/550e8400-e29b-41d4-a716-446655440000 → /items/:uuid\n * - Dates: /logs/2024-01-15 → /logs/:date\n */\nexport class SpanNameNormalizingProcessor implements SpanProcessor {\n private readonly wrappedProcessor: SpanProcessor;\n private readonly normalizer: SpanNameNormalizerFn;\n\n constructor(\n wrappedProcessor: SpanProcessor,\n options: SpanNameNormalizingProcessorOptions,\n ) {\n this.wrappedProcessor = wrappedProcessor;\n this.normalizer = resolveNormalizer(options.normalizer);\n }\n\n /**\n * Normalize span name on start (when Span is mutable)\n */\n onStart(span: Span, parentContext: Context): void {\n try {\n const originalName = span.name;\n const normalizedName = this.normalizer(originalName);\n\n if (normalizedName !== originalName) {\n span.updateName(normalizedName);\n }\n } catch {\n // If normalizer throws, keep original name (fail-open)\n }\n\n this.wrappedProcessor.onStart(span, parentContext);\n }\n\n /**\n * Pass through onEnd unchanged\n */\n onEnd(span: ReadableSpan): void {\n this.wrappedProcessor.onEnd(span);\n }\n\n forceFlush(): Promise<void> {\n return this.wrappedProcessor.forceFlush();\n }\n\n shutdown(): Promise<void> {\n return this.wrappedProcessor.shutdown();\n }\n}\n\n/**\n * Export built-in patterns for advanced users who want to compose their own normalizers\n */\nexport { NORMALIZER_PATTERNS, NORMALIZER_PRESETS };\n"],"mappings":";;;;AA6DA,MAAM,sBAAsB;CAE1B,WAAW;CAGX,MAAM;CAGN,WAAW;CAGX,UAAU;CAGV,MAAM;CAGN,SAAS;CAGT,WAAW;CAGX,OAAO;AACT;;;;AAKA,MAAM,qBAGF;;;;;CAKF,aAAa,SAAyB;EACpC,OAAO,KACJ,WAAW,oBAAoB,MAAM,QAAQ,CAAC,CAC9C,WAAW,oBAAoB,WAAW,QAAQ,CAAC,CACnD,WAAW,oBAAoB,UAAU,YAAY,CAAC,CACtD,WAAW,oBAAoB,SAAS,QAAQ,CAAC,CACjD,WAAW,oBAAoB,WAAW,aAAa,CAAC,CACxD,WAAW,oBAAoB,OAAO,SAAS,CAAC,CAChD,WAAW,oBAAoB,WAAW,MAAM;CACrD;;;;;CAMA,UAAU,SAAyB;EAEjC,OAAO,KACJ,WAAW,oBAAoB,MAAM,QAAQ,CAAC,CAC9C,WAAW,oBAAoB,WAAW,MAAM;CACrD;;;;CAKA,UAAU,SAAyB;EACjC,OAAO,KACJ,WAAW,oBAAoB,MAAM,QAAQ,CAAC,CAC9C,WAAW,oBAAoB,WAAW,MAAM;CACrD;AACF;;;;AAKA,SAAS,kBACP,QACsB;CACtB,IAAI,OAAO,WAAW,YACpB,OAAO;CAGT,MAAM,SAAS,mBAAmB;CAClC,IAAI,CAAC,QACH,MAAM,IAAI,MACR,yCAAyC,OAAO,wBACxB,OAAO,KAAK,kBAAkB,CAAC,CAAC,KAAK,IAAI,GACnE;CAGF,OAAO;AACT;;;;;;;;;;;;AAaA,IAAa,+BAAb,MAAmE;CACjE,AAAiB;CACjB,AAAiB;CAEjB,YACE,kBACA,SACA;EACA,KAAK,mBAAmB;EACxB,KAAK,aAAa,kBAAkB,QAAQ,UAAU;CACxD;;;;CAKA,QAAQ,MAAY,eAA8B;EAChD,IAAI;GACF,MAAM,eAAe,KAAK;GAC1B,MAAM,iBAAiB,KAAK,WAAW,YAAY;GAEnD,IAAI,mBAAmB,cACrB,KAAK,WAAW,cAAc;EAElC,QAAQ,CAER;EAEA,KAAK,iBAAiB,QAAQ,MAAM,aAAa;CACnD;;;;CAKA,MAAM,MAA0B;EAC9B,KAAK,iBAAiB,MAAM,IAAI;CAClC;CAEA,aAA4B;EAC1B,OAAO,KAAK,iBAAiB,WAAW;CAC1C;CAEA,WAA0B;EACxB,OAAO,KAAK,iBAAiB,SAAS;CACxC;AACF"}
@@ -1,6 +1,5 @@
1
1
  import { ReadableSpan, Span, SpanProcessor } from "@opentelemetry/sdk-trace-base";
2
2
  import { Context } from "@opentelemetry/api";
3
-
4
3
  //#region src/tail-sampling-processor.d.ts
5
4
  declare class TailSamplingSpanProcessor implements SpanProcessor {
6
5
  private wrappedProcessor;
@@ -1 +1 @@
1
- {"version":3,"file":"tail-sampling-processor.d.cts","names":[],"sources":["../src/tail-sampling-processor.ts"],"mappings":";;;;cAyBa,yBAAA,YAAqC,aAAA;EAAA,QACxC,gBAAA;cAEI,gBAAA,EAAkB,aAAA;EAI9B,OAAA,CAAQ,IAAA,EAAM,IAAA,EAAM,aAAA,EAAe,OAAA;EAInC,KAAA,CAAM,IAAA,EAAM,YAAA;EAWZ,UAAA,IAAc,OAAA;EAId,QAAA,IAAY,OAAA;AAAA"}
1
+ {"version":3,"file":"tail-sampling-processor.d.cts","names":[],"sources":["../src/tail-sampling-processor.ts"],"mappings":";;;cAyBa,qCAAqC;UACxC;EAEI,YAAA,kBAAkB;EAI9B,QAAQ,MAAM,MAAM,eAAe;EAInC,MAAM,MAAM;EAWZ,cAAc;EAId,YAAY"}
@@ -1,6 +1,5 @@
1
1
  import { ReadableSpan, Span, SpanProcessor } from "@opentelemetry/sdk-trace-base";
2
2
  import { Context } from "@opentelemetry/api";
3
-
4
3
  //#region src/tail-sampling-processor.d.ts
5
4
  declare class TailSamplingSpanProcessor implements SpanProcessor {
6
5
  private wrappedProcessor;
@@ -1 +1 @@
1
- {"version":3,"file":"tail-sampling-processor.d.ts","names":[],"sources":["../src/tail-sampling-processor.ts"],"mappings":";;;;cAyBa,yBAAA,YAAqC,aAAA;EAAA,QACxC,gBAAA;cAEI,gBAAA,EAAkB,aAAA;EAI9B,OAAA,CAAQ,IAAA,EAAM,IAAA,EAAM,aAAA,EAAe,OAAA;EAInC,KAAA,CAAM,IAAA,EAAM,YAAA;EAWZ,UAAA,IAAc,OAAA;EAId,QAAA,IAAY,OAAA;AAAA"}
1
+ {"version":3,"file":"tail-sampling-processor.d.ts","names":[],"sources":["../src/tail-sampling-processor.ts"],"mappings":";;;cAyBa,qCAAqC;UACxC;EAEI,YAAA,kBAAkB;EAI9B,QAAQ,MAAM,MAAM,eAAe;EAInC,MAAM,MAAM;EAWZ,cAAc;EAId,YAAY"}
@@ -1 +1 @@
1
- {"version":3,"file":"test-span-collector.cjs","names":["SpanStatusCode"],"sources":["../src/test-span-collector.ts"],"sourcesContent":["/**\n * TestSpanCollector — SpanExporter that groups finished spans by traceId\n * and drains per-trace for embedding in test metadata.\n *\n * Wire it to a tracer provider, register that provider with autotel, then\n * create spans through autotel's tracer so finished spans reach the collector.\n *\n * @example\n * ```typescript\n * import { TestSpanCollector } from 'autotel/test-span-collector';\n * import { SimpleSpanProcessor } from 'autotel/processors';\n * import { setAutotelTracerProvider, getAutotelTracer } from 'autotel';\n * import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node';\n *\n * const collector = new TestSpanCollector();\n *\n * // OTel SDK v2: pass processors at construction, then register the provider.\n * const provider = new NodeTracerProvider({\n * spanProcessors: [new SimpleSpanProcessor(collector)],\n * });\n * setAutotelTracerProvider(provider);\n *\n * const tracer = getAutotelTracer('test');\n * const root = tracer.startSpan('operation');\n * // ...work, child spans...\n * root.end();\n *\n * const spans = collector.drainTrace(\n * root.spanContext().traceId,\n * root.spanContext().spanId,\n * );\n * // spans contains the root span and its descendants\n * ```\n *\n * For high-level instrumentation written with `span()` / `track()`, prefer\n * `createTraceCollector()` from `autotel/testing`. For a plain low-level\n * exporter, use `InMemorySpanExporter` from `autotel/exporters`.\n */\n\nimport type { SpanExporter, ReadableSpan } from '@opentelemetry/sdk-trace-base';\nimport { SpanStatusCode } from '@opentelemetry/api';\n\n/** @see ExportResultCode from @opentelemetry/core */\nconst ExportResultCode = { SUCCESS: 0, FAILED: 1 } as const;\n\n/** Attribute value types that survive serialization */\ntype SerializableValue =\n | string\n | number\n | boolean\n | string[]\n | number[]\n | boolean[];\n\n/**\n * Portable serialized span for embedding in test metadata.\n * `startTimeMs` is derived from OTel HrTime — epoch-based wall-clock ms in the current SDK.\n *\n * Defined as a `type` (not `interface`) so it is assignable to\n * `Record<string, unknown>` in TypeScript 6+ strict mode.\n */\nexport type SerializedSpan = {\n spanId: string;\n parentSpanId?: string;\n name: string;\n startTimeMs: number;\n durationMs: number;\n status: 'ok' | 'error' | 'unset';\n statusMessage?: string;\n attributes?: Record<string, SerializableValue>;\n};\n\nexport class TestSpanCollector implements SpanExporter {\n private traces = new Map<string, ReadableSpan[]>();\n\n export(\n spans: ReadableSpan[],\n callback: (result: { code: number }) => void,\n ): void {\n for (const span of spans) {\n const traceId = span.spanContext().traceId;\n let list = this.traces.get(traceId);\n if (!list) {\n list = [];\n this.traces.set(traceId, list);\n }\n list.push(span);\n }\n callback({ code: ExportResultCode.SUCCESS });\n }\n\n /**\n * Drain and serialize spans that are descendants of `rootSpanId` within `traceId`.\n * Filters to the subtree rooted at the test span to prevent cross-test mixing.\n * Removes the entire traceId entry from the collector.\n */\n drainTrace(traceId: string, rootSpanId: string): SerializedSpan[] {\n const spans = this.peekTrace(traceId, rootSpanId);\n this.traces.delete(traceId);\n return spans;\n }\n\n /**\n * Serialize the finished spans of `traceId` without removing them — safe to\n * call repeatedly while a trace is still growing (e.g. a scenario checker\n * polling until an async flow's completion boundary closes).\n *\n * With `rootSpanId`, filters to that span's subtree like {@link drainTrace}.\n */\n peekTrace(traceId: string, rootSpanId?: string): SerializedSpan[] {\n const allSpans = this.traces.get(traceId);\n if (!allSpans?.length) return [];\n if (rootSpanId === undefined) return allSpans.map((s) => serializeSpan(s));\n\n // Build spanId → span index for efficient parent-chain walking\n const byId = new Map<string, ReadableSpan>();\n for (const s of allSpans) byId.set(s.spanContext().spanId, s);\n\n // Filter to spans that are the root or descendants of rootSpanId\n const included = allSpans.filter((s) => {\n let id: string | undefined = s.spanContext().spanId;\n while (id) {\n if (id === rootSpanId) return true;\n const parent = byId.get(id);\n const parentId = parent?.parentSpanContext?.spanId || undefined;\n if (parentId === id) break; // cycle guard\n id = parentId;\n }\n return false;\n });\n\n return included.map((s) => serializeSpan(s));\n }\n\n shutdown(): Promise<void> {\n this.traces.clear();\n return Promise.resolve();\n }\n\n forceFlush(): Promise<void> {\n return Promise.resolve();\n }\n}\n\nfunction hrTimeToMs(hr: [number, number]): number {\n return hr[0] * 1000 + hr[1] / 1_000_000;\n}\n\nfunction isSerializable(v: unknown): v is SerializableValue {\n if (typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean')\n return true;\n if (Array.isArray(v) && v.length > 0) {\n const t = typeof v[0];\n return (\n (t === 'string' || t === 'number' || t === 'boolean') &&\n v.every((e) => typeof e === t)\n );\n }\n return false;\n}\n\nexport function serializeSpan(span: ReadableSpan): SerializedSpan {\n const attrs: Record<string, SerializableValue> = {};\n for (const [k, v] of Object.entries(span.attributes)) {\n if (isSerializable(v)) attrs[k] = v;\n }\n return {\n spanId: span.spanContext().spanId,\n parentSpanId: span.parentSpanContext?.spanId || undefined,\n name: span.name,\n startTimeMs: hrTimeToMs(span.startTime as [number, number]),\n durationMs: hrTimeToMs(span.duration as [number, number]),\n status:\n span.status.code === SpanStatusCode.ERROR\n ? 'error'\n : span.status.code === SpanStatusCode.OK\n ? 'ok'\n : 'unset',\n statusMessage: span.status.message || undefined,\n attributes: Object.keys(attrs).length > 0 ? attrs : undefined,\n };\n}\n"],"mappings":";;;;;AA2CA,MAAM,mBAAmB;CAAE,SAAS;CAAG,QAAQ;AAAE;AA6BjD,IAAa,oBAAb,MAAuD;CACrD,AAAQ,yBAAS,IAAI,IAA4B;CAEjD,OACE,OACA,UACM;EACN,KAAK,MAAM,QAAQ,OAAO;GACxB,MAAM,UAAU,KAAK,YAAY,CAAC,CAAC;GACnC,IAAI,OAAO,KAAK,OAAO,IAAI,OAAO;GAClC,IAAI,CAAC,MAAM;IACT,OAAO,CAAC;IACR,KAAK,OAAO,IAAI,SAAS,IAAI;GAC/B;GACA,KAAK,KAAK,IAAI;EAChB;EACA,SAAS,EAAE,MAAM,iBAAiB,QAAQ,CAAC;CAC7C;;;;;;CAOA,WAAW,SAAiB,YAAsC;EAChE,MAAM,QAAQ,KAAK,UAAU,SAAS,UAAU;EAChD,KAAK,OAAO,OAAO,OAAO;EAC1B,OAAO;CACT;;;;;;;;CASA,UAAU,SAAiB,YAAuC;EAChE,MAAM,WAAW,KAAK,OAAO,IAAI,OAAO;EACxC,IAAI,CAAC,UAAU,QAAQ,OAAO,CAAC;EAC/B,IAAI,eAAe,QAAW,OAAO,SAAS,KAAK,MAAM,cAAc,CAAC,CAAC;EAGzE,MAAM,uBAAO,IAAI,IAA0B;EAC3C,KAAK,MAAM,KAAK,UAAU,KAAK,IAAI,EAAE,YAAY,CAAC,CAAC,QAAQ,CAAC;EAe5D,OAZiB,SAAS,QAAQ,MAAM;GACtC,IAAI,KAAyB,EAAE,YAAY,CAAC,CAAC;GAC7C,OAAO,IAAI;IACT,IAAI,OAAO,YAAY,OAAO;IAE9B,MAAM,WADS,KAAK,IAAI,EACF,CAAC,EAAE,mBAAmB,UAAU;IACtD,IAAI,aAAa,IAAI;IACrB,KAAK;GACP;GACA,OAAO;EACT,CAEc,CAAC,CAAC,KAAK,MAAM,cAAc,CAAC,CAAC;CAC7C;CAEA,WAA0B;EACxB,KAAK,OAAO,MAAM;EAClB,OAAO,QAAQ,QAAQ;CACzB;CAEA,aAA4B;EAC1B,OAAO,QAAQ,QAAQ;CACzB;AACF;AAEA,SAAS,WAAW,IAA8B;CAChD,OAAO,GAAG,KAAK,MAAO,GAAG,KAAK;AAChC;AAEA,SAAS,eAAe,GAAoC;CAC1D,IAAI,OAAO,MAAM,YAAY,OAAO,MAAM,YAAY,OAAO,MAAM,WACjE,OAAO;CACT,IAAI,MAAM,QAAQ,CAAC,KAAK,EAAE,SAAS,GAAG;EACpC,MAAM,IAAI,OAAO,EAAE;EACnB,QACG,MAAM,YAAY,MAAM,YAAY,MAAM,cAC3C,EAAE,OAAO,MAAM,OAAO,MAAM,CAAC;CAEjC;CACA,OAAO;AACT;AAEA,SAAgB,cAAc,MAAoC;CAChE,MAAM,QAA2C,CAAC;CAClD,KAAK,MAAM,CAAC,GAAG,MAAM,OAAO,QAAQ,KAAK,UAAU,GACjD,IAAI,eAAe,CAAC,GAAG,MAAM,KAAK;CAEpC,OAAO;EACL,QAAQ,KAAK,YAAY,CAAC,CAAC;EAC3B,cAAc,KAAK,mBAAmB,UAAU;EAChD,MAAM,KAAK;EACX,aAAa,WAAW,KAAK,SAA6B;EAC1D,YAAY,WAAW,KAAK,QAA4B;EACxD,QACE,KAAK,OAAO,SAASA,kCAAe,QAChC,UACA,KAAK,OAAO,SAASA,kCAAe,KAClC,OACA;EACR,eAAe,KAAK,OAAO,WAAW;EACtC,YAAY,OAAO,KAAK,KAAK,CAAC,CAAC,SAAS,IAAI,QAAQ;CACtD;AACF"}
1
+ {"version":3,"file":"test-span-collector.cjs","names":["SpanStatusCode"],"sources":["../src/test-span-collector.ts"],"sourcesContent":["/**\n * TestSpanCollector — SpanExporter that groups finished spans by traceId\n * and drains per-trace for embedding in test metadata.\n *\n * Wire it to a tracer provider, register that provider with autotel, then\n * create spans through autotel's tracer so finished spans reach the collector.\n *\n * @example\n * ```typescript\n * import { TestSpanCollector } from 'autotel/test-span-collector';\n * import { SimpleSpanProcessor } from 'autotel/processors';\n * import { setAutotelTracerProvider, getAutotelTracer } from 'autotel';\n * import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node';\n *\n * const collector = new TestSpanCollector();\n *\n * // OTel SDK v2: pass processors at construction, then register the provider.\n * const provider = new NodeTracerProvider({\n * spanProcessors: [new SimpleSpanProcessor(collector)],\n * });\n * setAutotelTracerProvider(provider);\n *\n * const tracer = getAutotelTracer('test');\n * const root = tracer.startSpan('operation');\n * // ...work, child spans...\n * root.end();\n *\n * const spans = collector.drainTrace(\n * root.spanContext().traceId,\n * root.spanContext().spanId,\n * );\n * // spans contains the root span and its descendants\n * ```\n *\n * For high-level instrumentation written with `span()` / `track()`, prefer\n * `createTraceCollector()` from `autotel/testing`. For a plain low-level\n * exporter, use `InMemorySpanExporter` from `autotel/exporters`.\n */\n\nimport type { SpanExporter, ReadableSpan } from '@opentelemetry/sdk-trace-base';\nimport { SpanStatusCode } from '@opentelemetry/api';\n\n/** @see ExportResultCode from @opentelemetry/core */\nconst ExportResultCode = { SUCCESS: 0, FAILED: 1 } as const;\n\n/** Attribute value types that survive serialization */\ntype SerializableValue =\n string | number | boolean | string[] | number[] | boolean[];\n\n/**\n * Portable serialized span for embedding in test metadata.\n * `startTimeMs` is derived from OTel HrTime — epoch-based wall-clock ms in the current SDK.\n *\n * Defined as a `type` (not `interface`) so it is assignable to\n * `Record<string, unknown>` in TypeScript 6+ strict mode.\n */\nexport type SerializedSpan = {\n spanId: string;\n parentSpanId?: string;\n name: string;\n startTimeMs: number;\n durationMs: number;\n status: 'ok' | 'error' | 'unset';\n statusMessage?: string;\n attributes?: Record<string, SerializableValue>;\n};\n\nexport class TestSpanCollector implements SpanExporter {\n private traces = new Map<string, ReadableSpan[]>();\n\n export(\n spans: ReadableSpan[],\n callback: (result: { code: number }) => void,\n ): void {\n for (const span of spans) {\n const traceId = span.spanContext().traceId;\n let list = this.traces.get(traceId);\n if (!list) {\n list = [];\n this.traces.set(traceId, list);\n }\n list.push(span);\n }\n callback({ code: ExportResultCode.SUCCESS });\n }\n\n /**\n * Drain and serialize spans that are descendants of `rootSpanId` within `traceId`.\n * Filters to the subtree rooted at the test span to prevent cross-test mixing.\n * Removes the entire traceId entry from the collector.\n */\n drainTrace(traceId: string, rootSpanId: string): SerializedSpan[] {\n const spans = this.peekTrace(traceId, rootSpanId);\n this.traces.delete(traceId);\n return spans;\n }\n\n /**\n * Serialize the finished spans of `traceId` without removing them — safe to\n * call repeatedly while a trace is still growing (e.g. a scenario checker\n * polling until an async flow's completion boundary closes).\n *\n * With `rootSpanId`, filters to that span's subtree like {@link drainTrace}.\n */\n peekTrace(traceId: string, rootSpanId?: string): SerializedSpan[] {\n const allSpans = this.traces.get(traceId);\n if (!allSpans?.length) return [];\n if (rootSpanId === undefined) return allSpans.map((s) => serializeSpan(s));\n\n // Build spanId → span index for efficient parent-chain walking\n const byId = new Map<string, ReadableSpan>();\n for (const s of allSpans) byId.set(s.spanContext().spanId, s);\n\n // Filter to spans that are the root or descendants of rootSpanId\n const included = allSpans.filter((s) => {\n let id: string | undefined = s.spanContext().spanId;\n while (id) {\n if (id === rootSpanId) return true;\n const parent = byId.get(id);\n const parentId = parent?.parentSpanContext?.spanId || undefined;\n if (parentId === id) break; // cycle guard\n id = parentId;\n }\n return false;\n });\n\n return included.map((s) => serializeSpan(s));\n }\n\n shutdown(): Promise<void> {\n this.traces.clear();\n return Promise.resolve();\n }\n\n forceFlush(): Promise<void> {\n return Promise.resolve();\n }\n}\n\nfunction hrTimeToMs(hr: [number, number]): number {\n return hr[0] * 1000 + hr[1] / 1_000_000;\n}\n\nfunction isSerializable(v: unknown): v is SerializableValue {\n if (typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean')\n return true;\n if (Array.isArray(v) && v.length > 0) {\n const t = typeof v[0];\n return (\n (t === 'string' || t === 'number' || t === 'boolean') &&\n v.every((e) => typeof e === t)\n );\n }\n return false;\n}\n\nexport function serializeSpan(span: ReadableSpan): SerializedSpan {\n const attrs: Record<string, SerializableValue> = {};\n for (const [k, v] of Object.entries(span.attributes)) {\n if (isSerializable(v)) attrs[k] = v;\n }\n return {\n spanId: span.spanContext().spanId,\n parentSpanId: span.parentSpanContext?.spanId || undefined,\n name: span.name,\n startTimeMs: hrTimeToMs(span.startTime as [number, number]),\n durationMs: hrTimeToMs(span.duration as [number, number]),\n status:\n span.status.code === SpanStatusCode.ERROR\n ? 'error'\n : span.status.code === SpanStatusCode.OK\n ? 'ok'\n : 'unset',\n statusMessage: span.status.message || undefined,\n attributes: Object.keys(attrs).length > 0 ? attrs : undefined,\n };\n}\n"],"mappings":";;;;;AA2CA,MAAM,mBAAmB;CAAE,SAAS;CAAG,QAAQ;AAAE;AAwBjD,IAAa,oBAAb,MAAuD;CACrD,AAAQ,yBAAS,IAAI,IAA4B;CAEjD,OACE,OACA,UACM;EACN,KAAK,MAAM,QAAQ,OAAO;GACxB,MAAM,UAAU,KAAK,YAAY,CAAC,CAAC;GACnC,IAAI,OAAO,KAAK,OAAO,IAAI,OAAO;GAClC,IAAI,CAAC,MAAM;IACT,OAAO,CAAC;IACR,KAAK,OAAO,IAAI,SAAS,IAAI;GAC/B;GACA,KAAK,KAAK,IAAI;EAChB;EACA,SAAS,EAAE,MAAM,iBAAiB,QAAQ,CAAC;CAC7C;;;;;;CAOA,WAAW,SAAiB,YAAsC;EAChE,MAAM,QAAQ,KAAK,UAAU,SAAS,UAAU;EAChD,KAAK,OAAO,OAAO,OAAO;EAC1B,OAAO;CACT;;;;;;;;CASA,UAAU,SAAiB,YAAuC;EAChE,MAAM,WAAW,KAAK,OAAO,IAAI,OAAO;EACxC,IAAI,CAAC,UAAU,QAAQ,OAAO,CAAC;EAC/B,IAAI,eAAe,QAAW,OAAO,SAAS,KAAK,MAAM,cAAc,CAAC,CAAC;EAGzE,MAAM,uBAAO,IAAI,IAA0B;EAC3C,KAAK,MAAM,KAAK,UAAU,KAAK,IAAI,EAAE,YAAY,CAAC,CAAC,QAAQ,CAAC;EAe5D,OAZiB,SAAS,QAAQ,MAAM;GACtC,IAAI,KAAyB,EAAE,YAAY,CAAC,CAAC;GAC7C,OAAO,IAAI;IACT,IAAI,OAAO,YAAY,OAAO;IAE9B,MAAM,WADS,KAAK,IAAI,EACF,CAAC,EAAE,mBAAmB,UAAU;IACtD,IAAI,aAAa,IAAI;IACrB,KAAK;GACP;GACA,OAAO;EACT,CAEc,CAAC,CAAC,KAAK,MAAM,cAAc,CAAC,CAAC;CAC7C;CAEA,WAA0B;EACxB,KAAK,OAAO,MAAM;EAClB,OAAO,QAAQ,QAAQ;CACzB;CAEA,aAA4B;EAC1B,OAAO,QAAQ,QAAQ;CACzB;AACF;AAEA,SAAS,WAAW,IAA8B;CAChD,OAAO,GAAG,KAAK,MAAO,GAAG,KAAK;AAChC;AAEA,SAAS,eAAe,GAAoC;CAC1D,IAAI,OAAO,MAAM,YAAY,OAAO,MAAM,YAAY,OAAO,MAAM,WACjE,OAAO;CACT,IAAI,MAAM,QAAQ,CAAC,KAAK,EAAE,SAAS,GAAG;EACpC,MAAM,IAAI,OAAO,EAAE;EACnB,QACG,MAAM,YAAY,MAAM,YAAY,MAAM,cAC3C,EAAE,OAAO,MAAM,OAAO,MAAM,CAAC;CAEjC;CACA,OAAO;AACT;AAEA,SAAgB,cAAc,MAAoC;CAChE,MAAM,QAA2C,CAAC;CAClD,KAAK,MAAM,CAAC,GAAG,MAAM,OAAO,QAAQ,KAAK,UAAU,GACjD,IAAI,eAAe,CAAC,GAAG,MAAM,KAAK;CAEpC,OAAO;EACL,QAAQ,KAAK,YAAY,CAAC,CAAC;EAC3B,cAAc,KAAK,mBAAmB,UAAU;EAChD,MAAM,KAAK;EACX,aAAa,WAAW,KAAK,SAA6B;EAC1D,YAAY,WAAW,KAAK,QAA4B;EACxD,QACE,KAAK,OAAO,SAASA,kCAAe,QAChC,UACA,KAAK,OAAO,SAASA,kCAAe,KAClC,OACA;EACR,eAAe,KAAK,OAAO,WAAW;EACtC,YAAY,OAAO,KAAK,KAAK,CAAC,CAAC,SAAS,IAAI,QAAQ;CACtD;AACF"}
@@ -1,5 +1,4 @@
1
1
  import { ReadableSpan, SpanExporter } from "@opentelemetry/sdk-trace-base";
2
-
3
2
  //#region src/test-span-collector.d.ts
4
3
  /** Attribute value types that survive serialization */
5
4
  type SerializableValue = string | number | boolean | string[] | number[] | boolean[];
@@ -1 +1 @@
1
- {"version":3,"file":"test-span-collector.d.cts","names":[],"sources":["../src/test-span-collector.ts"],"mappings":";;;;KA8CK,iBAAA;;;;;;;;KAeO,cAAA;EACV,MAAA;EACA,YAAA;EACA,IAAA;EACA,WAAA;EACA,UAAA;EACA,MAAA;EACA,aAAA;EACA,UAAA,GAAa,MAAM,SAAS,iBAAA;AAAA;AAAA,cAGjB,iBAAA,YAA6B,YAAA;EAAA,QAChC,MAAA;EAER,MAAA,CACE,KAAA,EAAO,YAAA,IACP,QAAA,GAAW,MAAA;IAAU,IAAA;EAAA;EAoF0B;;AAAc;;;EAjE/D,UAAA,CAAW,OAAA,UAAiB,UAAA,WAAqB,cAAA;;;;;;;;EAajD,SAAA,CAAU,OAAA,UAAiB,UAAA,YAAsB,cAAA;EAyBjD,QAAA,IAAY,OAAA;EAKZ,UAAA,IAAc,OAAA;AAAA;AAAA,iBAsBA,aAAA,CAAc,IAAA,EAAM,YAAA,GAAe,cAAc"}
1
+ {"version":3,"file":"test-span-collector.d.cts","names":[],"sources":["../src/test-span-collector.ts"],"mappings":";;;KA8CK;;;;;;;;KAUO;EACV;EACA;EACA;EACA;EACA;EACA;EACA;EACA,aAAa,eAAe;;cAGjB,6BAA6B;UAChC;EAER,OACE,OAAO,gBACP,WAAW;IAAU;;;;;;;EAmBvB,WAAW,iBAAiB,qBAAqB;;;;;;;;EAajD,UAAU,iBAAiB,sBAAsB;EAyBjD,YAAY;EAKZ,cAAc;;iBAsBA,cAAc,MAAM,eAAe"}
@@ -1,5 +1,4 @@
1
1
  import { ReadableSpan, SpanExporter } from "@opentelemetry/sdk-trace-base";
2
-
3
2
  //#region src/test-span-collector.d.ts
4
3
  /** Attribute value types that survive serialization */
5
4
  type SerializableValue = string | number | boolean | string[] | number[] | boolean[];
@@ -1 +1 @@
1
- {"version":3,"file":"test-span-collector.d.ts","names":[],"sources":["../src/test-span-collector.ts"],"mappings":";;;;KA8CK,iBAAA;;;;;;;;KAeO,cAAA;EACV,MAAA;EACA,YAAA;EACA,IAAA;EACA,WAAA;EACA,UAAA;EACA,MAAA;EACA,aAAA;EACA,UAAA,GAAa,MAAM,SAAS,iBAAA;AAAA;AAAA,cAGjB,iBAAA,YAA6B,YAAA;EAAA,QAChC,MAAA;EAER,MAAA,CACE,KAAA,EAAO,YAAA,IACP,QAAA,GAAW,MAAA;IAAU,IAAA;EAAA;EAoF0B;;AAAc;;;EAjE/D,UAAA,CAAW,OAAA,UAAiB,UAAA,WAAqB,cAAA;;;;;;;;EAajD,SAAA,CAAU,OAAA,UAAiB,UAAA,YAAsB,cAAA;EAyBjD,QAAA,IAAY,OAAA;EAKZ,UAAA,IAAc,OAAA;AAAA;AAAA,iBAsBA,aAAA,CAAc,IAAA,EAAM,YAAA,GAAe,cAAc"}
1
+ {"version":3,"file":"test-span-collector.d.ts","names":[],"sources":["../src/test-span-collector.ts"],"mappings":";;;KA8CK;;;;;;;;KAUO;EACV;EACA;EACA;EACA;EACA;EACA;EACA;EACA,aAAa,eAAe;;cAGjB,6BAA6B;UAChC;EAER,OACE,OAAO,gBACP,WAAW;IAAU;;;;;;;EAmBvB,WAAW,iBAAiB,qBAAqB;;;;;;;;EAajD,UAAU,iBAAiB,sBAAsB;EAyBjD,YAAY;EAKZ,cAAc;;iBAsBA,cAAc,MAAM,eAAe"}
@@ -1 +1 @@
1
- {"version":3,"file":"test-span-collector.js","names":[],"sources":["../src/test-span-collector.ts"],"sourcesContent":["/**\n * TestSpanCollector — SpanExporter that groups finished spans by traceId\n * and drains per-trace for embedding in test metadata.\n *\n * Wire it to a tracer provider, register that provider with autotel, then\n * create spans through autotel's tracer so finished spans reach the collector.\n *\n * @example\n * ```typescript\n * import { TestSpanCollector } from 'autotel/test-span-collector';\n * import { SimpleSpanProcessor } from 'autotel/processors';\n * import { setAutotelTracerProvider, getAutotelTracer } from 'autotel';\n * import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node';\n *\n * const collector = new TestSpanCollector();\n *\n * // OTel SDK v2: pass processors at construction, then register the provider.\n * const provider = new NodeTracerProvider({\n * spanProcessors: [new SimpleSpanProcessor(collector)],\n * });\n * setAutotelTracerProvider(provider);\n *\n * const tracer = getAutotelTracer('test');\n * const root = tracer.startSpan('operation');\n * // ...work, child spans...\n * root.end();\n *\n * const spans = collector.drainTrace(\n * root.spanContext().traceId,\n * root.spanContext().spanId,\n * );\n * // spans contains the root span and its descendants\n * ```\n *\n * For high-level instrumentation written with `span()` / `track()`, prefer\n * `createTraceCollector()` from `autotel/testing`. For a plain low-level\n * exporter, use `InMemorySpanExporter` from `autotel/exporters`.\n */\n\nimport type { SpanExporter, ReadableSpan } from '@opentelemetry/sdk-trace-base';\nimport { SpanStatusCode } from '@opentelemetry/api';\n\n/** @see ExportResultCode from @opentelemetry/core */\nconst ExportResultCode = { SUCCESS: 0, FAILED: 1 } as const;\n\n/** Attribute value types that survive serialization */\ntype SerializableValue =\n | string\n | number\n | boolean\n | string[]\n | number[]\n | boolean[];\n\n/**\n * Portable serialized span for embedding in test metadata.\n * `startTimeMs` is derived from OTel HrTime — epoch-based wall-clock ms in the current SDK.\n *\n * Defined as a `type` (not `interface`) so it is assignable to\n * `Record<string, unknown>` in TypeScript 6+ strict mode.\n */\nexport type SerializedSpan = {\n spanId: string;\n parentSpanId?: string;\n name: string;\n startTimeMs: number;\n durationMs: number;\n status: 'ok' | 'error' | 'unset';\n statusMessage?: string;\n attributes?: Record<string, SerializableValue>;\n};\n\nexport class TestSpanCollector implements SpanExporter {\n private traces = new Map<string, ReadableSpan[]>();\n\n export(\n spans: ReadableSpan[],\n callback: (result: { code: number }) => void,\n ): void {\n for (const span of spans) {\n const traceId = span.spanContext().traceId;\n let list = this.traces.get(traceId);\n if (!list) {\n list = [];\n this.traces.set(traceId, list);\n }\n list.push(span);\n }\n callback({ code: ExportResultCode.SUCCESS });\n }\n\n /**\n * Drain and serialize spans that are descendants of `rootSpanId` within `traceId`.\n * Filters to the subtree rooted at the test span to prevent cross-test mixing.\n * Removes the entire traceId entry from the collector.\n */\n drainTrace(traceId: string, rootSpanId: string): SerializedSpan[] {\n const spans = this.peekTrace(traceId, rootSpanId);\n this.traces.delete(traceId);\n return spans;\n }\n\n /**\n * Serialize the finished spans of `traceId` without removing them — safe to\n * call repeatedly while a trace is still growing (e.g. a scenario checker\n * polling until an async flow's completion boundary closes).\n *\n * With `rootSpanId`, filters to that span's subtree like {@link drainTrace}.\n */\n peekTrace(traceId: string, rootSpanId?: string): SerializedSpan[] {\n const allSpans = this.traces.get(traceId);\n if (!allSpans?.length) return [];\n if (rootSpanId === undefined) return allSpans.map((s) => serializeSpan(s));\n\n // Build spanId → span index for efficient parent-chain walking\n const byId = new Map<string, ReadableSpan>();\n for (const s of allSpans) byId.set(s.spanContext().spanId, s);\n\n // Filter to spans that are the root or descendants of rootSpanId\n const included = allSpans.filter((s) => {\n let id: string | undefined = s.spanContext().spanId;\n while (id) {\n if (id === rootSpanId) return true;\n const parent = byId.get(id);\n const parentId = parent?.parentSpanContext?.spanId || undefined;\n if (parentId === id) break; // cycle guard\n id = parentId;\n }\n return false;\n });\n\n return included.map((s) => serializeSpan(s));\n }\n\n shutdown(): Promise<void> {\n this.traces.clear();\n return Promise.resolve();\n }\n\n forceFlush(): Promise<void> {\n return Promise.resolve();\n }\n}\n\nfunction hrTimeToMs(hr: [number, number]): number {\n return hr[0] * 1000 + hr[1] / 1_000_000;\n}\n\nfunction isSerializable(v: unknown): v is SerializableValue {\n if (typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean')\n return true;\n if (Array.isArray(v) && v.length > 0) {\n const t = typeof v[0];\n return (\n (t === 'string' || t === 'number' || t === 'boolean') &&\n v.every((e) => typeof e === t)\n );\n }\n return false;\n}\n\nexport function serializeSpan(span: ReadableSpan): SerializedSpan {\n const attrs: Record<string, SerializableValue> = {};\n for (const [k, v] of Object.entries(span.attributes)) {\n if (isSerializable(v)) attrs[k] = v;\n }\n return {\n spanId: span.spanContext().spanId,\n parentSpanId: span.parentSpanContext?.spanId || undefined,\n name: span.name,\n startTimeMs: hrTimeToMs(span.startTime as [number, number]),\n durationMs: hrTimeToMs(span.duration as [number, number]),\n status:\n span.status.code === SpanStatusCode.ERROR\n ? 'error'\n : span.status.code === SpanStatusCode.OK\n ? 'ok'\n : 'unset',\n statusMessage: span.status.message || undefined,\n attributes: Object.keys(attrs).length > 0 ? attrs : undefined,\n };\n}\n"],"mappings":";;;;AA2CA,MAAM,mBAAmB;CAAE,SAAS;CAAG,QAAQ;AAAE;AA6BjD,IAAa,oBAAb,MAAuD;CACrD,AAAQ,yBAAS,IAAI,IAA4B;CAEjD,OACE,OACA,UACM;EACN,KAAK,MAAM,QAAQ,OAAO;GACxB,MAAM,UAAU,KAAK,YAAY,CAAC,CAAC;GACnC,IAAI,OAAO,KAAK,OAAO,IAAI,OAAO;GAClC,IAAI,CAAC,MAAM;IACT,OAAO,CAAC;IACR,KAAK,OAAO,IAAI,SAAS,IAAI;GAC/B;GACA,KAAK,KAAK,IAAI;EAChB;EACA,SAAS,EAAE,MAAM,iBAAiB,QAAQ,CAAC;CAC7C;;;;;;CAOA,WAAW,SAAiB,YAAsC;EAChE,MAAM,QAAQ,KAAK,UAAU,SAAS,UAAU;EAChD,KAAK,OAAO,OAAO,OAAO;EAC1B,OAAO;CACT;;;;;;;;CASA,UAAU,SAAiB,YAAuC;EAChE,MAAM,WAAW,KAAK,OAAO,IAAI,OAAO;EACxC,IAAI,CAAC,UAAU,QAAQ,OAAO,CAAC;EAC/B,IAAI,eAAe,QAAW,OAAO,SAAS,KAAK,MAAM,cAAc,CAAC,CAAC;EAGzE,MAAM,uBAAO,IAAI,IAA0B;EAC3C,KAAK,MAAM,KAAK,UAAU,KAAK,IAAI,EAAE,YAAY,CAAC,CAAC,QAAQ,CAAC;EAe5D,OAZiB,SAAS,QAAQ,MAAM;GACtC,IAAI,KAAyB,EAAE,YAAY,CAAC,CAAC;GAC7C,OAAO,IAAI;IACT,IAAI,OAAO,YAAY,OAAO;IAE9B,MAAM,WADS,KAAK,IAAI,EACF,CAAC,EAAE,mBAAmB,UAAU;IACtD,IAAI,aAAa,IAAI;IACrB,KAAK;GACP;GACA,OAAO;EACT,CAEc,CAAC,CAAC,KAAK,MAAM,cAAc,CAAC,CAAC;CAC7C;CAEA,WAA0B;EACxB,KAAK,OAAO,MAAM;EAClB,OAAO,QAAQ,QAAQ;CACzB;CAEA,aAA4B;EAC1B,OAAO,QAAQ,QAAQ;CACzB;AACF;AAEA,SAAS,WAAW,IAA8B;CAChD,OAAO,GAAG,KAAK,MAAO,GAAG,KAAK;AAChC;AAEA,SAAS,eAAe,GAAoC;CAC1D,IAAI,OAAO,MAAM,YAAY,OAAO,MAAM,YAAY,OAAO,MAAM,WACjE,OAAO;CACT,IAAI,MAAM,QAAQ,CAAC,KAAK,EAAE,SAAS,GAAG;EACpC,MAAM,IAAI,OAAO,EAAE;EACnB,QACG,MAAM,YAAY,MAAM,YAAY,MAAM,cAC3C,EAAE,OAAO,MAAM,OAAO,MAAM,CAAC;CAEjC;CACA,OAAO;AACT;AAEA,SAAgB,cAAc,MAAoC;CAChE,MAAM,QAA2C,CAAC;CAClD,KAAK,MAAM,CAAC,GAAG,MAAM,OAAO,QAAQ,KAAK,UAAU,GACjD,IAAI,eAAe,CAAC,GAAG,MAAM,KAAK;CAEpC,OAAO;EACL,QAAQ,KAAK,YAAY,CAAC,CAAC;EAC3B,cAAc,KAAK,mBAAmB,UAAU;EAChD,MAAM,KAAK;EACX,aAAa,WAAW,KAAK,SAA6B;EAC1D,YAAY,WAAW,KAAK,QAA4B;EACxD,QACE,KAAK,OAAO,SAAS,eAAe,QAChC,UACA,KAAK,OAAO,SAAS,eAAe,KAClC,OACA;EACR,eAAe,KAAK,OAAO,WAAW;EACtC,YAAY,OAAO,KAAK,KAAK,CAAC,CAAC,SAAS,IAAI,QAAQ;CACtD;AACF"}
1
+ {"version":3,"file":"test-span-collector.js","names":[],"sources":["../src/test-span-collector.ts"],"sourcesContent":["/**\n * TestSpanCollector — SpanExporter that groups finished spans by traceId\n * and drains per-trace for embedding in test metadata.\n *\n * Wire it to a tracer provider, register that provider with autotel, then\n * create spans through autotel's tracer so finished spans reach the collector.\n *\n * @example\n * ```typescript\n * import { TestSpanCollector } from 'autotel/test-span-collector';\n * import { SimpleSpanProcessor } from 'autotel/processors';\n * import { setAutotelTracerProvider, getAutotelTracer } from 'autotel';\n * import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node';\n *\n * const collector = new TestSpanCollector();\n *\n * // OTel SDK v2: pass processors at construction, then register the provider.\n * const provider = new NodeTracerProvider({\n * spanProcessors: [new SimpleSpanProcessor(collector)],\n * });\n * setAutotelTracerProvider(provider);\n *\n * const tracer = getAutotelTracer('test');\n * const root = tracer.startSpan('operation');\n * // ...work, child spans...\n * root.end();\n *\n * const spans = collector.drainTrace(\n * root.spanContext().traceId,\n * root.spanContext().spanId,\n * );\n * // spans contains the root span and its descendants\n * ```\n *\n * For high-level instrumentation written with `span()` / `track()`, prefer\n * `createTraceCollector()` from `autotel/testing`. For a plain low-level\n * exporter, use `InMemorySpanExporter` from `autotel/exporters`.\n */\n\nimport type { SpanExporter, ReadableSpan } from '@opentelemetry/sdk-trace-base';\nimport { SpanStatusCode } from '@opentelemetry/api';\n\n/** @see ExportResultCode from @opentelemetry/core */\nconst ExportResultCode = { SUCCESS: 0, FAILED: 1 } as const;\n\n/** Attribute value types that survive serialization */\ntype SerializableValue =\n string | number | boolean | string[] | number[] | boolean[];\n\n/**\n * Portable serialized span for embedding in test metadata.\n * `startTimeMs` is derived from OTel HrTime — epoch-based wall-clock ms in the current SDK.\n *\n * Defined as a `type` (not `interface`) so it is assignable to\n * `Record<string, unknown>` in TypeScript 6+ strict mode.\n */\nexport type SerializedSpan = {\n spanId: string;\n parentSpanId?: string;\n name: string;\n startTimeMs: number;\n durationMs: number;\n status: 'ok' | 'error' | 'unset';\n statusMessage?: string;\n attributes?: Record<string, SerializableValue>;\n};\n\nexport class TestSpanCollector implements SpanExporter {\n private traces = new Map<string, ReadableSpan[]>();\n\n export(\n spans: ReadableSpan[],\n callback: (result: { code: number }) => void,\n ): void {\n for (const span of spans) {\n const traceId = span.spanContext().traceId;\n let list = this.traces.get(traceId);\n if (!list) {\n list = [];\n this.traces.set(traceId, list);\n }\n list.push(span);\n }\n callback({ code: ExportResultCode.SUCCESS });\n }\n\n /**\n * Drain and serialize spans that are descendants of `rootSpanId` within `traceId`.\n * Filters to the subtree rooted at the test span to prevent cross-test mixing.\n * Removes the entire traceId entry from the collector.\n */\n drainTrace(traceId: string, rootSpanId: string): SerializedSpan[] {\n const spans = this.peekTrace(traceId, rootSpanId);\n this.traces.delete(traceId);\n return spans;\n }\n\n /**\n * Serialize the finished spans of `traceId` without removing them — safe to\n * call repeatedly while a trace is still growing (e.g. a scenario checker\n * polling until an async flow's completion boundary closes).\n *\n * With `rootSpanId`, filters to that span's subtree like {@link drainTrace}.\n */\n peekTrace(traceId: string, rootSpanId?: string): SerializedSpan[] {\n const allSpans = this.traces.get(traceId);\n if (!allSpans?.length) return [];\n if (rootSpanId === undefined) return allSpans.map((s) => serializeSpan(s));\n\n // Build spanId → span index for efficient parent-chain walking\n const byId = new Map<string, ReadableSpan>();\n for (const s of allSpans) byId.set(s.spanContext().spanId, s);\n\n // Filter to spans that are the root or descendants of rootSpanId\n const included = allSpans.filter((s) => {\n let id: string | undefined = s.spanContext().spanId;\n while (id) {\n if (id === rootSpanId) return true;\n const parent = byId.get(id);\n const parentId = parent?.parentSpanContext?.spanId || undefined;\n if (parentId === id) break; // cycle guard\n id = parentId;\n }\n return false;\n });\n\n return included.map((s) => serializeSpan(s));\n }\n\n shutdown(): Promise<void> {\n this.traces.clear();\n return Promise.resolve();\n }\n\n forceFlush(): Promise<void> {\n return Promise.resolve();\n }\n}\n\nfunction hrTimeToMs(hr: [number, number]): number {\n return hr[0] * 1000 + hr[1] / 1_000_000;\n}\n\nfunction isSerializable(v: unknown): v is SerializableValue {\n if (typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean')\n return true;\n if (Array.isArray(v) && v.length > 0) {\n const t = typeof v[0];\n return (\n (t === 'string' || t === 'number' || t === 'boolean') &&\n v.every((e) => typeof e === t)\n );\n }\n return false;\n}\n\nexport function serializeSpan(span: ReadableSpan): SerializedSpan {\n const attrs: Record<string, SerializableValue> = {};\n for (const [k, v] of Object.entries(span.attributes)) {\n if (isSerializable(v)) attrs[k] = v;\n }\n return {\n spanId: span.spanContext().spanId,\n parentSpanId: span.parentSpanContext?.spanId || undefined,\n name: span.name,\n startTimeMs: hrTimeToMs(span.startTime as [number, number]),\n durationMs: hrTimeToMs(span.duration as [number, number]),\n status:\n span.status.code === SpanStatusCode.ERROR\n ? 'error'\n : span.status.code === SpanStatusCode.OK\n ? 'ok'\n : 'unset',\n statusMessage: span.status.message || undefined,\n attributes: Object.keys(attrs).length > 0 ? attrs : undefined,\n };\n}\n"],"mappings":";;;;AA2CA,MAAM,mBAAmB;CAAE,SAAS;CAAG,QAAQ;AAAE;AAwBjD,IAAa,oBAAb,MAAuD;CACrD,AAAQ,yBAAS,IAAI,IAA4B;CAEjD,OACE,OACA,UACM;EACN,KAAK,MAAM,QAAQ,OAAO;GACxB,MAAM,UAAU,KAAK,YAAY,CAAC,CAAC;GACnC,IAAI,OAAO,KAAK,OAAO,IAAI,OAAO;GAClC,IAAI,CAAC,MAAM;IACT,OAAO,CAAC;IACR,KAAK,OAAO,IAAI,SAAS,IAAI;GAC/B;GACA,KAAK,KAAK,IAAI;EAChB;EACA,SAAS,EAAE,MAAM,iBAAiB,QAAQ,CAAC;CAC7C;;;;;;CAOA,WAAW,SAAiB,YAAsC;EAChE,MAAM,QAAQ,KAAK,UAAU,SAAS,UAAU;EAChD,KAAK,OAAO,OAAO,OAAO;EAC1B,OAAO;CACT;;;;;;;;CASA,UAAU,SAAiB,YAAuC;EAChE,MAAM,WAAW,KAAK,OAAO,IAAI,OAAO;EACxC,IAAI,CAAC,UAAU,QAAQ,OAAO,CAAC;EAC/B,IAAI,eAAe,QAAW,OAAO,SAAS,KAAK,MAAM,cAAc,CAAC,CAAC;EAGzE,MAAM,uBAAO,IAAI,IAA0B;EAC3C,KAAK,MAAM,KAAK,UAAU,KAAK,IAAI,EAAE,YAAY,CAAC,CAAC,QAAQ,CAAC;EAe5D,OAZiB,SAAS,QAAQ,MAAM;GACtC,IAAI,KAAyB,EAAE,YAAY,CAAC,CAAC;GAC7C,OAAO,IAAI;IACT,IAAI,OAAO,YAAY,OAAO;IAE9B,MAAM,WADS,KAAK,IAAI,EACF,CAAC,EAAE,mBAAmB,UAAU;IACtD,IAAI,aAAa,IAAI;IACrB,KAAK;GACP;GACA,OAAO;EACT,CAEc,CAAC,CAAC,KAAK,MAAM,cAAc,CAAC,CAAC;CAC7C;CAEA,WAA0B;EACxB,KAAK,OAAO,MAAM;EAClB,OAAO,QAAQ,QAAQ;CACzB;CAEA,aAA4B;EAC1B,OAAO,QAAQ,QAAQ;CACzB;AACF;AAEA,SAAS,WAAW,IAA8B;CAChD,OAAO,GAAG,KAAK,MAAO,GAAG,KAAK;AAChC;AAEA,SAAS,eAAe,GAAoC;CAC1D,IAAI,OAAO,MAAM,YAAY,OAAO,MAAM,YAAY,OAAO,MAAM,WACjE,OAAO;CACT,IAAI,MAAM,QAAQ,CAAC,KAAK,EAAE,SAAS,GAAG;EACpC,MAAM,IAAI,OAAO,EAAE;EACnB,QACG,MAAM,YAAY,MAAM,YAAY,MAAM,cAC3C,EAAE,OAAO,MAAM,OAAO,MAAM,CAAC;CAEjC;CACA,OAAO;AACT;AAEA,SAAgB,cAAc,MAAoC;CAChE,MAAM,QAA2C,CAAC;CAClD,KAAK,MAAM,CAAC,GAAG,MAAM,OAAO,QAAQ,KAAK,UAAU,GACjD,IAAI,eAAe,CAAC,GAAG,MAAM,KAAK;CAEpC,OAAO;EACL,QAAQ,KAAK,YAAY,CAAC,CAAC;EAC3B,cAAc,KAAK,mBAAmB,UAAU;EAChD,MAAM,KAAK;EACX,aAAa,WAAW,KAAK,SAA6B;EAC1D,YAAY,WAAW,KAAK,QAA4B;EACxD,QACE,KAAK,OAAO,SAAS,eAAe,QAChC,UACA,KAAK,OAAO,SAAS,eAAe,KAClC,OACA;EACR,eAAe,KAAK,OAAO,WAAW;EACtC,YAAY,OAAO,KAAK,KAAK,CAAC,CAAC,SAAS,IAAI,QAAQ;CACtD;AACF"}