autotel 4.2.5 → 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 (387) 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 +10 -15
  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 +11 -16
  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 +13 -1
  251. package/dist/test-span-collector.cjs.map +1 -1
  252. package/dist/test-span-collector.d.cts +8 -1
  253. package/dist/test-span-collector.d.cts.map +1 -1
  254. package/dist/test-span-collector.d.ts +8 -1
  255. package/dist/test-span-collector.d.ts.map +1 -1
  256. package/dist/test-span-collector.js +13 -1
  257. package/dist/test-span-collector.js.map +1 -1
  258. package/dist/testing.cjs +76 -11
  259. package/dist/testing.cjs.map +1 -1
  260. package/dist/testing.d.cts +30 -5
  261. package/dist/testing.d.cts.map +1 -1
  262. package/dist/testing.d.ts +30 -5
  263. package/dist/testing.d.ts.map +1 -1
  264. package/dist/testing.js +73 -12
  265. package/dist/testing.js.map +1 -1
  266. package/dist/{trace-context-Cijqoi6e.d.ts → trace-context-IuyoPyNq.d.cts} +9 -9
  267. package/dist/trace-context-IuyoPyNq.d.cts.map +1 -0
  268. package/dist/{trace-context-Cijqoi6e.d.cts → trace-context-IuyoPyNq.d.ts} +9 -9
  269. package/dist/trace-context-IuyoPyNq.d.ts.map +1 -0
  270. package/dist/trace-factory-validation-BjVAinz6.js +13 -0
  271. package/dist/trace-factory-validation-BjVAinz6.js.map +1 -0
  272. package/dist/trace-factory-validation-Dc8epn87.cjs +19 -0
  273. package/dist/trace-factory-validation-Dc8epn87.cjs.map +1 -0
  274. package/dist/trace-helpers.cjs +7 -7
  275. package/dist/trace-helpers.cjs.map +1 -1
  276. package/dist/trace-helpers.d.cts +6 -7
  277. package/dist/trace-helpers.d.cts.map +1 -1
  278. package/dist/trace-helpers.d.ts +6 -7
  279. package/dist/trace-helpers.d.ts.map +1 -1
  280. package/dist/trace-helpers.js +7 -7
  281. package/dist/trace-helpers.js.map +1 -1
  282. package/dist/tracer-provider.cjs +27 -0
  283. package/dist/tracer-provider.cjs.map +1 -1
  284. package/dist/tracer-provider.d.cts +19 -2
  285. package/dist/tracer-provider.d.cts.map +1 -1
  286. package/dist/tracer-provider.d.ts +19 -2
  287. package/dist/tracer-provider.d.ts.map +1 -1
  288. package/dist/tracer-provider.js +27 -1
  289. package/dist/tracer-provider.js.map +1 -1
  290. package/dist/{track-D7O7oIZa.js → track-BBcxEJEC.js} +7 -12
  291. package/dist/track-BBcxEJEC.js.map +1 -0
  292. package/dist/{track-eovhCJ-I.cjs → track-BI6dmZ2X.cjs} +6 -17
  293. package/dist/track-BI6dmZ2X.cjs.map +1 -0
  294. package/dist/validate.cjs.map +1 -1
  295. package/dist/validate.d.cts +1 -2
  296. package/dist/validate.d.cts.map +1 -1
  297. package/dist/validate.d.ts +1 -2
  298. package/dist/validate.d.ts.map +1 -1
  299. package/dist/validate.js.map +1 -1
  300. package/dist/validation-attributes.d.cts +17 -8
  301. package/dist/validation-attributes.d.cts.map +1 -1
  302. package/dist/validation-attributes.d.ts +17 -8
  303. package/dist/validation-attributes.d.ts.map +1 -1
  304. package/dist/webhook.cjs +5 -4
  305. package/dist/webhook.cjs.map +1 -1
  306. package/dist/webhook.d.cts +4 -4
  307. package/dist/webhook.d.cts.map +1 -1
  308. package/dist/webhook.d.ts +4 -4
  309. package/dist/webhook.d.ts.map +1 -1
  310. package/dist/webhook.js +5 -4
  311. package/dist/webhook.js.map +1 -1
  312. package/dist/workflow-distributed.cjs +5 -5
  313. package/dist/workflow-distributed.cjs.map +1 -1
  314. package/dist/workflow-distributed.d.cts +25 -15
  315. package/dist/workflow-distributed.d.cts.map +1 -1
  316. package/dist/workflow-distributed.d.ts +25 -15
  317. package/dist/workflow-distributed.d.ts.map +1 -1
  318. package/dist/workflow-distributed.js +5 -5
  319. package/dist/workflow-distributed.js.map +1 -1
  320. package/dist/workflow.cjs +24 -30
  321. package/dist/workflow.cjs.map +1 -1
  322. package/dist/workflow.d.cts +11 -6
  323. package/dist/workflow.d.cts.map +1 -1
  324. package/dist/workflow.d.ts +11 -6
  325. package/dist/workflow.d.ts.map +1 -1
  326. package/dist/workflow.js +24 -30
  327. package/dist/workflow.js.map +1 -1
  328. package/dist/yaml-config.d.cts +2 -3
  329. package/dist/yaml-config.d.cts.map +1 -1
  330. package/dist/yaml-config.d.ts +2 -3
  331. package/dist/yaml-config.d.ts.map +1 -1
  332. package/package.json +23 -14
  333. package/skills/analyze-traces/SKILL.md +42 -13
  334. package/skills/autotel-core/SKILL.md +24 -17
  335. package/skills/autotel-events/SKILL.md +1 -1
  336. package/skills/autotel-frameworks/SKILL.md +41 -6
  337. package/skills/autotel-instrumentation/SKILL.md +14 -9
  338. package/skills/autotel-request-logging/SKILL.md +19 -14
  339. package/skills/autotel-structured-errors/SKILL.md +2 -2
  340. package/skills/build-audit-trails/SKILL.md +11 -11
  341. package/skills/build-audit-trails/references/audit-queries.md +3 -3
  342. package/skills/debug-missing-spans/SKILL.md +12 -12
  343. package/skills/migrate-to-autotel/SKILL.md +20 -20
  344. package/skills/review-otel-patterns/SKILL.md +44 -42
  345. package/skills/review-otel-patterns/references/code-review.md +4 -4
  346. package/skills/review-otel-patterns/references/processor-pipeline.md +3 -3
  347. package/skills/review-otel-patterns/references/structured-errors.md +1 -1
  348. package/skills/review-otel-patterns/references/wide-spans.md +2 -2
  349. package/skills/tune-sampling/SKILL.md +91 -16
  350. package/dist/attribute-redacting-processor-DtTS9xxh.d.cts.map +0 -1
  351. package/dist/attribute-redacting-processor-DtTS9xxh.d.ts.map +0 -1
  352. package/dist/canonical-log-line-processor-BcRuAdRk.d.ts.map +0 -1
  353. package/dist/canonical-log-line-processor-CcrDi6TO.cjs.map +0 -1
  354. package/dist/canonical-log-line-processor-FLezvFCB.js.map +0 -1
  355. package/dist/canonical-log-line-processor-xvjMgtpF.d.cts.map +0 -1
  356. package/dist/define-event-CreknLm3.d.ts.map +0 -1
  357. package/dist/define-event-HZRizPwz.d.cts.map +0 -1
  358. package/dist/event-BEm5IJ8U.js.map +0 -1
  359. package/dist/event-rzA1X5h3.cjs.map +0 -1
  360. package/dist/event-subscriber-D1XLkPzi.d.cts.map +0 -1
  361. package/dist/event-subscriber-D1XLkPzi.d.ts.map +0 -1
  362. package/dist/event-testing-BqRnI0z4.d.cts.map +0 -1
  363. package/dist/event-testing-CfFs3to2.d.ts.map +0 -1
  364. package/dist/filtering-span-processor-B8R8B7Uk.d.cts.map +0 -1
  365. package/dist/filtering-span-processor-B8R8B7Uk.d.ts.map +0 -1
  366. package/dist/functional-BCtxvYir.cjs +0 -1315
  367. package/dist/functional-BCtxvYir.cjs.map +0 -1
  368. package/dist/functional-CrAq8Az8.js +0 -1265
  369. package/dist/functional-CrAq8Az8.js.map +0 -1
  370. package/dist/index-Ck06vlW2.d.ts.map +0 -1
  371. package/dist/index-eKuioqT1.d.cts.map +0 -1
  372. package/dist/init-B7u-DjxM.d.ts.map +0 -1
  373. package/dist/init-BvBB45-T.js.map +0 -1
  374. package/dist/init-BxJEJ9Tn.cjs.map +0 -1
  375. package/dist/init-DSrRmVnz.d.cts.map +0 -1
  376. package/dist/logger-BauSUhUv.d.cts.map +0 -1
  377. package/dist/logger-BauSUhUv.d.ts.map +0 -1
  378. package/dist/metric-testing-DXdK3-Q3.d.ts.map +0 -1
  379. package/dist/metric-testing-MxvzChDp.d.cts.map +0 -1
  380. package/dist/sampling-CR0Va1VB.d.ts.map +0 -1
  381. package/dist/sampling-DfYhDJij.d.cts.map +0 -1
  382. package/dist/span-name-normalizer-8ZOGJuwv.d.cts.map +0 -1
  383. package/dist/span-name-normalizer-8ZOGJuwv.d.ts.map +0 -1
  384. package/dist/trace-context-Cijqoi6e.d.cts.map +0 -1
  385. package/dist/trace-context-Cijqoi6e.d.ts.map +0 -1
  386. package/dist/track-D7O7oIZa.js.map +0 -1
  387. package/dist/track-eovhCJ-I.cjs.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"validate.js","names":[],"sources":["../src/validate.ts"],"sourcesContent":["/**\n * Validation telemetry — connect runtime input validation (Zod or any\n * `safeParse` schema) to your traces and metrics at the boundaries where bad\n * data actually enters: HTTP bodies, events, messages.\n *\n * Today a `safeParse` failure either throws (no span, no metric, no alert) or\n * is silently swallowed in a handler. `defineValidator` makes the mismatch\n * **observable** — a `validation.*` span attribute set and a counter\n * incremented — with a per-validator `observe` vs `reject` mode:\n *\n * - `reject` (default): record telemetry, then throw a structured 400-shaped\n * error so the boundary can fail cleanly.\n * - `observe`: record telemetry, return the raw input so the handler continues\n * — useful for measuring real-world drift before you enforce it.\n *\n * **Not a security feature by default.** A malformed body is usually a bug or\n * version skew, not an attack. Validation telemetry is first-class on its own\n * metric; escalation to the security path is a deliberate opt-in via\n * {@link onValidationMismatch} (e.g. wired by `autotel-audit`), never automatic.\n *\n * **PII-safe by construction.** Only field *paths*, issue *codes*, and the\n * declared *type* are ever recorded — never the offending value, and never a\n * validator's error `message` (which routinely embeds the received value).\n */\n\nimport { trace } from '@opentelemetry/api';\nimport { createCounter } from './metric-helpers';\nimport {\n createStructuredError,\n type StructuredError,\n} from './structured-error';\nimport { hashJson } from './stable-hash';\nimport type { SchemaLike } from './define-event';\nimport {\n VALIDATION_ATTR,\n VALIDATION_ISSUE_CAP,\n VALIDATION_METRICS,\n} from './validation-attributes';\n\nexport type { SchemaLike } from './define-event';\n\nexport type ValidationMode = 'observe' | 'reject';\nexport type ValidationSeverity = 'info' | 'warning' | 'error';\n\n/** A single failing field, stripped of any payload values. */\nexport interface ValidationIssue {\n /** Dotted field path, e.g. `items.0.price`. Never a value. */\n path: string;\n /** Issue code (e.g. Zod's `invalid_type`, `too_small`). Never a value. */\n code: string;\n /** Declared type/constraint summary, e.g. `string`. Never a received value. */\n expected?: string;\n}\n\n/** Everything the recorder needs — already PII-stripped by the caller. */\nexport interface ValidationMismatch {\n /** Contract id, e.g. `POST /orders` or `order.placed`. */\n name: string;\n boundary: string;\n mode: ValidationMode;\n issues: ValidationIssue[];\n hash?: string;\n severity?: ValidationSeverity;\n}\n\nlet mismatchCounter: ReturnType<typeof createCounter> | undefined;\nfunction counter(): ReturnType<typeof createCounter> {\n if (!mismatchCounter) {\n mismatchCounter = createCounter(VALIDATION_METRICS.mismatches, {\n description: 'Input payloads that did not match their declared shape',\n });\n }\n return mismatchCounter;\n}\n\ntype MismatchListener = (mismatch: ValidationMismatch) => void;\nconst listeners = new Set<MismatchListener>();\n\n/**\n * Register an explicit handler called on every recorded mismatch — the opt-in\n * seam for escalating to security events, a webhook, or a custom sink. There is\n * no automatic, package-presence-driven escalation: nothing fires here unless\n * you (or a package you wire up) register a handler.\n *\n * Multiple subscribers coexist: a package (e.g. `autotel-audit` bridging to\n * security events) and your own app code (a webhook, a logger) can both\n * register and all fire. Returns an unsubscribe fn that removes only this\n * handler; registering the same function twice is a no-op (Set semantics).\n */\nexport function onValidationMismatch(handler: MismatchListener): () => void {\n listeners.add(handler);\n return () => {\n listeners.delete(handler);\n };\n}\n\nconst truncate = (values: string[]): string =>\n values.slice(0, VALIDATION_ISSUE_CAP).join(',');\n\n/**\n * Record a validation mismatch as telemetry: `validation.*` attributes on the\n * active span (if any) and an increment on `autotel.validation.mismatches`.\n * Fail-open — never throws, so instrumentation can't break the boundary.\n */\nexport function recordValidationMismatch(mismatch: ValidationMismatch): void {\n try {\n const paths = mismatch.issues.map((i) => i.path).filter(Boolean);\n const codes = [...new Set(mismatch.issues.map((i) => i.code))];\n\n const span = trace.getActiveSpan();\n if (span) {\n span.setAttributes({\n [VALIDATION_ATTR.name]: mismatch.name,\n [VALIDATION_ATTR.boundary]: mismatch.boundary,\n [VALIDATION_ATTR.mode]: mismatch.mode,\n [VALIDATION_ATTR.issueCount]: mismatch.issues.length,\n [VALIDATION_ATTR.issuePaths]: truncate(paths),\n [VALIDATION_ATTR.issueCodes]: truncate(codes),\n ...(mismatch.hash ? { [VALIDATION_ATTR.hash]: mismatch.hash } : {}),\n ...(mismatch.severity\n ? { [VALIDATION_ATTR.severity]: mismatch.severity }\n : {}),\n });\n }\n\n try {\n counter().add(1, {\n boundary: mismatch.boundary,\n validation: mismatch.name,\n mode: mismatch.mode,\n });\n } catch {\n // meter not initialised yet — skip the count, keep the span attrs\n }\n\n // Dispatch to every subscriber with per-listener fault isolation: one\n // throwing subscriber must not starve its peers or break the boundary.\n // Set iteration tolerates concurrent (un)subscription safely.\n for (const listener of listeners) {\n try {\n listener(mismatch);\n } catch {\n // a misbehaving subscriber must not break the boundary or its peers\n }\n }\n } catch {\n // fail-open: telemetry must never break the validated boundary\n }\n}\n\n/**\n * Normalise an arbitrary validation error into PII-safe issues. Reads only\n * `path`, `code`, and (when it is a declared type name) `expected` — and never\n * `message`, `received`, or any value-bearing field. Understands the Zod shape\n * (`error.issues`) and a generic `error.errors` fallback; returns `[]` for\n * anything unrecognised.\n */\nexport function formatValidationIssues(error: unknown): ValidationIssue[] {\n const raw = extractRawIssues(error);\n return raw.map((issue) => toSafeIssue(issue));\n}\n\nfunction extractRawIssues(error: unknown): Array<Record<string, unknown>> {\n if (error && typeof error === 'object') {\n const candidate =\n (error as { issues?: unknown }).issues ??\n (error as { errors?: unknown }).errors;\n if (Array.isArray(candidate)) {\n return candidate.filter(\n (i): i is Record<string, unknown> =>\n i !== null && typeof i === 'object',\n );\n }\n }\n return [];\n}\n\nfunction toSafeIssue(issue: Record<string, unknown>): ValidationIssue {\n const rawPath = issue.path;\n const path = Array.isArray(rawPath)\n ? rawPath.map(String).join('.')\n : typeof rawPath === 'string'\n ? rawPath\n : '';\n const code = typeof issue.code === 'string' ? issue.code : 'invalid';\n // `expected` is a declared type name in Zod (e.g. 'string'); safe. We never\n // read `received`/`message`/`value`, which can carry the offending payload.\n const expected =\n typeof issue.expected === 'string' ? issue.expected : undefined;\n return expected ? { path, code, expected } : { path, code };\n}\n\nexport interface DefineValidatorOptions<S> {\n /** Where validation runs. Defaults to `input`. */\n boundary?: string;\n /** `reject` (default): record then throw. `observe`: record then continue. */\n onMismatch?: ValidationMode;\n /** Project the schema to JSON Schema for a stable `validation.hash`. */\n toJsonSchema?: (schema: S) => unknown;\n severity?: ValidationSeverity;\n /** Build the error thrown in `reject` mode (defaults to a 400 structured error). */\n onReject?: (issues: ValidationIssue[], name: string) => Error;\n}\n\nexport type ValidatorResult<T> =\n | { success: true; data: T }\n | { success: false; issues: ValidationIssue[] };\n\nexport interface Validator<T> {\n readonly name: string;\n readonly mode: ValidationMode;\n /** Validate and record on failure; never throws. */\n safeParse(input: unknown): ValidatorResult<T>;\n /**\n * Validate, record on failure, then apply the mode: `reject` throws,\n * `observe` returns the raw input so the handler can continue.\n */\n parse(input: unknown): T;\n}\n\nfunction defaultRejectError(\n issues: ValidationIssue[],\n name: string,\n): StructuredError {\n return createStructuredError({\n name: 'ValidationError',\n status: 400,\n code: 'validation_failed',\n message: `Input for \"${name}\" did not match its declared shape.`,\n why: `${issues.length} field(s) failed validation: ${issues\n .map((i) => i.path || '(root)')\n .slice(0, VALIDATION_ISSUE_CAP)\n .join(', ')}.`,\n fix: 'Send a payload that matches the schema, or switch this validator to observe mode while you investigate.',\n // PII-safe: paths + codes only, no received values.\n details: { validation: name, issues },\n });\n}\n\n/**\n * Declare an expected input shape once and get a validator that records every\n * mismatch as telemetry.\n *\n * @example\n * ```ts\n * import { z } from 'zod';\n * import { defineValidator } from 'autotel/validate';\n *\n * const OrderBody = defineValidator('POST /orders', z.object({\n * items: z.array(z.object({ sku: z.string(), qty: z.number().int() })),\n * }), { boundary: 'http', toJsonSchema: (s) => z.toJSONSchema(s) });\n *\n * // reject mode (default): records + throws a 400-shaped structured error\n * const order = OrderBody.parse(req.body);\n *\n * // observe mode: records, returns the result, never throws\n * const result = OrderBody.safeParse(req.body);\n * if (!result.success) metrics.onDrift(result.issues);\n * ```\n */\nexport function defineValidator<T, S extends SchemaLike<T>>(\n name: string,\n schema: S,\n options: DefineValidatorOptions<S> = {},\n): Validator<T> {\n const mode = options.onMismatch ?? 'reject';\n const boundary = options.boundary ?? 'input';\n const hash = options.toJsonSchema\n ? hashJson(options.toJsonSchema(schema))\n : undefined;\n\n const record = (issues: ValidationIssue[]): void => {\n recordValidationMismatch({\n name,\n boundary,\n mode,\n issues,\n hash,\n severity: options.severity,\n });\n };\n\n return {\n name,\n mode,\n safeParse(input: unknown): ValidatorResult<T> {\n const parsed = schema.safeParse(input);\n if (parsed.success) return { success: true, data: parsed.data };\n const issues = formatValidationIssues(parsed.error);\n record(issues);\n return { success: false, issues };\n },\n parse(input: unknown): T {\n const parsed = schema.safeParse(input);\n if (parsed.success) return parsed.data;\n const issues = formatValidationIssues(parsed.error);\n record(issues);\n if (mode === 'reject') {\n throw (\n options.onReject?.(issues, name) ?? defaultRejectError(issues, name)\n );\n }\n // observe: continue with the raw input (documented type caveat)\n return input as T;\n },\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"validate.js","names":[],"sources":["../src/validate.ts"],"sourcesContent":["/**\n * Validation telemetry — connect runtime input validation (Zod or any\n * `safeParse` schema) to your traces and metrics at the boundaries where bad\n * data actually enters: HTTP bodies, events, messages.\n *\n * Today a `safeParse` failure either throws (no span, no metric, no alert) or\n * is silently swallowed in a handler. `defineValidator` makes the mismatch\n * **observable** — a `validation.*` span attribute set and a counter\n * incremented — with a per-validator `observe` vs `reject` mode:\n *\n * - `reject` (default): record telemetry, then throw a structured 400-shaped\n * error so the boundary can fail cleanly.\n * - `observe`: record telemetry, return the raw input so the handler continues\n * — useful for measuring real-world drift before you enforce it.\n *\n * **Not a security feature by default.** A malformed body is usually a bug or\n * version skew, not an attack. Validation telemetry is first-class on its own\n * metric; escalation to the security path is a deliberate opt-in via\n * {@link onValidationMismatch} (e.g. wired by `autotel-audit`), never automatic.\n *\n * **PII-safe by construction.** Only field *paths*, issue *codes*, and the\n * declared *type* are ever recorded — never the offending value, and never a\n * validator's error `message` (which routinely embeds the received value).\n */\n\nimport { trace } from '@opentelemetry/api';\nimport { createCounter } from './metric-helpers';\nimport {\n createStructuredError,\n type StructuredError,\n} from './structured-error';\nimport { hashJson } from './stable-hash';\nimport type { SchemaLike } from './define-event';\nimport {\n VALIDATION_ATTR,\n VALIDATION_ISSUE_CAP,\n VALIDATION_METRICS,\n} from './validation-attributes';\n\nexport type { SchemaLike } from './define-event';\n\nexport type ValidationMode = 'observe' | 'reject';\nexport type ValidationSeverity = 'info' | 'warning' | 'error';\n\n/** A single failing field, stripped of any payload values. */\nexport interface ValidationIssue {\n /** Dotted field path, e.g. `items.0.price`. Never a value. */\n path: string;\n /** Issue code (e.g. Zod's `invalid_type`, `too_small`). Never a value. */\n code: string;\n /** Declared type/constraint summary, e.g. `string`. Never a received value. */\n expected?: string;\n}\n\n/** Everything the recorder needs — already PII-stripped by the caller. */\nexport interface ValidationMismatch {\n /** Contract id, e.g. `POST /orders` or `order.placed`. */\n name: string;\n boundary: string;\n mode: ValidationMode;\n issues: ValidationIssue[];\n hash?: string;\n severity?: ValidationSeverity;\n}\n\nlet mismatchCounter: ReturnType<typeof createCounter> | undefined;\nfunction counter(): ReturnType<typeof createCounter> {\n if (!mismatchCounter) {\n mismatchCounter = createCounter(VALIDATION_METRICS.mismatches, {\n description: 'Input payloads that did not match their declared shape',\n });\n }\n return mismatchCounter;\n}\n\ntype MismatchListener = (mismatch: ValidationMismatch) => void;\nconst listeners = new Set<MismatchListener>();\n\n/**\n * Register an explicit handler called on every recorded mismatch — the opt-in\n * seam for escalating to security events, a webhook, or a custom sink. There is\n * no automatic, package-presence-driven escalation: nothing fires here unless\n * you (or a package you wire up) register a handler.\n *\n * Multiple subscribers coexist: a package (e.g. `autotel-audit` bridging to\n * security events) and your own app code (a webhook, a logger) can both\n * register and all fire. Returns an unsubscribe fn that removes only this\n * handler; registering the same function twice is a no-op (Set semantics).\n */\nexport function onValidationMismatch(handler: MismatchListener): () => void {\n listeners.add(handler);\n return () => {\n listeners.delete(handler);\n };\n}\n\nconst truncate = (values: string[]): string =>\n values.slice(0, VALIDATION_ISSUE_CAP).join(',');\n\n/**\n * Record a validation mismatch as telemetry: `validation.*` attributes on the\n * active span (if any) and an increment on `autotel.validation.mismatches`.\n * Fail-open — never throws, so instrumentation can't break the boundary.\n */\nexport function recordValidationMismatch(mismatch: ValidationMismatch): void {\n try {\n const paths = mismatch.issues.map((i) => i.path).filter(Boolean);\n const codes = [...new Set(mismatch.issues.map((i) => i.code))];\n\n const span = trace.getActiveSpan();\n if (span) {\n span.setAttributes({\n [VALIDATION_ATTR.name]: mismatch.name,\n [VALIDATION_ATTR.boundary]: mismatch.boundary,\n [VALIDATION_ATTR.mode]: mismatch.mode,\n [VALIDATION_ATTR.issueCount]: mismatch.issues.length,\n [VALIDATION_ATTR.issuePaths]: truncate(paths),\n [VALIDATION_ATTR.issueCodes]: truncate(codes),\n ...(mismatch.hash ? { [VALIDATION_ATTR.hash]: mismatch.hash } : {}),\n ...(mismatch.severity\n ? { [VALIDATION_ATTR.severity]: mismatch.severity }\n : {}),\n });\n }\n\n try {\n counter().add(1, {\n boundary: mismatch.boundary,\n validation: mismatch.name,\n mode: mismatch.mode,\n });\n } catch {\n // meter not initialised yet — skip the count, keep the span attrs\n }\n\n // Dispatch to every subscriber with per-listener fault isolation: one\n // throwing subscriber must not starve its peers or break the boundary.\n // Set iteration tolerates concurrent (un)subscription safely.\n for (const listener of listeners) {\n try {\n listener(mismatch);\n } catch {\n // a misbehaving subscriber must not break the boundary or its peers\n }\n }\n } catch {\n // fail-open: telemetry must never break the validated boundary\n }\n}\n\n/**\n * Normalise an arbitrary validation error into PII-safe issues. Reads only\n * `path`, `code`, and (when it is a declared type name) `expected` — and never\n * `message`, `received`, or any value-bearing field. Understands the Zod shape\n * (`error.issues`) and a generic `error.errors` fallback; returns `[]` for\n * anything unrecognised.\n */\nexport function formatValidationIssues(error: unknown): ValidationIssue[] {\n const raw = extractRawIssues(error);\n return raw.map((issue) => toSafeIssue(issue));\n}\n\nfunction extractRawIssues(error: unknown): Array<Record<string, unknown>> {\n if (error && typeof error === 'object') {\n const candidate =\n (error as { issues?: unknown }).issues ??\n (error as { errors?: unknown }).errors;\n if (Array.isArray(candidate)) {\n return candidate.filter(\n (i): i is Record<string, unknown> =>\n i !== null && typeof i === 'object',\n );\n }\n }\n return [];\n}\n\nfunction toSafeIssue(issue: Record<string, unknown>): ValidationIssue {\n const rawPath = issue.path;\n const path = Array.isArray(rawPath)\n ? rawPath.map(String).join('.')\n : typeof rawPath === 'string'\n ? rawPath\n : '';\n const code = typeof issue.code === 'string' ? issue.code : 'invalid';\n // `expected` is a declared type name in Zod (e.g. 'string'); safe. We never\n // read `received`/`message`/`value`, which can carry the offending payload.\n const expected =\n typeof issue.expected === 'string' ? issue.expected : undefined;\n return expected ? { path, code, expected } : { path, code };\n}\n\nexport interface DefineValidatorOptions<S> {\n /** Where validation runs. Defaults to `input`. */\n boundary?: string;\n /** `reject` (default): record then throw. `observe`: record then continue. */\n onMismatch?: ValidationMode;\n /** Project the schema to JSON Schema for a stable `validation.hash`. */\n toJsonSchema?: (schema: S) => unknown;\n severity?: ValidationSeverity;\n /** Build the error thrown in `reject` mode (defaults to a 400 structured error). */\n onReject?: (issues: ValidationIssue[], name: string) => Error;\n}\n\nexport type ValidatorResult<T> =\n { success: true; data: T } | { success: false; issues: ValidationIssue[] };\n\nexport interface Validator<T> {\n readonly name: string;\n readonly mode: ValidationMode;\n /** Validate and record on failure; never throws. */\n safeParse(input: unknown): ValidatorResult<T>;\n /**\n * Validate, record on failure, then apply the mode: `reject` throws,\n * `observe` returns the raw input so the handler can continue.\n */\n parse(input: unknown): T;\n}\n\nfunction defaultRejectError(\n issues: ValidationIssue[],\n name: string,\n): StructuredError {\n return createStructuredError({\n name: 'ValidationError',\n status: 400,\n code: 'validation_failed',\n message: `Input for \"${name}\" did not match its declared shape.`,\n why: `${issues.length} field(s) failed validation: ${issues\n .map((i) => i.path || '(root)')\n .slice(0, VALIDATION_ISSUE_CAP)\n .join(', ')}.`,\n fix: 'Send a payload that matches the schema, or switch this validator to observe mode while you investigate.',\n // PII-safe: paths + codes only, no received values.\n details: { validation: name, issues },\n });\n}\n\n/**\n * Declare an expected input shape once and get a validator that records every\n * mismatch as telemetry.\n *\n * @example\n * ```ts\n * import { z } from 'zod';\n * import { defineValidator } from 'autotel/validate';\n *\n * const OrderBody = defineValidator('POST /orders', z.object({\n * items: z.array(z.object({ sku: z.string(), qty: z.number().int() })),\n * }), { boundary: 'http', toJsonSchema: (s) => z.toJSONSchema(s) });\n *\n * // reject mode (default): records + throws a 400-shaped structured error\n * const order = OrderBody.parse(req.body);\n *\n * // observe mode: records, returns the result, never throws\n * const result = OrderBody.safeParse(req.body);\n * if (!result.success) metrics.onDrift(result.issues);\n * ```\n */\nexport function defineValidator<T, S extends SchemaLike<T>>(\n name: string,\n schema: S,\n options: DefineValidatorOptions<S> = {},\n): Validator<T> {\n const mode = options.onMismatch ?? 'reject';\n const boundary = options.boundary ?? 'input';\n const hash = options.toJsonSchema\n ? hashJson(options.toJsonSchema(schema))\n : undefined;\n\n const record = (issues: ValidationIssue[]): void => {\n recordValidationMismatch({\n name,\n boundary,\n mode,\n issues,\n hash,\n severity: options.severity,\n });\n };\n\n return {\n name,\n mode,\n safeParse(input: unknown): ValidatorResult<T> {\n const parsed = schema.safeParse(input);\n if (parsed.success) return { success: true, data: parsed.data };\n const issues = formatValidationIssues(parsed.error);\n record(issues);\n return { success: false, issues };\n },\n parse(input: unknown): T {\n const parsed = schema.safeParse(input);\n if (parsed.success) return parsed.data;\n const issues = formatValidationIssues(parsed.error);\n record(issues);\n if (mode === 'reject') {\n throw (\n options.onReject?.(issues, name) ?? defaultRejectError(issues, name)\n );\n }\n // observe: continue with the raw input (documented type caveat)\n return input as T;\n },\n };\n}\n"],"mappings":"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"}
@@ -13,18 +13,27 @@
13
13
  * rename here the way you'd treat a breaking change to any other contract.
14
14
  */
15
15
  declare const VALIDATION_ATTR: {
16
- /** Contract id of the validated boundary, e.g. `POST /orders`, `order.placed`. */readonly name: "validation.name"; /** Where validation ran: `http` | `event` | `message` | a custom label. */
17
- readonly boundary: "validation.boundary"; /** `observe` (recorded, request continues) or `reject` (recorded, then failed). */
18
- readonly mode: "validation.mode"; /** Stable hash of the declared shape, when a JSON-schema projection is given. */
19
- readonly hash: "validation.hash"; /** `info` | `warning` | `error`. */
20
- readonly severity: "validation.severity"; /** Number of failing fields. */
21
- readonly issueCount: "validation.issue.count"; /** Comma-separated failing field paths (capped). Never contains values. */
22
- readonly issuePaths: "validation.issue.paths"; /** Comma-separated distinct issue codes (capped). Never contains values. */
16
+ /** Contract id of the validated boundary, e.g. `POST /orders`, `order.placed`. */
17
+ readonly name: "validation.name";
18
+ /** Where validation ran: `http` | `event` | `message` | a custom label. */
19
+ readonly boundary: "validation.boundary";
20
+ /** `observe` (recorded, request continues) or `reject` (recorded, then failed). */
21
+ readonly mode: "validation.mode";
22
+ /** Stable hash of the declared shape, when a JSON-schema projection is given. */
23
+ readonly hash: "validation.hash";
24
+ /** `info` | `warning` | `error`. */
25
+ readonly severity: "validation.severity";
26
+ /** Number of failing fields. */
27
+ readonly issueCount: "validation.issue.count";
28
+ /** Comma-separated failing field paths (capped). Never contains values. */
29
+ readonly issuePaths: "validation.issue.paths";
30
+ /** Comma-separated distinct issue codes (capped). Never contains values. */
23
31
  readonly issueCodes: "validation.issue.codes";
24
32
  };
25
33
  type ValidationAttributeKey = (typeof VALIDATION_ATTR)[keyof typeof VALIDATION_ATTR];
26
34
  declare const VALIDATION_METRICS: {
27
- /** Counter, labelled `{ boundary, validation, mode }`. */readonly mismatches: "autotel.validation.mismatches";
35
+ /** Counter, labelled `{ boundary, validation, mode }`. */
36
+ readonly mismatches: "autotel.validation.mismatches";
28
37
  };
29
38
  /** Max field paths / codes stamped onto a span, to bound attribute size. */
30
39
  declare const VALIDATION_ISSUE_CAP = 20;
@@ -1 +1 @@
1
- {"version":3,"file":"validation-attributes.d.cts","names":[],"sources":["../src/validation-attributes.ts"],"mappings":";;AAcA;;;;;;;;;;;;cAAa,eAAA;sHAmBqB;EAAA,0CACxB;EAAA,kCAEG;EAAA;;iDAMoB;EAAA,+CAAA;EAAA;;KATrB,sBAAA,WACF,eAAA,eAA8B,eAAe;AAAA,cAE1C,kBAAA;qEAGH,UAAA;AAAA;;cAGG,oBAAA"}
1
+ {"version":3,"file":"validation-attributes.d.cts","names":[],"sources":["../src/validation-attributes.ts"],"mappings":";;;;;;;;;;;;;;cAca;;;;;;;;;;;;;;;;;;KAmBD,iCACF,8BAA8B;cAE3B;;WAAA;;;cAMA"}
@@ -13,18 +13,27 @@
13
13
  * rename here the way you'd treat a breaking change to any other contract.
14
14
  */
15
15
  declare const VALIDATION_ATTR: {
16
- /** Contract id of the validated boundary, e.g. `POST /orders`, `order.placed`. */readonly name: "validation.name"; /** Where validation ran: `http` | `event` | `message` | a custom label. */
17
- readonly boundary: "validation.boundary"; /** `observe` (recorded, request continues) or `reject` (recorded, then failed). */
18
- readonly mode: "validation.mode"; /** Stable hash of the declared shape, when a JSON-schema projection is given. */
19
- readonly hash: "validation.hash"; /** `info` | `warning` | `error`. */
20
- readonly severity: "validation.severity"; /** Number of failing fields. */
21
- readonly issueCount: "validation.issue.count"; /** Comma-separated failing field paths (capped). Never contains values. */
22
- readonly issuePaths: "validation.issue.paths"; /** Comma-separated distinct issue codes (capped). Never contains values. */
16
+ /** Contract id of the validated boundary, e.g. `POST /orders`, `order.placed`. */
17
+ readonly name: "validation.name";
18
+ /** Where validation ran: `http` | `event` | `message` | a custom label. */
19
+ readonly boundary: "validation.boundary";
20
+ /** `observe` (recorded, request continues) or `reject` (recorded, then failed). */
21
+ readonly mode: "validation.mode";
22
+ /** Stable hash of the declared shape, when a JSON-schema projection is given. */
23
+ readonly hash: "validation.hash";
24
+ /** `info` | `warning` | `error`. */
25
+ readonly severity: "validation.severity";
26
+ /** Number of failing fields. */
27
+ readonly issueCount: "validation.issue.count";
28
+ /** Comma-separated failing field paths (capped). Never contains values. */
29
+ readonly issuePaths: "validation.issue.paths";
30
+ /** Comma-separated distinct issue codes (capped). Never contains values. */
23
31
  readonly issueCodes: "validation.issue.codes";
24
32
  };
25
33
  type ValidationAttributeKey = (typeof VALIDATION_ATTR)[keyof typeof VALIDATION_ATTR];
26
34
  declare const VALIDATION_METRICS: {
27
- /** Counter, labelled `{ boundary, validation, mode }`. */readonly mismatches: "autotel.validation.mismatches";
35
+ /** Counter, labelled `{ boundary, validation, mode }`. */
36
+ readonly mismatches: "autotel.validation.mismatches";
28
37
  };
29
38
  /** Max field paths / codes stamped onto a span, to bound attribute size. */
30
39
  declare const VALIDATION_ISSUE_CAP = 20;
@@ -1 +1 @@
1
- {"version":3,"file":"validation-attributes.d.ts","names":[],"sources":["../src/validation-attributes.ts"],"mappings":";;AAcA;;;;;;;;;;;;cAAa,eAAA;sHAmBqB;EAAA,0CACxB;EAAA,kCAEG;EAAA;;iDAMoB;EAAA,+CAAA;EAAA;;KATrB,sBAAA,WACF,eAAA,eAA8B,eAAe;AAAA,cAE1C,kBAAA;qEAGH,UAAA;AAAA;;cAGG,oBAAA"}
1
+ {"version":3,"file":"validation-attributes.d.ts","names":[],"sources":["../src/validation-attributes.ts"],"mappings":";;;;;;;;;;;;;;cAca;;;;;;;;;;;;;;;;;;KAmBD,iCACF,8BAA8B;cAE3B;;WAAA;;;cAMA"}
package/dist/webhook.cjs CHANGED
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
2
  const require_structured_error = require('./structured-error-CHg7DoIQ.cjs');
3
- const require_functional = require('./functional-BCtxvYir.cjs');
3
+ const require_functional = require('./functional-Cj7PSItS.cjs');
4
4
  const require_correlated_events = require('./correlated-events-kSwLo3mi.cjs');
5
5
  let _opentelemetry_api = require("@opentelemetry/api");
6
6
 
@@ -15,6 +15,7 @@ let _opentelemetry_api = require("@opentelemetry/api");
15
15
  * @example Stripe payment webhook
16
16
  * ```typescript
17
17
  * import { createParkingLot, InMemoryTraceContextStore } from 'autotel/webhook';
18
+ * import { withTracing } from 'autotel';
18
19
  *
19
20
  * const parkingLot = createParkingLot({
20
21
  * store: new InMemoryTraceContextStore(),
@@ -22,7 +23,7 @@ let _opentelemetry_api = require("@opentelemetry/api");
22
23
  * });
23
24
  *
24
25
  * // When initiating payment
25
- * export const initiatePayment = trace(ctx => async (orderId: string) => {
26
+ * export const initiatePayment = withTracing({})(ctx => async (orderId: string) => {
26
27
  * await parkingLot.park(`payment:${orderId}`, { orderId });
27
28
  * await stripeClient.createPaymentIntent({ metadata: { orderId } });
28
29
  * });
@@ -201,10 +202,10 @@ function createParkingLot(config) {
201
202
  },
202
203
  traceCallback(callbackConfig) {
203
204
  return (fnFactory) => {
204
- return require_functional.trace({
205
+ return require_functional.withTracing({
205
206
  name: callbackConfig.name,
206
207
  spanKind: _opentelemetry_api.SpanKind.SERVER
207
- }, (baseCtx) => {
208
+ })((baseCtx) => {
208
209
  return async (...args) => {
209
210
  const correlationKey = callbackConfig.correlationKeyFrom(args);
210
211
  const parkedContext = await parkingLot.retrieve(correlationKey);
@@ -1 +1 @@
1
- {"version":3,"file":"webhook.cjs","names":["otelTrace","trace","SpanKind"],"sources":["../src/webhook.ts"],"sourcesContent":["/**\n * Webhook and callback tracing with the \"Parking Lot\" pattern\n *\n * When initiating async operations that return hours/days later (webhooks,\n * payment callbacks, human approvals), you can't keep a span open. This module\n * provides utilities to \"park\" trace context and retrieve it when callbacks arrive.\n *\n * @example Stripe payment webhook\n * ```typescript\n * import { createParkingLot, InMemoryTraceContextStore } from 'autotel/webhook';\n *\n * const parkingLot = createParkingLot({\n * store: new InMemoryTraceContextStore(),\n * defaultTTLMs: 24 * 60 * 60 * 1000, // 24 hours\n * });\n *\n * // When initiating payment\n * export const initiatePayment = trace(ctx => async (orderId: string) => {\n * await parkingLot.park(`payment:${orderId}`, { orderId });\n * await stripeClient.createPaymentIntent({ metadata: { orderId } });\n * });\n *\n * // When Stripe webhook arrives (hours later)\n * export const handleStripeWebhook = parkingLot.traceCallback({\n * name: 'stripe.webhook.payment_intent.succeeded',\n * correlationKeyFrom: (event) => `payment:${event.data.object.metadata.orderId}`,\n * })(ctx => async (event: Stripe.Event) => {\n * // ctx.parkedContext contains the original trace context\n * // ctx.elapsedMs shows time since payment was initiated\n * await fulfillOrder(event.data.object);\n * });\n * ```\n *\n * @module\n */\n\nimport { SpanKind, trace as otelTrace } from '@opentelemetry/api';\nimport type { SpanContext, Link } from '@opentelemetry/api';\nimport { emitCorrelatedEvent } from './correlated-events';\nimport { trace } from './functional';\nimport type { AttributeValue, TraceContext } from './trace-context';\nimport { recordStructuredError } from './structured-error';\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Stored trace context for parking lot pattern\n */\nexport interface StoredTraceContext {\n /** Trace ID from the original span */\n traceId: string;\n\n /** Span ID from the original span */\n spanId: string;\n\n /** Trace flags (sampling decision) */\n traceFlags: number;\n\n /** When the context was parked */\n parkedAt: number;\n\n /** Optional TTL in milliseconds */\n ttlMs?: number;\n\n /** User-provided metadata */\n metadata?: Record<string, string>;\n}\n\n/**\n * Interface for trace context storage backends\n *\n * Implement this interface to use different storage backends (Redis, DynamoDB, etc.)\n */\nexport interface TraceContextStore {\n /**\n * Save trace context with a correlation key\n *\n * @param key - Unique correlation key (e.g., \"payment:order-123\")\n * @param context - The trace context to store\n */\n save(key: string, context: StoredTraceContext): Promise<void>;\n\n /**\n * Load trace context by correlation key\n *\n * @param key - The correlation key used when parking\n * @returns The stored context, or null if not found/expired\n */\n load(key: string): Promise<StoredTraceContext | null>;\n\n /**\n * Delete trace context by correlation key\n *\n * @param key - The correlation key to delete\n */\n delete(key: string): Promise<void>;\n}\n\n/**\n * Configuration for creating a parking lot\n */\nexport interface ParkingLotConfig {\n /** Storage backend for parked contexts */\n store: TraceContextStore;\n\n /** Default TTL in milliseconds (default: 24 hours) */\n defaultTTLMs?: number;\n\n /** Prefix for all correlation keys (default: \"parkingLot:\") */\n keyPrefix?: string;\n\n /** Whether to auto-delete after retrieval (default: true) */\n autoDeleteOnRetrieve?: boolean;\n\n /** Callback when context expires or is not found */\n onMiss?: (correlationKey: string) => void;\n}\n\n/**\n * Configuration for traceCallback wrapper\n */\nexport interface CallbackConfig {\n /** Span name for the callback handler */\n name: string;\n\n /**\n * Extract correlation key from callback arguments\n *\n * @example\n * ```typescript\n * correlationKeyFrom: (event) => `payment:${event.data.orderId}`\n * ```\n */\n correlationKeyFrom: (args: unknown[]) => string;\n\n /** Additional span attributes */\n attributes?: Record<string, string | number | boolean>;\n\n /** Whether to fail if parked context is not found (default: false) */\n requireParkedContext?: boolean;\n}\n\n/**\n * Extended context for callback handlers\n */\nexport interface CallbackContext extends TraceContext {\n /** The retrieved parked context, if found */\n parkedContext: StoredTraceContext | null;\n\n /** Time elapsed since context was parked (ms), or null if not found */\n elapsedMs: number | null;\n\n /** The correlation key used for retrieval */\n correlationKey: string;\n}\n\n/**\n * The parking lot instance\n */\nexport interface ParkingLot {\n /**\n * Park current trace context before initiating async operation\n *\n * Call this before sending a webhook, initiating a payment, or starting\n * any operation that will complete via callback.\n *\n * @param correlationKey - Unique key to retrieve context later (e.g., \"payment:order-123\")\n * @param metadata - Optional metadata to store with the context\n * @returns The correlation key (with prefix applied)\n *\n * @example\n * ```typescript\n * await parkingLot.park(`payment:${orderId}`, {\n * customerId: customer.id,\n * amount: payment.amount.toString(),\n * });\n * ```\n */\n park(\n correlationKey: string,\n metadata?: Record<string, string>,\n ): Promise<string>;\n\n /**\n * Retrieve parked context when callback arrives\n *\n * @param correlationKey - The key used when parking\n * @returns The stored context, or null if not found/expired\n */\n retrieve(correlationKey: string): Promise<StoredTraceContext | null>;\n\n /**\n * Wrap a callback handler with automatic context retrieval and linking\n *\n * Creates a traced function that:\n * 1. Extracts correlation key from arguments\n * 2. Retrieves parked context from storage\n * 3. Creates a span link to the original trace\n * 4. Provides elapsed time since parking\n *\n * @param config - Callback configuration\n * @returns Factory function for the callback handler\n *\n * @example\n * ```typescript\n * export const handleWebhook = parkingLot.traceCallback({\n * name: 'webhook.payment.completed',\n * correlationKeyFrom: (args) => `payment:${args[0].orderId}`,\n * })(ctx => async (event) => {\n * console.log(`Payment completed after ${ctx.elapsedMs}ms`);\n * await processPayment(event);\n * });\n * ```\n */\n traceCallback<TArgs extends unknown[], TReturn>(\n config: CallbackConfig,\n ): (\n fnFactory: (ctx: CallbackContext) => (...args: TArgs) => Promise<TReturn>,\n ) => (...args: TArgs) => Promise<TReturn>;\n\n /**\n * Manually create a span link from stored context\n *\n * Useful when you need more control over span creation.\n *\n * @param storedContext - The stored trace context\n * @returns A span link that can be added to a span\n */\n createLink(storedContext: StoredTraceContext): Link;\n\n /**\n * Check if a parked context exists (without retrieving/deleting it)\n *\n * @param correlationKey - The key to check\n * @returns True if context exists and hasn't expired\n */\n exists(correlationKey: string): Promise<boolean>;\n}\n\n// ============================================================================\n// In-Memory Store (for testing and development)\n// ============================================================================\n\n/**\n * In-memory trace context store\n *\n * Useful for testing and development. For production, use a persistent\n * store like Redis or DynamoDB.\n *\n * @example\n * ```typescript\n * const store = new InMemoryTraceContextStore();\n * const parkingLot = createParkingLot({ store });\n * ```\n */\nexport class InMemoryTraceContextStore implements TraceContextStore {\n private store = new Map<string, StoredTraceContext>();\n private cleanupInterval: ReturnType<typeof setInterval> | null = null;\n\n constructor(\n private options: {\n /** Cleanup interval in ms (default: 60000) */\n cleanupIntervalMs?: number;\n } = {},\n ) {\n // Start periodic cleanup of expired entries\n const cleanupMs = options.cleanupIntervalMs ?? 60_000;\n if (cleanupMs > 0) {\n this.cleanupInterval = setInterval(() => this.cleanup(), cleanupMs);\n // Don't prevent process exit\n if (this.cleanupInterval.unref) {\n this.cleanupInterval.unref();\n }\n }\n }\n\n async save(key: string, context: StoredTraceContext): Promise<void> {\n this.store.set(key, context);\n }\n\n async load(key: string): Promise<StoredTraceContext | null> {\n const context = this.store.get(key);\n if (!context) {\n return null;\n }\n\n // Check TTL expiration\n if (context.ttlMs) {\n const age = Date.now() - context.parkedAt;\n if (age > context.ttlMs) {\n this.store.delete(key);\n return null;\n }\n }\n\n return context;\n }\n\n async delete(key: string): Promise<void> {\n this.store.delete(key);\n }\n\n /**\n * Get number of stored contexts (for testing)\n */\n get size(): number {\n return this.store.size;\n }\n\n /**\n * Clear all stored contexts (for testing)\n */\n clear(): void {\n this.store.clear();\n }\n\n /**\n * Stop the cleanup interval\n */\n destroy(): void {\n if (this.cleanupInterval) {\n clearInterval(this.cleanupInterval);\n this.cleanupInterval = null;\n }\n }\n\n private cleanup(): void {\n const now = Date.now();\n for (const [key, context] of this.store.entries()) {\n if (context.ttlMs) {\n const age = now - context.parkedAt;\n if (age > context.ttlMs) {\n this.store.delete(key);\n }\n }\n }\n }\n}\n\n// ============================================================================\n// Parking Lot Factory\n// ============================================================================\n\n/**\n * Create a parking lot for trace context storage and retrieval\n *\n * @param config - Parking lot configuration\n * @returns A parking lot instance\n *\n * @example Basic usage\n * ```typescript\n * const parkingLot = createParkingLot({\n * store: new InMemoryTraceContextStore(),\n * defaultTTLMs: 24 * 60 * 60 * 1000, // 24 hours\n * });\n * ```\n *\n * @example With Redis store\n * ```typescript\n * class RedisTraceContextStore implements TraceContextStore {\n * constructor(private redis: Redis) {}\n *\n * async save(key: string, context: StoredTraceContext) {\n * const ttlSeconds = context.ttlMs ? Math.ceil(context.ttlMs / 1000) : 86400;\n * await this.redis.setex(key, ttlSeconds, JSON.stringify(context));\n * }\n *\n * async load(key: string) {\n * const data = await this.redis.get(key);\n * return data ? JSON.parse(data) : null;\n * }\n *\n * async delete(key: string) {\n * await this.redis.del(key);\n * }\n * }\n *\n * const parkingLot = createParkingLot({\n * store: new RedisTraceContextStore(redis),\n * });\n * ```\n */\nexport function createParkingLot(config: ParkingLotConfig): ParkingLot {\n const {\n store,\n defaultTTLMs = 24 * 60 * 60 * 1000, // 24 hours\n keyPrefix = 'parkingLot:',\n autoDeleteOnRetrieve = true,\n onMiss,\n } = config;\n\n /**\n * Get current span context from active context\n */\n function getCurrentSpanContext(): SpanContext | null {\n const activeSpan = otelTrace.getActiveSpan();\n if (!activeSpan) {\n return null;\n }\n return activeSpan.spanContext();\n }\n\n /**\n * Apply key prefix\n */\n function prefixKey(key: string): string {\n return `${keyPrefix}${key}`;\n }\n\n const parkingLot: ParkingLot = {\n async park(\n correlationKey: string,\n metadata?: Record<string, string>,\n ): Promise<string> {\n const spanContext = getCurrentSpanContext();\n const fullKey = prefixKey(correlationKey);\n\n const storedContext: StoredTraceContext = {\n traceId: spanContext?.traceId ?? '',\n spanId: spanContext?.spanId ?? '',\n traceFlags: spanContext?.traceFlags ?? 0,\n parkedAt: Date.now(),\n ttlMs: defaultTTLMs,\n metadata,\n };\n\n await store.save(fullKey, storedContext);\n\n const activeSpan = otelTrace.getActiveSpan();\n if (activeSpan) {\n const parkAttrs: Record<string, AttributeValue> = {\n 'parking_lot.correlation_key': correlationKey,\n 'parking_lot.ttl_ms': defaultTTLMs,\n ...(metadata &&\n Object.fromEntries(\n Object.entries(metadata).map(([k, v]) => [\n `parking_lot.metadata.${k}`,\n v,\n ]),\n )),\n };\n emitCorrelatedEvent(\n {\n setAttribute: (k, v) => activeSpan.setAttribute(k, v),\n setAttributes: (a) => activeSpan.setAttributes(a),\n addEvent: (n, a) => activeSpan.addEvent(n, a),\n },\n 'trace_context_parked',\n parkAttrs,\n );\n }\n\n // Return the unprefixed key so callers can use the same key for retrieve()\n return correlationKey;\n },\n\n async retrieve(correlationKey: string): Promise<StoredTraceContext | null> {\n const fullKey = prefixKey(correlationKey);\n const storedContext = await store.load(fullKey);\n\n if (!storedContext) {\n onMiss?.(correlationKey);\n return null;\n }\n\n if (autoDeleteOnRetrieve) {\n await store.delete(fullKey);\n }\n\n return storedContext;\n },\n\n traceCallback<TArgs extends unknown[], TReturn>(\n callbackConfig: CallbackConfig,\n ): (\n fnFactory: (ctx: CallbackContext) => (...args: TArgs) => Promise<TReturn>,\n ) => (...args: TArgs) => Promise<TReturn> {\n return (\n fnFactory: (\n ctx: CallbackContext,\n ) => (...args: TArgs) => Promise<TReturn>,\n ): ((...args: TArgs) => Promise<TReturn>) => {\n return trace<TArgs, TReturn>(\n {\n name: callbackConfig.name,\n spanKind: SpanKind.SERVER,\n },\n (baseCtx) => {\n return async (...args: TArgs) => {\n // Extract correlation key from arguments\n const correlationKey = callbackConfig.correlationKeyFrom(args);\n\n // Retrieve parked context\n const parkedContext = await parkingLot.retrieve(correlationKey);\n\n // Calculate elapsed time\n const elapsedMs = parkedContext\n ? Date.now() - parkedContext.parkedAt\n : null;\n\n // Set span attributes\n baseCtx.setAttribute(\n 'parking_lot.correlation_key',\n correlationKey,\n );\n\n if (parkedContext) {\n baseCtx.setAttribute('parking_lot.elapsed_ms', elapsedMs!);\n baseCtx.setAttribute(\n 'parking_lot.original_trace_id',\n parkedContext.traceId,\n );\n baseCtx.setAttribute(\n 'parking_lot.original_span_id',\n parkedContext.spanId,\n );\n\n // Add metadata as attributes\n if (parkedContext.metadata) {\n for (const [key, value] of Object.entries(\n parkedContext.metadata,\n )) {\n baseCtx.setAttribute(`parking_lot.metadata.${key}`, value);\n }\n }\n\n // Create span link to original trace\n const link = parkingLot.createLink(parkedContext);\n baseCtx.addLinks([link]);\n\n emitCorrelatedEvent(baseCtx, 'parked_context_retrieved', {\n 'parking_lot.correlation_key': correlationKey,\n 'parking_lot.elapsed_ms': elapsedMs!,\n 'parking_lot.original_trace_id': parkedContext.traceId,\n });\n } else {\n baseCtx.setAttribute('parking_lot.context_found', false);\n\n if (callbackConfig.requireParkedContext) {\n const error = new Error(\n `Required parked context not found for key: ${correlationKey}`,\n );\n recordStructuredError(baseCtx, error);\n throw error;\n }\n }\n\n // Apply custom attributes\n if (callbackConfig.attributes) {\n for (const [key, value] of Object.entries(\n callbackConfig.attributes,\n )) {\n baseCtx.setAttribute(key, value);\n }\n }\n\n // Create extended context\n const callbackCtx: CallbackContext = {\n ...baseCtx,\n parkedContext,\n elapsedMs,\n correlationKey,\n };\n\n // Execute user's function\n const userFn = fnFactory(callbackCtx);\n return userFn(...args);\n };\n },\n );\n };\n },\n\n createLink(storedContext: StoredTraceContext): Link {\n return {\n context: {\n traceId: storedContext.traceId,\n spanId: storedContext.spanId,\n traceFlags: storedContext.traceFlags,\n isRemote: true,\n },\n attributes: {\n 'link.type': 'parking_lot',\n 'parking_lot.parked_at': storedContext.parkedAt,\n ...(storedContext.metadata && {\n 'parking_lot.has_metadata': true,\n }),\n },\n };\n },\n\n async exists(correlationKey: string): Promise<boolean> {\n const fullKey = prefixKey(correlationKey);\n const context = await store.load(fullKey);\n return context !== null;\n },\n };\n\n return parkingLot;\n}\n\n// ============================================================================\n// Utility Functions\n// ============================================================================\n\n/**\n * Create a correlation key from multiple parts\n *\n * @param parts - Key parts to join\n * @returns A correlation key string\n *\n * @example\n * ```typescript\n * const key = createCorrelationKey('payment', orderId, 'stripe');\n * // Returns: \"payment:order-123:stripe\"\n * ```\n */\nexport function createCorrelationKey(...parts: (string | number)[]): string {\n return parts.map(String).join(':');\n}\n\n/**\n * Extract span context from stored context for manual linking\n *\n * @param storedContext - The stored trace context\n * @returns SpanContext compatible object\n */\nexport function toSpanContext(storedContext: StoredTraceContext): SpanContext {\n return {\n traceId: storedContext.traceId,\n spanId: storedContext.spanId,\n traceFlags: storedContext.traceFlags,\n isRemote: true,\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"webhook.cjs","names":["otelTrace","withTracing","SpanKind"],"sources":["../src/webhook.ts"],"sourcesContent":["/**\n * Webhook and callback tracing with the \"Parking Lot\" pattern\n *\n * When initiating async operations that return hours/days later (webhooks,\n * payment callbacks, human approvals), you can't keep a span open. This module\n * provides utilities to \"park\" trace context and retrieve it when callbacks arrive.\n *\n * @example Stripe payment webhook\n * ```typescript\n * import { createParkingLot, InMemoryTraceContextStore } from 'autotel/webhook';\n * import { withTracing } from 'autotel';\n *\n * const parkingLot = createParkingLot({\n * store: new InMemoryTraceContextStore(),\n * defaultTTLMs: 24 * 60 * 60 * 1000, // 24 hours\n * });\n *\n * // When initiating payment\n * export const initiatePayment = withTracing({})(ctx => async (orderId: string) => {\n * await parkingLot.park(`payment:${orderId}`, { orderId });\n * await stripeClient.createPaymentIntent({ metadata: { orderId } });\n * });\n *\n * // When Stripe webhook arrives (hours later)\n * export const handleStripeWebhook = parkingLot.traceCallback({\n * name: 'stripe.webhook.payment_intent.succeeded',\n * correlationKeyFrom: (event) => `payment:${event.data.object.metadata.orderId}`,\n * })(ctx => async (event: Stripe.Event) => {\n * // ctx.parkedContext contains the original trace context\n * // ctx.elapsedMs shows time since payment was initiated\n * await fulfillOrder(event.data.object);\n * });\n * ```\n *\n * @module\n */\n\nimport { SpanKind, trace as otelTrace } from '@opentelemetry/api';\nimport type { SpanContext, Link } from '@opentelemetry/api';\nimport { emitCorrelatedEvent } from './correlated-events';\nimport { withTracing } from './functional';\nimport type { AttributeValue, TraceContext } from './trace-context';\nimport { recordStructuredError } from './structured-error';\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Stored trace context for parking lot pattern\n */\nexport interface StoredTraceContext {\n /** Trace ID from the original span */\n traceId: string;\n\n /** Span ID from the original span */\n spanId: string;\n\n /** Trace flags (sampling decision) */\n traceFlags: number;\n\n /** When the context was parked */\n parkedAt: number;\n\n /** Optional TTL in milliseconds */\n ttlMs?: number;\n\n /** User-provided metadata */\n metadata?: Record<string, string>;\n}\n\n/**\n * Interface for trace context storage backends\n *\n * Implement this interface to use different storage backends (Redis, DynamoDB, etc.)\n */\nexport interface TraceContextStore {\n /**\n * Save trace context with a correlation key\n *\n * @param key - Unique correlation key (e.g., \"payment:order-123\")\n * @param context - The trace context to store\n */\n save(key: string, context: StoredTraceContext): Promise<void>;\n\n /**\n * Load trace context by correlation key\n *\n * @param key - The correlation key used when parking\n * @returns The stored context, or null if not found/expired\n */\n load(key: string): Promise<StoredTraceContext | null>;\n\n /**\n * Delete trace context by correlation key\n *\n * @param key - The correlation key to delete\n */\n delete(key: string): Promise<void>;\n}\n\n/**\n * Configuration for creating a parking lot\n */\nexport interface ParkingLotConfig {\n /** Storage backend for parked contexts */\n store: TraceContextStore;\n\n /** Default TTL in milliseconds (default: 24 hours) */\n defaultTTLMs?: number;\n\n /** Prefix for all correlation keys (default: \"parkingLot:\") */\n keyPrefix?: string;\n\n /** Whether to auto-delete after retrieval (default: true) */\n autoDeleteOnRetrieve?: boolean;\n\n /** Callback when context expires or is not found */\n onMiss?: (correlationKey: string) => void;\n}\n\n/**\n * Configuration for traceCallback wrapper\n */\nexport interface CallbackConfig {\n /** Span name for the callback handler */\n name: string;\n\n /**\n * Extract correlation key from callback arguments\n *\n * @example\n * ```typescript\n * correlationKeyFrom: (event) => `payment:${event.data.orderId}`\n * ```\n */\n correlationKeyFrom: (args: unknown[]) => string;\n\n /** Additional span attributes */\n attributes?: Record<string, string | number | boolean>;\n\n /** Whether to fail if parked context is not found (default: false) */\n requireParkedContext?: boolean;\n}\n\n/**\n * Extended context for callback handlers\n */\nexport interface CallbackContext extends TraceContext {\n /** The retrieved parked context, if found */\n parkedContext: StoredTraceContext | null;\n\n /** Time elapsed since context was parked (ms), or null if not found */\n elapsedMs: number | null;\n\n /** The correlation key used for retrieval */\n correlationKey: string;\n}\n\n/**\n * The parking lot instance\n */\nexport interface ParkingLot {\n /**\n * Park current trace context before initiating async operation\n *\n * Call this before sending a webhook, initiating a payment, or starting\n * any operation that will complete via callback.\n *\n * @param correlationKey - Unique key to retrieve context later (e.g., \"payment:order-123\")\n * @param metadata - Optional metadata to store with the context\n * @returns The correlation key (with prefix applied)\n *\n * @example\n * ```typescript\n * await parkingLot.park(`payment:${orderId}`, {\n * customerId: customer.id,\n * amount: payment.amount.toString(),\n * });\n * ```\n */\n park(\n correlationKey: string,\n metadata?: Record<string, string>,\n ): Promise<string>;\n\n /**\n * Retrieve parked context when callback arrives\n *\n * @param correlationKey - The key used when parking\n * @returns The stored context, or null if not found/expired\n */\n retrieve(correlationKey: string): Promise<StoredTraceContext | null>;\n\n /**\n * Wrap a callback handler with automatic context retrieval and linking\n *\n * Creates a traced function that:\n * 1. Extracts correlation key from arguments\n * 2. Retrieves parked context from storage\n * 3. Creates a span link to the original trace\n * 4. Provides elapsed time since parking\n *\n * @param config - Callback configuration\n * @returns Factory function for the callback handler\n *\n * @example\n * ```typescript\n * export const handleWebhook = parkingLot.traceCallback({\n * name: 'webhook.payment.completed',\n * correlationKeyFrom: (args) => `payment:${args[0].orderId}`,\n * })(ctx => async (event) => {\n * console.log(`Payment completed after ${ctx.elapsedMs}ms`);\n * await processPayment(event);\n * });\n * ```\n */\n traceCallback(\n config: CallbackConfig,\n ): <TArgs extends unknown[], TReturn>(\n fnFactory: (ctx: CallbackContext) => (...args: TArgs) => Promise<TReturn>,\n ) => (...args: TArgs) => Promise<TReturn>;\n\n /**\n * Manually create a span link from stored context\n *\n * Useful when you need more control over span creation.\n *\n * @param storedContext - The stored trace context\n * @returns A span link that can be added to a span\n */\n createLink(storedContext: StoredTraceContext): Link;\n\n /**\n * Check if a parked context exists (without retrieving/deleting it)\n *\n * @param correlationKey - The key to check\n * @returns True if context exists and hasn't expired\n */\n exists(correlationKey: string): Promise<boolean>;\n}\n\n// ============================================================================\n// In-Memory Store (for testing and development)\n// ============================================================================\n\n/**\n * In-memory trace context store\n *\n * Useful for testing and development. For production, use a persistent\n * store like Redis or DynamoDB.\n *\n * @example\n * ```typescript\n * const store = new InMemoryTraceContextStore();\n * const parkingLot = createParkingLot({ store });\n * ```\n */\nexport class InMemoryTraceContextStore implements TraceContextStore {\n private store = new Map<string, StoredTraceContext>();\n private cleanupInterval: ReturnType<typeof setInterval> | null = null;\n\n constructor(\n private options: {\n /** Cleanup interval in ms (default: 60000) */\n cleanupIntervalMs?: number;\n } = {},\n ) {\n // Start periodic cleanup of expired entries\n const cleanupMs = options.cleanupIntervalMs ?? 60_000;\n if (cleanupMs > 0) {\n this.cleanupInterval = setInterval(() => this.cleanup(), cleanupMs);\n // Don't prevent process exit\n if (this.cleanupInterval.unref) {\n this.cleanupInterval.unref();\n }\n }\n }\n\n async save(key: string, context: StoredTraceContext): Promise<void> {\n this.store.set(key, context);\n }\n\n async load(key: string): Promise<StoredTraceContext | null> {\n const context = this.store.get(key);\n if (!context) {\n return null;\n }\n\n // Check TTL expiration\n if (context.ttlMs) {\n const age = Date.now() - context.parkedAt;\n if (age > context.ttlMs) {\n this.store.delete(key);\n return null;\n }\n }\n\n return context;\n }\n\n async delete(key: string): Promise<void> {\n this.store.delete(key);\n }\n\n /**\n * Get number of stored contexts (for testing)\n */\n get size(): number {\n return this.store.size;\n }\n\n /**\n * Clear all stored contexts (for testing)\n */\n clear(): void {\n this.store.clear();\n }\n\n /**\n * Stop the cleanup interval\n */\n destroy(): void {\n if (this.cleanupInterval) {\n clearInterval(this.cleanupInterval);\n this.cleanupInterval = null;\n }\n }\n\n private cleanup(): void {\n const now = Date.now();\n for (const [key, context] of this.store.entries()) {\n if (context.ttlMs) {\n const age = now - context.parkedAt;\n if (age > context.ttlMs) {\n this.store.delete(key);\n }\n }\n }\n }\n}\n\n// ============================================================================\n// Parking Lot Factory\n// ============================================================================\n\n/**\n * Create a parking lot for trace context storage and retrieval\n *\n * @param config - Parking lot configuration\n * @returns A parking lot instance\n *\n * @example Basic usage\n * ```typescript\n * const parkingLot = createParkingLot({\n * store: new InMemoryTraceContextStore(),\n * defaultTTLMs: 24 * 60 * 60 * 1000, // 24 hours\n * });\n * ```\n *\n * @example With Redis store\n * ```typescript\n * class RedisTraceContextStore implements TraceContextStore {\n * constructor(private redis: Redis) {}\n *\n * async save(key: string, context: StoredTraceContext) {\n * const ttlSeconds = context.ttlMs ? Math.ceil(context.ttlMs / 1000) : 86400;\n * await this.redis.setex(key, ttlSeconds, JSON.stringify(context));\n * }\n *\n * async load(key: string) {\n * const data = await this.redis.get(key);\n * return data ? JSON.parse(data) : null;\n * }\n *\n * async delete(key: string) {\n * await this.redis.del(key);\n * }\n * }\n *\n * const parkingLot = createParkingLot({\n * store: new RedisTraceContextStore(redis),\n * });\n * ```\n */\nexport function createParkingLot(config: ParkingLotConfig): ParkingLot {\n const {\n store,\n defaultTTLMs = 24 * 60 * 60 * 1000, // 24 hours\n keyPrefix = 'parkingLot:',\n autoDeleteOnRetrieve = true,\n onMiss,\n } = config;\n\n /**\n * Get current span context from active context\n */\n function getCurrentSpanContext(): SpanContext | null {\n const activeSpan = otelTrace.getActiveSpan();\n if (!activeSpan) {\n return null;\n }\n return activeSpan.spanContext();\n }\n\n /**\n * Apply key prefix\n */\n function prefixKey(key: string): string {\n return `${keyPrefix}${key}`;\n }\n\n const parkingLot: ParkingLot = {\n async park(\n correlationKey: string,\n metadata?: Record<string, string>,\n ): Promise<string> {\n const spanContext = getCurrentSpanContext();\n const fullKey = prefixKey(correlationKey);\n\n const storedContext: StoredTraceContext = {\n traceId: spanContext?.traceId ?? '',\n spanId: spanContext?.spanId ?? '',\n traceFlags: spanContext?.traceFlags ?? 0,\n parkedAt: Date.now(),\n ttlMs: defaultTTLMs,\n metadata,\n };\n\n await store.save(fullKey, storedContext);\n\n const activeSpan = otelTrace.getActiveSpan();\n if (activeSpan) {\n const parkAttrs: Record<string, AttributeValue> = {\n 'parking_lot.correlation_key': correlationKey,\n 'parking_lot.ttl_ms': defaultTTLMs,\n ...(metadata &&\n Object.fromEntries(\n Object.entries(metadata).map(([k, v]) => [\n `parking_lot.metadata.${k}`,\n v,\n ]),\n )),\n };\n emitCorrelatedEvent(\n {\n setAttribute: (k, v) => activeSpan.setAttribute(k, v),\n setAttributes: (a) => activeSpan.setAttributes(a),\n addEvent: (n, a) => activeSpan.addEvent(n, a),\n },\n 'trace_context_parked',\n parkAttrs,\n );\n }\n\n // Return the unprefixed key so callers can use the same key for retrieve()\n return correlationKey;\n },\n\n async retrieve(correlationKey: string): Promise<StoredTraceContext | null> {\n const fullKey = prefixKey(correlationKey);\n const storedContext = await store.load(fullKey);\n\n if (!storedContext) {\n onMiss?.(correlationKey);\n return null;\n }\n\n if (autoDeleteOnRetrieve) {\n await store.delete(fullKey);\n }\n\n return storedContext;\n },\n\n traceCallback(\n callbackConfig: CallbackConfig,\n ): <TArgs extends unknown[], TReturn>(\n fnFactory: (ctx: CallbackContext) => (...args: TArgs) => Promise<TReturn>,\n ) => (...args: TArgs) => Promise<TReturn> {\n return <TArgs extends unknown[], TReturn>(\n fnFactory: (\n ctx: CallbackContext,\n ) => (...args: TArgs) => Promise<TReturn>,\n ): ((...args: TArgs) => Promise<TReturn>) => {\n return withTracing<TArgs, TReturn>({\n name: callbackConfig.name,\n spanKind: SpanKind.SERVER,\n })((baseCtx) => {\n return async (...args: TArgs) => {\n // Extract correlation key from arguments\n const correlationKey = callbackConfig.correlationKeyFrom(args);\n\n // Retrieve parked context\n const parkedContext = await parkingLot.retrieve(correlationKey);\n\n // Calculate elapsed time\n const elapsedMs = parkedContext\n ? Date.now() - parkedContext.parkedAt\n : null;\n\n // Set span attributes\n baseCtx.setAttribute('parking_lot.correlation_key', correlationKey);\n\n if (parkedContext) {\n baseCtx.setAttribute('parking_lot.elapsed_ms', elapsedMs!);\n baseCtx.setAttribute(\n 'parking_lot.original_trace_id',\n parkedContext.traceId,\n );\n baseCtx.setAttribute(\n 'parking_lot.original_span_id',\n parkedContext.spanId,\n );\n\n // Add metadata as attributes\n if (parkedContext.metadata) {\n for (const [key, value] of Object.entries(\n parkedContext.metadata,\n )) {\n baseCtx.setAttribute(`parking_lot.metadata.${key}`, value);\n }\n }\n\n // Create span link to original trace\n const link = parkingLot.createLink(parkedContext);\n baseCtx.addLinks([link]);\n\n emitCorrelatedEvent(baseCtx, 'parked_context_retrieved', {\n 'parking_lot.correlation_key': correlationKey,\n 'parking_lot.elapsed_ms': elapsedMs!,\n 'parking_lot.original_trace_id': parkedContext.traceId,\n });\n } else {\n baseCtx.setAttribute('parking_lot.context_found', false);\n\n if (callbackConfig.requireParkedContext) {\n const error = new Error(\n `Required parked context not found for key: ${correlationKey}`,\n );\n recordStructuredError(baseCtx, error);\n throw error;\n }\n }\n\n // Apply custom attributes\n if (callbackConfig.attributes) {\n for (const [key, value] of Object.entries(\n callbackConfig.attributes,\n )) {\n baseCtx.setAttribute(key, value);\n }\n }\n\n // Create extended context\n const callbackCtx: CallbackContext = {\n ...baseCtx,\n parkedContext,\n elapsedMs,\n correlationKey,\n };\n\n // Execute user's function\n const userFn = fnFactory(callbackCtx);\n return userFn(...args);\n };\n }) as (...args: TArgs) => Promise<TReturn>; // async factory always returns a Promise\n };\n },\n\n createLink(storedContext: StoredTraceContext): Link {\n return {\n context: {\n traceId: storedContext.traceId,\n spanId: storedContext.spanId,\n traceFlags: storedContext.traceFlags,\n isRemote: true,\n },\n attributes: {\n 'link.type': 'parking_lot',\n 'parking_lot.parked_at': storedContext.parkedAt,\n ...(storedContext.metadata && {\n 'parking_lot.has_metadata': true,\n }),\n },\n };\n },\n\n async exists(correlationKey: string): Promise<boolean> {\n const fullKey = prefixKey(correlationKey);\n const context = await store.load(fullKey);\n return context !== null;\n },\n };\n\n return parkingLot;\n}\n\n// ============================================================================\n// Utility Functions\n// ============================================================================\n\n/**\n * Create a correlation key from multiple parts\n *\n * @param parts - Key parts to join\n * @returns A correlation key string\n *\n * @example\n * ```typescript\n * const key = createCorrelationKey('payment', orderId, 'stripe');\n * // Returns: \"payment:order-123:stripe\"\n * ```\n */\nexport function createCorrelationKey(...parts: (string | number)[]): string {\n return parts.map(String).join(':');\n}\n\n/**\n * Extract span context from stored context for manual linking\n *\n * @param storedContext - The stored trace context\n * @returns SpanContext compatible object\n */\nexport function toSpanContext(storedContext: StoredTraceContext): SpanContext {\n return {\n traceId: storedContext.traceId,\n spanId: storedContext.spanId,\n traceFlags: storedContext.traceFlags,\n isRemote: true,\n };\n}\n"],"mappings":"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"}
@@ -1,6 +1,5 @@
1
- import { n as TraceContext } from "./trace-context-Cijqoi6e.cjs";
1
+ import { n as TraceContext } from "./trace-context-IuyoPyNq.cjs";
2
2
  import { Link, SpanContext } from "@opentelemetry/api";
3
-
4
3
  //#region src/webhook.d.ts
5
4
  /**
6
5
  * Stored trace context for parking lot pattern
@@ -145,7 +144,7 @@ interface ParkingLot {
145
144
  * });
146
145
  * ```
147
146
  */
148
- traceCallback<TArgs extends unknown[], TReturn>(config: CallbackConfig): (fnFactory: (ctx: CallbackContext) => (...args: TArgs) => Promise<TReturn>) => (...args: TArgs) => Promise<TReturn>;
147
+ traceCallback(config: CallbackConfig): <TArgs extends unknown[], TReturn>(fnFactory: (ctx: CallbackContext) => (...args: TArgs) => Promise<TReturn>) => (...args: TArgs) => Promise<TReturn>;
149
148
  /**
150
149
  * Manually create a span link from stored context
151
150
  *
@@ -180,7 +179,8 @@ declare class InMemoryTraceContextStore implements TraceContextStore {
180
179
  private store;
181
180
  private cleanupInterval;
182
181
  constructor(options?: {
183
- /** Cleanup interval in ms (default: 60000) */cleanupIntervalMs?: number;
182
+ /** Cleanup interval in ms (default: 60000) */
183
+ cleanupIntervalMs?: number;
184
184
  });
185
185
  save(key: string, context: StoredTraceContext): Promise<void>;
186
186
  load(key: string): Promise<StoredTraceContext | null>;
@@ -1 +1 @@
1
- {"version":3,"file":"webhook.d.cts","names":[],"sources":["../src/webhook.ts"],"mappings":";;;;;;;UAkDiB,kBAAA;EAqDA;EAnDf,OAAA;;EAGA,MAAA;EAkDA;EA/CA,UAAA;EAkDA;EA/CA,QAAA;EAqDA;EAlDA,KAAA;EAqDU;EAlDV,QAAA,GAAW,MAAM;AAAA;AAwDnB;;;;;AAAA,UAhDiB,iBAAA;EA4DM;;;;;AAMD;EA3DpB,IAAA,CAAK,GAAA,UAAa,OAAA,EAAS,kBAAA,GAAqB,OAAA;EAiEjB;;;;;;EAzD/B,IAAA,CAAK,GAAA,WAAc,OAAA,CAAQ,kBAAA;EAiE3B;;AAAc;AAMhB;;EAhEE,MAAA,CAAO,GAAA,WAAc,OAAA;AAAA;;;;UAMN,gBAAA;EAoHI;EAlHnB,KAAA,EAAO,iBAAiB;EAkH2C;EA/GnE,YAAA;EAgHe;EA7Gf,SAAA;EA6GyB;EA1GzB,oBAAA;EAoH+C;EAjH/C,MAAA,IAAU,cAAA;AAAA;;;;UAMK,cAAA;EA2Db;EAzDF,IAAA;EAkEA;;;;;;;;EAxDA,kBAAA,GAAqB,IAAA;EAoFF;EAjFnB,UAAA,GAAa,MAAM;EAiFwB;EA9E3C,oBAAA;AAAA;;;;UAMe,eAAA,SAAwB,YAAY;EAyE1B;EAvEzB,aAAA,EAAe,kBAAA;EAiFf;EA9EA,SAAA;EA8EW;EA3EX,cAAA;AAAA;;;;UAMe,UAAA;EAgGJ;;;;;;;;;;;;;;;;;;EA7EX,IAAA,CACE,cAAA,UACA,QAAA,GAAW,MAAA,mBACV,OAAA;EA+FQ;;;;;;EAvFX,QAAA,CAAS,cAAA,WAAyB,OAAA,CAAQ,kBAAA;EA2FT;;;;;;;;;AA8ClB;AAwDjB;;;;;;;;AAAsE;AA2OtE;;;;EAnZE,aAAA,mCACE,MAAA,EAAQ,cAAA,IAER,SAAA,GAAY,GAAA,EAAK,eAAA,SAAwB,IAAA,EAAM,KAAA,KAAU,OAAA,CAAQ,OAAA,UAC1D,IAAA,EAAM,KAAA,KAAU,OAAA,CAAQ,OAAA;EAyZnB;;;;;;;;EA/Yd,UAAA,CAAW,aAAA,EAAe,kBAAA,GAAqB,IAAA;;;;;;;EAQ/C,MAAA,CAAO,cAAA,WAAyB,OAAA;AAAA;;;;;;;;;;;;;cAmBrB,yBAAA,YAAqC,iBAAA;EAAA,QAKtC,OAAA;EAAA,QAJF,KAAA;EAAA,QACA,eAAA;cAGE,OAAA;kDAEN,iBAAA;EAAA;EAcE,IAAA,CAAK,GAAA,UAAa,OAAA,EAAS,kBAAA,GAAqB,OAAA;EAIhD,IAAA,CAAK,GAAA,WAAc,OAAA,CAAQ,kBAAA;EAkB3B,MAAA,CAAO,GAAA,WAAc,OAAA;;;;MAOvB,IAAA;;;;EAOJ,KAAA;;;;EAOA,OAAA;EAAA,QAOQ,OAAA;AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAwDM,gBAAA,CAAiB,MAAA,EAAQ,gBAAA,GAAmB,UAAU;;;;;;;;;;;;;iBA2OtD,oBAAA,IAAwB,KAAK;;;;;;;iBAU7B,aAAA,CAAc,aAAA,EAAe,kBAAA,GAAqB,WAAW"}
1
+ {"version":3,"file":"webhook.d.cts","names":[],"sources":["../src/webhook.ts"],"mappings":";;;;;;UAmDiB;;EAEf;;EAGA;;EAGA;;EAGA;;EAGA;;EAGA,WAAW;;;;;;;UAQI;;;;;;;EAOf,KAAK,aAAa,SAAS,qBAAqB;;;;;;;EAQhD,KAAK,cAAc,QAAQ;;;;;;EAO3B,OAAO,cAAc;;;;;UAMN;;EAEf,OAAO;;EAGP;;EAGA;;EAGA;;EAGA,UAAU;;;;;UAMK;;EAEf;;;;;;;;;EAUA,qBAAqB;;EAGrB,aAAa;;EAGb;;;;;UAMe,wBAAwB;;EAEvC,eAAe;;EAGf;;EAGA;;;;;UAMe;;;;;;;;;;;;;;;;;;;EAmBf,KACE,wBACA,WAAW,yBACV;;;;;;;EAQH,SAAS,yBAAyB,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;EAyB1C,cACE,QAAQ,kBACN,yBAAyB,SAC3B,YAAY,KAAK,wBAAwB,MAAM,UAAU,QAAQ,iBAC1D,MAAM,UAAU,QAAQ;;;;;;;;;EAUjC,WAAW,eAAe,qBAAqB;;;;;;;EAQ/C,OAAO,yBAAyB;;;;;;;;;;;;;;cAmBrB,qCAAqC;UAKtC;UAJF;UACA;EAGE,YAAA;;IAEN;;EAcE,KAAK,aAAa,SAAS,qBAAqB;EAIhD,KAAK,cAAc,QAAQ;EAkB3B,OAAO,cAAc;;;;MAOvB;;;;EAOJ;;;;EAOA;UAOQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAwDM,iBAAiB,QAAQ,mBAAmB;;;;;;;;;;;;;iBAqO5C,wBAAwB;;;;;;;iBAUxB,cAAc,eAAe,qBAAqB"}
package/dist/webhook.d.ts CHANGED
@@ -1,6 +1,5 @@
1
- import { n as TraceContext } from "./trace-context-Cijqoi6e.js";
1
+ import { n as TraceContext } from "./trace-context-IuyoPyNq.js";
2
2
  import { Link, SpanContext } from "@opentelemetry/api";
3
-
4
3
  //#region src/webhook.d.ts
5
4
  /**
6
5
  * Stored trace context for parking lot pattern
@@ -145,7 +144,7 @@ interface ParkingLot {
145
144
  * });
146
145
  * ```
147
146
  */
148
- traceCallback<TArgs extends unknown[], TReturn>(config: CallbackConfig): (fnFactory: (ctx: CallbackContext) => (...args: TArgs) => Promise<TReturn>) => (...args: TArgs) => Promise<TReturn>;
147
+ traceCallback(config: CallbackConfig): <TArgs extends unknown[], TReturn>(fnFactory: (ctx: CallbackContext) => (...args: TArgs) => Promise<TReturn>) => (...args: TArgs) => Promise<TReturn>;
149
148
  /**
150
149
  * Manually create a span link from stored context
151
150
  *
@@ -180,7 +179,8 @@ declare class InMemoryTraceContextStore implements TraceContextStore {
180
179
  private store;
181
180
  private cleanupInterval;
182
181
  constructor(options?: {
183
- /** Cleanup interval in ms (default: 60000) */cleanupIntervalMs?: number;
182
+ /** Cleanup interval in ms (default: 60000) */
183
+ cleanupIntervalMs?: number;
184
184
  });
185
185
  save(key: string, context: StoredTraceContext): Promise<void>;
186
186
  load(key: string): Promise<StoredTraceContext | null>;
@@ -1 +1 @@
1
- {"version":3,"file":"webhook.d.ts","names":[],"sources":["../src/webhook.ts"],"mappings":";;;;;;;UAkDiB,kBAAA;EAqDA;EAnDf,OAAA;;EAGA,MAAA;EAkDA;EA/CA,UAAA;EAkDA;EA/CA,QAAA;EAqDA;EAlDA,KAAA;EAqDU;EAlDV,QAAA,GAAW,MAAM;AAAA;AAwDnB;;;;;AAAA,UAhDiB,iBAAA;EA4DM;;;;;AAMD;EA3DpB,IAAA,CAAK,GAAA,UAAa,OAAA,EAAS,kBAAA,GAAqB,OAAA;EAiEjB;;;;;;EAzD/B,IAAA,CAAK,GAAA,WAAc,OAAA,CAAQ,kBAAA;EAiE3B;;AAAc;AAMhB;;EAhEE,MAAA,CAAO,GAAA,WAAc,OAAA;AAAA;;;;UAMN,gBAAA;EAoHI;EAlHnB,KAAA,EAAO,iBAAiB;EAkH2C;EA/GnE,YAAA;EAgHe;EA7Gf,SAAA;EA6GyB;EA1GzB,oBAAA;EAoH+C;EAjH/C,MAAA,IAAU,cAAA;AAAA;;;;UAMK,cAAA;EA2Db;EAzDF,IAAA;EAkEA;;;;;;;;EAxDA,kBAAA,GAAqB,IAAA;EAoFF;EAjFnB,UAAA,GAAa,MAAM;EAiFwB;EA9E3C,oBAAA;AAAA;;;;UAMe,eAAA,SAAwB,YAAY;EAyE1B;EAvEzB,aAAA,EAAe,kBAAA;EAiFf;EA9EA,SAAA;EA8EW;EA3EX,cAAA;AAAA;;;;UAMe,UAAA;EAgGJ;;;;;;;;;;;;;;;;;;EA7EX,IAAA,CACE,cAAA,UACA,QAAA,GAAW,MAAA,mBACV,OAAA;EA+FQ;;;;;;EAvFX,QAAA,CAAS,cAAA,WAAyB,OAAA,CAAQ,kBAAA;EA2FT;;;;;;;;;AA8ClB;AAwDjB;;;;;;;;AAAsE;AA2OtE;;;;EAnZE,aAAA,mCACE,MAAA,EAAQ,cAAA,IAER,SAAA,GAAY,GAAA,EAAK,eAAA,SAAwB,IAAA,EAAM,KAAA,KAAU,OAAA,CAAQ,OAAA,UAC1D,IAAA,EAAM,KAAA,KAAU,OAAA,CAAQ,OAAA;EAyZnB;;;;;;;;EA/Yd,UAAA,CAAW,aAAA,EAAe,kBAAA,GAAqB,IAAA;;;;;;;EAQ/C,MAAA,CAAO,cAAA,WAAyB,OAAA;AAAA;;;;;;;;;;;;;cAmBrB,yBAAA,YAAqC,iBAAA;EAAA,QAKtC,OAAA;EAAA,QAJF,KAAA;EAAA,QACA,eAAA;cAGE,OAAA;kDAEN,iBAAA;EAAA;EAcE,IAAA,CAAK,GAAA,UAAa,OAAA,EAAS,kBAAA,GAAqB,OAAA;EAIhD,IAAA,CAAK,GAAA,WAAc,OAAA,CAAQ,kBAAA;EAkB3B,MAAA,CAAO,GAAA,WAAc,OAAA;;;;MAOvB,IAAA;;;;EAOJ,KAAA;;;;EAOA,OAAA;EAAA,QAOQ,OAAA;AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAwDM,gBAAA,CAAiB,MAAA,EAAQ,gBAAA,GAAmB,UAAU;;;;;;;;;;;;;iBA2OtD,oBAAA,IAAwB,KAAK;;;;;;;iBAU7B,aAAA,CAAc,aAAA,EAAe,kBAAA,GAAqB,WAAW"}
1
+ {"version":3,"file":"webhook.d.ts","names":[],"sources":["../src/webhook.ts"],"mappings":";;;;;;UAmDiB;;EAEf;;EAGA;;EAGA;;EAGA;;EAGA;;EAGA,WAAW;;;;;;;UAQI;;;;;;;EAOf,KAAK,aAAa,SAAS,qBAAqB;;;;;;;EAQhD,KAAK,cAAc,QAAQ;;;;;;EAO3B,OAAO,cAAc;;;;;UAMN;;EAEf,OAAO;;EAGP;;EAGA;;EAGA;;EAGA,UAAU;;;;;UAMK;;EAEf;;;;;;;;;EAUA,qBAAqB;;EAGrB,aAAa;;EAGb;;;;;UAMe,wBAAwB;;EAEvC,eAAe;;EAGf;;EAGA;;;;;UAMe;;;;;;;;;;;;;;;;;;;EAmBf,KACE,wBACA,WAAW,yBACV;;;;;;;EAQH,SAAS,yBAAyB,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;EAyB1C,cACE,QAAQ,kBACN,yBAAyB,SAC3B,YAAY,KAAK,wBAAwB,MAAM,UAAU,QAAQ,iBAC1D,MAAM,UAAU,QAAQ;;;;;;;;;EAUjC,WAAW,eAAe,qBAAqB;;;;;;;EAQ/C,OAAO,yBAAyB;;;;;;;;;;;;;;cAmBrB,qCAAqC;UAKtC;UAJF;UACA;EAGE,YAAA;;IAEN;;EAcE,KAAK,aAAa,SAAS,qBAAqB;EAIhD,KAAK,cAAc,QAAQ;EAkB3B,OAAO,cAAc;;;;MAOvB;;;;EAOJ;;;;EAOA;UAOQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAwDM,iBAAiB,QAAQ,mBAAmB;;;;;;;;;;;;;iBAqO5C,wBAAwB;;;;;;;iBAUxB,cAAc,eAAe,qBAAqB"}
package/dist/webhook.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { r as recordStructuredError } from "./structured-error-9--cxBay.js";
2
- import { a as trace$1 } from "./functional-CrAq8Az8.js";
2
+ import { c as withTracing } from "./functional-Bit6noRu.js";
3
3
  import { t as emitCorrelatedEvent } from "./correlated-events-Bzh5y-UB.js";
4
4
  import { SpanKind, trace } from "@opentelemetry/api";
5
5
 
@@ -14,6 +14,7 @@ import { SpanKind, trace } from "@opentelemetry/api";
14
14
  * @example Stripe payment webhook
15
15
  * ```typescript
16
16
  * import { createParkingLot, InMemoryTraceContextStore } from 'autotel/webhook';
17
+ * import { withTracing } from 'autotel';
17
18
  *
18
19
  * const parkingLot = createParkingLot({
19
20
  * store: new InMemoryTraceContextStore(),
@@ -21,7 +22,7 @@ import { SpanKind, trace } from "@opentelemetry/api";
21
22
  * });
22
23
  *
23
24
  * // When initiating payment
24
- * export const initiatePayment = trace(ctx => async (orderId: string) => {
25
+ * export const initiatePayment = withTracing({})(ctx => async (orderId: string) => {
25
26
  * await parkingLot.park(`payment:${orderId}`, { orderId });
26
27
  * await stripeClient.createPaymentIntent({ metadata: { orderId } });
27
28
  * });
@@ -200,10 +201,10 @@ function createParkingLot(config) {
200
201
  },
201
202
  traceCallback(callbackConfig) {
202
203
  return (fnFactory) => {
203
- return trace$1({
204
+ return withTracing({
204
205
  name: callbackConfig.name,
205
206
  spanKind: SpanKind.SERVER
206
- }, (baseCtx) => {
207
+ })((baseCtx) => {
207
208
  return async (...args) => {
208
209
  const correlationKey = callbackConfig.correlationKeyFrom(args);
209
210
  const parkedContext = await parkingLot.retrieve(correlationKey);
@@ -1 +1 @@
1
- {"version":3,"file":"webhook.js","names":["otelTrace","trace"],"sources":["../src/webhook.ts"],"sourcesContent":["/**\n * Webhook and callback tracing with the \"Parking Lot\" pattern\n *\n * When initiating async operations that return hours/days later (webhooks,\n * payment callbacks, human approvals), you can't keep a span open. This module\n * provides utilities to \"park\" trace context and retrieve it when callbacks arrive.\n *\n * @example Stripe payment webhook\n * ```typescript\n * import { createParkingLot, InMemoryTraceContextStore } from 'autotel/webhook';\n *\n * const parkingLot = createParkingLot({\n * store: new InMemoryTraceContextStore(),\n * defaultTTLMs: 24 * 60 * 60 * 1000, // 24 hours\n * });\n *\n * // When initiating payment\n * export const initiatePayment = trace(ctx => async (orderId: string) => {\n * await parkingLot.park(`payment:${orderId}`, { orderId });\n * await stripeClient.createPaymentIntent({ metadata: { orderId } });\n * });\n *\n * // When Stripe webhook arrives (hours later)\n * export const handleStripeWebhook = parkingLot.traceCallback({\n * name: 'stripe.webhook.payment_intent.succeeded',\n * correlationKeyFrom: (event) => `payment:${event.data.object.metadata.orderId}`,\n * })(ctx => async (event: Stripe.Event) => {\n * // ctx.parkedContext contains the original trace context\n * // ctx.elapsedMs shows time since payment was initiated\n * await fulfillOrder(event.data.object);\n * });\n * ```\n *\n * @module\n */\n\nimport { SpanKind, trace as otelTrace } from '@opentelemetry/api';\nimport type { SpanContext, Link } from '@opentelemetry/api';\nimport { emitCorrelatedEvent } from './correlated-events';\nimport { trace } from './functional';\nimport type { AttributeValue, TraceContext } from './trace-context';\nimport { recordStructuredError } from './structured-error';\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Stored trace context for parking lot pattern\n */\nexport interface StoredTraceContext {\n /** Trace ID from the original span */\n traceId: string;\n\n /** Span ID from the original span */\n spanId: string;\n\n /** Trace flags (sampling decision) */\n traceFlags: number;\n\n /** When the context was parked */\n parkedAt: number;\n\n /** Optional TTL in milliseconds */\n ttlMs?: number;\n\n /** User-provided metadata */\n metadata?: Record<string, string>;\n}\n\n/**\n * Interface for trace context storage backends\n *\n * Implement this interface to use different storage backends (Redis, DynamoDB, etc.)\n */\nexport interface TraceContextStore {\n /**\n * Save trace context with a correlation key\n *\n * @param key - Unique correlation key (e.g., \"payment:order-123\")\n * @param context - The trace context to store\n */\n save(key: string, context: StoredTraceContext): Promise<void>;\n\n /**\n * Load trace context by correlation key\n *\n * @param key - The correlation key used when parking\n * @returns The stored context, or null if not found/expired\n */\n load(key: string): Promise<StoredTraceContext | null>;\n\n /**\n * Delete trace context by correlation key\n *\n * @param key - The correlation key to delete\n */\n delete(key: string): Promise<void>;\n}\n\n/**\n * Configuration for creating a parking lot\n */\nexport interface ParkingLotConfig {\n /** Storage backend for parked contexts */\n store: TraceContextStore;\n\n /** Default TTL in milliseconds (default: 24 hours) */\n defaultTTLMs?: number;\n\n /** Prefix for all correlation keys (default: \"parkingLot:\") */\n keyPrefix?: string;\n\n /** Whether to auto-delete after retrieval (default: true) */\n autoDeleteOnRetrieve?: boolean;\n\n /** Callback when context expires or is not found */\n onMiss?: (correlationKey: string) => void;\n}\n\n/**\n * Configuration for traceCallback wrapper\n */\nexport interface CallbackConfig {\n /** Span name for the callback handler */\n name: string;\n\n /**\n * Extract correlation key from callback arguments\n *\n * @example\n * ```typescript\n * correlationKeyFrom: (event) => `payment:${event.data.orderId}`\n * ```\n */\n correlationKeyFrom: (args: unknown[]) => string;\n\n /** Additional span attributes */\n attributes?: Record<string, string | number | boolean>;\n\n /** Whether to fail if parked context is not found (default: false) */\n requireParkedContext?: boolean;\n}\n\n/**\n * Extended context for callback handlers\n */\nexport interface CallbackContext extends TraceContext {\n /** The retrieved parked context, if found */\n parkedContext: StoredTraceContext | null;\n\n /** Time elapsed since context was parked (ms), or null if not found */\n elapsedMs: number | null;\n\n /** The correlation key used for retrieval */\n correlationKey: string;\n}\n\n/**\n * The parking lot instance\n */\nexport interface ParkingLot {\n /**\n * Park current trace context before initiating async operation\n *\n * Call this before sending a webhook, initiating a payment, or starting\n * any operation that will complete via callback.\n *\n * @param correlationKey - Unique key to retrieve context later (e.g., \"payment:order-123\")\n * @param metadata - Optional metadata to store with the context\n * @returns The correlation key (with prefix applied)\n *\n * @example\n * ```typescript\n * await parkingLot.park(`payment:${orderId}`, {\n * customerId: customer.id,\n * amount: payment.amount.toString(),\n * });\n * ```\n */\n park(\n correlationKey: string,\n metadata?: Record<string, string>,\n ): Promise<string>;\n\n /**\n * Retrieve parked context when callback arrives\n *\n * @param correlationKey - The key used when parking\n * @returns The stored context, or null if not found/expired\n */\n retrieve(correlationKey: string): Promise<StoredTraceContext | null>;\n\n /**\n * Wrap a callback handler with automatic context retrieval and linking\n *\n * Creates a traced function that:\n * 1. Extracts correlation key from arguments\n * 2. Retrieves parked context from storage\n * 3. Creates a span link to the original trace\n * 4. Provides elapsed time since parking\n *\n * @param config - Callback configuration\n * @returns Factory function for the callback handler\n *\n * @example\n * ```typescript\n * export const handleWebhook = parkingLot.traceCallback({\n * name: 'webhook.payment.completed',\n * correlationKeyFrom: (args) => `payment:${args[0].orderId}`,\n * })(ctx => async (event) => {\n * console.log(`Payment completed after ${ctx.elapsedMs}ms`);\n * await processPayment(event);\n * });\n * ```\n */\n traceCallback<TArgs extends unknown[], TReturn>(\n config: CallbackConfig,\n ): (\n fnFactory: (ctx: CallbackContext) => (...args: TArgs) => Promise<TReturn>,\n ) => (...args: TArgs) => Promise<TReturn>;\n\n /**\n * Manually create a span link from stored context\n *\n * Useful when you need more control over span creation.\n *\n * @param storedContext - The stored trace context\n * @returns A span link that can be added to a span\n */\n createLink(storedContext: StoredTraceContext): Link;\n\n /**\n * Check if a parked context exists (without retrieving/deleting it)\n *\n * @param correlationKey - The key to check\n * @returns True if context exists and hasn't expired\n */\n exists(correlationKey: string): Promise<boolean>;\n}\n\n// ============================================================================\n// In-Memory Store (for testing and development)\n// ============================================================================\n\n/**\n * In-memory trace context store\n *\n * Useful for testing and development. For production, use a persistent\n * store like Redis or DynamoDB.\n *\n * @example\n * ```typescript\n * const store = new InMemoryTraceContextStore();\n * const parkingLot = createParkingLot({ store });\n * ```\n */\nexport class InMemoryTraceContextStore implements TraceContextStore {\n private store = new Map<string, StoredTraceContext>();\n private cleanupInterval: ReturnType<typeof setInterval> | null = null;\n\n constructor(\n private options: {\n /** Cleanup interval in ms (default: 60000) */\n cleanupIntervalMs?: number;\n } = {},\n ) {\n // Start periodic cleanup of expired entries\n const cleanupMs = options.cleanupIntervalMs ?? 60_000;\n if (cleanupMs > 0) {\n this.cleanupInterval = setInterval(() => this.cleanup(), cleanupMs);\n // Don't prevent process exit\n if (this.cleanupInterval.unref) {\n this.cleanupInterval.unref();\n }\n }\n }\n\n async save(key: string, context: StoredTraceContext): Promise<void> {\n this.store.set(key, context);\n }\n\n async load(key: string): Promise<StoredTraceContext | null> {\n const context = this.store.get(key);\n if (!context) {\n return null;\n }\n\n // Check TTL expiration\n if (context.ttlMs) {\n const age = Date.now() - context.parkedAt;\n if (age > context.ttlMs) {\n this.store.delete(key);\n return null;\n }\n }\n\n return context;\n }\n\n async delete(key: string): Promise<void> {\n this.store.delete(key);\n }\n\n /**\n * Get number of stored contexts (for testing)\n */\n get size(): number {\n return this.store.size;\n }\n\n /**\n * Clear all stored contexts (for testing)\n */\n clear(): void {\n this.store.clear();\n }\n\n /**\n * Stop the cleanup interval\n */\n destroy(): void {\n if (this.cleanupInterval) {\n clearInterval(this.cleanupInterval);\n this.cleanupInterval = null;\n }\n }\n\n private cleanup(): void {\n const now = Date.now();\n for (const [key, context] of this.store.entries()) {\n if (context.ttlMs) {\n const age = now - context.parkedAt;\n if (age > context.ttlMs) {\n this.store.delete(key);\n }\n }\n }\n }\n}\n\n// ============================================================================\n// Parking Lot Factory\n// ============================================================================\n\n/**\n * Create a parking lot for trace context storage and retrieval\n *\n * @param config - Parking lot configuration\n * @returns A parking lot instance\n *\n * @example Basic usage\n * ```typescript\n * const parkingLot = createParkingLot({\n * store: new InMemoryTraceContextStore(),\n * defaultTTLMs: 24 * 60 * 60 * 1000, // 24 hours\n * });\n * ```\n *\n * @example With Redis store\n * ```typescript\n * class RedisTraceContextStore implements TraceContextStore {\n * constructor(private redis: Redis) {}\n *\n * async save(key: string, context: StoredTraceContext) {\n * const ttlSeconds = context.ttlMs ? Math.ceil(context.ttlMs / 1000) : 86400;\n * await this.redis.setex(key, ttlSeconds, JSON.stringify(context));\n * }\n *\n * async load(key: string) {\n * const data = await this.redis.get(key);\n * return data ? JSON.parse(data) : null;\n * }\n *\n * async delete(key: string) {\n * await this.redis.del(key);\n * }\n * }\n *\n * const parkingLot = createParkingLot({\n * store: new RedisTraceContextStore(redis),\n * });\n * ```\n */\nexport function createParkingLot(config: ParkingLotConfig): ParkingLot {\n const {\n store,\n defaultTTLMs = 24 * 60 * 60 * 1000, // 24 hours\n keyPrefix = 'parkingLot:',\n autoDeleteOnRetrieve = true,\n onMiss,\n } = config;\n\n /**\n * Get current span context from active context\n */\n function getCurrentSpanContext(): SpanContext | null {\n const activeSpan = otelTrace.getActiveSpan();\n if (!activeSpan) {\n return null;\n }\n return activeSpan.spanContext();\n }\n\n /**\n * Apply key prefix\n */\n function prefixKey(key: string): string {\n return `${keyPrefix}${key}`;\n }\n\n const parkingLot: ParkingLot = {\n async park(\n correlationKey: string,\n metadata?: Record<string, string>,\n ): Promise<string> {\n const spanContext = getCurrentSpanContext();\n const fullKey = prefixKey(correlationKey);\n\n const storedContext: StoredTraceContext = {\n traceId: spanContext?.traceId ?? '',\n spanId: spanContext?.spanId ?? '',\n traceFlags: spanContext?.traceFlags ?? 0,\n parkedAt: Date.now(),\n ttlMs: defaultTTLMs,\n metadata,\n };\n\n await store.save(fullKey, storedContext);\n\n const activeSpan = otelTrace.getActiveSpan();\n if (activeSpan) {\n const parkAttrs: Record<string, AttributeValue> = {\n 'parking_lot.correlation_key': correlationKey,\n 'parking_lot.ttl_ms': defaultTTLMs,\n ...(metadata &&\n Object.fromEntries(\n Object.entries(metadata).map(([k, v]) => [\n `parking_lot.metadata.${k}`,\n v,\n ]),\n )),\n };\n emitCorrelatedEvent(\n {\n setAttribute: (k, v) => activeSpan.setAttribute(k, v),\n setAttributes: (a) => activeSpan.setAttributes(a),\n addEvent: (n, a) => activeSpan.addEvent(n, a),\n },\n 'trace_context_parked',\n parkAttrs,\n );\n }\n\n // Return the unprefixed key so callers can use the same key for retrieve()\n return correlationKey;\n },\n\n async retrieve(correlationKey: string): Promise<StoredTraceContext | null> {\n const fullKey = prefixKey(correlationKey);\n const storedContext = await store.load(fullKey);\n\n if (!storedContext) {\n onMiss?.(correlationKey);\n return null;\n }\n\n if (autoDeleteOnRetrieve) {\n await store.delete(fullKey);\n }\n\n return storedContext;\n },\n\n traceCallback<TArgs extends unknown[], TReturn>(\n callbackConfig: CallbackConfig,\n ): (\n fnFactory: (ctx: CallbackContext) => (...args: TArgs) => Promise<TReturn>,\n ) => (...args: TArgs) => Promise<TReturn> {\n return (\n fnFactory: (\n ctx: CallbackContext,\n ) => (...args: TArgs) => Promise<TReturn>,\n ): ((...args: TArgs) => Promise<TReturn>) => {\n return trace<TArgs, TReturn>(\n {\n name: callbackConfig.name,\n spanKind: SpanKind.SERVER,\n },\n (baseCtx) => {\n return async (...args: TArgs) => {\n // Extract correlation key from arguments\n const correlationKey = callbackConfig.correlationKeyFrom(args);\n\n // Retrieve parked context\n const parkedContext = await parkingLot.retrieve(correlationKey);\n\n // Calculate elapsed time\n const elapsedMs = parkedContext\n ? Date.now() - parkedContext.parkedAt\n : null;\n\n // Set span attributes\n baseCtx.setAttribute(\n 'parking_lot.correlation_key',\n correlationKey,\n );\n\n if (parkedContext) {\n baseCtx.setAttribute('parking_lot.elapsed_ms', elapsedMs!);\n baseCtx.setAttribute(\n 'parking_lot.original_trace_id',\n parkedContext.traceId,\n );\n baseCtx.setAttribute(\n 'parking_lot.original_span_id',\n parkedContext.spanId,\n );\n\n // Add metadata as attributes\n if (parkedContext.metadata) {\n for (const [key, value] of Object.entries(\n parkedContext.metadata,\n )) {\n baseCtx.setAttribute(`parking_lot.metadata.${key}`, value);\n }\n }\n\n // Create span link to original trace\n const link = parkingLot.createLink(parkedContext);\n baseCtx.addLinks([link]);\n\n emitCorrelatedEvent(baseCtx, 'parked_context_retrieved', {\n 'parking_lot.correlation_key': correlationKey,\n 'parking_lot.elapsed_ms': elapsedMs!,\n 'parking_lot.original_trace_id': parkedContext.traceId,\n });\n } else {\n baseCtx.setAttribute('parking_lot.context_found', false);\n\n if (callbackConfig.requireParkedContext) {\n const error = new Error(\n `Required parked context not found for key: ${correlationKey}`,\n );\n recordStructuredError(baseCtx, error);\n throw error;\n }\n }\n\n // Apply custom attributes\n if (callbackConfig.attributes) {\n for (const [key, value] of Object.entries(\n callbackConfig.attributes,\n )) {\n baseCtx.setAttribute(key, value);\n }\n }\n\n // Create extended context\n const callbackCtx: CallbackContext = {\n ...baseCtx,\n parkedContext,\n elapsedMs,\n correlationKey,\n };\n\n // Execute user's function\n const userFn = fnFactory(callbackCtx);\n return userFn(...args);\n };\n },\n );\n };\n },\n\n createLink(storedContext: StoredTraceContext): Link {\n return {\n context: {\n traceId: storedContext.traceId,\n spanId: storedContext.spanId,\n traceFlags: storedContext.traceFlags,\n isRemote: true,\n },\n attributes: {\n 'link.type': 'parking_lot',\n 'parking_lot.parked_at': storedContext.parkedAt,\n ...(storedContext.metadata && {\n 'parking_lot.has_metadata': true,\n }),\n },\n };\n },\n\n async exists(correlationKey: string): Promise<boolean> {\n const fullKey = prefixKey(correlationKey);\n const context = await store.load(fullKey);\n return context !== null;\n },\n };\n\n return parkingLot;\n}\n\n// ============================================================================\n// Utility Functions\n// ============================================================================\n\n/**\n * Create a correlation key from multiple parts\n *\n * @param parts - Key parts to join\n * @returns A correlation key string\n *\n * @example\n * ```typescript\n * const key = createCorrelationKey('payment', orderId, 'stripe');\n * // Returns: \"payment:order-123:stripe\"\n * ```\n */\nexport function createCorrelationKey(...parts: (string | number)[]): string {\n return parts.map(String).join(':');\n}\n\n/**\n * Extract span context from stored context for manual linking\n *\n * @param storedContext - The stored trace context\n * @returns SpanContext compatible object\n */\nexport function toSpanContext(storedContext: StoredTraceContext): SpanContext {\n return {\n traceId: storedContext.traceId,\n spanId: storedContext.spanId,\n traceFlags: storedContext.traceFlags,\n isRemote: true,\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"webhook.js","names":["otelTrace"],"sources":["../src/webhook.ts"],"sourcesContent":["/**\n * Webhook and callback tracing with the \"Parking Lot\" pattern\n *\n * When initiating async operations that return hours/days later (webhooks,\n * payment callbacks, human approvals), you can't keep a span open. This module\n * provides utilities to \"park\" trace context and retrieve it when callbacks arrive.\n *\n * @example Stripe payment webhook\n * ```typescript\n * import { createParkingLot, InMemoryTraceContextStore } from 'autotel/webhook';\n * import { withTracing } from 'autotel';\n *\n * const parkingLot = createParkingLot({\n * store: new InMemoryTraceContextStore(),\n * defaultTTLMs: 24 * 60 * 60 * 1000, // 24 hours\n * });\n *\n * // When initiating payment\n * export const initiatePayment = withTracing({})(ctx => async (orderId: string) => {\n * await parkingLot.park(`payment:${orderId}`, { orderId });\n * await stripeClient.createPaymentIntent({ metadata: { orderId } });\n * });\n *\n * // When Stripe webhook arrives (hours later)\n * export const handleStripeWebhook = parkingLot.traceCallback({\n * name: 'stripe.webhook.payment_intent.succeeded',\n * correlationKeyFrom: (event) => `payment:${event.data.object.metadata.orderId}`,\n * })(ctx => async (event: Stripe.Event) => {\n * // ctx.parkedContext contains the original trace context\n * // ctx.elapsedMs shows time since payment was initiated\n * await fulfillOrder(event.data.object);\n * });\n * ```\n *\n * @module\n */\n\nimport { SpanKind, trace as otelTrace } from '@opentelemetry/api';\nimport type { SpanContext, Link } from '@opentelemetry/api';\nimport { emitCorrelatedEvent } from './correlated-events';\nimport { withTracing } from './functional';\nimport type { AttributeValue, TraceContext } from './trace-context';\nimport { recordStructuredError } from './structured-error';\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Stored trace context for parking lot pattern\n */\nexport interface StoredTraceContext {\n /** Trace ID from the original span */\n traceId: string;\n\n /** Span ID from the original span */\n spanId: string;\n\n /** Trace flags (sampling decision) */\n traceFlags: number;\n\n /** When the context was parked */\n parkedAt: number;\n\n /** Optional TTL in milliseconds */\n ttlMs?: number;\n\n /** User-provided metadata */\n metadata?: Record<string, string>;\n}\n\n/**\n * Interface for trace context storage backends\n *\n * Implement this interface to use different storage backends (Redis, DynamoDB, etc.)\n */\nexport interface TraceContextStore {\n /**\n * Save trace context with a correlation key\n *\n * @param key - Unique correlation key (e.g., \"payment:order-123\")\n * @param context - The trace context to store\n */\n save(key: string, context: StoredTraceContext): Promise<void>;\n\n /**\n * Load trace context by correlation key\n *\n * @param key - The correlation key used when parking\n * @returns The stored context, or null if not found/expired\n */\n load(key: string): Promise<StoredTraceContext | null>;\n\n /**\n * Delete trace context by correlation key\n *\n * @param key - The correlation key to delete\n */\n delete(key: string): Promise<void>;\n}\n\n/**\n * Configuration for creating a parking lot\n */\nexport interface ParkingLotConfig {\n /** Storage backend for parked contexts */\n store: TraceContextStore;\n\n /** Default TTL in milliseconds (default: 24 hours) */\n defaultTTLMs?: number;\n\n /** Prefix for all correlation keys (default: \"parkingLot:\") */\n keyPrefix?: string;\n\n /** Whether to auto-delete after retrieval (default: true) */\n autoDeleteOnRetrieve?: boolean;\n\n /** Callback when context expires or is not found */\n onMiss?: (correlationKey: string) => void;\n}\n\n/**\n * Configuration for traceCallback wrapper\n */\nexport interface CallbackConfig {\n /** Span name for the callback handler */\n name: string;\n\n /**\n * Extract correlation key from callback arguments\n *\n * @example\n * ```typescript\n * correlationKeyFrom: (event) => `payment:${event.data.orderId}`\n * ```\n */\n correlationKeyFrom: (args: unknown[]) => string;\n\n /** Additional span attributes */\n attributes?: Record<string, string | number | boolean>;\n\n /** Whether to fail if parked context is not found (default: false) */\n requireParkedContext?: boolean;\n}\n\n/**\n * Extended context for callback handlers\n */\nexport interface CallbackContext extends TraceContext {\n /** The retrieved parked context, if found */\n parkedContext: StoredTraceContext | null;\n\n /** Time elapsed since context was parked (ms), or null if not found */\n elapsedMs: number | null;\n\n /** The correlation key used for retrieval */\n correlationKey: string;\n}\n\n/**\n * The parking lot instance\n */\nexport interface ParkingLot {\n /**\n * Park current trace context before initiating async operation\n *\n * Call this before sending a webhook, initiating a payment, or starting\n * any operation that will complete via callback.\n *\n * @param correlationKey - Unique key to retrieve context later (e.g., \"payment:order-123\")\n * @param metadata - Optional metadata to store with the context\n * @returns The correlation key (with prefix applied)\n *\n * @example\n * ```typescript\n * await parkingLot.park(`payment:${orderId}`, {\n * customerId: customer.id,\n * amount: payment.amount.toString(),\n * });\n * ```\n */\n park(\n correlationKey: string,\n metadata?: Record<string, string>,\n ): Promise<string>;\n\n /**\n * Retrieve parked context when callback arrives\n *\n * @param correlationKey - The key used when parking\n * @returns The stored context, or null if not found/expired\n */\n retrieve(correlationKey: string): Promise<StoredTraceContext | null>;\n\n /**\n * Wrap a callback handler with automatic context retrieval and linking\n *\n * Creates a traced function that:\n * 1. Extracts correlation key from arguments\n * 2. Retrieves parked context from storage\n * 3. Creates a span link to the original trace\n * 4. Provides elapsed time since parking\n *\n * @param config - Callback configuration\n * @returns Factory function for the callback handler\n *\n * @example\n * ```typescript\n * export const handleWebhook = parkingLot.traceCallback({\n * name: 'webhook.payment.completed',\n * correlationKeyFrom: (args) => `payment:${args[0].orderId}`,\n * })(ctx => async (event) => {\n * console.log(`Payment completed after ${ctx.elapsedMs}ms`);\n * await processPayment(event);\n * });\n * ```\n */\n traceCallback(\n config: CallbackConfig,\n ): <TArgs extends unknown[], TReturn>(\n fnFactory: (ctx: CallbackContext) => (...args: TArgs) => Promise<TReturn>,\n ) => (...args: TArgs) => Promise<TReturn>;\n\n /**\n * Manually create a span link from stored context\n *\n * Useful when you need more control over span creation.\n *\n * @param storedContext - The stored trace context\n * @returns A span link that can be added to a span\n */\n createLink(storedContext: StoredTraceContext): Link;\n\n /**\n * Check if a parked context exists (without retrieving/deleting it)\n *\n * @param correlationKey - The key to check\n * @returns True if context exists and hasn't expired\n */\n exists(correlationKey: string): Promise<boolean>;\n}\n\n// ============================================================================\n// In-Memory Store (for testing and development)\n// ============================================================================\n\n/**\n * In-memory trace context store\n *\n * Useful for testing and development. For production, use a persistent\n * store like Redis or DynamoDB.\n *\n * @example\n * ```typescript\n * const store = new InMemoryTraceContextStore();\n * const parkingLot = createParkingLot({ store });\n * ```\n */\nexport class InMemoryTraceContextStore implements TraceContextStore {\n private store = new Map<string, StoredTraceContext>();\n private cleanupInterval: ReturnType<typeof setInterval> | null = null;\n\n constructor(\n private options: {\n /** Cleanup interval in ms (default: 60000) */\n cleanupIntervalMs?: number;\n } = {},\n ) {\n // Start periodic cleanup of expired entries\n const cleanupMs = options.cleanupIntervalMs ?? 60_000;\n if (cleanupMs > 0) {\n this.cleanupInterval = setInterval(() => this.cleanup(), cleanupMs);\n // Don't prevent process exit\n if (this.cleanupInterval.unref) {\n this.cleanupInterval.unref();\n }\n }\n }\n\n async save(key: string, context: StoredTraceContext): Promise<void> {\n this.store.set(key, context);\n }\n\n async load(key: string): Promise<StoredTraceContext | null> {\n const context = this.store.get(key);\n if (!context) {\n return null;\n }\n\n // Check TTL expiration\n if (context.ttlMs) {\n const age = Date.now() - context.parkedAt;\n if (age > context.ttlMs) {\n this.store.delete(key);\n return null;\n }\n }\n\n return context;\n }\n\n async delete(key: string): Promise<void> {\n this.store.delete(key);\n }\n\n /**\n * Get number of stored contexts (for testing)\n */\n get size(): number {\n return this.store.size;\n }\n\n /**\n * Clear all stored contexts (for testing)\n */\n clear(): void {\n this.store.clear();\n }\n\n /**\n * Stop the cleanup interval\n */\n destroy(): void {\n if (this.cleanupInterval) {\n clearInterval(this.cleanupInterval);\n this.cleanupInterval = null;\n }\n }\n\n private cleanup(): void {\n const now = Date.now();\n for (const [key, context] of this.store.entries()) {\n if (context.ttlMs) {\n const age = now - context.parkedAt;\n if (age > context.ttlMs) {\n this.store.delete(key);\n }\n }\n }\n }\n}\n\n// ============================================================================\n// Parking Lot Factory\n// ============================================================================\n\n/**\n * Create a parking lot for trace context storage and retrieval\n *\n * @param config - Parking lot configuration\n * @returns A parking lot instance\n *\n * @example Basic usage\n * ```typescript\n * const parkingLot = createParkingLot({\n * store: new InMemoryTraceContextStore(),\n * defaultTTLMs: 24 * 60 * 60 * 1000, // 24 hours\n * });\n * ```\n *\n * @example With Redis store\n * ```typescript\n * class RedisTraceContextStore implements TraceContextStore {\n * constructor(private redis: Redis) {}\n *\n * async save(key: string, context: StoredTraceContext) {\n * const ttlSeconds = context.ttlMs ? Math.ceil(context.ttlMs / 1000) : 86400;\n * await this.redis.setex(key, ttlSeconds, JSON.stringify(context));\n * }\n *\n * async load(key: string) {\n * const data = await this.redis.get(key);\n * return data ? JSON.parse(data) : null;\n * }\n *\n * async delete(key: string) {\n * await this.redis.del(key);\n * }\n * }\n *\n * const parkingLot = createParkingLot({\n * store: new RedisTraceContextStore(redis),\n * });\n * ```\n */\nexport function createParkingLot(config: ParkingLotConfig): ParkingLot {\n const {\n store,\n defaultTTLMs = 24 * 60 * 60 * 1000, // 24 hours\n keyPrefix = 'parkingLot:',\n autoDeleteOnRetrieve = true,\n onMiss,\n } = config;\n\n /**\n * Get current span context from active context\n */\n function getCurrentSpanContext(): SpanContext | null {\n const activeSpan = otelTrace.getActiveSpan();\n if (!activeSpan) {\n return null;\n }\n return activeSpan.spanContext();\n }\n\n /**\n * Apply key prefix\n */\n function prefixKey(key: string): string {\n return `${keyPrefix}${key}`;\n }\n\n const parkingLot: ParkingLot = {\n async park(\n correlationKey: string,\n metadata?: Record<string, string>,\n ): Promise<string> {\n const spanContext = getCurrentSpanContext();\n const fullKey = prefixKey(correlationKey);\n\n const storedContext: StoredTraceContext = {\n traceId: spanContext?.traceId ?? '',\n spanId: spanContext?.spanId ?? '',\n traceFlags: spanContext?.traceFlags ?? 0,\n parkedAt: Date.now(),\n ttlMs: defaultTTLMs,\n metadata,\n };\n\n await store.save(fullKey, storedContext);\n\n const activeSpan = otelTrace.getActiveSpan();\n if (activeSpan) {\n const parkAttrs: Record<string, AttributeValue> = {\n 'parking_lot.correlation_key': correlationKey,\n 'parking_lot.ttl_ms': defaultTTLMs,\n ...(metadata &&\n Object.fromEntries(\n Object.entries(metadata).map(([k, v]) => [\n `parking_lot.metadata.${k}`,\n v,\n ]),\n )),\n };\n emitCorrelatedEvent(\n {\n setAttribute: (k, v) => activeSpan.setAttribute(k, v),\n setAttributes: (a) => activeSpan.setAttributes(a),\n addEvent: (n, a) => activeSpan.addEvent(n, a),\n },\n 'trace_context_parked',\n parkAttrs,\n );\n }\n\n // Return the unprefixed key so callers can use the same key for retrieve()\n return correlationKey;\n },\n\n async retrieve(correlationKey: string): Promise<StoredTraceContext | null> {\n const fullKey = prefixKey(correlationKey);\n const storedContext = await store.load(fullKey);\n\n if (!storedContext) {\n onMiss?.(correlationKey);\n return null;\n }\n\n if (autoDeleteOnRetrieve) {\n await store.delete(fullKey);\n }\n\n return storedContext;\n },\n\n traceCallback(\n callbackConfig: CallbackConfig,\n ): <TArgs extends unknown[], TReturn>(\n fnFactory: (ctx: CallbackContext) => (...args: TArgs) => Promise<TReturn>,\n ) => (...args: TArgs) => Promise<TReturn> {\n return <TArgs extends unknown[], TReturn>(\n fnFactory: (\n ctx: CallbackContext,\n ) => (...args: TArgs) => Promise<TReturn>,\n ): ((...args: TArgs) => Promise<TReturn>) => {\n return withTracing<TArgs, TReturn>({\n name: callbackConfig.name,\n spanKind: SpanKind.SERVER,\n })((baseCtx) => {\n return async (...args: TArgs) => {\n // Extract correlation key from arguments\n const correlationKey = callbackConfig.correlationKeyFrom(args);\n\n // Retrieve parked context\n const parkedContext = await parkingLot.retrieve(correlationKey);\n\n // Calculate elapsed time\n const elapsedMs = parkedContext\n ? Date.now() - parkedContext.parkedAt\n : null;\n\n // Set span attributes\n baseCtx.setAttribute('parking_lot.correlation_key', correlationKey);\n\n if (parkedContext) {\n baseCtx.setAttribute('parking_lot.elapsed_ms', elapsedMs!);\n baseCtx.setAttribute(\n 'parking_lot.original_trace_id',\n parkedContext.traceId,\n );\n baseCtx.setAttribute(\n 'parking_lot.original_span_id',\n parkedContext.spanId,\n );\n\n // Add metadata as attributes\n if (parkedContext.metadata) {\n for (const [key, value] of Object.entries(\n parkedContext.metadata,\n )) {\n baseCtx.setAttribute(`parking_lot.metadata.${key}`, value);\n }\n }\n\n // Create span link to original trace\n const link = parkingLot.createLink(parkedContext);\n baseCtx.addLinks([link]);\n\n emitCorrelatedEvent(baseCtx, 'parked_context_retrieved', {\n 'parking_lot.correlation_key': correlationKey,\n 'parking_lot.elapsed_ms': elapsedMs!,\n 'parking_lot.original_trace_id': parkedContext.traceId,\n });\n } else {\n baseCtx.setAttribute('parking_lot.context_found', false);\n\n if (callbackConfig.requireParkedContext) {\n const error = new Error(\n `Required parked context not found for key: ${correlationKey}`,\n );\n recordStructuredError(baseCtx, error);\n throw error;\n }\n }\n\n // Apply custom attributes\n if (callbackConfig.attributes) {\n for (const [key, value] of Object.entries(\n callbackConfig.attributes,\n )) {\n baseCtx.setAttribute(key, value);\n }\n }\n\n // Create extended context\n const callbackCtx: CallbackContext = {\n ...baseCtx,\n parkedContext,\n elapsedMs,\n correlationKey,\n };\n\n // Execute user's function\n const userFn = fnFactory(callbackCtx);\n return userFn(...args);\n };\n }) as (...args: TArgs) => Promise<TReturn>; // async factory always returns a Promise\n };\n },\n\n createLink(storedContext: StoredTraceContext): Link {\n return {\n context: {\n traceId: storedContext.traceId,\n spanId: storedContext.spanId,\n traceFlags: storedContext.traceFlags,\n isRemote: true,\n },\n attributes: {\n 'link.type': 'parking_lot',\n 'parking_lot.parked_at': storedContext.parkedAt,\n ...(storedContext.metadata && {\n 'parking_lot.has_metadata': true,\n }),\n },\n };\n },\n\n async exists(correlationKey: string): Promise<boolean> {\n const fullKey = prefixKey(correlationKey);\n const context = await store.load(fullKey);\n return context !== null;\n },\n };\n\n return parkingLot;\n}\n\n// ============================================================================\n// Utility Functions\n// ============================================================================\n\n/**\n * Create a correlation key from multiple parts\n *\n * @param parts - Key parts to join\n * @returns A correlation key string\n *\n * @example\n * ```typescript\n * const key = createCorrelationKey('payment', orderId, 'stripe');\n * // Returns: \"payment:order-123:stripe\"\n * ```\n */\nexport function createCorrelationKey(...parts: (string | number)[]): string {\n return parts.map(String).join(':');\n}\n\n/**\n * Extract span context from stored context for manual linking\n *\n * @param storedContext - The stored trace context\n * @returns SpanContext compatible object\n */\nexport function toSpanContext(storedContext: StoredTraceContext): SpanContext {\n return {\n traceId: storedContext.traceId,\n spanId: storedContext.spanId,\n traceFlags: storedContext.traceFlags,\n isRemote: true,\n };\n}\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
- const require_functional = require('./functional-BCtxvYir.cjs');
2
+ const require_functional = require('./functional-Cj7PSItS.cjs');
3
3
  const require_correlated_events = require('./correlated-events-kSwLo3mi.cjs');
4
4
  const require_business_baggage = require('./business-baggage.cjs');
5
5
  let _opentelemetry_api = require("@opentelemetry/api");
@@ -171,10 +171,10 @@ const WorkflowBaggage = require_business_baggage.createSafeBaggageSchema(workflo
171
171
  function traceDistributedWorkflow(config) {
172
172
  const spanName = `workflow.${config.name}`;
173
173
  return (fnFactory) => {
174
- return require_functional.trace({
174
+ return require_functional.withTracing({
175
175
  name: spanName,
176
176
  spanKind: _opentelemetry_api.SpanKind.INTERNAL
177
- }, (baseCtx) => {
177
+ })((baseCtx) => {
178
178
  return async (...args) => {
179
179
  const workflowId = config.workflowIdFrom(...args);
180
180
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -292,10 +292,10 @@ function traceDistributedWorkflow(config) {
292
292
  function traceDistributedStep(config) {
293
293
  const spanName = `workflow.step.${config.name}`;
294
294
  return (fnFactory) => {
295
- return require_functional.trace({
295
+ return require_functional.withTracing({
296
296
  name: spanName,
297
297
  spanKind: _opentelemetry_api.SpanKind.INTERNAL
298
- }, (baseCtx) => {
298
+ })((baseCtx) => {
299
299
  return async (...args) => {
300
300
  let baggageValues = null;
301
301
  const extractBaggage = config.extractBaggage ?? true;