@koda-sl/baker-cli 0.513.0 → 0.514.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 (1077) hide show
  1. package/README.md +2 -2
  2. package/dist/account-D6LEVVWT.js +82 -0
  3. package/dist/account-D6LEVVWT.js.map +1 -0
  4. package/dist/account-SGK3IJ6N.js +82 -0
  5. package/dist/account-SGK3IJ6N.js.map +1 -0
  6. package/dist/account-assets-J5Z4URLQ.js +290 -0
  7. package/dist/account-level-4SBYYHRS.js +611 -0
  8. package/dist/account-level-4SBYYHRS.js.map +1 -0
  9. package/dist/account-reads-2MSUVG46.js +219 -0
  10. package/dist/account-users-MHC7AD6K.js +64 -0
  11. package/dist/account-users-MHC7AD6K.js.map +1 -0
  12. package/dist/account-writes-QQQC54WC.js +68 -0
  13. package/dist/account-writes-QQQC54WC.js.map +1 -0
  14. package/dist/accounts-2O6I7YBN.js +80 -0
  15. package/dist/accounts-2O6I7YBN.js.map +1 -0
  16. package/dist/accounts-QNE5QWBG.js +701 -0
  17. package/dist/accounts-QNE5QWBG.js.map +1 -0
  18. package/dist/actions-D7OWCP36.js +55 -0
  19. package/dist/activecampaign-LBS4M4C4.js +938 -0
  20. package/dist/activecampaign-LBS4M4C4.js.map +1 -0
  21. package/dist/ad-group-writes-CJTTUUIT.js +172 -0
  22. package/dist/ad-group-writes-CJTTUUIT.js.map +1 -0
  23. package/dist/ad-groups-HLGSVH74.js +70 -0
  24. package/dist/ad-groups-HLGSVH74.js.map +1 -0
  25. package/dist/ad-writes-3UE3U7VE.js +176 -0
  26. package/dist/ad-writes-3UE3U7VE.js.map +1 -0
  27. package/dist/add-HFQB2NTU.js +107 -0
  28. package/dist/add-HFQB2NTU.js.map +1 -0
  29. package/dist/add-IUYE5H6A.js +305 -0
  30. package/dist/add-IUYE5H6A.js.map +1 -0
  31. package/dist/ads-HLUJB5SQ.js +48 -0
  32. package/dist/ads-MAOI7S6V.js +115 -0
  33. package/dist/ads-PYNDMHUD.js +79 -0
  34. package/dist/ads-PYNDMHUD.js.map +1 -0
  35. package/dist/adsets-QRKICEFD.js +182 -0
  36. package/dist/advertisers-XXM6POO5.js +123 -0
  37. package/dist/advertisers-XXM6POO5.js.map +1 -0
  38. package/dist/analytics-AULKYNNT.js +447 -0
  39. package/dist/analytics-AULKYNNT.js.map +1 -0
  40. package/dist/analytics-DNDTC643.js +2437 -0
  41. package/dist/analytics-DNDTC643.js.map +1 -0
  42. package/dist/asset-writes-JLVUAVUZ.js +50 -0
  43. package/dist/assign-2GQVQKKO.js +80 -0
  44. package/dist/assign-2GQVQKKO.js.map +1 -0
  45. package/dist/audience-size-C3YHQPZB.js +76 -0
  46. package/dist/audience-size-C3YHQPZB.js.map +1 -0
  47. package/dist/audience-writes-CDKQB3SM.js +327 -0
  48. package/dist/audience-writes-CDKQB3SM.js.map +1 -0
  49. package/dist/audiences-CDDI5HR5.js +86 -0
  50. package/dist/audiences-M3A3QK52.js +280 -0
  51. package/dist/audiences-M3A3QK52.js.map +1 -0
  52. package/dist/audit-FLPJ7GTE.js +154 -0
  53. package/dist/audit-FLPJ7GTE.js.map +1 -0
  54. package/dist/autocomplete-ZYMHHV7E.js +114 -0
  55. package/dist/autocomplete-ZYMHHV7E.js.map +1 -0
  56. package/dist/avatars-WZ2JDMHH.js +38 -0
  57. package/dist/bid-pricing-H3M63PTW.js +103 -0
  58. package/dist/bid-pricing-H3M63PTW.js.map +1 -0
  59. package/dist/brand-IZTTBZII.js +22 -0
  60. package/dist/brief-OPQ7ICOY.js +543 -0
  61. package/dist/brief-OPQ7ICOY.js.map +1 -0
  62. package/dist/campaign-groups-IQSDKOVI.js +99 -0
  63. package/dist/campaign-groups-IQSDKOVI.js.map +1 -0
  64. package/dist/campaign-writes-APAAV4ZU.js +168 -0
  65. package/dist/campaign-writes-APAAV4ZU.js.map +1 -0
  66. package/dist/campaign-writes-BN4PZP4Z.js +176 -0
  67. package/dist/campaign-writes-BN4PZP4Z.js.map +1 -0
  68. package/dist/campaigns-4Y3JQC2U.js +111 -0
  69. package/dist/campaigns-GA34MEN2.js +78 -0
  70. package/dist/campaigns-GA34MEN2.js.map +1 -0
  71. package/dist/campaigns-HES5MDUG.js +130 -0
  72. package/dist/campaigns-K2FVD3XW.js +113 -0
  73. package/dist/campaigns-K2FVD3XW.js.map +1 -0
  74. package/dist/canvas-IQ753Q2O.js +46 -0
  75. package/dist/capabilities-ZITW4RF7.js +147 -0
  76. package/dist/capabilities-ZITW4RF7.js.map +1 -0
  77. package/dist/catalogs-TGLSJEK5.js +781 -0
  78. package/dist/catalogs-TGLSJEK5.js.map +1 -0
  79. package/dist/catalogs-ZR7DECMG.js +130 -0
  80. package/dist/catalogs-ZR7DECMG.js.map +1 -0
  81. package/dist/changes-VWKTM27U.js +135 -0
  82. package/dist/chatgpt-OPOD2WHB.js +47 -0
  83. package/dist/chats-AL2SYBJO.js +279 -0
  84. package/dist/chats-AL2SYBJO.js.map +1 -0
  85. package/dist/check-U2YLRD6H.js +107 -0
  86. package/dist/check-U2YLRD6H.js.map +1 -0
  87. package/dist/chunk-2GYCWRF3.js +170 -0
  88. package/dist/chunk-2L7F7KR4.js +61 -0
  89. package/dist/chunk-44ZRCSR7.js +228 -0
  90. package/dist/chunk-46YLDIQG.js +4254 -0
  91. package/dist/chunk-4EDWXZ3I.js +363 -0
  92. package/dist/chunk-4FFBGR5L.js +66 -0
  93. package/dist/chunk-4UQRXKYJ.js +219 -0
  94. package/dist/chunk-4UZUUSVL.js +592 -0
  95. package/dist/chunk-4XHCEYHI.js +831 -0
  96. package/dist/chunk-4Z6PVR3M.js +479 -0
  97. package/dist/chunk-5Y5FZV2W.js +122 -0
  98. package/dist/chunk-6HJXLGDV.js +392 -0
  99. package/dist/chunk-6S5CWGDA.js +429 -0
  100. package/dist/chunk-6VYROVWO.js +494 -0
  101. package/dist/chunk-7BUIOP36.js +287 -0
  102. package/dist/chunk-AQRXXC6E.js +319 -0
  103. package/dist/chunk-ARBXT73X.js +387 -0
  104. package/dist/chunk-BEZHQIEI.js +97 -0
  105. package/dist/chunk-BFURWHVR.js +547 -0
  106. package/dist/chunk-BX6B25KK.js +358 -0
  107. package/dist/chunk-BX6B25KK.js.map +1 -0
  108. package/dist/chunk-DSCEXBDR.js +676 -0
  109. package/dist/chunk-FLWDFPXE.js +23 -0
  110. package/dist/chunk-FRNBRN2C.js +1646 -0
  111. package/dist/chunk-G4FG5EQO.js +110 -0
  112. package/dist/chunk-G4FG5EQO.js.map +1 -0
  113. package/dist/chunk-GE3VT3CA.js +51 -0
  114. package/dist/chunk-GW2WRZZA.js +24026 -0
  115. package/dist/chunk-GW2WRZZA.js.map +1 -0
  116. package/dist/chunk-HGBJQGO5.js +142 -0
  117. package/dist/chunk-I7AYMJZA.js +783 -0
  118. package/dist/chunk-IRM273C7.js +207 -0
  119. package/dist/chunk-IWKD56AY.js +156 -0
  120. package/dist/chunk-JLAQWMVF.js +3868 -0
  121. package/dist/chunk-JOE42ZW6.js +2984 -0
  122. package/dist/chunk-JOE42ZW6.js.map +1 -0
  123. package/dist/chunk-K6NULQN7.js +57 -0
  124. package/dist/chunk-L2BRAC2J.js +19 -0
  125. package/dist/chunk-LGMI4LSY.js +455 -0
  126. package/dist/chunk-NB2FU6EZ.js +179 -0
  127. package/dist/chunk-NKQWDEB7.js +115 -0
  128. package/dist/chunk-NKQWDEB7.js.map +1 -0
  129. package/dist/chunk-NWF2LRKO.js +188 -0
  130. package/dist/chunk-OG47L5GG.js +275 -0
  131. package/dist/chunk-OR6KVLI5.js +169 -0
  132. package/dist/chunk-PC3FJEZH.js +7211 -0
  133. package/dist/chunk-PC3FJEZH.js.map +1 -0
  134. package/dist/chunk-Q3GSVLE5.js +4274 -0
  135. package/dist/chunk-Q3GSVLE5.js.map +1 -0
  136. package/dist/chunk-QDQZ62FL.js +207 -0
  137. package/dist/chunk-QIOFOD4K.js +344 -0
  138. package/dist/chunk-R2SYF5FC.js +249 -0
  139. package/dist/chunk-RTH2I55B.js +942 -0
  140. package/dist/chunk-S6VFDNSQ.js +63 -0
  141. package/dist/chunk-SG2TDQEE.js +150 -0
  142. package/dist/chunk-SOPVIIUW.js +68 -0
  143. package/dist/chunk-SOPVIIUW.js.map +1 -0
  144. package/dist/chunk-T3P6O2VU.js +89 -0
  145. package/dist/chunk-TXDE2QJ3.js +604 -0
  146. package/dist/chunk-UDIEKCIR.js +203 -0
  147. package/dist/chunk-USCADMM3.js +163 -0
  148. package/dist/chunk-UUGISMNE.js +910 -0
  149. package/dist/chunk-VDPOCAQ2.js +197 -0
  150. package/dist/chunk-VEJ3QX6M.js +103 -0
  151. package/dist/chunk-VFJB7OG6.js +91 -0
  152. package/dist/chunk-VRIPVLE7.js +8954 -0
  153. package/dist/chunk-WFY7KSHB.js +274 -0
  154. package/dist/chunk-WLESMH4J.js +45 -0
  155. package/dist/chunk-XHQYFNNK.js +846 -0
  156. package/dist/chunk-XOQNM7I4.js +396 -0
  157. package/dist/chunk-XOQNM7I4.js.map +1 -0
  158. package/dist/chunk-YILZR3XI.js +224 -0
  159. package/dist/chunk-YJFLXOH4.js +191 -0
  160. package/dist/chunk-YWHZYR6S.js +297 -0
  161. package/dist/claim-GOLUTAJ3.js +200 -0
  162. package/dist/claim-GOLUTAJ3.js.map +1 -0
  163. package/dist/cli.js +30 -30
  164. package/dist/companies-4AJJGTMC.js +198 -0
  165. package/dist/companies-4AJJGTMC.js.map +1 -0
  166. package/dist/company-MOSPLRZJ.js +308 -0
  167. package/dist/company-MOSPLRZJ.js.map +1 -0
  168. package/dist/complete-777BDV45.js +84 -0
  169. package/dist/complete-777BDV45.js.map +1 -0
  170. package/dist/connect-3YY7MF3I.js +45 -0
  171. package/dist/conversation-KROMHDHK.js +96 -0
  172. package/dist/conversation-KROMHDHK.js.map +1 -0
  173. package/dist/conversion-report-JZHI2IHY.js +127 -0
  174. package/dist/conversion-report-JZHI2IHY.js.map +1 -0
  175. package/dist/conversion-upload-command-7RSZ5ZQU.js +439 -0
  176. package/dist/conversion-writes-SNCLQDXQ.js +122 -0
  177. package/dist/conversion-writes-SNCLQDXQ.js.map +1 -0
  178. package/dist/conversions-43EYP2W2.js +140 -0
  179. package/dist/conversions-43EYP2W2.js.map +1 -0
  180. package/dist/conversions-EUOWILCQ.js +78 -0
  181. package/dist/conversions-EUOWILCQ.js.map +1 -0
  182. package/dist/copy-writes-CIHIFLY6.js +170 -0
  183. package/dist/copy-writes-CIHIFLY6.js.map +1 -0
  184. package/dist/create-3WOZO2XT.js +239 -0
  185. package/dist/create-3WOZO2XT.js.map +1 -0
  186. package/dist/create-7IFYLX7P.js +210 -0
  187. package/dist/create-7IFYLX7P.js.map +1 -0
  188. package/dist/create-FOHIQC3E.js +62 -0
  189. package/dist/create-FOHIQC3E.js.map +1 -0
  190. package/dist/create-UWQSQTK6.js +135 -0
  191. package/dist/create-UWQSQTK6.js.map +1 -0
  192. package/dist/creatives-2DR3QHJW.js +58 -0
  193. package/dist/creatives-BB6S2OF6.js +89 -0
  194. package/dist/creatives-KUO67WYA.js +223 -0
  195. package/dist/creatives-KUO67WYA.js.map +1 -0
  196. package/dist/credentials-IP4L37XP.js +290 -0
  197. package/dist/credentials-IP4L37XP.js.map +1 -0
  198. package/dist/critique-LGDNMLBL.js +232 -0
  199. package/dist/critique-LGDNMLBL.js.map +1 -0
  200. package/dist/critique-LJMFAURK.js +1451 -0
  201. package/dist/critique-LJMFAURK.js.map +1 -0
  202. package/dist/crm-BNYW7PF7.js +323 -0
  203. package/dist/crm-BNYW7PF7.js.map +1 -0
  204. package/dist/crm-J53V2QTN.js +227 -0
  205. package/dist/crm-J53V2QTN.js.map +1 -0
  206. package/dist/currency-X7Y6TSLF.js +88 -0
  207. package/dist/custom-conversions-HAGG72LF.js +128 -0
  208. package/dist/customers-663K2OCP.js +59 -0
  209. package/dist/delete-5W3YVA4L.js +67 -0
  210. package/dist/delete-5W3YVA4L.js.map +1 -0
  211. package/dist/delete-6U7E6YUW.js +57 -0
  212. package/dist/delete-6U7E6YUW.js.map +1 -0
  213. package/dist/delete-G4NY6ZG6.js +69 -0
  214. package/dist/delete-G4NY6ZG6.js.map +1 -0
  215. package/dist/demographics-4Y5LTCU4.js +141 -0
  216. package/dist/demographics-4Y5LTCU4.js.map +1 -0
  217. package/dist/discard-RKCZNKMN.js +69 -0
  218. package/dist/discard-RKCZNKMN.js.map +1 -0
  219. package/dist/discover-6DORQWXY.js +172 -0
  220. package/dist/domain-PLA2O4FW.js +101 -0
  221. package/dist/domain-PLA2O4FW.js.map +1 -0
  222. package/dist/download-4EFLVSA4.js +172 -0
  223. package/dist/download-4EFLVSA4.js.map +1 -0
  224. package/dist/draft-4V7W5DV2.js +310 -0
  225. package/dist/draft-4V7W5DV2.js.map +1 -0
  226. package/dist/draft-HY3U2GHU.js +283 -0
  227. package/dist/draft-HY3U2GHU.js.map +1 -0
  228. package/dist/draft-JMNAGW7C.js +220 -0
  229. package/dist/draft-JMNAGW7C.js.map +1 -0
  230. package/dist/draft-R6EKORNS.js +308 -0
  231. package/dist/draft-TRPQCJ4B.js +335 -0
  232. package/dist/draft-TRPQCJ4B.js.map +1 -0
  233. package/dist/experiments-4V242DG2.js +236 -0
  234. package/dist/experiments-4V242DG2.js.map +1 -0
  235. package/dist/facets-WXJ2MCU6.js +117 -0
  236. package/dist/facets-WXJ2MCU6.js.map +1 -0
  237. package/dist/feedback-IRSRPXV6.js +111 -0
  238. package/dist/feedback-IRSRPXV6.js.map +1 -0
  239. package/dist/feedback-record-DT3R4NA3.js +265 -0
  240. package/dist/feedback-record-DT3R4NA3.js.map +1 -0
  241. package/dist/fetch-YLQP5VQL.js +179 -0
  242. package/dist/fetch-YLQP5VQL.js.map +1 -0
  243. package/dist/fit-BEFCY2B4.js +65 -0
  244. package/dist/flows-FDMF3F3P.js +250 -0
  245. package/dist/flows-FDMF3F3P.js.map +1 -0
  246. package/dist/fonts-C6XN5667.js +39 -0
  247. package/dist/forecast-BTPIXEQ5.js +169 -0
  248. package/dist/forecast-ZTXZD3HD.js +121 -0
  249. package/dist/forecast-ZTXZD3HD.js.map +1 -0
  250. package/dist/funnel-7KOEX7YM.js +397 -0
  251. package/dist/funnel-7KOEX7YM.js.map +1 -0
  252. package/dist/get-KBSKPTAV.js +59 -0
  253. package/dist/get-KBSKPTAV.js.map +1 -0
  254. package/dist/get-OSVQVMP6.js +82 -0
  255. package/dist/get-OSVQVMP6.js.map +1 -0
  256. package/dist/google-M6BJ22UL.js +3267 -0
  257. package/dist/google-ads-XCJVAXWF.js +198 -0
  258. package/dist/google-ads-XCJVAXWF.js.map +1 -0
  259. package/dist/history-3NTM5SFN.js +69 -0
  260. package/dist/history-3NTM5SFN.js.map +1 -0
  261. package/dist/history-4TN2A6BL.js +136 -0
  262. package/dist/history-4TN2A6BL.js.map +1 -0
  263. package/dist/history-5IWW2X33.js +102 -0
  264. package/dist/history-5IWW2X33.js.map +1 -0
  265. package/dist/hubspot-HMVSRJQZ.js +782 -0
  266. package/dist/hubspot-HMVSRJQZ.js.map +1 -0
  267. package/dist/ideas-M66FU45H.js +45 -0
  268. package/dist/images-MM5OPUKV.js +92 -0
  269. package/dist/ingest-GU3WEBN3.js +110 -0
  270. package/dist/ingest-GU3WEBN3.js.map +1 -0
  271. package/dist/ingest-KMLYCFF4.js +26 -0
  272. package/dist/insights-CQCWOCIT.js +132 -0
  273. package/dist/insights-CQCWOCIT.js.map +1 -0
  274. package/dist/insights-XZL2EM53.js +439 -0
  275. package/dist/insights-XZL2EM53.js.map +1 -0
  276. package/dist/inspect-4JO2J3D6.js +192 -0
  277. package/dist/inspect-4JO2J3D6.js.map +1 -0
  278. package/dist/instant-experiences-RM4MLJTL.js +260 -0
  279. package/dist/instant-experiences-RM4MLJTL.js.map +1 -0
  280. package/dist/intent-YCQCUCV3.js +95 -0
  281. package/dist/keyword-gap-PUSF7EKY.js +130 -0
  282. package/dist/keyword-metrics-JXBM22ZD.js +200 -0
  283. package/dist/keyword-metrics-JXBM22ZD.js.map +1 -0
  284. package/dist/keywords-for-site-OAYA7KP7.js +131 -0
  285. package/dist/keywords-for-site-OAYA7KP7.js.map +1 -0
  286. package/dist/landing-OZU5RRFV.js +28 -0
  287. package/dist/languages-SLFV73GE.js +40 -0
  288. package/dist/leads-Q7APIJRQ.js +115 -0
  289. package/dist/leads-Q7APIJRQ.js.map +1 -0
  290. package/dist/leads-ULYHTG22.js +540 -0
  291. package/dist/leads-ULYHTG22.js.map +1 -0
  292. package/dist/learn-NBBJRMVZ.js +207 -0
  293. package/dist/learn-NBBJRMVZ.js.map +1 -0
  294. package/dist/line-item-writes-X3L5NQEH.js +211 -0
  295. package/dist/line-item-writes-X3L5NQEH.js.map +1 -0
  296. package/dist/line-items-NJMEPEF5.js +107 -0
  297. package/dist/link-WZSNUQQ2.js +67 -0
  298. package/dist/link-WZSNUQQ2.js.map +1 -0
  299. package/dist/linkedin-55PAID62.js +1316 -0
  300. package/dist/linkedin-55PAID62.js.map +1 -0
  301. package/dist/linkedin-person-MVRI7LLC.js +139 -0
  302. package/dist/linkedin-person-MVRI7LLC.js.map +1 -0
  303. package/dist/list-6S54IAUX.js +67 -0
  304. package/dist/list-6S54IAUX.js.map +1 -0
  305. package/dist/list-QRJ4E3SC.js +154 -0
  306. package/dist/list-QRJ4E3SC.js.map +1 -0
  307. package/dist/list-RZUDFLJN.js +306 -0
  308. package/dist/list-RZUDFLJN.js.map +1 -0
  309. package/dist/locations-T2OMP6QN.js +40 -0
  310. package/dist/locations-W3VOYBWL.js +69 -0
  311. package/dist/locations-W3VOYBWL.js.map +1 -0
  312. package/dist/log-53SAPZZ3.js +95 -0
  313. package/dist/log-53SAPZZ3.js.map +1 -0
  314. package/dist/lookup-S5VG7AWH.js +23 -0
  315. package/dist/make-ZF3YIMKW.js +1370 -0
  316. package/dist/make-ZF3YIMKW.js.map +1 -0
  317. package/dist/map-J37QFJSV.js +436 -0
  318. package/dist/map-J37QFJSV.js.map +1 -0
  319. package/dist/mcp-YRIFPYNU.js +717 -0
  320. package/dist/mcp-YRIFPYNU.js.map +1 -0
  321. package/dist/media-P4T4ZBNS.js +18 -0
  322. package/dist/media-sources-EQI3U4TC.js +35 -0
  323. package/dist/members-GYVMSCKL.js +47 -0
  324. package/dist/members-GYVMSCKL.js.map +1 -0
  325. package/dist/meta-IPLWNYC7.js +122 -0
  326. package/dist/metrics-4YVJHYRI.js +152 -0
  327. package/dist/normalize-MFN3WCH5.js +144 -0
  328. package/dist/normalize-MFN3WCH5.js.map +1 -0
  329. package/dist/options-DLHDHO7H.js +125 -0
  330. package/dist/options-DLHDHO7H.js.map +1 -0
  331. package/dist/organization-C2VOA2SD.js +949 -0
  332. package/dist/organization-C2VOA2SD.js.map +1 -0
  333. package/dist/page-photos-OQ5QATQH.js +118 -0
  334. package/dist/page-photos-OQ5QATQH.js.map +1 -0
  335. package/dist/performance-NIF3CESZ.js +27 -0
  336. package/dist/performance-T6TBVHVU.js +5541 -0
  337. package/dist/performance-T6TBVHVU.js.map +1 -0
  338. package/dist/pixels-LQAJOZIM.js +62 -0
  339. package/dist/pixels-LQAJOZIM.js.map +1 -0
  340. package/dist/plan-7UF76Q6U.js +208 -0
  341. package/dist/plan-7UF76Q6U.js.map +1 -0
  342. package/dist/preview-LRJOQ7LM.js +218 -0
  343. package/dist/preview-LRJOQ7LM.js.map +1 -0
  344. package/dist/promoted-tweet-writes-Y6BBOWDL.js +183 -0
  345. package/dist/promoted-tweet-writes-Y6BBOWDL.js.map +1 -0
  346. package/dist/promoted-tweets-RJT53DRM.js +119 -0
  347. package/dist/publish-IOTMJCCY.js +344 -0
  348. package/dist/publish-IOTMJCCY.js.map +1 -0
  349. package/dist/query-XZMDGRO5.js +62 -0
  350. package/dist/questionnaire-DWEH5II4.js +94 -0
  351. package/dist/questionnaire-DWEH5II4.js.map +1 -0
  352. package/dist/reddit-JIHMAKAQ.js +29 -0
  353. package/dist/refresh-OQZ263CS.js +97 -0
  354. package/dist/refresh-OQZ263CS.js.map +1 -0
  355. package/dist/release-4CHFURO3.js +63 -0
  356. package/dist/release-4CHFURO3.js.map +1 -0
  357. package/dist/relevant-pages-FS7GBVXK.js +99 -0
  358. package/dist/remove-IJQPHPDI.js +53 -0
  359. package/dist/remove-IJQPHPDI.js.map +1 -0
  360. package/dist/reopen-YTLPVKUR.js +66 -0
  361. package/dist/reopen-YTLPVKUR.js.map +1 -0
  362. package/dist/rerun-C7NB7B35.js +95 -0
  363. package/dist/rerun-C7NB7B35.js.map +1 -0
  364. package/dist/research-PNBACCCP.js +72 -0
  365. package/dist/research-TLHNPK3O.js +265 -0
  366. package/dist/research-TLHNPK3O.js.map +1 -0
  367. package/dist/reset-JVLW7HA2.js +57 -0
  368. package/dist/reset-JVLW7HA2.js.map +1 -0
  369. package/dist/reset-Y6FXLAQE.js +57 -0
  370. package/dist/reset-Y6FXLAQE.js.map +1 -0
  371. package/dist/resolve-YLRESES5.js +124 -0
  372. package/dist/resolve-YLRESES5.js.map +1 -0
  373. package/dist/retry-3V3IGDRR.js +65 -0
  374. package/dist/retry-3V3IGDRR.js.map +1 -0
  375. package/dist/retry-AEKQLQMP.js +63 -0
  376. package/dist/retry-AEKQLQMP.js.map +1 -0
  377. package/dist/revenue-E4GQIOUY.js +91 -0
  378. package/dist/revenue-E4GQIOUY.js.map +1 -0
  379. package/dist/rule-writes-UAVWT4XS.js +525 -0
  380. package/dist/rule-writes-UAVWT4XS.js.map +1 -0
  381. package/dist/rules-34BQAWU3.js +99 -0
  382. package/dist/rules-34BQAWU3.js.map +1 -0
  383. package/dist/rules-ZVS2MWED.js +217 -0
  384. package/dist/rules-add-CM5XUYNS.js +465 -0
  385. package/dist/rules-add-CM5XUYNS.js.map +1 -0
  386. package/dist/run-MD64RDHZ.js +69 -0
  387. package/dist/run-MD64RDHZ.js.map +1 -0
  388. package/dist/run-QCNRR3CA.js +70 -0
  389. package/dist/scaffold-ad-NADPY3CB.js +1516 -0
  390. package/dist/scaffold-ad-NADPY3CB.js.map +1 -0
  391. package/dist/scaffold-static-ad-5YEYYVYH.js +1015 -0
  392. package/dist/scaffold-static-ad-5YEYYVYH.js.map +1 -0
  393. package/dist/scaffold-video-W7DF2CBJ.js +888 -0
  394. package/dist/scaffold-video-W7DF2CBJ.js.map +1 -0
  395. package/dist/schedule-CTRRF6TS.js +64 -0
  396. package/dist/schedule-CTRRF6TS.js.map +1 -0
  397. package/dist/scheduled-agents-OXYAVSIE.js +55 -0
  398. package/dist/schema-BEUYOGSW.js +135 -0
  399. package/dist/schema-BEUYOGSW.js.map +1 -0
  400. package/dist/search-GFJKWPKN.js +177 -0
  401. package/dist/search-GFJKWPKN.js.map +1 -0
  402. package/dist/select-S72CCL57.js +106 -0
  403. package/dist/select-S72CCL57.js.map +1 -0
  404. package/dist/sends-LAY5C74B.js +349 -0
  405. package/dist/sends-LAY5C74B.js.map +1 -0
  406. package/dist/set-prompt-YYFDRXRI.js +198 -0
  407. package/dist/set-prompt-YYFDRXRI.js.map +1 -0
  408. package/dist/setup-links-D4JJLESN.js +144 -0
  409. package/dist/setup-links-D4JJLESN.js.map +1 -0
  410. package/dist/shopify-T4UWFIQC.js +3958 -0
  411. package/dist/shopify-T4UWFIQC.js.map +1 -0
  412. package/dist/site-BBZAUZA3.js +180 -0
  413. package/dist/site-BBZAUZA3.js.map +1 -0
  414. package/dist/sources-BUPV7H7F.js +457 -0
  415. package/dist/sources-BUPV7H7F.js.map +1 -0
  416. package/dist/sources-FLUGHSVP.js +30 -0
  417. package/dist/stale-HP54JS5X.js +76 -0
  418. package/dist/stale-HP54JS5X.js.map +1 -0
  419. package/dist/status-CLSWQFCQ.js +65 -0
  420. package/dist/status-CLSWQFCQ.js.map +1 -0
  421. package/dist/studies-EPO2AM4M.js +250 -0
  422. package/dist/study-writes-7FP443QQ.js +470 -0
  423. package/dist/study-writes-7FP443QQ.js.map +1 -0
  424. package/dist/sync-4YTVVEVC.js +57 -0
  425. package/dist/sync-4YTVVEVC.js.map +1 -0
  426. package/dist/sync-definition-OGQ5XTQE.js +69 -0
  427. package/dist/tags-CZTRQNXV.js +26 -0
  428. package/dist/tags-FDQXWTCQ.js +23 -0
  429. package/dist/tags-FDQXWTCQ.js.map +1 -0
  430. package/dist/tags-R67DKRZ6.js +23 -0
  431. package/dist/tags-R67DKRZ6.js.map +1 -0
  432. package/dist/tags-SA2LU6AW.js +227 -0
  433. package/dist/tags-SA2LU6AW.js.map +1 -0
  434. package/dist/tags-ZOXX6VRE.js +23 -0
  435. package/dist/tags-ZOXX6VRE.js.map +1 -0
  436. package/dist/targeting-NKZFEZD7.js +762 -0
  437. package/dist/targeting-NKZFEZD7.js.map +1 -0
  438. package/dist/targeting-criteria-AQYXVSGN.js +113 -0
  439. package/dist/targets-UUVCEG3F.js +106 -0
  440. package/dist/targets-UUVCEG3F.js.map +1 -0
  441. package/dist/testimonials-G3OCNPBY.js +32 -0
  442. package/dist/themes-FEVYZZCW.js +95 -0
  443. package/dist/top-companies-BQGWKYUK.js +111 -0
  444. package/dist/top-companies-BQGWKYUK.js.map +1 -0
  445. package/dist/transcript-U46AEOEI.js +165 -0
  446. package/dist/transcript-U46AEOEI.js.map +1 -0
  447. package/dist/translate-AS7JVKYM.js +609 -0
  448. package/dist/translate-AS7JVKYM.js.map +1 -0
  449. package/dist/triggers-ECRBPYYF.js +144 -0
  450. package/dist/triggers-ECRBPYYF.js.map +1 -0
  451. package/dist/unlink-VDNCSRJV.js +70 -0
  452. package/dist/unlink-VDNCSRJV.js.map +1 -0
  453. package/dist/unstage-ATDXUYP2.js +88 -0
  454. package/dist/unstage-ATDXUYP2.js.map +1 -0
  455. package/dist/unstage-ZA3WQBEA.js +96 -0
  456. package/dist/unstage-ZA3WQBEA.js.map +1 -0
  457. package/dist/update-FJ6TBV3E.js +101 -0
  458. package/dist/update-FJ6TBV3E.js.map +1 -0
  459. package/dist/update-HDBJCSTI.js +66 -0
  460. package/dist/update-HDBJCSTI.js.map +1 -0
  461. package/dist/update-U43DJRGV.js +155 -0
  462. package/dist/update-U43DJRGV.js.map +1 -0
  463. package/dist/update-Z7TIUBMJ.js +192 -0
  464. package/dist/update-Z7TIUBMJ.js.map +1 -0
  465. package/dist/upload-LXCFSRIU.js +129 -0
  466. package/dist/upload-LXCFSRIU.js.map +1 -0
  467. package/dist/url-options-Z4BQBEMI.js +131 -0
  468. package/dist/url-options-Z4BQBEMI.js.map +1 -0
  469. package/dist/url-params-J7RKP5RB.js +101 -0
  470. package/dist/url-params-J7RKP5RB.js.map +1 -0
  471. package/dist/validate-VR3HEOMJ.js +122 -0
  472. package/dist/validate-VR3HEOMJ.js.map +1 -0
  473. package/dist/videos-36AXDUGZ.js +41 -0
  474. package/dist/viewer-check-6HL4OUQY.js +83 -0
  475. package/dist/web-7BRJRQX7.js +143 -0
  476. package/dist/web-7BRJRQX7.js.map +1 -0
  477. package/dist/winning-ads-GMM5W2O4.js +86 -0
  478. package/dist/write-commands-IM5DPG3R.js +1502 -0
  479. package/dist/write-commands-IM5DPG3R.js.map +1 -0
  480. package/dist/write-commands-OGVDWWBV.js +1172 -0
  481. package/dist/write-commands-OGVDWWBV.js.map +1 -0
  482. package/dist/x-HGFAL3NY.js +47 -0
  483. package/package.json +1 -1
  484. package/dist/account-UUAEQWO3.js +0 -80
  485. package/dist/account-UUAEQWO3.js.map +0 -1
  486. package/dist/account-WLQBAQMD.js +0 -80
  487. package/dist/account-WLQBAQMD.js.map +0 -1
  488. package/dist/account-assets-O7IA37RE.js +0 -290
  489. package/dist/account-level-EU7BWCO7.js +0 -609
  490. package/dist/account-level-EU7BWCO7.js.map +0 -1
  491. package/dist/account-reads-SNOWDHHF.js +0 -219
  492. package/dist/account-users-NV37Z2XJ.js +0 -62
  493. package/dist/account-users-NV37Z2XJ.js.map +0 -1
  494. package/dist/account-writes-TU3WJRSI.js +0 -66
  495. package/dist/account-writes-TU3WJRSI.js.map +0 -1
  496. package/dist/accounts-MZ6SETJJ.js +0 -78
  497. package/dist/accounts-MZ6SETJJ.js.map +0 -1
  498. package/dist/accounts-U7ZBEAY6.js +0 -699
  499. package/dist/accounts-U7ZBEAY6.js.map +0 -1
  500. package/dist/actions-4JYHXRFH.js +0 -55
  501. package/dist/activecampaign-IDBV26HH.js +0 -936
  502. package/dist/activecampaign-IDBV26HH.js.map +0 -1
  503. package/dist/ad-group-writes-HLBWN4E4.js +0 -170
  504. package/dist/ad-group-writes-HLBWN4E4.js.map +0 -1
  505. package/dist/ad-groups-33NZD6J3.js +0 -68
  506. package/dist/ad-groups-33NZD6J3.js.map +0 -1
  507. package/dist/ad-writes-7ZDNDZ2E.js +0 -174
  508. package/dist/ad-writes-7ZDNDZ2E.js.map +0 -1
  509. package/dist/add-AF42LQQQ.js +0 -105
  510. package/dist/add-AF42LQQQ.js.map +0 -1
  511. package/dist/add-LJRZYJ7I.js +0 -303
  512. package/dist/add-LJRZYJ7I.js.map +0 -1
  513. package/dist/ads-KBE5JXCH.js +0 -115
  514. package/dist/ads-LXWXOQHX.js +0 -48
  515. package/dist/ads-X3AM6M7L.js +0 -77
  516. package/dist/ads-X3AM6M7L.js.map +0 -1
  517. package/dist/adsets-YWBJFPAZ.js +0 -182
  518. package/dist/advertisers-6FOCN2W3.js +0 -121
  519. package/dist/advertisers-6FOCN2W3.js.map +0 -1
  520. package/dist/analytics-64HBRJJE.js +0 -2435
  521. package/dist/analytics-64HBRJJE.js.map +0 -1
  522. package/dist/analytics-VY5NL2GL.js +0 -445
  523. package/dist/analytics-VY5NL2GL.js.map +0 -1
  524. package/dist/asset-writes-72DEVXGO.js +0 -48
  525. package/dist/assign-N2I2BM67.js +0 -78
  526. package/dist/assign-N2I2BM67.js.map +0 -1
  527. package/dist/audience-size-H5DEZXJN.js +0 -74
  528. package/dist/audience-size-H5DEZXJN.js.map +0 -1
  529. package/dist/audience-writes-KCHLNFSI.js +0 -325
  530. package/dist/audience-writes-KCHLNFSI.js.map +0 -1
  531. package/dist/audiences-QAH42HIM.js +0 -86
  532. package/dist/audiences-WCDWN64L.js +0 -268
  533. package/dist/audiences-WCDWN64L.js.map +0 -1
  534. package/dist/audit-2AAF6AOX.js +0 -152
  535. package/dist/audit-2AAF6AOX.js.map +0 -1
  536. package/dist/autocomplete-3GSKRNCZ.js +0 -112
  537. package/dist/autocomplete-3GSKRNCZ.js.map +0 -1
  538. package/dist/avatars-N6W6HUPP.js +0 -38
  539. package/dist/bid-pricing-IMITXHXN.js +0 -101
  540. package/dist/bid-pricing-IMITXHXN.js.map +0 -1
  541. package/dist/brand-ITWQVZD2.js +0 -22
  542. package/dist/brief-JJNEYPYB.js +0 -541
  543. package/dist/brief-JJNEYPYB.js.map +0 -1
  544. package/dist/campaign-groups-TTQLP32Q.js +0 -97
  545. package/dist/campaign-groups-TTQLP32Q.js.map +0 -1
  546. package/dist/campaign-writes-MAKPHQ3V.js +0 -166
  547. package/dist/campaign-writes-MAKPHQ3V.js.map +0 -1
  548. package/dist/campaign-writes-XO74BALZ.js +0 -174
  549. package/dist/campaign-writes-XO74BALZ.js.map +0 -1
  550. package/dist/campaigns-5BIDXYSR.js +0 -111
  551. package/dist/campaigns-LS65QM6W.js +0 -76
  552. package/dist/campaigns-LS65QM6W.js.map +0 -1
  553. package/dist/campaigns-XZOQ2VTK.js +0 -130
  554. package/dist/campaigns-XZPXCS7I.js +0 -111
  555. package/dist/campaigns-XZPXCS7I.js.map +0 -1
  556. package/dist/canvas-SL3RMOY5.js +0 -46
  557. package/dist/capabilities-LSR5GCE6.js +0 -145
  558. package/dist/capabilities-LSR5GCE6.js.map +0 -1
  559. package/dist/catalogs-JLCSFZYC.js +0 -779
  560. package/dist/catalogs-JLCSFZYC.js.map +0 -1
  561. package/dist/catalogs-T23RKYFC.js +0 -128
  562. package/dist/catalogs-T23RKYFC.js.map +0 -1
  563. package/dist/changes-36C754R4.js +0 -135
  564. package/dist/chatgpt-3WFOPURY.js +0 -47
  565. package/dist/chats-GGYTXNJS.js +0 -277
  566. package/dist/chats-GGYTXNJS.js.map +0 -1
  567. package/dist/check-P7UPBBG7.js +0 -105
  568. package/dist/check-P7UPBBG7.js.map +0 -1
  569. package/dist/chunk-2TOIY2WN.js +0 -4254
  570. package/dist/chunk-4D46PS6J.js +0 -344
  571. package/dist/chunk-4GAEB6NK.js +0 -228
  572. package/dist/chunk-5XTGG4PV.js +0 -274
  573. package/dist/chunk-6JCX3YLM.js +0 -249
  574. package/dist/chunk-7J3NZBPV.js +0 -2982
  575. package/dist/chunk-7J3NZBPV.js.map +0 -1
  576. package/dist/chunk-ALMYGH5U.js +0 -479
  577. package/dist/chunk-AQKRSEBQ.js +0 -103
  578. package/dist/chunk-BL6RTOK3.js +0 -297
  579. package/dist/chunk-C6CBBZBO.js +0 -203
  580. package/dist/chunk-CCEDZTP7.js +0 -391
  581. package/dist/chunk-CCEDZTP7.js.map +0 -1
  582. package/dist/chunk-CKQTJ2LV.js +0 -89
  583. package/dist/chunk-D3KLDIPQ.js +0 -45
  584. package/dist/chunk-DL2UJNJ7.js +0 -8954
  585. package/dist/chunk-DQ54POZN.js +0 -363
  586. package/dist/chunk-EDLZGTGN.js +0 -216
  587. package/dist/chunk-EDLZGTGN.js.map +0 -1
  588. package/dist/chunk-EXSHBYNQ.js +0 -349
  589. package/dist/chunk-EXSHBYNQ.js.map +0 -1
  590. package/dist/chunk-FFFJBNHA.js +0 -63
  591. package/dist/chunk-FKQK22IX.js +0 -51
  592. package/dist/chunk-FLK6QHOY.js +0 -142
  593. package/dist/chunk-GACSA5A6.js +0 -1646
  594. package/dist/chunk-HJKYTNAZ.js +0 -61
  595. package/dist/chunk-INUU3752.js +0 -392
  596. package/dist/chunk-IQHO2ABF.js +0 -197
  597. package/dist/chunk-J3UWMBOP.js +0 -547
  598. package/dist/chunk-JDR3HUEL.js +0 -191
  599. package/dist/chunk-JI77AKT6.js +0 -19
  600. package/dist/chunk-JNURZHZJ.js +0 -942
  601. package/dist/chunk-JSNMKTFM.js +0 -592
  602. package/dist/chunk-K4FLWDEX.js +0 -122
  603. package/dist/chunk-KBRJSPIM.js +0 -676
  604. package/dist/chunk-LAC6YHHX.js +0 -97
  605. package/dist/chunk-M7HD42M6.js +0 -24017
  606. package/dist/chunk-M7HD42M6.js.map +0 -1
  607. package/dist/chunk-MGYC2IRC.js +0 -783
  608. package/dist/chunk-MI4HXE7M.js +0 -23
  609. package/dist/chunk-MJ7RNDD4.js +0 -910
  610. package/dist/chunk-OBJCNGZL.js +0 -207
  611. package/dist/chunk-OJGYYH6K.js +0 -179
  612. package/dist/chunk-OLBLXJNI.js +0 -224
  613. package/dist/chunk-PFTYKINE.js +0 -207
  614. package/dist/chunk-PLBITHV6.js +0 -846
  615. package/dist/chunk-Q6JMMBSQ.js +0 -831
  616. package/dist/chunk-RAQHUMKS.js +0 -169
  617. package/dist/chunk-SA22KXWT.js +0 -163
  618. package/dist/chunk-SIXU5PDF.js +0 -66
  619. package/dist/chunk-SXMK5AEJ.js +0 -494
  620. package/dist/chunk-TTLD66NI.js +0 -319
  621. package/dist/chunk-TVAIOC77.js +0 -287
  622. package/dist/chunk-TVTSA4EN.js +0 -188
  623. package/dist/chunk-V5NHJU3M.js +0 -604
  624. package/dist/chunk-VIPKDXGU.js +0 -4229
  625. package/dist/chunk-VIPKDXGU.js.map +0 -1
  626. package/dist/chunk-W4D7C74B.js +0 -387
  627. package/dist/chunk-WF25CCLD.js +0 -170
  628. package/dist/chunk-WQPUX3BA.js +0 -275
  629. package/dist/chunk-WR3FR3K3.js +0 -156
  630. package/dist/chunk-XDNJC62G.js +0 -91
  631. package/dist/chunk-XE6N4SZI.js +0 -429
  632. package/dist/chunk-XGPU2VDU.js +0 -219
  633. package/dist/chunk-XUJZO7MZ.js +0 -57
  634. package/dist/chunk-Y67LUJBE.js +0 -455
  635. package/dist/chunk-Y7XSDJEA.js +0 -3868
  636. package/dist/chunk-ZNNQK366.js +0 -7198
  637. package/dist/chunk-ZNNQK366.js.map +0 -1
  638. package/dist/chunk-ZNRGH4NS.js +0 -150
  639. package/dist/claim-4KUETSW3.js +0 -198
  640. package/dist/claim-4KUETSW3.js.map +0 -1
  641. package/dist/companies-XFO6WPVT.js +0 -196
  642. package/dist/companies-XFO6WPVT.js.map +0 -1
  643. package/dist/company-OJGISG7K.js +0 -306
  644. package/dist/company-OJGISG7K.js.map +0 -1
  645. package/dist/complete-CXOST5J3.js +0 -82
  646. package/dist/complete-CXOST5J3.js.map +0 -1
  647. package/dist/connect-HPQ32KGV.js +0 -43
  648. package/dist/conversation-D5HKCTDA.js +0 -94
  649. package/dist/conversation-D5HKCTDA.js.map +0 -1
  650. package/dist/conversion-report-DKXFHLLX.js +0 -125
  651. package/dist/conversion-report-DKXFHLLX.js.map +0 -1
  652. package/dist/conversion-upload-command-KTYAY7IG.js +0 -439
  653. package/dist/conversion-writes-7ODJDAUP.js +0 -120
  654. package/dist/conversion-writes-7ODJDAUP.js.map +0 -1
  655. package/dist/conversions-BKVGNNC6.js +0 -76
  656. package/dist/conversions-BKVGNNC6.js.map +0 -1
  657. package/dist/conversions-SYNDEB6D.js +0 -138
  658. package/dist/conversions-SYNDEB6D.js.map +0 -1
  659. package/dist/copy-writes-P4WK6OPM.js +0 -168
  660. package/dist/copy-writes-P4WK6OPM.js.map +0 -1
  661. package/dist/create-7D4VD4TC.js +0 -208
  662. package/dist/create-7D4VD4TC.js.map +0 -1
  663. package/dist/create-7QDEAWZS.js +0 -133
  664. package/dist/create-7QDEAWZS.js.map +0 -1
  665. package/dist/create-IQSYEC4M.js +0 -60
  666. package/dist/create-IQSYEC4M.js.map +0 -1
  667. package/dist/create-TQSCM7MG.js +0 -237
  668. package/dist/create-TQSCM7MG.js.map +0 -1
  669. package/dist/creatives-JPKA2RHU.js +0 -89
  670. package/dist/creatives-RNOL4F4E.js +0 -58
  671. package/dist/creatives-RXI27T2D.js +0 -221
  672. package/dist/creatives-RXI27T2D.js.map +0 -1
  673. package/dist/credentials-PYW55B52.js +0 -288
  674. package/dist/credentials-PYW55B52.js.map +0 -1
  675. package/dist/critique-2SYHNAW5.js +0 -1449
  676. package/dist/critique-2SYHNAW5.js.map +0 -1
  677. package/dist/critique-72GFZSHA.js +0 -230
  678. package/dist/critique-72GFZSHA.js.map +0 -1
  679. package/dist/crm-EATDSXXB.js +0 -225
  680. package/dist/crm-EATDSXXB.js.map +0 -1
  681. package/dist/crm-XXP3VBYD.js +0 -321
  682. package/dist/crm-XXP3VBYD.js.map +0 -1
  683. package/dist/currency-R4ORE2U3.js +0 -88
  684. package/dist/custom-conversions-5HJBB5XL.js +0 -128
  685. package/dist/customers-OFZTEXST.js +0 -57
  686. package/dist/delete-FGVZIK4R.js +0 -55
  687. package/dist/delete-FGVZIK4R.js.map +0 -1
  688. package/dist/delete-HBZZL4UA.js +0 -67
  689. package/dist/delete-HBZZL4UA.js.map +0 -1
  690. package/dist/delete-JOUXMRIX.js +0 -65
  691. package/dist/delete-JOUXMRIX.js.map +0 -1
  692. package/dist/demographics-SB7NBYOI.js +0 -139
  693. package/dist/demographics-SB7NBYOI.js.map +0 -1
  694. package/dist/discard-YYGIRDII.js +0 -67
  695. package/dist/discard-YYGIRDII.js.map +0 -1
  696. package/dist/discover-GSJRZUOC.js +0 -172
  697. package/dist/domain-3KRAJAUH.js +0 -99
  698. package/dist/domain-3KRAJAUH.js.map +0 -1
  699. package/dist/download-QCBKJ6OR.js +0 -170
  700. package/dist/download-QCBKJ6OR.js.map +0 -1
  701. package/dist/draft-7CGFFZTH.js +0 -333
  702. package/dist/draft-7CGFFZTH.js.map +0 -1
  703. package/dist/draft-BI4NW6AU.js +0 -308
  704. package/dist/draft-BI4NW6AU.js.map +0 -1
  705. package/dist/draft-LPVK6YHI.js +0 -281
  706. package/dist/draft-LPVK6YHI.js.map +0 -1
  707. package/dist/draft-QF5JEUTH.js +0 -218
  708. package/dist/draft-QF5JEUTH.js.map +0 -1
  709. package/dist/draft-WYDMLNYA.js +0 -308
  710. package/dist/experiments-ZCEYRP3H.js +0 -234
  711. package/dist/experiments-ZCEYRP3H.js.map +0 -1
  712. package/dist/facets-G7ZDPI5Q.js +0 -115
  713. package/dist/facets-G7ZDPI5Q.js.map +0 -1
  714. package/dist/feedback-3U3CL2VB.js +0 -109
  715. package/dist/feedback-3U3CL2VB.js.map +0 -1
  716. package/dist/feedback-record-4ZHPENIP.js +0 -263
  717. package/dist/feedback-record-4ZHPENIP.js.map +0 -1
  718. package/dist/fetch-PNVNNWTP.js +0 -177
  719. package/dist/fetch-PNVNNWTP.js.map +0 -1
  720. package/dist/fit-53DGYRFD.js +0 -63
  721. package/dist/flows-JXLQBP5Q.js +0 -248
  722. package/dist/flows-JXLQBP5Q.js.map +0 -1
  723. package/dist/fonts-LFMBUT4R.js +0 -37
  724. package/dist/forecast-4AG5F7YC.js +0 -169
  725. package/dist/forecast-VZAXI74O.js +0 -119
  726. package/dist/forecast-VZAXI74O.js.map +0 -1
  727. package/dist/funnel-EACC35UD.js +0 -395
  728. package/dist/funnel-EACC35UD.js.map +0 -1
  729. package/dist/get-AXBVYGAC.js +0 -57
  730. package/dist/get-AXBVYGAC.js.map +0 -1
  731. package/dist/get-D6A27Q4E.js +0 -80
  732. package/dist/get-D6A27Q4E.js.map +0 -1
  733. package/dist/google-NAA4BASO.js +0 -3267
  734. package/dist/google-ads-OIYUNDXQ.js +0 -196
  735. package/dist/google-ads-OIYUNDXQ.js.map +0 -1
  736. package/dist/history-5MRLNTCI.js +0 -67
  737. package/dist/history-5MRLNTCI.js.map +0 -1
  738. package/dist/history-IDYNIB6R.js +0 -134
  739. package/dist/history-IDYNIB6R.js.map +0 -1
  740. package/dist/history-IOUKN3WD.js +0 -100
  741. package/dist/history-IOUKN3WD.js.map +0 -1
  742. package/dist/hubspot-HL6WGC4G.js +0 -780
  743. package/dist/hubspot-HL6WGC4G.js.map +0 -1
  744. package/dist/ideas-NK3KQHAK.js +0 -43
  745. package/dist/images-2XS3JT7E.js +0 -92
  746. package/dist/ingest-DHXWZZX4.js +0 -24
  747. package/dist/ingest-FGTJRCWK.js +0 -108
  748. package/dist/ingest-FGTJRCWK.js.map +0 -1
  749. package/dist/insights-M3CYB22P.js +0 -437
  750. package/dist/insights-M3CYB22P.js.map +0 -1
  751. package/dist/insights-XCT3ONC7.js +0 -130
  752. package/dist/insights-XCT3ONC7.js.map +0 -1
  753. package/dist/inspect-53GGYRC4.js +0 -190
  754. package/dist/inspect-53GGYRC4.js.map +0 -1
  755. package/dist/instant-experiences-HT3HB4QZ.js +0 -258
  756. package/dist/instant-experiences-HT3HB4QZ.js.map +0 -1
  757. package/dist/intent-NSHW7EJT.js +0 -95
  758. package/dist/keyword-gap-ITTWKBWZ.js +0 -130
  759. package/dist/keyword-metrics-LWPE3HHW.js +0 -198
  760. package/dist/keyword-metrics-LWPE3HHW.js.map +0 -1
  761. package/dist/keywords-for-site-5K25VJ35.js +0 -129
  762. package/dist/keywords-for-site-5K25VJ35.js.map +0 -1
  763. package/dist/landing-QONUUP3C.js +0 -28
  764. package/dist/languages-MEFQQBJ2.js +0 -40
  765. package/dist/leads-FSSISJGL.js +0 -113
  766. package/dist/leads-FSSISJGL.js.map +0 -1
  767. package/dist/leads-SZOGCQFK.js +0 -538
  768. package/dist/leads-SZOGCQFK.js.map +0 -1
  769. package/dist/learn-FWWJWBDN.js +0 -205
  770. package/dist/learn-FWWJWBDN.js.map +0 -1
  771. package/dist/line-item-writes-A3UQ44NG.js +0 -209
  772. package/dist/line-item-writes-A3UQ44NG.js.map +0 -1
  773. package/dist/line-items-OH426JGT.js +0 -107
  774. package/dist/link-67E4NVEX.js +0 -65
  775. package/dist/link-67E4NVEX.js.map +0 -1
  776. package/dist/linkedin-CT2OSQ2I.js +0 -1314
  777. package/dist/linkedin-CT2OSQ2I.js.map +0 -1
  778. package/dist/linkedin-person-OSNLPLJJ.js +0 -137
  779. package/dist/linkedin-person-OSNLPLJJ.js.map +0 -1
  780. package/dist/list-J4OUXAKN.js +0 -65
  781. package/dist/list-J4OUXAKN.js.map +0 -1
  782. package/dist/list-L2OQK4W3.js +0 -304
  783. package/dist/list-L2OQK4W3.js.map +0 -1
  784. package/dist/list-LHMDAA2D.js +0 -152
  785. package/dist/list-LHMDAA2D.js.map +0 -1
  786. package/dist/locations-RLWAS6AC.js +0 -40
  787. package/dist/locations-UKBOT6SB.js +0 -67
  788. package/dist/locations-UKBOT6SB.js.map +0 -1
  789. package/dist/log-4EIV54V4.js +0 -93
  790. package/dist/log-4EIV54V4.js.map +0 -1
  791. package/dist/lookup-ZJIP2M4X.js +0 -22
  792. package/dist/make-MFU3CSN7.js +0 -1368
  793. package/dist/make-MFU3CSN7.js.map +0 -1
  794. package/dist/map-VWWWMQLK.js +0 -434
  795. package/dist/map-VWWWMQLK.js.map +0 -1
  796. package/dist/mcp-EKLO2VD7.js +0 -715
  797. package/dist/mcp-EKLO2VD7.js.map +0 -1
  798. package/dist/media-ZWN7E777.js +0 -18
  799. package/dist/media-sources-7L622UTF.js +0 -35
  800. package/dist/members-QK5M5NCJ.js +0 -45
  801. package/dist/members-QK5M5NCJ.js.map +0 -1
  802. package/dist/meta-EHKXH257.js +0 -122
  803. package/dist/metrics-K6FDCMK7.js +0 -152
  804. package/dist/normalize-WI4OGKH5.js +0 -142
  805. package/dist/normalize-WI4OGKH5.js.map +0 -1
  806. package/dist/options-FKJ2QXI6.js +0 -123
  807. package/dist/options-FKJ2QXI6.js.map +0 -1
  808. package/dist/organization-7A7MPUDG.js +0 -947
  809. package/dist/organization-7A7MPUDG.js.map +0 -1
  810. package/dist/page-photos-HWKQOSCU.js +0 -116
  811. package/dist/page-photos-HWKQOSCU.js.map +0 -1
  812. package/dist/performance-K4VB6L6Z.js +0 -25
  813. package/dist/performance-LHAL53YO.js +0 -5539
  814. package/dist/performance-LHAL53YO.js.map +0 -1
  815. package/dist/pixels-Z7ZGXAGB.js +0 -60
  816. package/dist/pixels-Z7ZGXAGB.js.map +0 -1
  817. package/dist/plan-TVQSEKYL.js +0 -207
  818. package/dist/plan-TVQSEKYL.js.map +0 -1
  819. package/dist/preview-CWRGDFCP.js +0 -216
  820. package/dist/preview-CWRGDFCP.js.map +0 -1
  821. package/dist/promoted-tweet-writes-V4ZVJOJU.js +0 -181
  822. package/dist/promoted-tweet-writes-V4ZVJOJU.js.map +0 -1
  823. package/dist/promoted-tweets-KPG4IUVD.js +0 -119
  824. package/dist/publish-Y465CVUD.js +0 -342
  825. package/dist/publish-Y465CVUD.js.map +0 -1
  826. package/dist/query-QM7MBQD7.js +0 -62
  827. package/dist/questionnaire-LEZJLMAV.js +0 -93
  828. package/dist/questionnaire-LEZJLMAV.js.map +0 -1
  829. package/dist/reddit-Z7DOVIGR.js +0 -27
  830. package/dist/refresh-A6TW2Z2F.js +0 -95
  831. package/dist/refresh-A6TW2Z2F.js.map +0 -1
  832. package/dist/release-UFESJ4TM.js +0 -61
  833. package/dist/release-UFESJ4TM.js.map +0 -1
  834. package/dist/relevant-pages-VOICMQ7U.js +0 -99
  835. package/dist/remove-YONNYA4G.js +0 -51
  836. package/dist/remove-YONNYA4G.js.map +0 -1
  837. package/dist/reopen-FYJU2IPO.js +0 -64
  838. package/dist/reopen-FYJU2IPO.js.map +0 -1
  839. package/dist/rerun-B5SBG6ZE.js +0 -93
  840. package/dist/rerun-B5SBG6ZE.js.map +0 -1
  841. package/dist/research-HW4EVOZU.js +0 -72
  842. package/dist/research-OV53DIBX.js +0 -263
  843. package/dist/research-OV53DIBX.js.map +0 -1
  844. package/dist/reset-R4SIQXFH.js +0 -55
  845. package/dist/reset-R4SIQXFH.js.map +0 -1
  846. package/dist/reset-X6ODAKYH.js +0 -55
  847. package/dist/reset-X6ODAKYH.js.map +0 -1
  848. package/dist/resolve-DNRJCLCR.js +0 -122
  849. package/dist/resolve-DNRJCLCR.js.map +0 -1
  850. package/dist/retry-D5MKBHUB.js +0 -63
  851. package/dist/retry-D5MKBHUB.js.map +0 -1
  852. package/dist/retry-DVFLE7JY.js +0 -61
  853. package/dist/retry-DVFLE7JY.js.map +0 -1
  854. package/dist/revenue-QGI6XWSK.js +0 -89
  855. package/dist/revenue-QGI6XWSK.js.map +0 -1
  856. package/dist/rule-writes-S3RN47OG.js +0 -523
  857. package/dist/rule-writes-S3RN47OG.js.map +0 -1
  858. package/dist/rules-KTJ2ZZ62.js +0 -217
  859. package/dist/rules-TR6BAC23.js +0 -97
  860. package/dist/rules-TR6BAC23.js.map +0 -1
  861. package/dist/rules-add-T5ZO5RLR.js +0 -463
  862. package/dist/rules-add-T5ZO5RLR.js.map +0 -1
  863. package/dist/run-WZ5IXWA4.js +0 -67
  864. package/dist/run-WZ5IXWA4.js.map +0 -1
  865. package/dist/run-YRR3KOUU.js +0 -68
  866. package/dist/scaffold-ad-DNUCGCIH.js +0 -1514
  867. package/dist/scaffold-ad-DNUCGCIH.js.map +0 -1
  868. package/dist/scaffold-static-ad-VR65EZAG.js +0 -1013
  869. package/dist/scaffold-static-ad-VR65EZAG.js.map +0 -1
  870. package/dist/scaffold-video-JLMKCCWE.js +0 -886
  871. package/dist/scaffold-video-JLMKCCWE.js.map +0 -1
  872. package/dist/schedule-VRHCVBZA.js +0 -62
  873. package/dist/schedule-VRHCVBZA.js.map +0 -1
  874. package/dist/scheduled-agents-DG7FTCUZ.js +0 -55
  875. package/dist/schema-WZKDWF5N.js +0 -133
  876. package/dist/schema-WZKDWF5N.js.map +0 -1
  877. package/dist/search-MN66QIKJ.js +0 -175
  878. package/dist/search-MN66QIKJ.js.map +0 -1
  879. package/dist/select-IPSM2BF2.js +0 -104
  880. package/dist/select-IPSM2BF2.js.map +0 -1
  881. package/dist/sends-6FI7WELX.js +0 -348
  882. package/dist/sends-6FI7WELX.js.map +0 -1
  883. package/dist/set-prompt-N6BD3DME.js +0 -196
  884. package/dist/set-prompt-N6BD3DME.js.map +0 -1
  885. package/dist/setup-links-GTA2AROS.js +0 -142
  886. package/dist/setup-links-GTA2AROS.js.map +0 -1
  887. package/dist/shopify-S4TG6WJH.js +0 -3956
  888. package/dist/shopify-S4TG6WJH.js.map +0 -1
  889. package/dist/site-NY622E3H.js +0 -178
  890. package/dist/site-NY622E3H.js.map +0 -1
  891. package/dist/sources-4F2XO7Z6.js +0 -455
  892. package/dist/sources-4F2XO7Z6.js.map +0 -1
  893. package/dist/sources-562ITR77.js +0 -30
  894. package/dist/stale-JKM6MXJF.js +0 -74
  895. package/dist/stale-JKM6MXJF.js.map +0 -1
  896. package/dist/status-AUWMUDA4.js +0 -63
  897. package/dist/status-AUWMUDA4.js.map +0 -1
  898. package/dist/studies-ZOEUY6YP.js +0 -250
  899. package/dist/study-writes-DTGMCNFP.js +0 -468
  900. package/dist/study-writes-DTGMCNFP.js.map +0 -1
  901. package/dist/sync-76MCGHX4.js +0 -55
  902. package/dist/sync-76MCGHX4.js.map +0 -1
  903. package/dist/sync-definition-NJPT6ABG.js +0 -67
  904. package/dist/tags-5KBOJJ5R.js +0 -26
  905. package/dist/tags-CSMLTHTU.js +0 -21
  906. package/dist/tags-CSMLTHTU.js.map +0 -1
  907. package/dist/tags-IFRZYM3G.js +0 -21
  908. package/dist/tags-IFRZYM3G.js.map +0 -1
  909. package/dist/tags-S44ADJ75.js +0 -21
  910. package/dist/tags-S44ADJ75.js.map +0 -1
  911. package/dist/tags-WR7IY3OT.js +0 -225
  912. package/dist/tags-WR7IY3OT.js.map +0 -1
  913. package/dist/targeting-VBQY3HUN.js +0 -760
  914. package/dist/targeting-VBQY3HUN.js.map +0 -1
  915. package/dist/targeting-criteria-FJWNK53T.js +0 -113
  916. package/dist/targets-2EHMMXD4.js +0 -104
  917. package/dist/targets-2EHMMXD4.js.map +0 -1
  918. package/dist/testimonials-WAPFIGFJ.js +0 -32
  919. package/dist/themes-B4LCQUXB.js +0 -95
  920. package/dist/top-companies-DJ2XQKMW.js +0 -109
  921. package/dist/top-companies-DJ2XQKMW.js.map +0 -1
  922. package/dist/transcript-J3LGOHCM.js +0 -163
  923. package/dist/transcript-J3LGOHCM.js.map +0 -1
  924. package/dist/translate-6RXO4C6Z.js +0 -607
  925. package/dist/translate-6RXO4C6Z.js.map +0 -1
  926. package/dist/triggers-CYXPNHQB.js +0 -142
  927. package/dist/triggers-CYXPNHQB.js.map +0 -1
  928. package/dist/unlink-5FJI7XZB.js +0 -68
  929. package/dist/unlink-5FJI7XZB.js.map +0 -1
  930. package/dist/unstage-54ZE2SZK.js +0 -86
  931. package/dist/unstage-54ZE2SZK.js.map +0 -1
  932. package/dist/unstage-MDSSPUKV.js +0 -95
  933. package/dist/unstage-MDSSPUKV.js.map +0 -1
  934. package/dist/update-BVBFNFMO.js +0 -99
  935. package/dist/update-BVBFNFMO.js.map +0 -1
  936. package/dist/update-GV3T7ZRC.js +0 -190
  937. package/dist/update-GV3T7ZRC.js.map +0 -1
  938. package/dist/update-PUQWUGPJ.js +0 -64
  939. package/dist/update-PUQWUGPJ.js.map +0 -1
  940. package/dist/update-YQBK6C3L.js +0 -153
  941. package/dist/update-YQBK6C3L.js.map +0 -1
  942. package/dist/upload-PXT5JAEV.js +0 -127
  943. package/dist/upload-PXT5JAEV.js.map +0 -1
  944. package/dist/url-options-IHL4OU4C.js +0 -130
  945. package/dist/url-options-IHL4OU4C.js.map +0 -1
  946. package/dist/url-params-IYUI6SFJ.js +0 -99
  947. package/dist/url-params-IYUI6SFJ.js.map +0 -1
  948. package/dist/validate-AD7F7LQ5.js +0 -120
  949. package/dist/validate-AD7F7LQ5.js.map +0 -1
  950. package/dist/videos-ZJCTODEB.js +0 -41
  951. package/dist/viewer-check-GWZNGWCO.js +0 -81
  952. package/dist/web-76CWAZNW.js +0 -141
  953. package/dist/web-76CWAZNW.js.map +0 -1
  954. package/dist/winning-ads-W7GTXTX7.js +0 -86
  955. package/dist/write-commands-K6M3RTVK.js +0 -1170
  956. package/dist/write-commands-K6M3RTVK.js.map +0 -1
  957. package/dist/write-commands-VPU53DLB.js +0 -1498
  958. package/dist/write-commands-VPU53DLB.js.map +0 -1
  959. package/dist/x-V4NNNWRU.js +0 -47
  960. /package/dist/{account-assets-O7IA37RE.js.map → account-assets-J5Z4URLQ.js.map} +0 -0
  961. /package/dist/{account-reads-SNOWDHHF.js.map → account-reads-2MSUVG46.js.map} +0 -0
  962. /package/dist/{actions-4JYHXRFH.js.map → actions-D7OWCP36.js.map} +0 -0
  963. /package/dist/{ads-LXWXOQHX.js.map → ads-HLUJB5SQ.js.map} +0 -0
  964. /package/dist/{ads-KBE5JXCH.js.map → ads-MAOI7S6V.js.map} +0 -0
  965. /package/dist/{adsets-YWBJFPAZ.js.map → adsets-QRKICEFD.js.map} +0 -0
  966. /package/dist/{asset-writes-72DEVXGO.js.map → asset-writes-JLVUAVUZ.js.map} +0 -0
  967. /package/dist/{audiences-QAH42HIM.js.map → audiences-CDDI5HR5.js.map} +0 -0
  968. /package/dist/{avatars-N6W6HUPP.js.map → avatars-WZ2JDMHH.js.map} +0 -0
  969. /package/dist/{brand-ITWQVZD2.js.map → brand-IZTTBZII.js.map} +0 -0
  970. /package/dist/{campaigns-5BIDXYSR.js.map → campaigns-4Y3JQC2U.js.map} +0 -0
  971. /package/dist/{campaigns-XZOQ2VTK.js.map → campaigns-HES5MDUG.js.map} +0 -0
  972. /package/dist/{canvas-SL3RMOY5.js.map → canvas-IQ753Q2O.js.map} +0 -0
  973. /package/dist/{changes-36C754R4.js.map → changes-VWKTM27U.js.map} +0 -0
  974. /package/dist/{chatgpt-3WFOPURY.js.map → chatgpt-OPOD2WHB.js.map} +0 -0
  975. /package/dist/{chunk-WF25CCLD.js.map → chunk-2GYCWRF3.js.map} +0 -0
  976. /package/dist/{chunk-HJKYTNAZ.js.map → chunk-2L7F7KR4.js.map} +0 -0
  977. /package/dist/{chunk-4GAEB6NK.js.map → chunk-44ZRCSR7.js.map} +0 -0
  978. /package/dist/{chunk-2TOIY2WN.js.map → chunk-46YLDIQG.js.map} +0 -0
  979. /package/dist/{chunk-DQ54POZN.js.map → chunk-4EDWXZ3I.js.map} +0 -0
  980. /package/dist/{chunk-SIXU5PDF.js.map → chunk-4FFBGR5L.js.map} +0 -0
  981. /package/dist/{chunk-XGPU2VDU.js.map → chunk-4UQRXKYJ.js.map} +0 -0
  982. /package/dist/{chunk-JSNMKTFM.js.map → chunk-4UZUUSVL.js.map} +0 -0
  983. /package/dist/{chunk-Q6JMMBSQ.js.map → chunk-4XHCEYHI.js.map} +0 -0
  984. /package/dist/{chunk-ALMYGH5U.js.map → chunk-4Z6PVR3M.js.map} +0 -0
  985. /package/dist/{chunk-K4FLWDEX.js.map → chunk-5Y5FZV2W.js.map} +0 -0
  986. /package/dist/{chunk-INUU3752.js.map → chunk-6HJXLGDV.js.map} +0 -0
  987. /package/dist/{chunk-XE6N4SZI.js.map → chunk-6S5CWGDA.js.map} +0 -0
  988. /package/dist/{chunk-SXMK5AEJ.js.map → chunk-6VYROVWO.js.map} +0 -0
  989. /package/dist/{chunk-TVAIOC77.js.map → chunk-7BUIOP36.js.map} +0 -0
  990. /package/dist/{chunk-TTLD66NI.js.map → chunk-AQRXXC6E.js.map} +0 -0
  991. /package/dist/{chunk-W4D7C74B.js.map → chunk-ARBXT73X.js.map} +0 -0
  992. /package/dist/{chunk-LAC6YHHX.js.map → chunk-BEZHQIEI.js.map} +0 -0
  993. /package/dist/{chunk-J3UWMBOP.js.map → chunk-BFURWHVR.js.map} +0 -0
  994. /package/dist/{chunk-KBRJSPIM.js.map → chunk-DSCEXBDR.js.map} +0 -0
  995. /package/dist/{chunk-MI4HXE7M.js.map → chunk-FLWDFPXE.js.map} +0 -0
  996. /package/dist/{chunk-GACSA5A6.js.map → chunk-FRNBRN2C.js.map} +0 -0
  997. /package/dist/{chunk-FKQK22IX.js.map → chunk-GE3VT3CA.js.map} +0 -0
  998. /package/dist/{chunk-FLK6QHOY.js.map → chunk-HGBJQGO5.js.map} +0 -0
  999. /package/dist/{chunk-MGYC2IRC.js.map → chunk-I7AYMJZA.js.map} +0 -0
  1000. /package/dist/{chunk-PFTYKINE.js.map → chunk-IRM273C7.js.map} +0 -0
  1001. /package/dist/{chunk-WR3FR3K3.js.map → chunk-IWKD56AY.js.map} +0 -0
  1002. /package/dist/{chunk-Y7XSDJEA.js.map → chunk-JLAQWMVF.js.map} +0 -0
  1003. /package/dist/{chunk-XUJZO7MZ.js.map → chunk-K6NULQN7.js.map} +0 -0
  1004. /package/dist/{chunk-JI77AKT6.js.map → chunk-L2BRAC2J.js.map} +0 -0
  1005. /package/dist/{chunk-Y67LUJBE.js.map → chunk-LGMI4LSY.js.map} +0 -0
  1006. /package/dist/{chunk-OJGYYH6K.js.map → chunk-NB2FU6EZ.js.map} +0 -0
  1007. /package/dist/{chunk-TVTSA4EN.js.map → chunk-NWF2LRKO.js.map} +0 -0
  1008. /package/dist/{chunk-WQPUX3BA.js.map → chunk-OG47L5GG.js.map} +0 -0
  1009. /package/dist/{chunk-RAQHUMKS.js.map → chunk-OR6KVLI5.js.map} +0 -0
  1010. /package/dist/{chunk-OBJCNGZL.js.map → chunk-QDQZ62FL.js.map} +0 -0
  1011. /package/dist/{chunk-4D46PS6J.js.map → chunk-QIOFOD4K.js.map} +0 -0
  1012. /package/dist/{chunk-6JCX3YLM.js.map → chunk-R2SYF5FC.js.map} +0 -0
  1013. /package/dist/{chunk-JNURZHZJ.js.map → chunk-RTH2I55B.js.map} +0 -0
  1014. /package/dist/{chunk-FFFJBNHA.js.map → chunk-S6VFDNSQ.js.map} +0 -0
  1015. /package/dist/{chunk-ZNRGH4NS.js.map → chunk-SG2TDQEE.js.map} +0 -0
  1016. /package/dist/{chunk-CKQTJ2LV.js.map → chunk-T3P6O2VU.js.map} +0 -0
  1017. /package/dist/{chunk-V5NHJU3M.js.map → chunk-TXDE2QJ3.js.map} +0 -0
  1018. /package/dist/{chunk-C6CBBZBO.js.map → chunk-UDIEKCIR.js.map} +0 -0
  1019. /package/dist/{chunk-SA22KXWT.js.map → chunk-USCADMM3.js.map} +0 -0
  1020. /package/dist/{chunk-MJ7RNDD4.js.map → chunk-UUGISMNE.js.map} +0 -0
  1021. /package/dist/{chunk-IQHO2ABF.js.map → chunk-VDPOCAQ2.js.map} +0 -0
  1022. /package/dist/{chunk-AQKRSEBQ.js.map → chunk-VEJ3QX6M.js.map} +0 -0
  1023. /package/dist/{chunk-XDNJC62G.js.map → chunk-VFJB7OG6.js.map} +0 -0
  1024. /package/dist/{chunk-DL2UJNJ7.js.map → chunk-VRIPVLE7.js.map} +0 -0
  1025. /package/dist/{chunk-5XTGG4PV.js.map → chunk-WFY7KSHB.js.map} +0 -0
  1026. /package/dist/{chunk-D3KLDIPQ.js.map → chunk-WLESMH4J.js.map} +0 -0
  1027. /package/dist/{chunk-PLBITHV6.js.map → chunk-XHQYFNNK.js.map} +0 -0
  1028. /package/dist/{chunk-OLBLXJNI.js.map → chunk-YILZR3XI.js.map} +0 -0
  1029. /package/dist/{chunk-JDR3HUEL.js.map → chunk-YJFLXOH4.js.map} +0 -0
  1030. /package/dist/{chunk-BL6RTOK3.js.map → chunk-YWHZYR6S.js.map} +0 -0
  1031. /package/dist/{connect-HPQ32KGV.js.map → connect-3YY7MF3I.js.map} +0 -0
  1032. /package/dist/{conversion-upload-command-KTYAY7IG.js.map → conversion-upload-command-7RSZ5ZQU.js.map} +0 -0
  1033. /package/dist/{creatives-RNOL4F4E.js.map → creatives-2DR3QHJW.js.map} +0 -0
  1034. /package/dist/{creatives-JPKA2RHU.js.map → creatives-BB6S2OF6.js.map} +0 -0
  1035. /package/dist/{currency-R4ORE2U3.js.map → currency-X7Y6TSLF.js.map} +0 -0
  1036. /package/dist/{custom-conversions-5HJBB5XL.js.map → custom-conversions-HAGG72LF.js.map} +0 -0
  1037. /package/dist/{customers-OFZTEXST.js.map → customers-663K2OCP.js.map} +0 -0
  1038. /package/dist/{discover-GSJRZUOC.js.map → discover-6DORQWXY.js.map} +0 -0
  1039. /package/dist/{draft-WYDMLNYA.js.map → draft-R6EKORNS.js.map} +0 -0
  1040. /package/dist/{fit-53DGYRFD.js.map → fit-BEFCY2B4.js.map} +0 -0
  1041. /package/dist/{fonts-LFMBUT4R.js.map → fonts-C6XN5667.js.map} +0 -0
  1042. /package/dist/{forecast-4AG5F7YC.js.map → forecast-BTPIXEQ5.js.map} +0 -0
  1043. /package/dist/{google-NAA4BASO.js.map → google-M6BJ22UL.js.map} +0 -0
  1044. /package/dist/{ideas-NK3KQHAK.js.map → ideas-M66FU45H.js.map} +0 -0
  1045. /package/dist/{images-2XS3JT7E.js.map → images-MM5OPUKV.js.map} +0 -0
  1046. /package/dist/{ingest-DHXWZZX4.js.map → ingest-KMLYCFF4.js.map} +0 -0
  1047. /package/dist/{intent-NSHW7EJT.js.map → intent-YCQCUCV3.js.map} +0 -0
  1048. /package/dist/{keyword-gap-ITTWKBWZ.js.map → keyword-gap-PUSF7EKY.js.map} +0 -0
  1049. /package/dist/{landing-QONUUP3C.js.map → landing-OZU5RRFV.js.map} +0 -0
  1050. /package/dist/{languages-MEFQQBJ2.js.map → languages-SLFV73GE.js.map} +0 -0
  1051. /package/dist/{line-items-OH426JGT.js.map → line-items-NJMEPEF5.js.map} +0 -0
  1052. /package/dist/{locations-RLWAS6AC.js.map → locations-T2OMP6QN.js.map} +0 -0
  1053. /package/dist/{lookup-ZJIP2M4X.js.map → lookup-S5VG7AWH.js.map} +0 -0
  1054. /package/dist/{media-ZWN7E777.js.map → media-P4T4ZBNS.js.map} +0 -0
  1055. /package/dist/{media-sources-7L622UTF.js.map → media-sources-EQI3U4TC.js.map} +0 -0
  1056. /package/dist/{meta-EHKXH257.js.map → meta-IPLWNYC7.js.map} +0 -0
  1057. /package/dist/{metrics-K6FDCMK7.js.map → metrics-4YVJHYRI.js.map} +0 -0
  1058. /package/dist/{performance-K4VB6L6Z.js.map → performance-NIF3CESZ.js.map} +0 -0
  1059. /package/dist/{promoted-tweets-KPG4IUVD.js.map → promoted-tweets-RJT53DRM.js.map} +0 -0
  1060. /package/dist/{query-QM7MBQD7.js.map → query-XZMDGRO5.js.map} +0 -0
  1061. /package/dist/{reddit-Z7DOVIGR.js.map → reddit-JIHMAKAQ.js.map} +0 -0
  1062. /package/dist/{relevant-pages-VOICMQ7U.js.map → relevant-pages-FS7GBVXK.js.map} +0 -0
  1063. /package/dist/{research-HW4EVOZU.js.map → research-PNBACCCP.js.map} +0 -0
  1064. /package/dist/{rules-KTJ2ZZ62.js.map → rules-ZVS2MWED.js.map} +0 -0
  1065. /package/dist/{run-YRR3KOUU.js.map → run-QCNRR3CA.js.map} +0 -0
  1066. /package/dist/{scheduled-agents-DG7FTCUZ.js.map → scheduled-agents-OXYAVSIE.js.map} +0 -0
  1067. /package/dist/{sources-562ITR77.js.map → sources-FLUGHSVP.js.map} +0 -0
  1068. /package/dist/{studies-ZOEUY6YP.js.map → studies-EPO2AM4M.js.map} +0 -0
  1069. /package/dist/{sync-definition-NJPT6ABG.js.map → sync-definition-OGQ5XTQE.js.map} +0 -0
  1070. /package/dist/{tags-5KBOJJ5R.js.map → tags-CZTRQNXV.js.map} +0 -0
  1071. /package/dist/{targeting-criteria-FJWNK53T.js.map → targeting-criteria-AQYXVSGN.js.map} +0 -0
  1072. /package/dist/{testimonials-WAPFIGFJ.js.map → testimonials-G3OCNPBY.js.map} +0 -0
  1073. /package/dist/{themes-B4LCQUXB.js.map → themes-FEVYZZCW.js.map} +0 -0
  1074. /package/dist/{videos-ZJCTODEB.js.map → videos-36AXDUGZ.js.map} +0 -0
  1075. /package/dist/{viewer-check-GWZNGWCO.js.map → viewer-check-6HL4OUQY.js.map} +0 -0
  1076. /package/dist/{winning-ads-W7GTXTX7.js.map → winning-ads-GMM5W2O4.js.map} +0 -0
  1077. /package/dist/{x-V4NNNWRU.js.map → x-HGFAL3NY.js.map} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../api/src/analytics/adParams.ts","../../api/src/analytics/eventKey.ts","../../api/src/analytics/sends.ts","../../api/src/analytics/peopleSearch.ts","../../api/src/analytics/sendsWire.ts","../../api/src/analytics/unstage.ts","../../api/src/analytics/wire.ts","../../api/src/analytics/conversions.ts","../../api/src/analytics/submissionAnswers.ts","../../api/src/analytics/busy.ts","../../api/src/analytics/conversionNames.ts","../../api/src/analytics/destinations.ts","../../api/src/analytics/row.ts","../../api/src/analytics/triggers.ts","../../api/src/analytics/feedNoise.ts","../../api/src/analytics/matchKeys.ts","../../api/src/analytics/trackingTemplate.ts"],"sourcesContent":["/**\n * What each ad platform can tell us about a click — one vocabulary per platform.\n *\n * Every paid platform stamps its own structure onto the landing URL, and the\n * platforms do not describe the same world. Google Ads knows a keyword, a match\n * type and a search network; Meta has none of those and never will, because\n * nobody bids on a keyword there. Meta knows which of its four properties\n * served the ad; Google has no equivalent. TikTok's `__AID__` is the ad *group*\n * and `__CID__` is the ad, which reads as the exact opposite of what those\n * letters suggest. And the parameter *name* each value lands under is whatever\n * the advertiser typed into their own tracking setup.\n *\n * So this module holds one thing per platform: **the dimensions that platform\n * can actually send, and the spellings we already recognise for each.**\n *\n * ## Why there is no cross-platform mapping, and no \"all platforms\"\n *\n * An earlier version had one mapping per company plus a shared layer, and it\n * was wrong in a way no report announces. `cid` is TikTok's ad and half the\n * internet's campaign. `campaignid` hand-tagged in a LinkedIn account holds\n * `{{CAMPAIGN_ID}}`, which is the level Meta and Google call an ad *set*. A\n * single company-wide answer to \"what does `cid` mean\" has to be wrong for\n * somebody, and the company it is wrong for gets a confident, plausible,\n * incorrect report.\n *\n * The deeper problem was the shared vocabulary itself. Offering \"Keyword\" as a\n * meaning for a parameter on Meta traffic is not merely unhelpful, it invites\n * an answer that cannot be true. So a mapping entry always names a platform,\n * and the meanings on offer are that platform's own.\n *\n * ## What this module does not do\n *\n * It does not tell an advertiser how to tag their ads. There is no template to\n * paste and no naming convention to adopt: the account is theirs, it is usually\n * already tagged, and a tool that opens by proposing a rewrite of somebody's\n * live tracking is a tool they close. {@link AdPlatformDimension.macro} exists\n * to *show what a platform is able to send*, not to instruct.\n *\n * ## UTM parameters are not in here, deliberately\n *\n * `utm_source`, `utm_medium`, `utm_campaign`, `utm_content` and `utm_term` mean\n * the same thing on every platform, have their own columns, and are read as\n * themselves everywhere. Nothing about them needs mapping. They are listed in\n * {@link RESERVED_PARAM_NAMES} so a mapping cannot claim one and report the\n * same value twice under two vocabularies.\n *\n * ## Roles are resolved when a report is read\n *\n * Never stamped onto the row at ingest. So a company that fixes its mapping\n * fixes its history rather than only its future, and a dimension added next\n * year applies to traffic collected today. The cost is that a role cannot carry\n * an index; that is the right trade for a dimension you break down by rather\n * than look up, and the click ids that *are* looked up are already columns.\n *\n * Dependency-free, like the rest of `analytics/`: this ships to the edge.\n */\n\n// ---------------------------------------------------------------------------\n// The vocabulary\n// ---------------------------------------------------------------------------\n\n/**\n * Every dimension any ad platform can tell us about a click.\n *\n * The union across platforms, and no platform offers all of it — which is the\n * point. {@link AD_PLATFORM_DEFINITIONS} says which of these each one can\n * actually send, and that subset is what a person is offered when they are\n * naming a parameter on that platform's traffic.\n *\n * The bar for membership is that a marketer would break a report down by it or\n * filter on it. That bar keeps out the opaque numbers several platforms will\n * happily send — Google's geo criterion ids, its target and extension ids, the\n * hotel-campaign macros — every one of which is an identifier a person cannot\n * read and no screen would ever group by. Capture is derived from this list, so\n * leaving one out also stops us storing values nobody was going to look at.\n *\n * The two entries at the end are the flexible part, they are on every platform,\n * and they are opposites:\n *\n * - `other` — capture it and let me filter by its own name, but do not claim it\n * means anything. For a parameter that carries real information this\n * vocabulary has no word for.\n * - `ignore` — this is not campaign information. Stop storing it and stop\n * listing it. For the session tokens, cache busters and app state that share\n * a query string with the tracking.\n */\nexport const AD_PARAM_ROLES = [\n \"campaign_id\",\n \"campaign_name\",\n \"adgroup_id\",\n \"adgroup_name\",\n \"ad_id\",\n \"ad_name\",\n \"keyword\",\n \"match_type\",\n \"search_query\",\n \"audience\",\n \"placement\",\n \"network\",\n \"site_source\",\n \"device\",\n \"product_id\",\n \"account_name\",\n \"other\",\n \"ignore\",\n] as const;\n\nexport type AdParamRole = (typeof AD_PARAM_ROLES)[number];\n\n/** On every platform, because they say something about the URL rather than the ad. */\nexport const UNIVERSAL_AD_PARAM_ROLES: readonly AdParamRole[] = [\"other\", \"ignore\"];\n\n/**\n * Dimensions no platform's macro vocabulary fills in, but any account can put\n * on its own URLs.\n *\n * A third tier, and it exists because the first two could not hold `audience`.\n * `AD_PLATFORM_DEFINITIONS` lists what a platform can *tell us*, and none of\n * the platforms exposes a macro for the remarketing list or interest segment a\n * click came from — so `audience` belonged to no platform, and once\n * `adPlatformKnowsRole` started gating writes it became a role that could be\n * asked for and never mapped, on any platform, by anyone. A `--role audience`\n * report then resolves to no spellings at all, which the SQL reads as \"arrived\n * carrying none of these\" and answers with a confident zero.\n *\n * The `keyword`-on-Meta reasoning does not apply to it. Nobody bids on a\n * keyword in Meta, so offering the role there invites an answer that cannot be\n * true; every platform on this list, by contrast, has audiences — an advertiser\n * writing `?aud=warm-retargeting` into their own tracking template on any of\n * them means exactly what {@link AD_PARAM_ROLE_SPECS} says. Answering that with\n * `other` would keep the value and lose the normalization, which is the only\n * reason a role exists rather than a parameter name.\n *\n * Kept separate from {@link UNIVERSAL_AD_PARAM_ROLES} rather than merged into\n * it: those two are answers about the reader's own URL and deliberately end\n * every list, and `adParams.test.ts` pins that.\n */\nexport const HAND_TAGGED_AD_PARAM_ROLES: readonly AdParamRole[] = [\"audience\"];\n\nexport interface AdParamRoleSpec {\n role: AdParamRole;\n /** What a marketer calls it. Shown in the dashboard and the CLI. */\n label: string;\n /** One line, written for someone deciding whether their parameter is this. */\n description: string;\n}\n\n/**\n * The three-tier hierarchy is normalized to campaign → ad group → ad, because\n * every platform has those three levels under four different sets of names and\n * comparing them across platforms is the entire point. Each platform's own\n * words for them are in {@link AD_PLATFORM_DEFINITIONS}, where the rename is\n * visible rather than implied.\n */\nexport const AD_PARAM_ROLE_SPECS: Readonly<Record<AdParamRole, AdParamRoleSpec>> = {\n campaign_id: { role: \"campaign_id\", label: \"Campaign ID\", description: \"The top level of this account's structure.\" },\n campaign_name: { role: \"campaign_name\", label: \"Campaign name\", description: \"The campaign's name.\" },\n adgroup_id: { role: \"adgroup_id\", label: \"Ad group ID\", description: \"The targeting container inside a campaign.\" },\n adgroup_name: { role: \"adgroup_name\", label: \"Ad group name\", description: \"The ad group's name.\" },\n ad_id: { role: \"ad_id\", label: \"Ad ID\", description: \"The individual ad that was clicked.\" },\n ad_name: { role: \"ad_name\", label: \"Ad name\", description: \"The individual ad's name.\" },\n keyword: {\n role: \"keyword\",\n label: \"Keyword\",\n description: \"The keyword that matched, as it is written in the account — not what the visitor typed.\",\n },\n match_type: {\n role: \"match_type\",\n label: \"Match type\",\n description: \"How loosely the keyword matched: broad, phrase or exact.\",\n },\n search_query: { role: \"search_query\", label: \"Search query\", description: \"What the visitor actually typed.\" },\n audience: {\n role: \"audience\",\n label: \"Audience\",\n description: \"The remarketing list or interest segment the visitor was in.\",\n },\n placement: {\n role: \"placement\",\n label: \"Placement\",\n description: \"Where the ad appeared — a feed, a story, a site on the display network.\",\n },\n network: {\n role: \"network\",\n label: \"Network\",\n description: \"Which network served it: search, a search partner, display, or a video surface.\",\n },\n site_source: {\n role: \"site_source\",\n label: \"App\",\n description: \"Which app the click came from, on platforms that span several.\",\n },\n device: { role: \"device\", label: \"Device\", description: \"Mobile, tablet or desktop, as the platform saw it.\" },\n product_id: { role: \"product_id\", label: \"Product ID\", description: \"The shopping item that was clicked.\" },\n account_name: {\n role: \"account_name\",\n label: \"Account name\",\n description: \"Which ad account paid for the click, where an agency runs several.\",\n },\n other: {\n role: \"other\",\n label: \"Something else\",\n description: \"Keep the values and let me filter by this name, without saying what it means.\",\n },\n ignore: {\n role: \"ignore\",\n label: \"Ignore it\",\n description: \"Not campaign information. Baker stops storing it and stops asking about it.\",\n },\n};\n\nexport function isAdParamRole(value: string): value is AdParamRole {\n return (AD_PARAM_ROLES as readonly string[]).includes(value);\n}\n\n/**\n * Names a mapping may never claim.\n *\n * The five UTM parameters are the same thing on every platform, have their own\n * columns on the event, and are already reported as themselves. Letting one be\n * mapped to a role would store the same value twice under two vocabularies and\n * leave the Campaigns report and the ad breakdown quietly disagreeing about\n * which one the reader is looking at.\n */\nexport const RESERVED_PARAM_NAMES: readonly string[] = [\n \"utm_source\",\n \"utm_medium\",\n \"utm_campaign\",\n \"utm_content\",\n \"utm_term\",\n \"utm_id\",\n];\n\nexport function isReservedParamName(name: string): boolean {\n return RESERVED_PARAM_NAMES.includes(normalizeAdParamName(name));\n}\n\n// ---------------------------------------------------------------------------\n// Platforms\n// ---------------------------------------------------------------------------\n\n/**\n * The platforms a mapping can belong to, in the order they appear as tabs.\n *\n * X is deliberately absent even though `twclid` is a click id we store: X\n * offers no dynamic URL parameters at all, so there is nothing for it to send\n * and nothing for a mapping to say. Its traffic still appears in every report —\n * it simply has no vocabulary of its own.\n */\nexport const AD_PLATFORMS = [\n \"google\",\n \"meta\",\n \"microsoft\",\n \"linkedin\",\n \"tiktok\",\n \"reddit\",\n \"pinterest\",\n \"snapchat\",\n \"chatgpt\",\n] as const;\nexport type AdPlatform = (typeof AD_PLATFORMS)[number];\n\nexport function isAdPlatform(value: string): value is AdPlatform {\n return (AD_PLATFORMS as readonly string[]).includes(value);\n}\n\n/** One thing a platform is able to put on the URL. */\nexport interface AdPlatformDimension {\n role: AdParamRole;\n /**\n * The platform's own macro for it, shown as evidence of what it can send.\n *\n * Not an instruction. Nothing generates a template from these and nobody is\n * asked to paste one — an account is usually already tagged, often by\n * somebody who has left, and a mapping tool that opens by proposing a rewrite\n * of live tracking is one people close.\n */\n macro: string;\n /**\n * Spellings already recognised for this dimension on this platform, most\n * canonical first.\n *\n * Safe to be generous here in a way a single cross-platform list never was:\n * `cid` can be TikTok's ad because it is only ever claimed on TikTok traffic.\n */\n names: readonly string[];\n /** Present when the platform's own naming contradicts what the value is. */\n note?: string;\n}\n\nexport interface AdPlatformDefinition {\n platform: AdPlatform;\n label: string;\n /** Where the list came from, so the next person can check it. */\n docs: string;\n /** What this platform can tell us — and, by omission, what it cannot. */\n dimensions: readonly AdPlatformDimension[];\n}\n\n/**\n * Google Ads (ValueTrack).\n *\n * The richest vocabulary here, and the only one with both a keyword and a match\n * type. Names and meanings come from the Google Ads API's own\n * ValueTrack-to-report mapping, which is authoritative where the help-centre\n * article omits several and keeps a few that no longer resolve.\n *\n * Left out on purpose: the geo criterion ids (`{loc_physical_ms}` and friends\n * are bare numbers with no name attached), `{targetid}`, `{feeditemid}` and the\n * fourteen hotel-campaign macros. Each is an identifier a person cannot read.\n * If a client runs hotel campaigns, this is the list to extend.\n */\nconst GOOGLE: AdPlatformDefinition = {\n platform: \"google\",\n label: \"Google Ads\",\n docs: \"https://developers.google.com/google-ads/api/docs/reporting/valuetrack-mapping\",\n dimensions: [\n { role: \"campaign_id\", macro: \"{campaignid}\", names: [\"campaign_id\", \"campaignid\", \"gad_campaignid\"] },\n { role: \"adgroup_id\", macro: \"{adgroupid}\", names: [\"adgroup_id\", \"adgroupid\"] },\n {\n role: \"ad_id\",\n macro: \"{creative}\",\n names: [\"ad_id\", \"creative\", \"adid\"],\n note: \"ValueTrack calls the ad id {creative}.\",\n },\n { role: \"keyword\", macro: \"{keyword}\", names: [\"keyword\", \"kw\"] },\n {\n role: \"match_type\",\n macro: \"{matchtype}\",\n names: [\"match_type\", \"matchtype\", \"mt\"],\n note: \"b broad, p phrase, e exact, a AI Max.\",\n },\n {\n role: \"network\",\n macro: \"{network}\",\n names: [\"network\", \"gad_source\"],\n note: \"g search, s partner, d display, ytv YouTube.\",\n },\n { role: \"device\", macro: \"{device}\", names: [\"device\"] },\n { role: \"placement\", macro: \"{placement}\", names: [\"placement\"] },\n { role: \"product_id\", macro: \"{product_id}\", names: [\"product_id\"], note: \"Shopping campaigns only.\" },\n ],\n};\n\n/**\n * Meta.\n *\n * Eight macros, and that is the whole language — no tracking template, no\n * string functions, no product-level macros. Meta cannot send a keyword, a\n * match type or a search query, and no amount of configuration will make it:\n * nobody bids on a keyword there. It is the clearest case for a per-platform\n * vocabulary, because offering \"Keyword\" here invites an answer that cannot be\n * true.\n *\n * The two it has that Google does not are the most useful and the least used:\n * `{{placement}}` separates feed from story from reels, and\n * `{{site_source_name}}` says which of Meta's four properties served the ad.\n */\nconst META: AdPlatformDefinition = {\n platform: \"meta\",\n label: \"Meta Ads\",\n docs: \"https://www.facebook.com/business/help/2360940870872492\",\n dimensions: [\n { role: \"campaign_id\", macro: \"{{campaign.id}}\", names: [\"campaign_id\", \"campaignid\"] },\n { role: \"campaign_name\", macro: \"{{campaign.name}}\", names: [\"campaign_name\", \"campaignname\"] },\n {\n role: \"adgroup_id\",\n macro: \"{{adset.id}}\",\n names: [\"adset_id\", \"adgroup_id\", \"adsetid\"],\n note: \"Meta calls an ad group an ad set.\",\n },\n { role: \"adgroup_name\", macro: \"{{adset.name}}\", names: [\"adset_name\", \"adgroup_name\"] },\n { role: \"ad_id\", macro: \"{{ad.id}}\", names: [\"ad_id\", \"adid\"] },\n { role: \"ad_name\", macro: \"{{ad.name}}\", names: [\"ad_name\", \"adname\"] },\n {\n role: \"placement\",\n macro: \"{{placement}}\",\n names: [\"placement\", \"placement_id\"],\n note: \"feed, story, reels and the rest.\",\n },\n {\n role: \"site_source\",\n macro: \"{{site_source_name}}\",\n names: [\"site_source\", \"site_source_name\"],\n note: \"fb, ig, an or msg.\",\n },\n ],\n};\n\n/**\n * Microsoft Advertising.\n *\n * Close to Google's by design — its tracking templates are built for accounts\n * imported from Google Ads — with one thing Google has no equivalent for:\n * `{QueryString}` returns what the visitor actually typed, which is the\n * difference between \"this keyword converts\" and \"the people reaching this\n * keyword are asking for something else\".\n */\nconst MICROSOFT: AdPlatformDefinition = {\n platform: \"microsoft\",\n label: \"Microsoft Advertising\",\n docs: \"https://learn.microsoft.com/en-us/advertising/guides/url-tracking-upgraded-urls\",\n dimensions: [\n { role: \"campaign_id\", macro: \"{CampaignId}\", names: [\"campaign_id\", \"campaignid\"] },\n { role: \"campaign_name\", macro: \"{Campaign}\", names: [\"campaign_name\", \"campaignname\"] },\n { role: \"adgroup_id\", macro: \"{AdGroupId}\", names: [\"adgroup_id\", \"adgroupid\"] },\n { role: \"adgroup_name\", macro: \"{AdGroup}\", names: [\"adgroup_name\", \"adgroupname\"] },\n { role: \"ad_id\", macro: \"{AdId}\", names: [\"ad_id\", \"adid\"] },\n { role: \"keyword\", macro: \"{Keyword}\", names: [\"keyword\", \"kw\"] },\n { role: \"match_type\", macro: \"{MatchType}\", names: [\"match_type\", \"matchtype\", \"mt\"] },\n {\n role: \"search_query\",\n macro: \"{QueryString}\",\n names: [\"search_query\", \"querystring\"],\n note: \"What the visitor typed. Google has no equivalent.\",\n },\n { role: \"network\", macro: \"{Network}\", names: [\"network\"] },\n { role: \"device\", macro: \"{Device}\", names: [\"device\"] },\n ],\n};\n\n/**\n * LinkedIn.\n *\n * Its hierarchy is account → campaign group → campaign → creative, one name out\n * of step with everyone else: a LinkedIn *campaign group* is what Meta and\n * Google call a campaign, and a LinkedIn *campaign* is their ad set or ad\n * group. The renames below are what let a report compare LinkedIn to Meta at\n * the same tier, and they are only safe because they are scoped here —\n * `campaign_id` on LinkedIn traffic is its ad-group level, and reading it as a\n * campaign would make every cross-platform comparison wrong by one tier.\n */\nconst LINKEDIN: AdPlatformDefinition = {\n platform: \"linkedin\",\n label: \"LinkedIn Ads\",\n docs: \"https://learn.microsoft.com/en-us/linkedin/marketing/integrations/ads-reporting/dynamic-utm-tracking\",\n dimensions: [\n { role: \"account_name\", macro: \"{{ACCOUNT_NAME}}\", names: [\"account_name\"] },\n {\n role: \"campaign_id\",\n macro: \"{{CAMPAIGN_GROUP_ID}}\",\n names: [\"campaign_group_id\", \"campaign_id\"],\n note: \"LinkedIn's campaign group is the level other platforms call a campaign.\",\n },\n { role: \"campaign_name\", macro: \"{{CAMPAIGN_GROUP_NAME}}\", names: [\"campaign_group_name\", \"campaign_name\"] },\n {\n role: \"adgroup_id\",\n macro: \"{{CAMPAIGN_ID}}\",\n names: [\"adgroup_id\"],\n note: \"LinkedIn's campaign is the level other platforms call an ad group.\",\n },\n { role: \"adgroup_name\", macro: \"{{CAMPAIGN_NAME}}\", names: [\"adgroup_name\"] },\n { role: \"ad_id\", macro: \"{{CREATIVE_ID}}\", names: [\"creative_id\", \"ad_id\"] },\n { role: \"ad_name\", macro: \"{{CREATIVE_NAME}}\", names: [\"creative_name\", \"ad_name\"] },\n ],\n};\n\n/**\n * TikTok.\n *\n * The two macros that matter most are named the opposite of what they hold:\n * `__AID__` is the ad *group* id, not the ad id, and `__CID__` is the creative\n * — the ad — not the campaign. Accounts tagged by hand get this wrong more\n * often than they get it right, which is why `cid` and `aid` are recognised\n * here as the ad and the ad group. Claiming those two spellings anywhere else\n * would be a disaster: `cid` is a campaign id on most of the internet.\n */\nconst TIKTOK: AdPlatformDefinition = {\n platform: \"tiktok\",\n label: \"TikTok Ads\",\n docs: \"https://ads.tiktok.com/help/article/track-offsite-web-events-with-utm-parameters\",\n dimensions: [\n { role: \"campaign_id\", macro: \"__CAMPAIGN_ID__\", names: [\"campaign_id\", \"campaignid\"] },\n { role: \"campaign_name\", macro: \"__CAMPAIGN_NAME__\", names: [\"campaign_name\", \"campaignname\"] },\n {\n role: \"adgroup_id\",\n macro: \"__AID__\",\n names: [\"adgroup_id\", \"aid\"],\n note: \"__AID__ is the ad GROUP id, not the ad id.\",\n },\n { role: \"adgroup_name\", macro: \"__AID_NAME__\", names: [\"adgroup_name\"] },\n {\n role: \"ad_id\",\n macro: \"__CID__\",\n names: [\"ad_id\", \"cid\"],\n note: \"__CID__ is the creative id, not the campaign id.\",\n },\n { role: \"ad_name\", macro: \"__CID_NAME__\", names: [\"ad_name\"] },\n { role: \"placement\", macro: \"__PLACEMENT__\", names: [\"placement\"], note: \"TikTok or TikTok Pangle.\" },\n ],\n};\n\n/**\n * Reddit.\n *\n * Ordinary double-brace macros with no traps. Only the ones every published\n * reference agrees on are listed: an unrecognised macro is left in the URL\n * verbatim, so a guess would show up as a literal `{{PLACEMENT}}` on somebody's\n * landing page rather than as a missing column.\n */\nconst REDDIT: AdPlatformDefinition = {\n platform: \"reddit\",\n label: \"Reddit Ads\",\n docs: \"https://business.reddithelp.com/s/article/URL-Parameters\",\n dimensions: [\n { role: \"campaign_id\", macro: \"{{CAMPAIGN_ID}}\", names: [\"campaign_id\", \"campaignid\"] },\n { role: \"campaign_name\", macro: \"{{CAMPAIGN_NAME}}\", names: [\"campaign_name\"] },\n { role: \"adgroup_id\", macro: \"{{ADGROUP_ID}}\", names: [\"adgroup_id\", \"ad_group_id\"] },\n { role: \"adgroup_name\", macro: \"{{ADGROUP_NAME}}\", names: [\"adgroup_name\", \"ad_group_name\"] },\n { role: \"ad_id\", macro: \"{{AD_ID}}\", names: [\"ad_id\", \"adid\"] },\n { role: \"ad_name\", macro: \"{{AD_NAME}}\", names: [\"ad_name\"] },\n { role: \"device\", macro: \"{{DEVICE_GROUP}}\", names: [\"device\", \"device_group\"] },\n ],\n};\n\n/**\n * Pinterest.\n *\n * Pinterest runs keyword-targeted campaigns and then refuses to send the\n * keyword: its own documentation states `{keyword}` is not supported for\n * tracking links on any ad. So the keyword is not in this vocabulary, and \"why\n * is my Pinterest keyword column empty\" stops being a question somebody spends\n * an afternoon on.\n */\nconst PINTEREST: AdPlatformDefinition = {\n platform: \"pinterest\",\n label: \"Pinterest Ads\",\n docs: \"https://help.pinterest.com/en/business/article/third-party-and-dynamic-tracking\",\n dimensions: [\n { role: \"campaign_id\", macro: \"{campaignid}\", names: [\"campaign_id\", \"campaignid\"] },\n { role: \"campaign_name\", macro: \"{campaign_name}\", names: [\"campaign_name\", \"campaignname\"] },\n { role: \"adgroup_id\", macro: \"{adgroupid}\", names: [\"adgroup_id\", \"adgroupid\"] },\n { role: \"adgroup_name\", macro: \"{ad_group_name}\", names: [\"adgroup_name\", \"adgroupname\"] },\n {\n role: \"ad_id\",\n macro: \"{adid}\",\n names: [\"ad_id\", \"adid\", \"creative_id\"],\n note: \"The promoted Pin, not the Pin itself.\",\n },\n { role: \"device\", macro: \"{device}\", names: [\"device\"], note: \"c computer, m mobile, t tablet.\" },\n { role: \"product_id\", macro: \"{product_id}\", names: [\"product_id\"], note: \"Shopping campaigns only.\" },\n ],\n};\n\n/**\n * Snapchat.\n *\n * Its macros are case-sensitive in a way none of the others are: `{{adSet.id}}`\n * resolves and `{{adset.id}}` — the spelling Meta uses for the same idea — does\n * not, and a mistyped macro is left in the URL rather than reported.\n */\nconst SNAPCHAT: AdPlatformDefinition = {\n platform: \"snapchat\",\n label: \"Snapchat Ads\",\n docs: \"https://businesshelp.snapchat.com/s/article/url-parameters\",\n dimensions: [\n { role: \"campaign_id\", macro: \"{{campaign.id}}\", names: [\"campaign_id\", \"campaignid\"] },\n { role: \"campaign_name\", macro: \"{{campaign.name}}\", names: [\"campaign_name\"] },\n {\n role: \"adgroup_id\",\n macro: \"{{adSet.id}}\",\n names: [\"adset_id\", \"adgroup_id\"],\n note: \"Case-sensitive: {{adset.id}} does not resolve.\",\n },\n { role: \"adgroup_name\", macro: \"{{adSet.name}}\", names: [\"adset_name\", \"adgroup_name\"] },\n { role: \"ad_id\", macro: \"{{ad.id}}\", names: [\"ad_id\", \"adid\"] },\n { role: \"ad_name\", macro: \"{{creative.name}}\", names: [\"ad_name\", \"creative_name\"] },\n ],\n};\n\n/**\n * ChatGPT Ads.\n *\n * Ids and nothing else: its tracking templates fill in five macros\n * (`CHATGPT_TRACKING_MACROS`) and refuse any other at publish, so there is no\n * name to send and no placement, device or keyword. `{oppref}` is its click id,\n * which it appends as `oppref` by itself and Baker stores as a column, and\n * `{ad_account_id}` names nothing a report breaks down by. The ad group is the level\n * that holds the context hints, which makes it the one a budget decision is\n * made at — and the one a template carrying only `utm_campaign={campaign_id}`\n * and `utm_content={ad_id}` loses, because the UTMs are reported as themselves\n * and never read as a role.\n */\nconst CHATGPT: AdPlatformDefinition = {\n platform: \"chatgpt\",\n label: \"ChatGPT Ads\",\n docs: \"https://help.openai.com/en/articles/20001212-create-ads-for-chatgpt\",\n dimensions: [\n { role: \"campaign_id\", macro: \"{campaign_id}\", names: [\"campaign_id\", \"campaignid\"] },\n { role: \"adgroup_id\", macro: \"{ad_group_id}\", names: [\"ad_group_id\", \"adgroup_id\"] },\n { role: \"ad_id\", macro: \"{ad_id}\", names: [\"ad_id\", \"adid\"] },\n ],\n};\n\nexport const AD_PLATFORM_DEFINITIONS: Readonly<Record<AdPlatform, AdPlatformDefinition>> = {\n google: GOOGLE,\n meta: META,\n microsoft: MICROSOFT,\n linkedin: LINKEDIN,\n tiktok: TIKTOK,\n reddit: REDDIT,\n pinterest: PINTEREST,\n snapchat: SNAPCHAT,\n chatgpt: CHATGPT,\n};\n\n/**\n * What this platform can be asked about, in the order a person looks for it.\n *\n * The list a \"read as\" control offers, and — because `validateAdParamMapping`\n * checks against it — exactly what may be stored for this platform. The two\n * have to be one list: a role that stores and is not offered renders the\n * control blank over a mapping that is really set.\n *\n * Three tiers, narrowest first: what the platform sends, what any account can\n * hand-tag on any platform, then the two answers about the reader's own URL.\n * `other` and `ignore` end every platform's list, which `adParams.test.ts`\n * pins.\n */\nexport function adPlatformRoles(platform: AdPlatform): AdParamRole[] {\n return [\n ...AD_PLATFORM_DEFINITIONS[platform].dimensions.map((entry) => entry.role),\n ...HAND_TAGGED_AD_PARAM_ROLES,\n ...UNIVERSAL_AD_PARAM_ROLES,\n ];\n}\n\n/** Whether this platform can carry that dimension — sent by it, or hand-tagged on it. */\nexport function adPlatformKnowsRole(platform: AdPlatform, role: AdParamRole): boolean {\n return adPlatformRoles(platform).includes(role);\n}\n\n/**\n * The dimensions without which a campaign is not usefully measurable.\n *\n * ## Why this exists, and why it is a short list\n *\n * This file's stated position was that it proposes nothing: an ad account\n * belongs to the client, it is usually already tagged, and a tool that opens by\n * demanding a rewrite of live tracking is one people close. That holds for\n * everything except the ad hierarchy, and there the cost of staying silent is\n * the whole report. A landing page whose URLs carry only `utm_campaign` can say\n * eighty visits came from Meta and nothing about which of nine ad sets or\n * thirty creatives sent them — so the one decision the report exists to inform,\n * move budget from this to that, cannot be made from it. Nobody discovers that\n * until they go looking for the breakdown and find it empty, by which point the\n * traffic it would have described is a month old and unrecoverable: an unmapped\n * parameter's *values* are never stored, so this is the rare configuration\n * whose absence cannot be fixed retroactively.\n *\n * Hence a warning rather than a preference. It is the only thing on this screen\n * that is stated as advice.\n *\n * ## Campaign, ad group, ad — and, where somebody is bidding on words, the words\n *\n * Four at most, and usually three. A recommendation naming eight things is one\n * nobody acts on, so placement, device, network and the rest stay merely\n * useful. `search_query` is left off for a different reason: only Microsoft\n * sends it, so making it essential would report every other platform as\n * incompletely tagged for a thing it cannot do.\n *\n * Ids, never names, at every tier a platform offers both. Renaming a campaign\n * splits its history in two under a name; an id survives the rename, which is\n * exactly what makes a month-over-month comparison possible.\n */\nconst ESSENTIAL_ROLES: readonly AdParamRole[] = [\"campaign_id\", \"adgroup_id\", \"ad_id\", \"keyword\", \"match_type\"];\n\n/**\n * The essential dimensions this platform can actually send.\n *\n * Intersected with the platform's own vocabulary rather than asserted, so Meta\n * is never told it is missing a keyword. That is the same rule the role\n * dropdown follows and for the same reason: a recommendation that cannot be\n * satisfied teaches people to ignore recommendations.\n */\nexport function essentialAdDimensions(platform: AdPlatform): readonly AdPlatformDimension[] {\n return AD_PLATFORM_DEFINITIONS[platform].dimensions.filter((entry) => ESSENTIAL_ROLES.includes(entry.role));\n}\n\n/**\n * Whether any platform at all offers this role.\n *\n * Separates the two ways a mapping can name an unreachable dimension, which\n * read identically to whoever hit them and need opposite fixes: \"Meta has no\n * keyword, use a platform that does\" is a correction the caller can act on,\n * while \"nothing anywhere reports this\" is a hole in the vocabulary and a bug\n * here. `adParams.test.ts` asserts the second can no longer exist, so this\n * exists to keep the *message* honest rather than to expect a `false`.\n */\nexport function anyPlatformKnowsRole(role: AdParamRole): boolean {\n return AD_PLATFORMS.some((platform) => adPlatformKnowsRole(platform, role));\n}\n\n// ---------------------------------------------------------------------------\n// Mapping\n// ---------------------------------------------------------------------------\n\n/** Parameter name, lowercased, to what it means — within one platform. */\nexport type AdParamMapping = Readonly<Record<string, AdParamRole>>;\n\n/**\n * A company's own entries, keyed `<platform>/<name>`.\n *\n * One flat record rather than a record of records, so a write stays a bag of\n * set/remove operations on keys and cannot be half-applied across two levels of\n * nesting. Every key names a platform; there is no cross-platform entry to\n * write and nothing reads one.\n */\nexport type StoredAdParamMapping = Readonly<Record<string, string>>;\n\nexport function adMappingKey(platform: AdPlatform, name: string): string {\n return `${platform}/${normalizeAdParamName(name)}`;\n}\n\nexport interface AdMappingEntry {\n platform: AdPlatform;\n name: string;\n}\n\nexport function parseAdMappingKey(key: string): AdMappingEntry | null {\n const separator = key.indexOf(\"/\");\n if (separator === -1) return null;\n const platform = key.slice(0, separator).trim().toLowerCase();\n const name = normalizeAdParamName(key.slice(separator + 1));\n if (!isAdPlatform(platform) || name === \"\") return null;\n return { platform, name };\n}\n\n/** Parameter names are compared lowercased; URLs are not consistent about case. */\nexport function normalizeAdParamName(raw: string): string {\n return raw.trim().toLowerCase().slice(0, 64);\n}\n\nfunction builtinFor(platform: AdPlatform): AdParamMapping {\n const mapping: Record<string, AdParamRole> = {};\n for (const dimension of AD_PLATFORM_DEFINITIONS[platform].dimensions) {\n for (const name of dimension.names) mapping[normalizeAdParamName(name)] = dimension.role;\n }\n return mapping;\n}\n\nconst BUILTIN: Readonly<Record<AdPlatform, AdParamMapping>> = Object.fromEntries(\n AD_PLATFORMS.map((platform) => [platform, builtinFor(platform)]),\n) as Record<AdPlatform, AdParamMapping>;\n\n/** The spellings this platform is already understood to use, before any configuration. */\nexport function builtinAdParamMapping(platform: AdPlatform): AdParamMapping {\n return BUILTIN[platform];\n}\n\n/**\n * Every spelling any platform can produce, which is what the edge captures.\n *\n * Capture is name-based and platform-blind — the Worker sees a query string,\n * not an ad account — so it has to be the union. That is correct: storing a\n * value the reports then read under one platform's vocabulary costs a map\n * entry, and refusing to store it because another platform means something else\n * by the name would lose the data entirely.\n */\nexport const EVERY_BUILTIN_AD_PARAM_NAME: readonly string[] = [\n ...new Set(AD_PLATFORMS.flatMap((platform) => Object.keys(BUILTIN[platform]))),\n].sort();\n\n/**\n * Parameters worth storing that no role can be named for.\n *\n * A preset entry is a *name for a role*, so a parameter only gets into\n * {@link EVERY_BUILTIN_AD_PARAM_NAME} if one of {@link AD_PARAM_ROLES} describes\n * it. These are real macros a live account emits and this vocabulary has no\n * word for: ValueTrack's two location ids, the feed and extension ids, the\n * device model. There is nothing dishonest to map them to — inventing a role\n * per macro would mean a role nobody ever breaks a report down by — and\n * dropping them means the values are gone rather than merely unlabelled.\n *\n * So capture is deliberately wider than mapping. They arrive, they show up in\n * `baker analytics ads` with no role, and a company that cares says what one of\n * them means with `baker analytics map`. That is the same path a company's own\n * private spelling takes, and it only works if the value was stored.\n *\n * Two things this is not. It is not \"every parameter\": a landing URL carries\n * client state and, on a magic link, an address — which is why `ref`, `source`\n * and friends are deliberately absent however common they are. And it is not a\n * place to park a name that *does* have a role; that belongs in the platform's\n * preset, where it also teaches the reports.\n */\nexport const CAPTURED_WITHOUT_ROLE: readonly string[] = [\n \"account_id\",\n \"audience\",\n \"device_model\",\n \"devicemodel\",\n \"experiment\",\n \"extensionid\",\n \"feed_item_id\",\n \"feeditemid\",\n \"loc_interest_ms\",\n \"loc_physical_ms\",\n \"location_interest\",\n \"location_physical\",\n \"target_id\",\n \"targetid\",\n].sort();\n\n/**\n * The mapping in force for a company on one platform.\n *\n * Two layers: what the platform is already understood to send, then the\n * company's own entries for it, which win. A company that uses `placement` for\n * something of its own must be able to say so, and quietly keeping our reading\n * because we happen to know the name would put their data under a role it does\n * not belong to.\n *\n * An entry naming a role that does not exist is skipped rather than throwing.\n * This reads persisted data that may predate a role being renamed or removed,\n * and a report must never fail because of a mapping written last year. The\n * write path reports the same entry loudly, which is where that belongs.\n */\nexport function resolveAdParamMapping(custom: StoredAdParamMapping | undefined, platform: AdPlatform): AdParamMapping {\n const mapping: Record<string, AdParamRole> = { ...BUILTIN[platform] };\n for (const [key, role] of Object.entries(custom ?? {})) {\n const entry = parseAdMappingKey(key);\n if (entry?.platform === platform && isAdParamRole(role)) mapping[entry.name] = role;\n }\n return mapping;\n}\n\n/**\n * Every parameter name that carries one role, sorted.\n *\n * This is what a query filters on. A company that renamed `kw` to `keyword`\n * last spring has both spellings in its history, so asking for the keyword role\n * has to ask for both — which is the whole reason roles are resolved when a\n * report is read rather than stamped on the row at ingest.\n */\nexport function adParamNamesForRole(mapping: AdParamMapping, role: AdParamRole): string[] {\n return Object.entries(mapping)\n .filter(([, mapped]) => mapped === role)\n .map(([name]) => name)\n .sort();\n}\n\n/**\n * The same question with no platform in hand: every spelling that carries the\n * role on any platform that can send it.\n *\n * The honest answer for a report spanning platforms, and honestly imprecise:\n * TikTok reads `cid` as the ad where most accounts mean a campaign by it, so\n * the union puts one value under two roles in two different reports. Nothing\n * can resolve that without knowing where the click came from, which is why\n * every screen picks a platform and only the cross-platform CLI read does not.\n */\nexport function adParamNamesForRoleAcrossPlatforms(\n custom: StoredAdParamMapping | undefined,\n role: AdParamRole,\n): string[] {\n const names = new Set<string>();\n for (const platform of AD_PLATFORMS) {\n for (const name of adParamNamesForRole(resolveAdParamMapping(custom, platform), role)) names.add(name);\n }\n return [...names].sort();\n}\n\n/**\n * The mapping to label ONE visit's parameters with, when the platform may not\n * be known.\n *\n * A lead panel shows the parameters a single visit arrived with and wants to\n * print \"Keyword\" rather than `kw`. Usually the visit names its platform — a\n * click id says so — and then this is simply that platform's mapping.\n *\n * When it does not, the honest answer is a merge that **drops every name the\n * platforms disagree about**. Labelling `cid` as an ad because TikTok says so,\n * on a visit that may well have come from Google, would put a confident wrong\n * word in front of a person; showing the raw name says exactly as much as we\n * know. A name every platform reads the same way is safe and keeps its label.\n */\nexport function labelAdParamMapping(custom: StoredAdParamMapping | undefined, platform: string): AdParamMapping {\n if (isAdPlatform(platform)) return resolveAdParamMapping(custom, platform);\n const merged: Record<string, AdParamRole> = {};\n const disputed = new Set<string>();\n for (const entry of AD_PLATFORMS) {\n for (const [name, role] of Object.entries(resolveAdParamMapping(custom, entry))) {\n if (merged[name] !== undefined && merged[name] !== role) disputed.add(name);\n merged[name] = role;\n }\n }\n for (const name of disputed) delete merged[name];\n return merged;\n}\n\n/**\n * What one parameter name means on one platform, or null if nothing has said.\n *\n * Normalizes first, which is the whole reason this is a function rather than an\n * index: URLs arrive with `Keyword`, `keyword` and `KEYWORD` and they are one\n * parameter. A caller reaching into the map directly gets a miss on two of the\n * three and shows a raw name beside a value it could have labelled.\n */\nexport function adParamRoleOf(mapping: AdParamMapping, name: string): AdParamRole | null {\n return mapping[normalizeAdParamName(name)] ?? null;\n}\n\n/**\n * Read a URL's captured parameters as roles.\n *\n * Where two spellings of one role are both present — which happens mid-rename,\n * and whenever an advertiser leaves an old parameter in place while adding a\n * new one — the alphabetically first name wins, so the same URL always resolves\n * the same way. Arbitrary, but stable, and a report that flickered between two\n * answers for the same visit would be worse.\n */\nexport function resolveAdDimensions(\n params: Readonly<Record<string, string>>,\n mapping: AdParamMapping,\n): Partial<Record<AdParamRole, string>> {\n const out: Partial<Record<AdParamRole, string>> = {};\n for (const name of Object.keys(params).sort()) {\n const value = params[name];\n if (typeof value !== \"string\" || value === \"\") continue;\n const role = mapping[normalizeAdParamName(name)];\n // `other` is captured but unnamed and `ignore` is not campaign information\n // at all. Neither is a dimension, so neither belongs in a dimension map.\n if (!role || role === \"other\" || role === \"ignore\") continue;\n if (out[role] === undefined) out[role] = value;\n }\n return out;\n}\n\n// ---------------------------------------------------------------------------\n// Tracking coverage — what a platform is not sending, and what to add\n// ---------------------------------------------------------------------------\n\n/** One essential dimension, and whether this account is actually sending it. */\nexport interface AdDimensionCoverage {\n role: AdParamRole;\n /** The platform's own macro, which is what goes in the URL. */\n macro: string;\n /**\n * The parameter name this account uses for it, or the canonical one to adopt.\n *\n * The account's own spelling wins wherever there is one — a company that has\n * been sending `kw` for two years should be told to keep sending `kw`, not to\n * change it. `names[0]` is the fallback, and only for a dimension nothing is\n * carrying yet.\n */\n name: string;\n /** Whether some parameter on this platform already carries this role. */\n covered: boolean;\n note?: string;\n}\n\n/**\n * Which of the essential dimensions this account's traffic actually carries.\n *\n * Read off **the parameter names observed arriving on this platform's clicks**,\n * resolved through the mapping — not off the mapping alone. The distinction is\n * the whole correctness of the warning: {@link resolveAdParamMapping} starts\n * from the built-in spellings, which are the names Baker would *recognise* if\n * they turned up, so a mapping-only reading reports every dimension as covered\n * for an account sending none of them. The question is what the ads are\n * sending, and only the traffic knows.\n *\n * `observed` is the inventory the discovery report already produces. An empty\n * inventory means either an untagged account or a platform nobody runs, and the\n * caller distinguishes those by whether the platform has visits at all — a\n * campaign that has not run yet must not be reported as badly tagged.\n */\nexport function adDimensionCoverage(\n platform: AdPlatform,\n custom: StoredAdParamMapping | undefined,\n /** Parameter names seen on this platform's traffic, however they are spelled. */\n observed: readonly string[],\n): AdDimensionCoverage[] {\n const mapping = resolveAdParamMapping(custom, platform);\n const carried = new Map<AdParamRole, string>();\n for (const name of observed) {\n const normalized = normalizeAdParamName(name);\n const role = mapping[normalized];\n // First spelling wins, so a rename in progress reports the older name and\n // a suggestion never asks for a parameter the account is already sending.\n if (role !== undefined && !carried.has(role)) carried.set(role, normalized);\n }\n return essentialAdDimensions(platform).map((dimension) => {\n const name = carried.get(dimension.role);\n return {\n role: dimension.role,\n macro: dimension.macro,\n name: name ?? dimension.names[0] ?? dimension.role,\n covered: name !== undefined,\n ...(dimension.note === undefined ? {} : { note: dimension.note }),\n };\n });\n}\n\n/**\n * The URL suffix that would start sending the dimensions this account is missing.\n *\n * Exactly the missing ones, in the platform's own macro language, ready to\n * paste into the account's tracking template or URL-parameters field. Nothing\n * about the ad itself and nothing that touches what is already there — the\n * existing parameters keep working and keep being read, which is what makes it\n * safe to hand to somebody who did not write the current tagging.\n *\n * Empty when nothing is missing, so a caller can tell \"here is what to add\"\n * from \"there is nothing to add\" without inspecting the string.\n */\nexport function missingParamSuffix(\n platform: AdPlatform,\n custom: StoredAdParamMapping | undefined,\n observed: readonly string[],\n): string {\n return adDimensionCoverage(platform, custom, observed)\n .filter((entry) => !entry.covered)\n .map((entry) => `${entry.name}=${entry.macro}`)\n .join(\"&\");\n}\n","/**\n * The name of a thing that happens on a published page.\n *\n * Baker's event stream is typed rows, not named events: a Form node's trigger\n * lives in `flow_slug` / `step_id` / `trigger_id`, a page-defined event in\n * `event_name`, an outbound click's destination in `event_name` again. Nothing\n * in it is a single value you could point at and say \"count that one\".\n *\n * An event key is that value, derived from the row rather than stored on it:\n *\n * ```\n * form:<flow_slug>:<step_id>:<trigger_id> a Form node's trigger\n * submit:<flow_slug> a Form reached its last node\n * page:<event_name> a named event: a data-baker-* event, a\n * video milestone, or one a client's\n * own server posted\n * exit:<event_name> an outbound click, by destination host\n * view:<landing_id> a landing page was viewed\n * ```\n *\n * ## Derived, never stored\n *\n * There is no `event_key` column and no producer change. The key is a SQL\n * expression over columns that already exist, which is what lets a page that\n * was published months ago start producing keys the moment an endpoint deploys\n * — and what keeps a conversion definition retroactive, since a definition\n * added today is matched against every row already collected.\n *\n * ## The SQL builds them; this module mostly reads them\n *\n * `eventKeySql` in `tinybird/definitions.ts` is the only thing that turns a row\n * into a key, so there is one producer and no chance of two vocabularies. This\n * module parses keys for display, and builds the one family that has to be\n * constructed without asking the store: the Form-builder star knows its flow,\n * node and trigger and has to name the key before any event has ever fired.\n * Every other family is discovered — a `page:` key is offered once an event\n * carrying that name has arrived, which is why there is no builder for it.\n * `eventKey.parity.test.ts` reads the SQL and fails if the two shapes drift.\n *\n * ## Why `:` is safe\n *\n * Flow slugs are directory names, node ids are UUIDs, and neither can contain a\n * colon. Trigger ids can (`iframe.<whatever the embed posts>`), so the trigger\n * takes everything after the third separator rather than being split on it.\n * Page event names and outbound hosts take everything after the first, for the\n * same reason.\n */\n\n/**\n * The families a key can belong to, and therefore everything that can be\n * counted.\n *\n * `view` is the newest and the only one that is not an outcome. A landing\n * being viewed is the first step of most funnels a marketer would draw —\n * \"saw the offer, asked for a quote, booked the visit\" — and until it had a\n * key the funnel could not start where the journey starts. It is a funnel\n * step and never a conversion: the conversions surfaces refuse to mark it\n * (`conversionArgs`, `analytics/conversions.ts`) and the catalogue does not\n * offer it, because a page view counted as an outcome would put the\n * conversion rate one click away from the traffic count.\n *\n * There was briefly a fifth, `server`, for an event a client's own server\n * posted. It is gone: a named event is a named event whoever sent it, and\n * splitting the vocabulary made a server-sent conversion a different kind of\n * thing on every screen that lists one. Which collector wrote it is `source` on\n * the row — a property a report can filter by, rather than a family half the\n * reports would have to learn about.\n *\n * `page:` keeps its prefix even though it now covers both. Renaming it to\n * something origin-neutral would silently stop counting every definition\n * already stored under the old spelling; only the label changes.\n */\nexport const EVENT_KEY_FAMILIES = [\"form\", \"submit\", \"page\", \"exit\", \"view\"] as const;\nexport type EventKeyFamily = (typeof EVENT_KEY_FAMILIES)[number];\n\nconst SEP = \":\";\n\n/**\n * The key for one trigger on one Form node.\n *\n * The only builder, because this is the only family whose key has to exist\n * before the event does — the star in the Form builder marks an outcome on a\n * node that may never have fired.\n */\nexport function formEventKey(flowSlug: string, nodeId: string, triggerId: string): string {\n return [\"form\", flowSlug, nodeId, triggerId].join(SEP);\n}\n\n/**\n * The key for one landing being viewed.\n *\n * The second builder, for the same reason as the first: a funnel step on a\n * landing is drawn from the Landings list, before the page has necessarily had\n * a visitor. `landing_id` is the landing's internal id — what the page stamps on\n * every row it sends — never its slug, which a rename changes.\n */\nexport function landingViewEventKey(landingId: string): string {\n return [\"view\", landingId].join(SEP);\n}\n\n/**\n * The key for ANY landing being viewed — the way a funnel's first step counts\n * everybody who arrived without naming each landing, and without going stale\n * the day a landing is added. Parsed as a `view` key with `*` for the id, so\n * both conversion write paths keep refusing it; `funnel_reach` is the one\n * reader that resolves it, after every exact key.\n */\nexport const ANY_LANDING_VIEW_KEY = \"view:*\";\n\nexport function isAnyLandingViewKey(key: string): boolean {\n return key === ANY_LANDING_VIEW_KEY;\n}\n\n/** A store sale, counted once from the store's own order: the key a store's sales are counted under. */\nexport const STORE_PURCHASE_KEY = \"page:purchase\";\n\n/**\n * The store's checkout record: the shopper's browser saying a checkout was\n * completed, kept for the order's consent and visitor. The same sale is already\n * `page:purchase`, from the store itself, so this is never an outcome or a\n * funnel step — counted next to it, every sale counts twice. The catalogue\n * leaves it out and the conversion and funnel writes refuse it.\n */\nexport const CHECKOUT_RECORD_KEY = \"page:purchase_observed\";\n\nexport function isCheckoutRecordKey(key: string): boolean {\n return key.trim() === CHECKOUT_RECORD_KEY;\n}\n\n/** Why a checkout record cannot be counted, and what to count instead. */\nexport const CHECKOUT_RECORD_REFUSAL = {\n message: `\"${CHECKOUT_RECORD_KEY}\" is the shopper's browser reporting a store order that \"${STORE_PURCHASE_KEY}\" already counts from the store itself, so counting it would count every sale twice.`,\n fix: `Count the store's sales with \"${STORE_PURCHASE_KEY}\" instead (e.g. --event ${STORE_PURCHASE_KEY} --name 'Store purchase'). If it is already counted, nothing more is needed.`,\n} as const;\n\n/** One event key, taken apart far enough to describe in words. */\nexport type ParsedEventKey =\n | { family: \"form\"; flowSlug: string; nodeId: string; triggerId: string }\n | { family: \"submit\"; flowSlug: string }\n | { family: \"page\"; eventName: string }\n | { family: \"exit\"; host: string }\n | { family: \"view\"; landingId: string };\n\n/** Whether a key names a page being viewed — a funnel step, and never an outcome. */\nexport function isLandingViewKey(key: string): boolean {\n return parseEventKey(key)?.family === \"view\";\n}\n\n/**\n * A key's parts, or `null` for anything this vocabulary does not cover.\n *\n * Tolerant on purpose: keys arrive from a Convex row somebody may have written\n * through the CLI, and one unreadable definition must not cost the other three\n * their row on the screen. A caller that gets `null` shows the raw key, which is\n * still the truth about what is being counted.\n */\nexport function parseEventKey(key: string): ParsedEventKey | null {\n // Split off the family, then hand the rest to whoever knows how many\n // separators of its own it has.\n const cut = key.indexOf(SEP);\n if (cut <= 0) return null;\n const family = key.slice(0, cut);\n const rest = key.slice(cut + 1);\n if (rest === \"\") return null;\n\n switch (family) {\n case \"form\": {\n // The trigger takes everything after the third separator: `iframe.<name>`\n // is named by whatever a client's embedded page posts and may contain one.\n const [flowSlug, nodeId, ...triggerParts] = rest.split(SEP);\n const triggerId = triggerParts.join(SEP);\n if (!flowSlug || !nodeId || triggerId === \"\") return null;\n return { family: \"form\", flowSlug, nodeId, triggerId };\n }\n case \"submit\":\n return { family: \"submit\", flowSlug: rest };\n case \"page\":\n return { family: \"page\", eventName: rest };\n case \"exit\":\n return { family: \"exit\", host: rest };\n case \"view\":\n return { family: \"view\", landingId: rest };\n default:\n return null;\n }\n}\n","/**\n * Where a system's event is sent: its sends.\n *\n * A send is what Meta, Google Ads, LinkedIn and X all call an offline\n * conversion — an event that happened away from the browser, a deal won in a\n * CRM, a booking confirmed by a calendar, a payment taken by a checkout — sent\n * to the ad platform from a server so the campaign that brought the person\n * gets the credit. In Baker it hangs off an **event one of the company's own\n * systems raises**, by its key: `page:deal_won` from HubSpot → Meta Purchase.\n * What happens on a page or in a Form already reaches the platform through\n * the pixels on the page and the Form's own side effects, so a send is never\n * set on those: a send used to hang off a conversion name, and the same Form\n * event then went to Meta twice.\n *\n * ## Nothing to map, on purpose\n *\n * A previous shape was a Form side effect verbatim: a per-destination form\n * with a variable picker, where somebody chose which field of the event went\n * into Meta's `fn` and Google's `hashed_first_name`. That is a mapping a person\n * should never have had to write, because there is nothing to decide: every\n * event in Baker's stream arrives in one shape whoever sent it\n * (`sendPayload.ts`), and every platform's server API takes the same\n * customer-matching block, the same click id, the same amount and currency.\n * So a send says only what a person can actually choose — **which** tag on\n * Tracking sends it and **what** the platform calls the event — and the\n * forwarding of who, which click, how much and where is the sender's job, done\n * the same way for every conversion in every company.\n *\n * ## A tag on Tracking, with its server credential\n *\n * Every platform here sends from the server through a tag the company already\n * has on Tracking — the Meta pixel, the Google Ads conversion tag, the LinkedIn\n * Insight Tag, the X pixel, the GA4 property — using the credential that tag\n * holds for exactly this: a Conversions API access token, an ad-account\n * connection, a Measurement Protocol secret. A tag without that credential can\n * fire in a browser and nowhere else, so it cannot be a destination for an\n * event that arrived with no browser anywhere. {@link tagSendsServerSide} is the\n * rule, and it is the same rule the Form's server-side senders apply\n * (`isCapiConfigured` in `packages/backend/convex/tags/utils.ts`, held in step\n * by a test there).\n *\n * ## It sends\n *\n * A send is live from the moment it is saved: a poller in the backend reads\n * every event counted under the name, resolves the person, and uploads it to\n * the platform within about five minutes. What happened to each upload is read\n * back beside the send, on the same screen it was configured on.\n */\n\nimport { propertyKeyProblem } from \"../event-sources/fields\";\nimport { renderTemplate, templateMentions } from \"../event-sources/template\";\nimport { parseEventKey } from \"./eventKey\";\n\n/**\n * The platforms a conversion can be sent to. Each id is the tag type on\n * Tracking whose credential does the sending, so the platform IS the tag's\n * type — one vocabulary, and the tag's own mark beside it everywhere.\n *\n * Mirrored by `SEND_PLATFORMS` in `packages/backend/convex/lib/validators.ts`,\n * held in step by `../funnel/vocabulary.parity.test.ts`.\n */\nexport const SEND_PLATFORMS = [\n \"meta\",\n \"chatgptAds\",\n \"googleAds\",\n \"linkedinInsightTag\",\n \"twitterAds\",\n \"googleAnalytics\",\n] as const;\nexport type SendPlatform = (typeof SEND_PLATFORMS)[number];\n\nexport function isSendPlatform(value: string): value is SendPlatform {\n return (SEND_PLATFORMS as readonly string[]).includes(value);\n}\n\nexport interface SendPlatformSpec {\n /** The platform's name, as the product says it. */\n title: string;\n /** What `event` names on this platform, in one line. */\n eventMeans: string;\n /** Whether `event` is chosen from the platform's own list, or is an id the platform issued. */\n eventKind: \"name\" | \"id\";\n /** The tag config fields that have to be filled for the tag to send from the server. */\n credentialFields: readonly string[];\n /** What to do on Tracking when no tag qualifies. */\n setupHint: string;\n}\n\n/**\n * The per-platform facts, from the senders in `packages/backend/convex/sideEffects/**`.\n *\n * `credentialFields` are the fields those senders read before anything else —\n * a Meta send with no token, a LinkedIn, Google Ads or X send\n * with no connected account, a GA4 send with no API secret, all stop before\n * the request is built.\n */\nexport const SEND_PLATFORM_SPECS: Readonly<Record<SendPlatform, SendPlatformSpec>> = {\n meta: {\n title: \"Meta\",\n eventMeans: \"A Meta event name — Lead, Purchase, Schedule, or one of your own\",\n eventKind: \"name\",\n credentialFields: [\"accessToken\"],\n setupHint: \"Add the Conversions API access token to the Meta pixel in Tracking\",\n },\n chatgptAds: {\n title: \"ChatGPT Ads\",\n eventMeans: \"A ChatGPT Ads event — lead_created, order_created, or a custom name\",\n eventKind: \"name\",\n credentialFields: [\"apiKey\"],\n setupHint: \"Add the Conversions API key to the ChatGPT Ads pixel in Tracking\",\n },\n googleAds: {\n title: \"Google Ads\",\n eventMeans: \"One of the account's conversion actions, picked from its list\",\n eventKind: \"id\",\n credentialFields: [\"oauthProviderId\"],\n setupHint: \"Connect the Google Ads conversion tag to a Google Ads account in Tracking\",\n },\n linkedinInsightTag: {\n title: \"LinkedIn\",\n eventMeans: \"The id of a conversion rule in LinkedIn Campaign Manager\",\n eventKind: \"id\",\n credentialFields: [\"oauthProviderId\"],\n setupHint: \"Connect the LinkedIn Insight Tag to a LinkedIn account in Tracking\",\n },\n twitterAds: {\n title: \"X\",\n eventMeans: \"The event tag id from X Events Manager, like tw-oka17-abc12\",\n eventKind: \"id\",\n credentialFields: [\"oauthProviderId\"],\n setupHint: \"Connect the X pixel to an X Ads account in Tracking\",\n },\n googleAnalytics: {\n title: \"Google Analytics\",\n eventMeans: \"A GA4 event name — generate_lead, purchase, or one of your own\",\n eventKind: \"name\",\n credentialFields: [\"apiSecret\"],\n setupHint: \"Add the Measurement Protocol API secret to the Google Analytics tag in Tracking\",\n },\n};\n\n/**\n * Whether a tag on Tracking can send this platform's events from the server:\n * it is the platform's own tag type and every credential field is filled.\n *\n * `secretsSet` names credential fields held but not readable — a redacted tag\n * view carries the NAMES of its secrets and never their values, and a Google\n * Ads or LinkedIn connection IS a secret field. Without it, every redacted\n * view of a perfectly configured tag reads as unable to send: that is how a\n * connection captured in a chat stayed invisible to the send it was captured\n * for, leaving the agent unable to stage the send and unable to publish the\n * tag without ending the chat.\n */\nexport function tagSendsServerSide(\n platform: SendPlatform,\n config: Record<string, unknown>,\n secretsSet: readonly string[] = [],\n): boolean {\n return SEND_PLATFORM_SPECS[platform].credentialFields.every((field) => {\n const value = config[field];\n return (typeof value === \"string\" && value.trim() !== \"\") || secretsSet.includes(field);\n });\n}\n\n/**\n * Where the events a send uploads come from: the API and the integrations\n * (`server` — anything posted to Baker or read by it, the store's `ingest`\n * rows), Baker's pixel on the company's own website (`website`), or both\n * (`any`).\n *\n * `any` is what a send is set up as now: one event has one set of\n * destinations, whichever door it arrives by. A company that reports a\n * `lead` from the page AND from its server is reporting one lead twice, and\n * both rows carry the same `event_uid` when both pass the same `eventId`\n * (`PIXEL_DEDUPE_SNIPPET`) — so one send uploads it once: the claim key is\n * the send and the event id, and the platform's own event id is the same\n * `event_uid` either way. Two sends, one per door, uploaded it twice under\n * two claims and left the dedup to the platform.\n *\n * `server` and `website` are what sends were stored as before, and still mean\n * exactly that door. Absent on a stored send means `server`, the only door\n * sends had before the pixel.\n *\n * Never a page Baker publishes: what happens there reaches the platforms\n * through the Form's own side effects.\n */\nexport const SEND_ORIGINS = [\"server\", \"website\", \"any\"] as const;\nexport type SendOrigin = (typeof SEND_ORIGINS)[number];\n\n/** The two doors a stored event row can have arrived by. */\nexport type SendRowOrigin = Exclude<SendOrigin, \"any\">;\n\n/** Which door a send uploads from, reading an absent one as the API's. */\nexport function sendOrigin(send: Pick<ConversionSend, \"origin\">): SendOrigin {\n return send.origin ?? \"server\";\n}\n\n/** Whether a send uploads an event that arrived through this door. */\nexport function sendUploadsFrom(send: Pick<ConversionSend, \"origin\">, door: SendRowOrigin): boolean {\n const origin = sendOrigin(send);\n return origin === \"any\" || origin === door;\n}\n\n/** How long a send's id may be on the wire. */\nexport const MAX_SEND_ID = 64;\n\n/** How long an event key may be on the wire. */\nexport const MAX_SEND_EVENT_KEY = 320;\n\n/**\n * The event a send is on, in words: the name the system gave it — what\n * `page:deal_won` is called on Sources and on Events. A key of another family\n * shows itself, which reads as wrong on purpose: no system raises one.\n */\nexport function describeSendEvent(eventKey: string): string {\n const parsed = parseEventKey(eventKey);\n return parsed?.family === \"page\" ? parsed.eventName : eventKey;\n}\n\n/**\n * One place a system's event is sent.\n *\n * Two choices and nothing else: the tag that sends, and what the platform\n * calls the event. Everything the send carries — who the person is, the click\n * that brought them, the amount and currency, when and where it happened — is\n * the event's own and is forwarded the same way every time; see\n * `sendPayload.ts` for the list.\n */\nexport interface ConversionSend {\n /** Stable within the company, so a review and an outcome can point at one row. `s1`, `s2` — something a person can type. */\n id: string;\n /** The event it sends, by key: `page:<name>`, as a Source, the API or the pixel raises it. */\n eventKey: string;\n /** Which door's events it uploads — see {@link SEND_ORIGINS}. Absent is `server`. */\n origin?: SendOrigin;\n platform: SendPlatform;\n /** The tag on Tracking that sends it. Must hold the platform's server credential. */\n tagId: string;\n /**\n * What the platform calls the event: a name, or an id it issued — see\n * `SEND_PLATFORM_SPECS[platform].eventMeans`.\n *\n * On a platform that takes a **name** (Meta, Google Analytics) this may be\n * a template over the event, exactly like a parameter: `{{properties.plan}}`,\n * or `Purchase · {{properties.tier}}`. One HubSpot event whose records carry\n * a plan then reaches Meta under the plan's own name, instead of needing one\n * source, one send and one conversion action per plan. An **id** is issued\n * by the platform and cannot be written from a record, so a template there\n * is refused rather than uploaded as a literal `{{…}}`.\n */\n event: string;\n /** The ad account the event is filed under, where the platform needs one named (LinkedIn's conversion rules belong to one). */\n account?: string;\n /**\n * Extra parameters the platform is sent with every upload, beyond who, the\n * click and the amount: platform parameter name → a template over the\n * event (`sendPayload.ts` `SEND_TEMPLATE_FIELDS`, plus `{{properties.<key>}}`\n * for the event's own properties). Where they land is the platform's own\n * — Meta's `custom_data`, a GA4 event parameter, a Google Ads custom\n * variable — see {@link SEND_PARAMETERS}.\n */\n parameters?: Record<string, string>;\n}\n\n/** What one platform does with extra parameters, and what it calls them. */\nexport interface SendParameterSpec {\n /** Whether the platform's server API takes extra parameters at all. */\n takes: boolean;\n /** Where they land, in the platform's words. Empty when it takes none. */\n where: string;\n /** Why the platform takes none. Empty when it does. */\n refusal: string;\n /** The names the platform already knows. Anything else is the company's own, unless `onlyKnown`. */\n known: readonly { key: string; label: string }[];\n /** Whether an unknown name is refused — X's conversions take a closed set. */\n onlyKnown: boolean;\n /** What a name may look like, when the platform is stricter than the store's own rule. */\n namePattern?: RegExp;\n nameHint?: string;\n /** How many one send may carry. */\n max: number;\n}\n\n/**\n * The per-platform facts about extra parameters, from the senders in\n * `packages/backend/convex/sideEffects/**` and the platforms' own documents.\n */\nexport const SEND_PARAMETERS: Readonly<Record<SendPlatform, SendParameterSpec>> = {\n meta: {\n takes: true,\n where: \"custom_data on the Meta event, beside value and currency\",\n refusal: \"\",\n known: [\n { key: \"content_name\", label: \"Content name\" },\n { key: \"content_category\", label: \"Content category\" },\n { key: \"content_ids\", label: \"Content ids\" },\n { key: \"content_type\", label: \"Content type\" },\n { key: \"order_id\", label: \"Order id\" },\n { key: \"predicted_ltv\", label: \"Predicted lifetime value\" },\n { key: \"num_items\", label: \"Number of items\" },\n { key: \"search_string\", label: \"Search string\" },\n { key: \"status\", label: \"Status\" },\n { key: \"delivery_category\", label: \"Delivery category\" },\n ],\n onlyKnown: false,\n max: 30,\n },\n chatgptAds: {\n takes: false,\n where: \"\",\n refusal: \"ChatGPT Ads receives the event, person, click and amount from the source event.\",\n known: [],\n onlyKnown: false,\n max: 0,\n },\n googleAds: {\n takes: true,\n where: \"custom variables on the conversion — each name must exist as a custom variable in the Google Ads account\",\n refusal: \"\",\n known: [],\n onlyKnown: false,\n max: 20,\n },\n linkedinInsightTag: {\n takes: false,\n where: \"\",\n refusal: \"LinkedIn's Conversions API takes who, the click, the amount and the currency, and nothing else.\",\n known: [],\n onlyKnown: true,\n max: 0,\n },\n twitterAds: {\n takes: true,\n where: \"the conversion's contents and description\",\n refusal: \"\",\n known: [\n { key: \"content_id\", label: \"Content id\" },\n { key: \"content_name\", label: \"Content name\" },\n { key: \"content_type\", label: \"Content type\" },\n { key: \"content_price\", label: \"Content price\" },\n { key: \"num_items\", label: \"Number of items\" },\n { key: \"content_group_id\", label: \"Content group id\" },\n { key: \"description\", label: \"Description\" },\n ],\n onlyKnown: true,\n max: 7,\n },\n googleAnalytics: {\n takes: true,\n where: \"event parameters on the GA4 event, beside value and currency\",\n refusal: \"\",\n known: [\n { key: \"item_name\", label: \"Item name\" },\n { key: \"item_category\", label: \"Item category\" },\n { key: \"method\", label: \"Method\" },\n { key: \"content_type\", label: \"Content type\" },\n { key: \"coupon\", label: \"Coupon\" },\n { key: \"search_term\", label: \"Search term\" },\n ],\n onlyKnown: false,\n namePattern: /^[A-Za-z][A-Za-z0-9_]{0,39}$/,\n nameHint: \"letters, digits and underscore, starting with a letter, at most 40 characters\",\n max: 25,\n },\n};\n\n/**\n * Whether what the platform is called the event by is written from the event\n * rather than typed: it carries at least one `{{…}}`.\n */\nexport function sendEventIsTemplated(event: string): boolean {\n return templateMentions(event).length > 0;\n}\n\n/**\n * Why this platform cannot be told its event that way, or null.\n *\n * Only the platforms that take a name of your own can take a template. Google\n * Ads, LinkedIn and X each identify a conversion by an id **they** issued —\n * there is no id for a value a record happens to hold, so a template there\n * would upload against a conversion action that does not exist, which every\n * one of those APIs answers with a generic \"not found\".\n */\nexport function sendEventProblem(platform: SendPlatform, event: string): string | null {\n if (!isSendPlatform(platform)) return null;\n if (!sendEventIsTemplated(event)) return null;\n const spec = SEND_PLATFORM_SPECS[platform];\n if (spec.eventKind === \"name\") return null;\n return `${spec.title} identifies a conversion by an id it issued, so it cannot be written from the event. Paste the id; pass what varies as an extra parameter instead.`;\n}\n\n/** The parameter names a platform sets itself; a send naming one would fight the sender. */\nconst PARAMETERS_SET_BY_BAKER: readonly string[] = [\n \"value\",\n \"currency\",\n \"transaction_id\",\n \"session_id\",\n \"engagement_time_msec\",\n];\n\n/**\n * The parameters of one send, trimmed: blank names and blank templates are\n * dropped rather than stored, so an editor row left empty is not a parameter.\n */\nexport function cleanParameters(parameters: Readonly<Record<string, string>> | undefined): Record<string, string> {\n const cleaned: Record<string, string> = {};\n for (const [key, template] of Object.entries(parameters ?? {})) {\n const name = key.trim();\n const value = template.trim();\n if (name !== \"\" && value !== \"\") cleaned[name] = value;\n }\n return cleaned;\n}\n\n/** Why one parameter name cannot be sent to a platform, or null when it can. */\nexport function parameterNameProblem(platform: SendPlatform, name: string): string | null {\n const spec = SEND_PARAMETERS[platform];\n const title = SEND_PLATFORM_SPECS[platform].title;\n if (!spec.takes) return spec.refusal;\n const problem = propertyKeyProblem(name);\n if (problem !== null) return problem;\n if (PARAMETERS_SET_BY_BAKER.includes(name))\n return `\"${name}\" is sent by Baker itself, from the event's amount and id.`;\n if (spec.onlyKnown && !spec.known.some((known) => known.key === name)) {\n return `${title} takes only ${spec.known.map((known) => known.key).join(\", \")} — not \"${name}\".`;\n }\n if (spec.namePattern !== undefined && !spec.namePattern.test(name)) {\n return `${title} names a parameter with ${spec.nameHint}; \"${name}\" is not one.`;\n }\n return null;\n}\n\nexport const SEND_PROBLEM_CODES = [\n \"unknown_platform\",\n \"duplicate_id\",\n \"missing_event_key\",\n \"not_a_system_event\",\n \"missing_tag\",\n \"missing_event\",\n \"bad_parameter\",\n \"too_many_parameters\",\n \"event_not_templatable\",\n] as const;\nexport type SendProblemCode = (typeof SEND_PROBLEM_CODES)[number];\n\nexport interface SendProblem {\n code: SendProblemCode;\n sendId: string;\n message: string;\n}\n\n/**\n * What is wrong with a list of sends on their own terms.\n *\n * Whether a system of the company raises the event, whether the tag exists,\n * belongs to this company and holds the credential — those are checked where\n * Sources and tags can be read, in the backend door, not here.\n */\nexport function sendProblems(sends: readonly ConversionSend[]): SendProblem[] {\n const problems: SendProblem[] = [];\n const ids = new Set<string>();\n\n for (const send of sends) {\n if (ids.has(send.id)) {\n problems.push({ code: \"duplicate_id\", sendId: send.id, message: \"Two sends share one id\" });\n }\n ids.add(send.id);\n\n if (!isSendPlatform(send.platform)) {\n problems.push({\n code: \"unknown_platform\",\n sendId: send.id,\n message: `\"${send.platform}\" is not somewhere a conversion can be sent — one of ${SEND_PLATFORMS.join(\", \")}`,\n });\n continue;\n }\n\n problems.push(...eventKeyProblems(send), ...destinationProblems(send), ...parameterProblems(send));\n }\n\n return problems;\n}\n\n/** Whether a system of this company could raise what the send is on. */\nfunction eventKeyProblems(send: ConversionSend): SendProblem[] {\n if (send.eventKey.trim() === \"\") {\n return [{ code: \"missing_event_key\", sendId: send.id, message: \"Say which event this sends, by its key\" }];\n }\n // Only a named event can come from a system. A Form's step, a landing view,\n // a click away — those happen in a browser, where the page's own pixels\n // already report them.\n if (parseEventKey(send.eventKey)?.family === \"page\") return [];\n return [\n {\n code: \"not_a_system_event\",\n sendId: send.id,\n message: `\"${send.eventKey}\" is not an event a system sends. A send is for a named event a Source or your server raises (page:<name>); what happens in a Form or on a page reaches the platform through the Form's own side effects`,\n },\n ];\n}\n\n/** What is missing on the platform's side: the tag that sends, and what it calls the event. */\nfunction destinationProblems(send: ConversionSend): SendProblem[] {\n const platform = SEND_PLATFORM_SPECS[send.platform];\n const problems: SendProblem[] = [];\n if (send.tagId.trim() === \"\") {\n problems.push({\n code: \"missing_tag\",\n sendId: send.id,\n message: `Say which ${platform.title} tag on Tracking sends it`,\n });\n }\n if (send.event.trim() === \"\") {\n problems.push({\n code: \"missing_event\",\n sendId: send.id,\n message: `${platform.title} needs the event said: ${platform.eventMeans.charAt(0).toLowerCase()}${platform.eventMeans.slice(1)}`,\n });\n return problems;\n }\n const templated = sendEventProblem(send.platform, send.event);\n if (templated !== null) problems.push({ code: \"event_not_templatable\", sendId: send.id, message: templated });\n return problems;\n}\n\n/** What is wrong with a send's extra parameters, on the platform's terms. */\nfunction parameterProblems(send: ConversionSend): SendProblem[] {\n const parameters = cleanParameters(send.parameters);\n const names = Object.keys(parameters);\n const problems: SendProblem[] = [];\n const spec = SEND_PARAMETERS[send.platform];\n if (spec.takes && names.length > spec.max) {\n problems.push({\n code: \"too_many_parameters\",\n sendId: send.id,\n message: `${SEND_PLATFORM_SPECS[send.platform].title} takes at most ${spec.max} extra parameters; this send names ${names.length}.`,\n });\n }\n for (const name of names) {\n const problem = parameterNameProblem(send.platform, name);\n if (problem !== null) problems.push({ code: \"bad_parameter\", sendId: send.id, message: problem });\n }\n return problems;\n}\n\n/**\n * A send in one line, for a chip, a review and the CLI alike: the platform\n * and the event it sends as, when it has one.\n */\nexport function describeSend(send: Pick<ConversionSend, \"platform\" | \"event\">): string {\n const title = isSendPlatform(send.platform) ? SEND_PLATFORM_SPECS[send.platform].title : send.platform;\n return send.event === \"\" ? title : `${title} · ${send.event}`;\n}\n\n/**\n * What the platform is told the event is called, for one event: the name as\n * typed, or the template filled in from the event's own fields.\n *\n * Null when a template reads nothing on this event for **any** of its\n * mentions. An empty event name is not a name, and Meta takes it as a\n * *different* event rather than refusing it; `Purchase · {{properties.tier}}`\n * on an event with no tier would be \"Purchase ·\", which is the same phantom\n * event with text in front. So the upload is held back and the reason\n * recorded instead.\n *\n * Here rather than in the backend's body builders because both the builder\n * and the CLI's preview have to agree on it, and because it is a pure string\n * function with no Convex in it.\n */\nexport function resolveSendEventName(\n send: Pick<ConversionSend, \"platform\" | \"event\">,\n value: (path: string) => string | undefined,\n): string | null {\n const event = send.event.trim();\n if (!isSendPlatform(send.platform) || SEND_PLATFORM_SPECS[send.platform].eventKind === \"id\") {\n return event === \"\" ? null : event;\n }\n if (!sendEventIsTemplated(event)) return event === \"\" ? null : event;\n if (templateMentions(event).some((path) => (value(path) ?? \"\").trim() === \"\")) return null;\n const rendered = renderTemplate(event, value);\n return rendered === undefined ? null : rendered;\n}\n\n/**\n * What happened to a send's uploads over a window, read back from the\n * side-effect event log. `pending` is dispatched and not yet settled — the\n * difference between \"wait\" and \"act\".\n */\nexport interface SendOutcome {\n sendId: string;\n dispatched: number;\n succeeded: number;\n failed: number;\n pending: number;\n /** The most recent failure, in the platform's words already stripped of Baker's wrapper. Empty when clean. */\n lastError: string;\n /** ISO instant of the most recent upload. Empty when nothing was ever sent. */\n lastAt: string;\n /**\n * Events not uploaded because they carried nothing the platform can match a\n * person by (`sendMatch`). Absent on a mirror written before skips were recorded.\n */\n skipped?: number;\n /** Why the latest one was skipped, in plain words. */\n lastSkipReason?: string;\n}\n\n/** An outcome in one short line, for a chip. */\nexport function describeOutcome(outcome: SendOutcome | undefined): string {\n const skipped = outcome?.skipped ?? 0;\n if (outcome === undefined || (outcome.dispatched === 0 && skipped === 0)) return \"Nothing sent yet\";\n if (outcome.dispatched === 0) return `${skipped} skipped: not enough to match`;\n const parts = [`${outcome.succeeded} sent`];\n if (outcome.failed > 0) parts.push(`${outcome.failed} failed`);\n if (outcome.pending > 0) parts.push(`${outcome.pending} on the way`);\n if (skipped > 0) parts.push(`${skipped} skipped`);\n return parts.join(\" · \");\n}\n","/**\n * Finding people by what somebody already holds about them.\n *\n * A marketer reconciling Baker against their CRM has a column of emails, phone\n * numbers, HubSpot contact or deal ids, or the click ids an ad platform showed\n * them — never the browser cookie or the digest the store keys a person by. So\n * a search takes those, one per line, and turns each into the keys the identity\n * graph is walked from:\n *\n * - **An email or a phone is hashed here**, under every normalization version\n * the store has written (`identity/`), because the store holds digests and\n * the plaintext never reaches the query. A national phone number is hashed as\n * typed — v1 guesses no country, and neither does this.\n * - **A bare number is two guesses**: a CRM contact id (HubSpot's are digits)\n * and a phone typed without its prefix. A wrong guess costs nothing — a\n * digest nobody holds matches no row.\n * - **A deal id is not a key at all.** Deals name contacts; the caller resolves\n * it through the CRM into contact ids before asking the store.\n * - **Anything else is a key taken verbatim**: a browser id, one of the ad\n * click ids, a Meta cookie. A prefix (`gclid:…`) says which; without one every\n * verbatim kind is tried.\n *\n * Pure but for the hashing, so the parsing is a table a test can read.\n */\n\nimport { z } from \"zod\";\nimport { externalIdDigest, isSha256Hex, normalizeIdentity, sha256Hex } from \"../identity\";\nimport type { IdentityKey, IdentityKeyKind } from \"./identityLinks\";\n\n/** How many people one search may name. A reconciliation is a day's leads, not a customer base. */\nexport const PEOPLE_SEARCH_MAX_TERMS = 50;\n\n/** The key kinds a value is taken verbatim as: nothing hashes them. */\nconst VERBATIM_KINDS = [\n \"browser\",\n \"gclid\",\n \"wbraid\",\n \"gbraid\",\n \"dclid\",\n \"fbclid\",\n \"ttclid\",\n \"twclid\",\n \"li_fat_id\",\n \"msclkid\",\n \"rdt_cid\",\n \"epik\",\n \"sccid\",\n \"oppref\",\n \"fbp\",\n \"fbc\",\n] as const satisfies readonly IdentityKeyKind[];\ntype VerbatimKind = (typeof VERBATIM_KINDS)[number];\n\n/**\n * What a term was read as.\n *\n * - `number`: bare digits — a CRM contact id, or a phone with no prefix.\n * - `digest`: a SHA-256 somebody already computed — an email's, a phone's or a CRM id's.\n * - `deal`: a CRM deal id, resolved to its contacts by the caller.\n * - `key`: a value with no recognisable shape, tried as every verbatim kind.\n */\nexport type PersonTermKind = \"email\" | \"phone\" | \"external\" | \"number\" | \"digest\" | \"deal\" | \"key\" | VerbatimKind;\n\nexport type PersonTerm = {\n /** Exactly what was typed, trimmed: what the answer is reported against. */\n input: string;\n kind: PersonTermKind;\n /** The value with any prefix removed. */\n value: string;\n};\n\n/** Prefixes a term may name its kind with, beyond the kinds' own names. */\nconst PREFIXES: Readonly<Record<string, PersonTermKind>> = {\n email: \"email\",\n mail: \"email\",\n phone: \"phone\",\n tel: \"phone\",\n external: \"external\",\n contact: \"external\",\n hubspot: \"external\",\n id: \"external\",\n deal: \"deal\",\n digest: \"digest\",\n hash: \"digest\",\n browser: \"browser\",\n anon: \"browser\",\n ...Object.fromEntries(VERBATIM_KINDS.map((kind) => [kind, kind])),\n};\n\nconst TERM_SEPARATORS = /[\\n\\r\\t,;]+/;\nconst EMAIL_SHAPE = /^[^\\s@]+@[^\\s@]+$/;\nconst DIGITS = /^\\d+$/;\n/** `+34 600 111 222`, `0034-600-111-222`: a prefix, then digits and what people type between them. */\nconst INTERNATIONAL_PHONE = /^(\\+|00)[\\d\\s().-]{7,}$/;\n/** A Meta cookie: `fb.1.<timestamp>.<rest>`. */\nconst META_COOKIE = /^fb\\.\\d\\./;\n\n/** The kind a value with no prefix looks like. */\nfunction kindOf(value: string): PersonTermKind {\n if (EMAIL_SHAPE.test(value)) return \"email\";\n if (isSha256Hex(value)) return \"digest\";\n if (INTERNATIONAL_PHONE.test(value)) return \"phone\";\n if (DIGITS.test(value)) return \"number\";\n if (META_COOKIE.test(value)) return \"fbc\";\n return \"key\";\n}\n\nfunction readTerm(input: string): PersonTerm {\n const colon = input.indexOf(\":\");\n if (colon > 0) {\n const kind = PREFIXES[input.slice(0, colon).trim().toLowerCase()];\n const value = input.slice(colon + 1).trim();\n if (kind !== undefined && value !== \"\") return { input, kind, value };\n }\n return { input, kind: kindOf(input), value: input };\n}\n\n/**\n * What makes two terms the same person: the kind and the value as the store\n * reads it — an email or phone normalized, a CRM id or digest lower-cased — so\n * `Ana@Empresa.com` and `ana@empresa.com` are one line, not two.\n */\nfunction sameness(term: PersonTerm): string {\n switch (term.kind) {\n case \"email\":\n case \"phone\":\n return `${term.kind}|${normalizeIdentity({ kind: term.kind, value: term.value }, 1)?.value ?? term.value}`;\n case \"external\":\n case \"digest\":\n return `${term.kind}|${term.value.toLowerCase()}`;\n default:\n return `${term.kind}|${term.value}`;\n }\n}\n\n/**\n * Every term in a pasted list, in order, once each.\n *\n * Split on lines, tabs, commas and semicolons — what a spreadsheet column, a\n * CSV row and a hand-typed list are separated by — and never on spaces, which\n * a phone number is written with. `dropped` counts the terms past\n * {@link PEOPLE_SEARCH_MAX_TERMS}, so the caller can say the list was cut.\n */\nexport function parsePeopleQuery(text: string | readonly string[]): { terms: PersonTerm[]; dropped: number } {\n const inputs = (typeof text === \"string\" ? [text] : text).flatMap((part) => part.split(TERM_SEPARATORS));\n const seen = new Set<string>();\n const terms: PersonTerm[] = [];\n for (const raw of inputs) {\n const input = raw.trim();\n if (input === \"\") continue;\n const term = readTerm(input);\n const identity = sameness(term);\n if (seen.has(identity)) continue;\n seen.add(identity);\n terms.push(term);\n }\n return {\n terms: terms.slice(0, PEOPLE_SEARCH_MAX_TERMS),\n dropped: Math.max(0, terms.length - PEOPLE_SEARCH_MAX_TERMS),\n };\n}\n\n/** A contact's digests under every version that accepts it, once each: the store holds v1 and may hold v2. */\nasync function contactKeys(kind: \"email\" | \"phone\", value: string): Promise<IdentityKey[]> {\n const canonicals = new Set<string>();\n for (const version of [1, 2] as const) {\n const canonical = normalizeIdentity({ kind, value }, version);\n if (canonical !== null) canonicals.add(canonical.value);\n }\n return await Promise.all([...canonicals].map(async (canonical) => ({ kind, value: await sha256Hex(canonical) })));\n}\n\nasync function externalKeys(value: string): Promise<IdentityKey[]> {\n const digest = await externalIdDigest(value);\n return digest === null ? [] : [{ kind: \"external\", value: digest }];\n}\n\n/**\n * The keys one term is looked up by, in the form the identity graph stores\n * them. Empty for a deal, which names no person until the CRM says whose it is.\n */\nexport async function personTermKeys(term: PersonTerm): Promise<IdentityKey[]> {\n const value = term.value.trim();\n switch (term.kind) {\n case \"email\":\n case \"phone\":\n return await contactKeys(term.kind, value);\n case \"external\":\n return await externalKeys(value);\n case \"number\":\n return [...(await externalKeys(value)), ...(await contactKeys(\"phone\", value))];\n case \"digest\": {\n const digest = value.toLowerCase();\n return ([\"email\", \"phone\", \"external\"] as const).map((kind) => ({ kind, value: digest }));\n }\n case \"deal\":\n return [];\n case \"key\":\n return VERBATIM_KINDS.map((kind) => ({ kind, value }));\n default:\n return [{ kind: term.kind, value }];\n }\n}\n\n/**\n * The seed list the store is asked with: `term|kind|value`, where `term` is the\n * position in `keysPerTerm` — the line of the search each key answers.\n *\n * A value holding a comma is dropped: the parameter travels comma-separated, so\n * it would arrive as two meaningless seeds. No digest or id the store keys on\n * contains one; a `|` inside a value is kept, since the store splits on the\n * first two only.\n */\nexport function peopleSeeds(keysPerTerm: readonly (readonly IdentityKey[])[]): string[] {\n return keysPerTerm.flatMap((keys, term) =>\n keys.filter((key) => !key.value.includes(\",\")).map((key) => `${term}|${key.kind}|${key.value}`),\n );\n}\n\n// ============================================================================\n// The wire\n// ============================================================================\n\n/** One line of a search, as long as anything a person is keyed by can be. */\nconst personTermInputSchema = z.string().trim().min(1).max(320);\n\n/** The search, as sent: the lines the reader typed or pasted, unparsed. */\nexport const peopleSearchInputSchema = z\n .array(personTermInputSchema)\n .min(1)\n .max(PEOPLE_SEARCH_MAX_TERMS * 4);\n\n/** `POST /api/analytics/people`: what Baker saw of each of these people. */\nexport const peopleLookupRequestSchema = z.object({ people: peopleSearchInputSchema });\nexport type PeopleLookupRequest = z.infer<typeof peopleLookupRequestSchema>;\n\n/**\n * How one line of a search was read, returned beside every answer so the\n * reader sees what Baker looked for — \"90001234567 → a HubSpot contact id, or\n * a phone\" — rather than a result they cannot connect to what they typed.\n */\nexport const peopleSearchTermSchema = z.object({\n /** Position in the search; what `personTerms` on a feed row points at. */\n term: z.number().int(),\n input: z.string(),\n kind: z.string(),\n /**\n * Why this line could not be looked up as asked, in the reader's words — a\n * deal HubSpot does not know, HubSpot not connected. Empty when it was.\n */\n problem: z.string().default(\"\"),\n /** A deal's contacts, as found in the CRM. Empty for every other kind. */\n contacts: z.array(z.string()).default([]),\n});\nexport type PeopleSearchTerm = z.infer<typeof peopleSearchTermSchema>;\n\n/** What Baker saw of one person, all time. Every count is zero for a line that matched nobody. */\nexport const peopleLookupRowSchema = peopleSearchTermSchema.extend({\n found: z.boolean(),\n events: z.number(),\n firstSeen: z.string(),\n lastSeen: z.string(),\n pageViews: z.number(),\n landings: z.number(),\n visits: z.number(),\n formSubmits: z.number(),\n /** What the company's own systems reported about them, by event name — CRM stages, orders. */\n systemEvents: z.array(z.string()),\n pageEvents: z.array(z.string()),\n uploads: z.number(),\n uploadedTo: z.array(z.string()),\n googleClick: z.boolean(),\n metaClick: z.boolean(),\n /** `utm_source`, else the referrer, of the first visit Baker saw. Empty when it saw none. */\n firstSource: z.string(),\n /** Every kind of key they are linked by: `email`, `phone`, `external`, `browser`, a click id. */\n knownBy: z.array(z.string()),\n});\nexport type PeopleLookupRow = z.infer<typeof peopleLookupRowSchema>;\n\nexport const peopleLookupDataSchema = z.object({\n people: z.array(peopleLookupRowSchema),\n /** Lines past the limit, not looked up. */\n dropped: z.number(),\n});\nexport type PeopleLookupData = z.infer<typeof peopleLookupDataSchema>;\n","/**\n * The request and response `baker analytics sends` and its Convex route both\n * speak — and the shape the dashboard's Sources page writes through its own\n * door, so one send is one row whoever set it.\n *\n * `set` names the sends to add or change and `remove` names the ids to drop;\n * neither is the whole list. Handing the whole list to a writer means a slow\n * read followed by a save erases everything saved in between, which is the\n * failure the ad-parameter mapping had to be rewritten to avoid.\n */\n\nimport { z } from \"zod\";\nimport { MAX_SEND_EVENT_KEY, MAX_SEND_ID, SEND_ORIGINS, SEND_PLATFORMS } from \"./sends\";\n\nexport const conversionSendSchema = z.object({\n /** Stable within the company. `s1`, `s2` — something a person can type. */\n id: z.string().min(1).max(MAX_SEND_ID),\n /** The event it sends, by key — `page:<name>`, from `events[]` of a plain read. */\n eventKey: z.string().min(1).max(MAX_SEND_EVENT_KEY),\n /** `website` for an event Baker's pixel raised on the company's own site; absent (or `server`) for the API and integrations. */\n origin: z.enum(SEND_ORIGINS).optional(),\n platform: z.enum(SEND_PLATFORMS),\n /** The tag's id on Tracking. Must be one of this company's tags of the platform's type, with its server credential set. */\n tagId: z.string().min(1).max(120),\n /** A Meta or GA4 event name; a Google Ads conversion action id; a LinkedIn conversion rule id; an X event tag id. */\n event: z.string().min(1).max(255),\n /** The LinkedIn ad account the conversion rule belongs to. */\n account: z.string().max(120).optional(),\n /** Extra platform parameters: name → template over the event. See `SEND_PARAMETERS[platform]`. */\n parameters: z.record(z.string().min(1).max(64), z.string().min(1).max(500)).optional(),\n});\nexport type ConversionSendWire = z.infer<typeof conversionSendSchema>;\n\n/** One upload of one send: which event, when, and what the platform said. */\nexport const sendUploadSchema = z.object({\n sendId: z.string(),\n eventUid: z.string(),\n /** The event's own name, as it was counted. Empty for a Form's submit, which has none. */\n eventName: z.string(),\n /** The event's key, as `conversions` spells it — what names a Form's submit. */\n eventKey: z.string(),\n /** The Form it happened in, for a `form:` or `submit:` key. */\n flowSlug: z.string(),\n /** ISO instant of the event itself. */\n eventAt: z.string(),\n /** ISO instant of the upload. */\n dispatchedAt: z.string(),\n /** `skipped`: never uploaded, because the event carried nothing the platform can match a person by. */\n outcome: z.enum([\"success\", \"failed\", \"pending\", \"skipped\"]),\n /** The platform's refusal in its own words, or why it was skipped. Empty otherwise. */\n error: z.string(),\n sessionId: z.string(),\n anonId: z.string(),\n});\nexport type SendUpload = z.infer<typeof sendUploadSchema>;\n\n/** How many sends one request may carry. Far above what any company needs; catches a loop. */\nconst MAX_SENDS_PER_REQUEST = 40;\n\nexport const sendsRequestSchema = z.object({\n set: z.array(conversionSendSchema).max(MAX_SENDS_PER_REQUEST).optional(),\n remove: z.array(z.string().min(1).max(MAX_SEND_ID)).max(MAX_SENDS_PER_REQUEST).optional(),\n /** How many days the outcomes beside each send cover. Default 30. */\n days: z.number().int().min(1).max(400).optional(),\n /** List one send's uploads over the window, newest first, by the send's id. */\n uploads: z.string().min(1).max(MAX_SEND_ID).optional(),\n /** With `uploads`: only the ones the platform refused. */\n failedOnly: z.boolean().optional(),\n /** With `uploads`: only the events skipped, with what each was missing. */\n skippedOnly: z.boolean().optional(),\n /** Also say when each live send was set up, and when an integration named after its event was removed. */\n history: z.boolean().optional(),\n});\nexport type SendsRequest = z.infer<typeof sendsRequestSchema>;\n\nexport const sendOutcomeSchema = z.object({\n sendId: z.string(),\n dispatched: z.number(),\n succeeded: z.number(),\n failed: z.number(),\n pending: z.number(),\n lastError: z.string(),\n lastAt: z.string(),\n /** Events not uploaded: they carried nothing this platform can match a person by. */\n skipped: z.number().optional(),\n lastSkipReason: z.string().optional(),\n});\n\n/**\n * What comes back: the sends in effect, with what each did; the sends this\n * chat has staged on top of them, when any; and which tags can send.\n */\nexport const sendsResponseSchema = z.object({\n ok: z.literal(true),\n data: z.object({\n /** The company's sends, as they are live. */\n sends: z.array(conversionSendSchema.extend({ outcome: sendOutcomeSchema.optional() })),\n /** The ops this chat has staged and not yet published. Absent outside a chat. */\n staged: z\n .object({\n set: z.array(conversionSendSchema),\n remove: z.array(z.string()),\n })\n .optional(),\n /** The platforms, what `event` means on each, what a tag needs on Tracking to send, and the tags that can. */\n platforms: z.array(\n z.object({\n platform: z.string(),\n title: z.string(),\n eventMeans: z.string(),\n setupHint: z.string(),\n tags: z.array(z.object({ tagId: z.string(), label: z.string() })),\n /**\n * The conversions the platform already holds, by the id a send's `event`\n * takes and the name the person gave them. Google Ads only: its `event`\n * is an id Google issued, and an id the account does not hold is\n * accepted and files against nothing. `label` is the conversion label\n * the account's tag setup shows (`AW-…/<label>`) — how the person knows it.\n */\n conversionActions: z\n .array(\n z.object({ id: z.string(), name: z.string(), primaryForGoal: z.boolean(), label: z.string().optional() }),\n )\n .optional(),\n }),\n ),\n /** Every field a send forwards. Nothing to map. */\n carries: z.array(z.object({ name: z.string(), label: z.string(), group: z.string(), hashed: z.boolean() })),\n /**\n * The events this company's systems raise, so a send can be pointed at\n * one: every Source by the key it raises, and every named event the store\n * saw arrive from a system.\n */\n events: z.array(\n z.object({\n eventKey: z.string(),\n /** The event's own name. */\n name: z.string(),\n /** `source` for a Source on Sources, `systems` for a name the API posted, `both` when a page sends it too, `website` for a name Baker's pixel sent — a send on it carries `origin: \"website\"`. */\n origin: z.string(),\n /**\n * The last 30 days of it: how many arrived, and how many each platform\n * could match a person on. The rest would be skipped by a send there.\n */\n match: z.object({ events: z.number(), platforms: z.record(z.string(), z.number()) }).optional(),\n }),\n ),\n /** What each platform does with extra parameters, and the names it knows. */\n parameters: z.array(\n z.object({\n platform: z.string(),\n takes: z.boolean(),\n where: z.string(),\n known: z.array(z.string()),\n max: z.number(),\n }),\n ),\n /** The event fields a parameter template may read, beside `properties.<key>`. */\n templateFields: z.array(z.object({ name: z.string(), label: z.string() })),\n /** One send's uploads, when `uploads` named one. */\n uploads: z.object({ sendId: z.string(), rows: z.array(sendUploadSchema) }).optional(),\n /**\n * When `history` was asked: when each live send was set up (epoch ms), and\n * when an integration named after its event was last removed, if one was —\n * what tells a send left on an event nothing raises apart from a mystery.\n */\n history: z\n .array(z.object({ sendId: z.string(), createdAt: z.number(), integrationRemovedAt: z.number().optional() }))\n .optional(),\n }),\n hints: z.array(z.string()).optional(),\n});\nexport type SendsResponse = z.infer<typeof sendsResponseSchema>;\n","/**\n * Contract for `/api/analytics/draft-op/remove` — `baker analytics unstage`.\n *\n * Takes back Measure changes this chat staged and has not published: a\n * campaign-parameter answer (`mapping`, ref `<platform>/<name>`), a conversion\n * (`conversion`, ref the event key), a send (`send`, ref the send id) or a\n * source (`source`, ref the source id or `src_temp_*`). Never touches anything\n * already live — to change that, stage the change instead.\n */\nimport { z } from \"zod\";\n\nexport const ANALYTICS_UNSTAGE_KINDS = [\"mapping\", \"conversion\", \"send\", \"source\"] as const;\nexport const analyticsUnstageKindSchema = z.enum(ANALYTICS_UNSTAGE_KINDS);\nexport type AnalyticsUnstageKind = z.infer<typeof analyticsUnstageKindSchema>;\n\nexport const analyticsUnstageRequestSchema = z\n .object({\n chatId: z.string().min(1),\n kind: analyticsUnstageKindSchema.optional(),\n ref: z.string().min(1).optional(),\n /** Everything staged — of `kind` when given, of every kind otherwise. */\n all: z.boolean().optional(),\n })\n .refine((body) => (body.all === true ? body.ref === undefined : body.kind !== undefined && body.ref !== undefined), {\n message:\n \"Pass --kind with --ref to take back one staged change, or --all (optionally with --kind) to take back everything staged.\",\n });\nexport type AnalyticsUnstageRequest = z.infer<typeof analyticsUnstageRequestSchema>;\n\nexport const analyticsUnstageResponseSchema = z.object({\n ok: z.literal(true),\n data: z.object({\n /** How many staged changes were taken back, per kind touched. */\n removed: z.partialRecord(analyticsUnstageKindSchema, z.number().int().min(0)),\n }),\n hints: z.array(z.string()).optional(),\n});\nexport type AnalyticsUnstageResponse = z.infer<typeof analyticsUnstageResponseSchema>;\n","/**\n * Wire contract for `/api/analytics/*` — reading this company's own web\n * analytics.\n *\n * Unlike `/api/ga4/*`, there is no OAuth and no external property to resolve:\n * the data is Baker's, keyed by the company the API key already identifies. A\n * freshly published landing has numbers immediately, and a client who never\n * connected Google Analytics still has a funnel.\n *\n * Rows live in the `web_analytics_events` Tinybird datasource and are read\n * through its published endpoints, never through hand-built SQL — so there is\n * no query string to escape and no dialect to guess at. Every row is stored,\n * so every count here is exact rather than an estimate.\n */\nimport { z } from \"zod\";\nimport { AD_PARAM_ROLES, AD_PLATFORMS } from \"./adParams\";\n\n/**\n * What the raw feed can be narrowed to about what was sent on: every delivery,\n * the refused ones, or a send's events that were skipped because they carried\n * nothing the platform can match a person by.\n */\nexport const SEND_FILTERS = [\"any\", \"failed\", \"skipped\"] as const;\nexport type SendFilter = (typeof SEND_FILTERS)[number];\n\nimport { CONVERSION_COUNT_MODES, MAX_CONVERSION_NAME } from \"./conversions\";\nimport { peopleSearchInputSchema, peopleSearchTermSchema } from \"./peopleSearch\";\nimport { boundSubmissionAnswers, cutToLength } from \"./submissionAnswers\";\n\n/** The closed ad-dimension vocabulary, as a request may name it. */\nexport const adParamRoleSchema = z.enum(AD_PARAM_ROLES);\n\n/** Datasource TTL. Long enough to compare a month against the same month last year. */\nexport const ANALYTICS_RETENTION_DAYS = 400;\n\n/** Default lookback when a request names no window. */\nexport const ANALYTICS_DEFAULT_DAYS = 30;\n\n/**\n * Below this many sessions the derived rates are noise, and the agent is told\n * to say so rather than present a conversion rate off nine visits.\n */\nexport const ANALYTICS_LOW_SAMPLE_SESSIONS = 100;\n\n/**\n * The row a conversion the attribution model could not credit is filed under,\n * in the spelling the analytics store emits.\n *\n * A sentinel and not a blank, because blank already means something: a visit\n * that arrived with no referrer, which is *direct*. Left blank, every\n * conversion the model declined to credit was displayed on the `direct` row —\n * measured on seeded data, `direct` read 787 visits and 101 conversions under a\n * model whose whole premise is that direct is never credited.\n *\n * It appears in three places that must agree — the SQL that emits it, the\n * shaper that must not fold it into `direct`, and the tables that label it —\n * so it is one exported constant rather than three string literals. Kept in\n * step with `ATTRIBUTION_UNATTRIBUTED` in `tinybird/definitions.ts`, which\n * cannot import from here, by `attributionModel.parity.test.ts`.\n */\nexport const ATTRIBUTION_UNATTRIBUTED = \"(unattributed)\";\n\n/** Rows per breakdown, compact vs `--full`. */\nexport const ANALYTICS_COMPACT_BREAKDOWN_LIMIT = 5;\nexport const ANALYTICS_FULL_BREAKDOWN_LIMIT = 100;\n\n/**\n * The bounds on a page of a list report.\n *\n * The size is capped because the row is the unit of work on the other side: a\n * visitor row carries its acquisition, its geography and its device, and two\n * hundred of them is already a screenful many times over. The page number is\n * capped for a duller reason — `OFFSET` in ClickHouse is read-and-discard, so\n * page 9,000 costs the scan of the 8,999 before it, and nobody navigating a\n * list by hand ever asks for it.\n */\nexport const ANALYTICS_MAX_PAGE_SIZE = 200;\nexport const ANALYTICS_MAX_PAGE = 500;\n\n/** What a screen asks for when it is showing a list a person scrolls. */\nexport const ANALYTICS_DEFAULT_PAGE_SIZE = 50;\n\n/**\n * Whether the platform can resolve this zone name.\n *\n * Asked of `Intl` rather than checked against a list we would then have to\n * maintain: the zone database ships with the runtime and gains entries without\n * us. Anything it throws on is a name no store downstream will accept either.\n */\nfunction isResolvableTimeZone(value: string): boolean {\n try {\n new Intl.DateTimeFormat(\"en-US\", { timeZone: value });\n return true;\n } catch {\n return false;\n }\n}\n\nexport const analyticsPresetSchema = z.enum([\n /** Everything that matters in one call — the default entry point. */\n \"overview\",\n /** Traffic shape: trend, sources, geo, device. */\n \"traffic\",\n /** Per-step drop-off for the company's Forms. */\n \"funnel\",\n /**\n * One Form in depth: its steps, where people went between them, and how many\n * converted at the node that actually produced the conversion.\n *\n * Distinct from `funnel`, which is a per-step table across every Form.\n * A Form with branches is not a table — a visitor who leaves step two for\n * the booking branch and a visitor who leaves it for nothing are the same\n * row there, and they are the whole question. `paths` returns the edges\n * between steps, which is what a Sankey draws.\n */\n \"flow\",\n /** One page in detail. */\n \"page\",\n /**\n * Every Form trigger that fired, and how much of it produced a side effect.\n *\n * Separate from `funnel` because it answers a different question: the funnel\n * measures the steps a visitor walked, this measures the things they *did* —\n * clicking the link that redirects them out, an embedded checkout posting\n * back — most of which have nothing configured against them and none of\n * which appear as a step.\n */\n \"triggers\",\n /** What each published build did, so a change can be attributed to a publish. */\n \"releases\",\n /**\n * One ad dimension broken down, plus what is actually on the URLs.\n *\n * Answers the question the ad platform cannot: not how many clicks a keyword\n * bought, but whether the people it brought read the page, filled the Form\n * or left immediately. Pair it with `adRole` to choose the dimension; the\n * parameter inventory comes back either way, because on a half-tagged\n * account \"there is no keyword on 80% of your paid traffic\" is the finding.\n */\n \"ads\",\n /**\n * Every landing side by side: traffic, entrances, conversion rate, and a\n * sparkline each, joined to the landing's real name and tags.\n *\n * The list view. `page` answers one page in depth and needs to be told which\n * one; this answers \"which of my pages is working\" without knowing the\n * answer first, and is what a landings screen renders.\n */\n \"landings\",\n /**\n * The same numbers grouped by the tag a landing is filed under.\n *\n * The unit a campaign is actually run at. Nobody asks how `/pricing-b` did;\n * they ask how the September push did, and September was six landings.\n */\n \"tags\",\n /**\n * Events a page defined for itself — a section seen, a video played, a\n * pricing toggle flipped.\n *\n * Separate from every other preset because the vocabulary is the page's\n * rather than the product's. Mixing them into `traffic` would produce a list\n * half of which the reader has to already know is bespoke.\n */\n \"events\",\n /**\n * Browser, OS, engine and viewport of real visitors.\n *\n * `traffic` reports the device family, which is enough to notice that mobile\n * converts worse and not enough to do anything about it. This is narrow\n * enough to reproduce a bug on.\n */\n \"devices\",\n /**\n * Paid acquisition, grouped the way money is spent.\n *\n * Distinct from `traffic` in two ways that matter. It groups by SESSION, so a\n * visitor counts once for the campaign they arrived on rather than once per\n * page they read. And it reports leads that were actually DELIVERED, not\n * `form_submit` events — a campaign whose forms all submit and whose CRM is\n * rejecting every one of them reads as converting perfectly on every other\n * preset here, and reads as zero delivered on this one.\n */\n \"paid\",\n /**\n * Where each submission actually went, and what each destination made of it.\n *\n * The preset for \"I filled in the form, where is my lead?\". One row per\n * submission listing every destination, its outcome, and the id the\n * destination gave the record it created — which is what turns \"it says\n * success\" into something a client can open and check.\n */\n \"delivery\",\n /**\n * Every attempt anybody made at a Form, delivered or not.\n *\n * The wider companion to `delivery`, and the one that answers \"I filled in\n * my own form, where is it?\" honestly. `delivery` starts from the dispatch,\n * so a Form wired to no destination produces no row in it at all — which is\n * correct for \"what did we hand to their CRM\" and reads as data loss to\n * everybody else. The grain here is the attempt: one visitor, one Form, one\n * visit, whether they finished, gave up halfway, or completed a Form that\n * sends nowhere.\n */\n \"submissions\",\n /**\n * Where the people who convert actually are, at whatever resolution is asked\n * for.\n *\n * `traffic` breaks acquisition down by country, which is the right grain for\n * \"which market is this campaign working in\" and the wrong one for every\n * decision a local advertiser makes. A dentist buying a five-mile radius or a\n * franchise choosing where to open is asking about postcodes, which is also\n * how the ad platforms sell that targeting.\n */\n \"geo\",\n /**\n * Which machines are fetching this site.\n *\n * Every other preset here excludes them, which is correct — a crawler is not\n * an audience — and a verify check now enforces that rather than trusting it. The rows are kept, though, and this is what\n * they are kept for: whether Google is still crawling a page that was\n * republished, whether Meta fetched the link preview before spend went\n * behind the URL, and how much of the traffic is now AI answer engines.\n */\n \"bots\",\n /**\n * Which companies are visiting — named by the visitor's own network at the\n * edge, never a person, and only for visitors who accepted marketing on a\n * company that switched it on. The website half of an account-based report.\n */\n \"companies\",\n /**\n * One visit, replayed end to end — and every visit the same person made\n * before it.\n *\n * The only preset that answers about a *person* rather than a population.\n * Every other report here aggregates, which is the right shape for \"is the\n * campaign working\" and useless for the question a marketer asks the moment\n * a lead arrives: who was this, where did they come from, what did they read,\n * and did their details reach the CRM. Answering that from an aggregate is\n * impossible by construction.\n *\n * Needs `sessionId`. Give `identityHash` as well and it also returns the\n * person's earlier visits, including the anonymous ones from before they ever\n * identified themselves — which is the difference between \"converted on the\n * first visit\" and \"converted on the fourth\", two very different businesses.\n */\n \"session\",\n /**\n * Everybody who visited, one row per person — converted or not.\n *\n * The unit here is a *person*, which no other preset has. `traffic` counts\n * visitors and cannot name one; `delivery` names the ones who became a lead\n * and is silent about the ninety-odd percent who did not; `session` describes\n * one visit and has to be told which. This is the list those are read from.\n *\n * Grouped by browser cookie, so somebody who came back on Wednesday is one\n * row and not two. Pass `onlyConverted` for the converted-only cut, and\n * `landingId` to ask who visited one page.\n *\n * Not offered as a `baker analytics` preset, for the same reason `realtime`\n * is not: it answers about individuals, and an agent writing a client report\n * should be reading the population.\n */\n \"people\",\n /**\n * Who is on the site right now.\n *\n * The only preset measured from `now()` rather than over a date window, and\n * the only one meant to be polled. Kept out of `overview` for exactly that\n * reason: a live counter has to refresh every few seconds and the rest of the\n * page must not refetch with it.\n *\n * Deliberately not offered as a `baker analytics` preset. The sandbox agent\n * works asynchronously — by the time it reads an answer, \"the last five\n * minutes\" names a window that has passed — so the only thing listing it\n * would do is add a choice the agent can never usefully pick to the menu it\n * reads before picking one.\n */\n \"realtime\",\n /**\n * What this company counts as a conversion, and everything it could count.\n *\n * The two halves of one question, which is why they are one preset: the list\n * of definitions with what each produced, and the catalogue of every event\n * these pages actually emitted with whether it is already counted. Reading\n * the first without the second is how the previous design failed — a\n * marketer could see that nothing was counted and had no way to find out what\n * was available to count.\n */\n \"conversions\",\n /**\n * Every event as it was stored, newest first — the one preset that does not\n * reduce.\n *\n * Deliberately not offered as a `baker analytics` preset. It is the raw feed\n * a person watches while wiring something up, and an agent asked to describe\n * an account wants the counts rather than the tape.\n */\n \"stream\",\n]);\nexport type AnalyticsPreset = z.infer<typeof analyticsPresetSchema>;\n\nexport const ANALYTICS_PRESETS = analyticsPresetSchema.options;\n\n/**\n * The four ways an event can reach Baker, as a reader picks between them.\n *\n * Two columns answer two different questions, and one filter used to read only\n * the first: `source` says **who wrote the row** (`client` for Baker's script\n * in a browser, `edge` for our Worker as it serves the HTML, `ingest` for a\n * third party's server, `server` for Baker's own dispatch), and `deployment`\n * says **where the page lives** (`baker`, `external`, `export`). The filter\n * tested `source = 'ingest'` and called everything else \"the browser\", which\n * was true only while every browser was on a page Baker published. The website\n * tag ended that: a client's own site now reports through the same `client`\n * collector as a landing does, and the two were indistinguishable on every\n * screen.\n *\n * Three values rather than two filters, because the two questions are not\n * independent. An ingest row is stamped `deployment: \"external\"` on purpose —\n * no edge row can exist for it — so a separate \"which site\" control would put\n * a server event under the client's website, and \"server + Baker's pages\"\n * would be a combination that selects nothing. These three are mutually\n * exclusive and exhaustive, so every row has exactly one and no choice is\n * empty by construction.\n *\n * ## Why `website` is a positive test\n *\n * `deployment` was added after the corpus existed and carries no default, so\n * every row written before it reads `''`. `website` therefore asks whether the\n * deployment **is** off-Baker rather than whether it is not `baker` — the\n * second reading would sweep the entire pre-column corpus into the client's\n * website and make the one filter built to isolate a handful of rows return\n * millions. Legacy rows land in `pages`, which is the same direction\n * `readOrigin` takes for the same reason.\n *\n * `export` travels with `external`: a downloaded build runs on the client's own\n * site too, and the distinction between the two is how the key was minted, not\n * where the visitor was.\n */\n/*\n * ## Four, since the API and an integration stopped sharing a door\n *\n * `server` was every `ingest` row: an event the client's server posted AND\n * one Baker read from HubSpot. On screen that was \"your systems\", and a\n * marketer asked what on earth their system was — they think in four places\n * an event comes from: Baker pages, the pixel, the API, and an integration\n * like HubSpot. So `server` is now the API alone — ingest rows carrying no\n * source name, or the server door's own (`SERVER_SOURCE_NAME` in\n * `@baker/api/event-sources`) — and `integrations` is every ingest row a named\n * source sent. Still exclusive and exhaustive.\n */\nexport const ANALYTICS_EVENT_DOORS = [\"pages\", \"website\", \"server\", \"integrations\"] as const;\nexport type AnalyticsEventDoor = (typeof ANALYTICS_EVENT_DOORS)[number];\n\n// ---------------------------------------------------------------------------\n// Request\n// ---------------------------------------------------------------------------\n\nexport const analyticsQueryRequestSchema = z\n .object({\n preset: analyticsPresetSchema,\n /** Lookback in days. Ignored when `startDate` is given. */\n days: z.coerce.number().int().positive().max(400).optional(),\n startDate: z.iso.date().optional(),\n endDate: z.iso.date().optional(),\n /** Required by the `page` preset; ignored by the others. */\n path: z.string().max(1024).optional(),\n /** Narrows `funnel` to one Form. Omitted means every Form. */\n flowSlug: z.string().max(120).optional(),\n /**\n * How far down the `geo` preset looks. Ignored by every other preset.\n *\n * A closed set rather than a column name, because it names a column and a\n * column name taken from a request must never reach the SQL.\n */\n grain: z.enum([\"country\", \"region\", \"city\", \"postal_code\", \"metro_code\"]).optional(),\n /**\n * Narrows `geo` to one country before going finer.\n *\n * Not optional decoration below country level: postcodes collide across\n * markets, so a bare `28001` is a Madrid postcode and a US ZIP at once.\n */\n country: z.string().max(8).optional(),\n /**\n * Which ad dimension to break down by, or to narrow every other report to.\n *\n * A role from the closed vocabulary — `keyword`, `match_type`,\n * `campaign_id` — never a raw parameter name. The company's own spellings\n * are resolved from its mapping server-side, so a caller asking for\n * \"keyword\" gets `kw` rows from before a rename as well as `keyword` rows\n * after it, and no request can name a column.\n */\n adRole: adParamRoleSchema.optional(),\n /**\n * Narrows to one value of `adRole`. Requires `adRole`.\n *\n * An empty string with `adRole` set is a real query — \"arrived carrying\n * none of these\" — which is how you size the untagged half of an account.\n * Omit both to leave the report unnarrowed.\n */\n adValue: z.string().max(255).optional(),\n /**\n * Read this report in one ad platform's vocabulary, and from its traffic.\n *\n * Two effects, and they belong together. A role resolves against the\n * mapping in force for that platform — so `cid` is TikTok's ad here and\n * nobody's campaign — and the report narrows to the sessions that arrived\n * from it, matched on the click id the platform stamps and, failing that,\n * on `utm_source`. Asking a platform's question of another platform's\n * traffic is how a keyword report ends up counting Meta visits that could\n * not have carried a keyword.\n *\n * Omit it for the union across every platform, which is the honest\n * superset and the right default for \"how is paid doing\".\n */\n adPlatform: z.enum(AD_PLATFORMS).optional(),\n /**\n * Read only what came in by one door. See {@link ANALYTICS_EVENT_DOORS}.\n *\n * Omit it for all of it, which is the default and the honest superset.\n * Only the reads where the split is a question somebody asks honour it —\n * the totals, the trend, the two page-event reads, the conversion\n * breakdown and the events catalogue. A session-scoped report cannot: an\n * ingest row carries no `session_id`, deliberately, so there is no visit\n * for it to be counted in.\n */\n eventOrigin: z.enum(ANALYTICS_EVENT_DOORS).optional(),\n /**\n * `stream` only: keep just one kind of event, named by the key\n * `conversions` hands back in its catalogue.\n *\n * The key rather than `eventName`, because three of the four families have\n * no name of their own — a Form's trigger, a submit, an outbound click —\n * and a filter that could only name the fourth would be a filter that hides\n * most of the feed from itself.\n */\n eventKey: z.string().optional(),\n /**\n * `stream` only: keep just what one source sent, by the source's name —\n * the `baker_source` every event from an address or a HubSpot read\n * carries. Names come from `baker analytics sources`.\n */\n sourceName: z.string().optional(),\n /**\n * `stream` only: keep just the uploads a conversion's sends made —\n * `any` for every upload, `failed` for the ones the platform refused.\n */\n sends: z.enum(SEND_FILTERS).optional(),\n /** `stream` only: keep just the uploads one send made, by its id (`s1`). */\n sendId: z.string().optional(),\n /** `stream` only: keep what went to one destination (`meta`, `httpWebhook`) — a Form's deliveries and sends' uploads alike. */\n sentTo: z.string().max(64).optional(),\n /** `stream` only: one event by its uid, and every upload made for it — with the platform's verdict on each. */\n eventUid: z.string().optional(),\n /**\n * `stream` only: list just the rows taken for machine traffic — a headless\n * browser, a datacenter — instead of the human feed. How an agent sees its\n * own test visit, which every report rightly leaves out.\n */\n machines: z.boolean().optional(),\n /** Return the long breakdowns and the per-day trend. */\n full: z.boolean().optional(),\n /**\n * IANA zone the day boundaries are drawn in. Defaults to UTC.\n *\n * Not cosmetic. Bucketing a Madrid client's traffic by UTC days moves two\n * hours of every evening into the following day, so \"Tuesday\" is not\n * Tuesday and a day-of-week comparison is wrong by however much traffic\n * arrives after 22:00 — which on a consumer landing is a lot of it.\n *\n * Validated against the platform's own zone database rather than an enum,\n * because the list is the platform's and not ours — but validated it must\n * be. The zone is substituted into the query as a string literal and\n * ClickHouse rejects one it cannot resolve, so an unrecognised zone used to\n * fail the whole report as an INTERNAL_ERROR with nothing naming the cause.\n *\n * Rejected rather than quietly defaulted to UTC. A silent fallback returns\n * a report that looks like the one that was asked for and is bucketed on\n * different days, which is the exact error this parameter exists to\n * prevent — better to say the zone is unknown than to answer for the wrong\n * one.\n */\n timezone: z\n .string()\n .max(64)\n .refine(isResolvableTimeZone, { message: \"Unknown timezone — pass an IANA zone such as Europe/Madrid\" })\n .optional(),\n /**\n * How wide one point of a series is.\n *\n * Chosen by the caller rather than derived from the window, because the\n * caller knows what it is drawing: the same 30 days is 30 points on a\n * dashboard and 4 on a slide. A 24-hour window at `day` granularity is one\n * bar, which is the shape this exists to prevent.\n */\n granularity: z.enum([\"hour\", \"day\", \"week\", \"month\"]).optional(),\n /**\n * Narrow to one landing by its `internalId`.\n *\n * The reference where `path` is a description: it survives the page being\n * renamed or moved, which is exactly when a before-and-after comparison\n * matters most and is exactly when `path` stops matching.\n */\n landingId: z.string().max(120).optional(),\n /** Narrows `landings` to the landings carrying one tag. */\n tag: z.string().max(32).optional(),\n /**\n * Which visit the `session` preset replays.\n *\n * Ignored by every other preset. Not a filter on the aggregates: narrowing\n * a population report to one visitor produces numbers that are individually\n * true and collectively meaningless.\n */\n sessionId: z.string().max(120).optional(),\n /**\n * Whose earlier visits to resolve, as a lowercase hex digest.\n *\n * A digest and never an address: the identity a page sends is hashed in the\n * browser, and nothing downstream of that ever holds the original.\n */\n identityHash: z.string().max(128).optional(),\n /** Which page-defined event the `events` preset breaks down. */\n eventName: z.string().max(120).optional(),\n /** Which of that event's own properties to break it down by. */\n property: z.string().max(64).optional(),\n /**\n * Narrow the breakdown to the rows carrying one property value.\n *\n * Not the same as breaking down by a second key. Video is the case that\n * demanded it: every milestone carries `autoplay`, and the retention curve\n * of the videos a visitor chose to play is a different curve from the one a\n * muted hero produces by playing to nobody. Grouping gives the two totals;\n * only filtering gives either curve.\n */\n where: z.object({ property: z.string().max(64), value: z.string().max(120) }).optional(),\n /**\n * An ordered funnel over page-defined event names.\n *\n * Data rather than schema, deliberately: defining a new funnel is a\n * parameter on a request, not a migration and a deploy. The order is the\n * caller's and is enforced — a session counts at step N only if it produced\n * step N after step N-1, or \"saw pricing then clicked demo\" and the reverse\n * would be the same number.\n */\n steps: z.array(z.string().max(120)).max(10).optional(),\n /**\n * Also return the immediately preceding window of the same length.\n *\n * The first question anyone asks of a number is \"compared to what\". Served\n * as a second full totals read rather than a delta computed in SQL, so the\n * comparison window is described in the response and a caller can render\n * either side of it.\n */\n compare: z.boolean().optional(),\n /** `devices` only: which facet to group on. */\n deviceGrain: z.enum([\"browser\", \"os\", \"engine\", \"device\", \"viewport\"]).optional(),\n /**\n * One browser, by its cookie id.\n *\n * Narrows `people` to a single person, narrows `stream` to everything\n * that person did, and replays that person's whole history on `session` —\n * every visit they made, not just the one. Given with `sessionId`, the\n * session wins and this is ignored.\n *\n * On `stream` it is a seed rather than a cookie filter: the browser plus\n * every address, phone, click id and platform cookie ever seen beside it.\n * Filtering on the cookie alone would drop exactly the rows the reader\n * came for — a deal a CRM posted names the person and carries no cookie.\n */\n anonId: z.string().max(120).optional(),\n /**\n * `stream` and `session`: one person by any key the store recognises them\n * by — a contact digest, a CRM id, a click id, a platform cookie — for the\n * rows a system posted about somebody no browser has named yet. A browser\n * wins when both are given.\n */\n personKind: z.string().max(40).optional(),\n personValue: z.string().max(255).optional(),\n /**\n * `stream` only: several people, each by whatever the reader holds about\n * them — an email, a phone, a CRM contact or deal id, a click id, a browser\n * id, `kind:value` to say which. Emails and phones are hashed by the\n * server; the plaintext never reaches the store. Wins over the single\n * person above, and every row comes back naming which of these it is\n * (`personTerms`), with how each was read in `peopleSearch`.\n */\n people: peopleSearchInputSchema.optional(),\n /**\n * `people` only: keep only the visitors who converted.\n *\n * A parameter rather than a client-side filter because the list is capped:\n * filtering the most recent 200 visitors down to the ones who converted\n * usually leaves two, and reads as \"we had two leads\".\n */\n onlyConverted: z.boolean().optional(),\n /**\n * Which page of a list-shaped report to read, 1-based.\n *\n * Only three reports are lists of individual things rather than\n * breakdowns — `people`, `submissions` and `delivery` — and only those\n * three take this. A breakdown is a top-N by construction: page two of\n * \"your busiest countries\" is not a thing anybody wants.\n *\n * It exists because the alternative silently stopped being true. Every one\n * of those lists was capped, the cap was invisible, and the summary line\n * above the list counted the rows it had been handed — so an account with\n * four thousand visitors read \"200 people visited in this period\" with no\n * indication that the number was the cap talking. A cap that is reported is\n * a page; a cap that is not is a wrong answer.\n */\n page: z.coerce.number().int().min(1).max(ANALYTICS_MAX_PAGE).optional(),\n /**\n * How many rows one page holds. Bounded, because the reader is a browser.\n *\n * Omitted, a list keeps the size it had before pagination existed, which is\n * what keeps `baker analytics submissions --full` returning the same 800\n * rows it always did for an agent that reads the whole thing in one go.\n */\n pageSize: z.coerce.number().int().min(1).max(ANALYTICS_MAX_PAGE_SIZE).optional(),\n })\n .refine((value) => value.preset !== \"page\" || typeof value.path === \"string\", {\n message: \"The 'page' preset needs --path\",\n path: [\"path\"],\n })\n .refine(\n (value) =>\n value.preset !== \"session\" ||\n typeof value.sessionId === \"string\" ||\n typeof value.anonId === \"string\" ||\n typeof value.personValue === \"string\",\n {\n message: \"The 'session' preset needs --session, a browser id or a person key\",\n path: [\"sessionId\"],\n },\n )\n .refine((value) => value.property === undefined || typeof value.eventName === \"string\", {\n message: \"Breaking an event down by a property needs --event too\",\n path: [\"property\"],\n })\n .refine((value) => value.where === undefined || typeof value.property === \"string\", {\n message: \"Narrowing with --where needs --event and --property too\",\n path: [\"where\"],\n });\nexport type AnalyticsQueryRequest = z.infer<typeof analyticsQueryRequestSchema>;\n\n// ---------------------------------------------------------------------------\n// Shared response pieces\n// ---------------------------------------------------------------------------\n\nexport const analyticsWindowSchema = z.object({\n startDate: z.iso.date(),\n endDate: z.iso.date(),\n days: z.number().int().positive(),\n /** True when the window reaches past the datasource TTL, so part of it is simply gone. */\n beyondRetention: z.boolean(),\n});\nexport type AnalyticsWindow = z.infer<typeof analyticsWindowSchema>;\n\n/**\n * Counts are exact — every row is stored, nothing is sampled.\n *\n * `pagesPerSession` stands in for a classic bounce rate: it carries the same\n * signal (at or near 1 means visitors leave from the page they land on) and is\n * a plain ratio of two counts rather than a definition every analytics vendor\n * disagrees about.\n */\nexport const analyticsTotalsSchema = z.object({\n /** Counted from `visitor_new`. */\n visitors: z.number().int().nonnegative(),\n /** Counted from `session_start`. */\n sessions: z.number().int().nonnegative(),\n pageViews: z.number().int().nonnegative(),\n /**\n * Every conversion, counting a visit twice when it converted twice.\n *\n * A Form's author can mark an outcome as counting every time it happens — a\n * download, a repeatable action — so this legitimately runs ahead of\n * {@link convertingSessions}, and of `sessions`.\n */\n conversions: z.number().int().nonnegative(),\n /**\n * Conversions in the window that no landing earned — by people none of the\n * company's pages saw. Not in `conversions`, which is a page metric; shown\n * beside it so a deal a CRM reported is never a silent zero. Zero under any\n * scope.\n */\n unplacedConversions: z.number().int().nonnegative().default(0),\n /** Visits that produced at least one conversion. Never above `sessions`. */\n convertingSessions: z.number().int().nonnegative(),\n /**\n * Converting visits per visit, 0–1. Null when there were no visits.\n *\n * Over converting visits rather than over conversions, because the second\n * ratio passes 1 the moment one visit converts twice — and a headline card\n * reading \"125%\" is read as a broken report, not as a visitor who did the\n * thing twice. The count above still says how often it happened.\n */\n conversionRate: z.number().min(0).max(1).nullable(),\n avgEngagementMs: z.number().nonnegative().nullable(),\n /** Page views per session. At or near 1 means visitors leave from the page they land on. */\n pagesPerSession: z.number().nonnegative().nullable(),\n});\nexport type AnalyticsTotals = z.infer<typeof analyticsTotalsSchema>;\n\nexport const analyticsTrendPointSchema = z.object({\n /**\n * The bucket's start, as an ISO instant.\n *\n * A full instant rather than a date, because a series is no longer always\n * daily: at `hour` granularity `2026-08-22` names twenty-four points, and at\n * `month` it names one that started three weeks earlier. It is also now\n * timezone-aware, so the same instant is a different calendar day depending\n * on the zone the caller asked for — which a bare `YYYY-MM-DD` cannot\n * express without silently choosing one.\n */\n bucket: z.string(),\n visitors: z.number().int().nonnegative(),\n sessions: z.number().int().nonnegative(),\n pageViews: z.number().int().nonnegative(),\n conversions: z.number().int().nonnegative(),\n});\nexport type AnalyticsTrendPoint = z.infer<typeof analyticsTrendPointSchema>;\n\n/** One landing in the list view, with its own shape over the window. */\nexport const analyticsLandingRowSchema = z.object({\n /** The landing's `internalId` — the reference every event carries. */\n landingId: z.string(),\n /** The Convex `landings._id`, so a dashboard can link straight to the row. */\n landingRef: z.string(),\n /**\n * What a person calls it, or `\"\"` when the dimension has not been mirrored\n * yet.\n *\n * Empty is a real and temporary state — a landing published before the\n * mirror existed, or one whose sync has not run — and it is reported rather\n * than papered over with the id, so a caller can say \"not synced yet\"\n * instead of showing something that looks like a name and is not.\n */\n name: z.string(),\n slug: z.string(),\n tags: z.array(z.string()),\n views: z.number().int().nonnegative(),\n sessions: z.number().int().nonnegative(),\n /** Sessions that BEGAN here — the page's Visits on screen, summing to the headline, and its rate's denominator. */\n entrances: z.number().int().nonnegative(),\n visitors: z.number().int().nonnegative(),\n /**\n * Conversions that fired ON this landing — rarely the number to show.\n *\n * A landing's outcome usually happens somewhere else: the client's checkout,\n * their shop, a CRM days later. None of those rows carries a landing id, so\n * this reads 0 for a page whose visits convert reliably.\n */\n conversions: z.number().int().nonnegative(),\n /** Visits that converted on this page at least once. */\n convertingSessions: z.number().int().nonnegative(),\n /**\n * Conversions this landing led to — what the page is bought for.\n *\n * The landing rule: a conversion on the page, or anywhere else when this was\n * the last landing its person viewed, up to 90 days before. So it includes\n * the sale a returning visitor made next week and the deal a CRM posted with\n * no cookie at all, and the landings add up to the headline.\n */\n creditedConversions: z.number().int().nonnegative(),\n /** People whose conversions are credited here. */\n creditedVisitors: z.number().int().nonnegative(),\n /** Visits credited at least one conversion here — the rate's numerator. */\n creditedVisits: z.number().int().nonnegative(),\n outboundClicks: z.number().int().nonnegative(),\n /**\n * `creditedVisits` over `entrances`, capped at 1 — the headline's converted\n * visits over visits, for one page, so the rows add up to the headline.\n * Null when no visit started here.\n *\n * Converting visits, not conversions: see {@link analyticsTotalsSchema}.\n */\n conversionRate: z.number().min(0).max(1).nullable(),\n avgEngagementMs: z.number().nonnegative().nullable(),\n /**\n * Views per bucket, zero-filled against the same axis every other landing\n * in the response uses — so the arrays are the same length and can be drawn\n * side by side without asking what each element means.\n */\n sparkline: z.array(z.number().int().nonnegative()),\n /**\n * The ad platforms actually sending this page traffic — `google`, `meta`,\n * `linkedin`, `tiktok`, `x`, `microsoft`.\n *\n * Derived from the click ids a visit arrived with, never from `utm_source`:\n * a label is whatever somebody typed into a tracking template, a `gclid` is a\n * click. Ordered by traffic, busiest first.\n */\n channels: z.array(z.string()),\n /**\n * Visits that started on the page, per entry of {@link channels}, in the\n * same order — so a page's marks add up to its share of Ads → Audience.\n *\n * The half that makes a mark readable. A mark on its own says \"advertised\n * here\", which is why it used to be withheld until three visits had arrived —\n * and withholding it is what made a page whose whole day was five Meta clicks\n * show nothing at all. The count says how much, so one test click and a live\n * campaign are distinguishable by reading rather than by absence.\n */\n channelSessions: z.array(z.number().int().nonnegative()),\n});\nexport type AnalyticsLandingRow = z.infer<typeof analyticsLandingRowSchema>;\n\nexport const analyticsTagRowSchema = z.object({\n tag: z.string(),\n landings: z.number().int().nonnegative(),\n views: z.number().int().nonnegative(),\n sessions: z.number().int().nonnegative(),\n entrances: z.number().int().nonnegative(),\n conversions: z.number().int().nonnegative(),\n /** Visits that converted on one of this tag's pages. */\n convertingSessions: z.number().int().nonnegative(),\n /** Converting visits per entrance. */\n conversionRate: z.number().min(0).max(1).nullable(),\n});\nexport type AnalyticsTagRow = z.infer<typeof analyticsTagRowSchema>;\n\n/**\n * Which collector produced an event, in the vocabulary a report filters by.\n *\n * Three words rather than the four values of `events.source`, because the split\n * this badge is about is \"did one team report this, or two\": `edge`, `client`\n * and `server` are all Baker measuring, and the difference between them is a\n * plumbing question nobody asks on a report.\n *\n * Coarser than {@link ANALYTICS_EVENT_DOORS} on purpose, and not the same\n * question. A door is a property of one row and says which of three ways it\n * arrived; this is a property of an event NAME across every row carrying it,\n * and splitting `site` into \"a landing\" and \"the client's own website\" would\n * make seven combinations of a badge whose whole job is to be read at a\n * glance. Filter by the door when the answer matters; this says only whether\n * more than one team is reporting the name.\n *\n * `both` is only ever a property of an aggregate — one event NAME arriving from\n * a page and from a CRM — never of a single row, and it is worth surfacing\n * rather than folding away: it means two teams are measuring one thing twice,\n * or one of them typed the other's name.\n */\nexport const ANALYTICS_EVENT_ORIGINS = [\"site\", \"systems\", \"both\"] as const;\nexport type AnalyticsEventOrigin = (typeof ANALYTICS_EVENT_ORIGINS)[number];\n\nexport const analyticsCustomEventRowSchema = z.object({\n eventName: z.string(),\n events: z.number().int().nonnegative(),\n sessions: z.number().int().nonnegative(),\n visitors: z.number().int().nonnegative(),\n /**\n * The property keys this event carries.\n *\n * The keys, not the values: a caller has to know what it can break the event\n * down BY before it can ask, and the values are unbounded where the keys are\n * capped. This is what lets a dashboard offer the breakdown without the\n * reader already knowing what the page chose to record.\n */\n propertyKeys: z.array(z.string()),\n /**\n * What these events were worth, per currency.\n *\n * A map and never one number, for the reason the person card's `money` is\n * one: a name carrying 99,882.50 EUR and 1,000 USD is not worth 100,882.50\n * of anything, and there is no rate in this product to make it so.\n *\n * The `''` key is a value a *page* reported — `value` with no currency,\n * which is a score on the page's own terms rather than money. Kept rather\n * than dropped, because for those events it is the only number there is.\n */\n money: z.record(z.string(), z.number()).default({}),\n landings: z.array(z.string()),\n /** Which collector produced this name — and `both` when more than one did. */\n origin: z.enum(ANALYTICS_EVENT_ORIGINS),\n});\nexport type AnalyticsCustomEventRow = z.infer<typeof analyticsCustomEventRowSchema>;\n\nexport const analyticsEventBreakdownRowSchema = z.object({\n value: z.string(),\n events: z.number().int().nonnegative(),\n sessions: z.number().int().nonnegative(),\n /** Per currency, exactly as {@link analyticsCustomEventRowSchema.money}. */\n money: z.record(z.string(), z.number()).default({}),\n});\nexport type AnalyticsEventBreakdownRow = z.infer<typeof analyticsEventBreakdownRowSchema>;\n\nexport const analyticsCustomFunnelStepSchema = z.object({\n stepIndex: z.number().int().positive(),\n stepName: z.string(),\n sessions: z.number().int().nonnegative(),\n /** Share of the sessions that reached the previous step. Null on the first. */\n stepRate: z.number().min(0).max(1).nullable(),\n /** Share of the sessions that reached step one. Null on the first. */\n overallRate: z.number().min(0).max(1).nullable(),\n});\nexport type AnalyticsCustomFunnelStep = z.infer<typeof analyticsCustomFunnelStepSchema>;\n\nexport const analyticsDeviceRowSchema = z.object({\n /** The bucket label — `Chrome 120.0`, `iOS 17.4`, `380–767`. */\n bucket: z.string(),\n sessions: z.number().int().nonnegative(),\n pageViews: z.number().int().nonnegative(),\n convertingSessions: z.number().int().nonnegative(),\n conversions: z.number().int().nonnegative(),\n /**\n * Converting sessions over sessions.\n *\n * Over sessions rather than conversions over sessions, because one session\n * can submit twice and a rate above 1 in a browser-comparison table reads as\n * a bug in the report rather than as a visitor who filled two forms.\n */\n conversionRate: z.number().min(0).max(1).nullable(),\n avgEngagementMs: z.number().nonnegative().nullable(),\n});\nexport type AnalyticsDeviceRow = z.infer<typeof analyticsDeviceRowSchema>;\n\nexport const analyticsPageRowSchema = z.object({\n path: z.string(),\n /**\n * The landing's own `internalId`, straight out of its `_definition.md`.\n *\n * The path says where the page is; this says which page it is, so a number\n * here lines up with a file the agent can open — and with the same page after\n * it has been renamed, which the path does not. Empty for a path with no\n * landing behind it.\n */\n landingId: z.string(),\n pageViews: z.number().int().nonnegative(),\n /** Sessions that began on this page — its value as a landing page. */\n entrances: z.number().int().nonnegative(),\n /**\n * Conversions that fired ON this page — \"which page converts\".\n *\n * Almost never the number to put beside Entrances. A landing page's job is to\n * start a visit that converts, and the conversion is usually somewhere else:\n * the shop, the checkout, a CRM a week later. Read alone this makes every\n * landing page of a real funnel report zero.\n */\n conversions: z.number().int().nonnegative(),\n /** Visits that converted on this page at least once. */\n convertingSessions: z.number().int().nonnegative(),\n /**\n * Conversions credited to this landing page by the landing rule — on it, or\n * anywhere when it was the last landing page its person viewed, up to 90\n * days before. The number a landing-page table means by \"conversions\".\n */\n creditedConversions: z.number().int().nonnegative(),\n /** People whose conversions are credited here. */\n creditedVisitors: z.number().int().nonnegative(),\n /** Credited people per entrance, 0–1. Null when nobody landed here. */\n conversionRate: z.number().min(0).max(1).nullable(),\n avgEngagementMs: z.number().nonnegative().nullable(),\n});\nexport type AnalyticsPageRow = z.infer<typeof analyticsPageRowSchema>;\n\nexport const analyticsSourceRowSchema = z.object({\n source: z.string(),\n medium: z.string(),\n sessions: z.number().int().nonnegative(),\n conversions: z.number().int().nonnegative(),\n conversionRate: z.number().min(0).max(1).nullable(),\n});\nexport type AnalyticsSourceRow = z.infer<typeof analyticsSourceRowSchema>;\n\nexport const analyticsCampaignRowSchema = z.object({\n utmSource: z.string(),\n utmMedium: z.string(),\n utmCampaign: z.string(),\n /**\n * The platform a click id proves, or `\"\"` when the visit carried none.\n *\n * What separates a campaign the ad breakdown already reports — with its\n * platform proven and its ad set and ad underneath — from one nothing else\n * can show: the newsletter, the partner link, the hand-tagged post. The two\n * are otherwise identical, because `utm_campaign={campaignid}` is the normal\n * tagging convention and produces the same ids in both places.\n *\n * Kept on the row rather than filtered out here: a CLI hint reads an empty\n * campaign list as \"no campaign tagging on any traffic\", and dropping the\n * paid ones would make that fire on a perfectly tagged account.\n */\n clickPlatform: z.string(),\n sessions: z.number().int().nonnegative(),\n conversions: z.number().int().nonnegative(),\n conversionRate: z.number().min(0).max(1).nullable(),\n});\nexport type AnalyticsCampaignRow = z.infer<typeof analyticsCampaignRowSchema>;\n\nexport const analyticsRealtimeMinuteSchema = z.object({\n /** The minute, as an ISO instant. */\n minute: z.string(),\n visitors: z.number().int().nonnegative(),\n pageViews: z.number().int().nonnegative(),\n});\nexport type AnalyticsRealtimeMinute = z.infer<typeof analyticsRealtimeMinuteSchema>;\n\nexport const analyticsRealtimePageSchema = z.object({\n path: z.string(),\n /** The landing this path belongs to; empty when no landing claims it. */\n landingId: z.string(),\n visitors: z.number().int().nonnegative(),\n});\nexport type AnalyticsRealtimePage = z.infer<typeof analyticsRealtimePageSchema>;\n\nexport const analyticsRealtimeSchema = z.object({\n /** Distinct visitors with any activity inside the active window. */\n activeNow: z.number().int().nonnegative(),\n /** How recently a visitor must have acted to be counted, in minutes. */\n activeWithinMinutes: z.number().int().positive(),\n minutes: z.array(analyticsRealtimeMinuteSchema),\n pages: z.array(analyticsRealtimePageSchema),\n});\nexport type AnalyticsRealtime = z.infer<typeof analyticsRealtimeSchema>;\n\nexport const analyticsDimensionRowSchema = z.object({\n key: z.string(),\n sessions: z.number().int().nonnegative(),\n conversions: z.number().int().nonnegative(),\n conversionRate: z.number().min(0).max(1).nullable(),\n});\nexport type AnalyticsDimensionRow = z.infer<typeof analyticsDimensionRowSchema>;\n\nexport const analyticsFunnelStepSchema = z.object({\n stepId: z.string(),\n stepIndex: z.number().int().nonnegative(),\n /** Visits that moved forward into this step. Never events — see `visits`. */\n views: z.number().int().nonnegative(),\n /** Visits that finished this step and moved on. */\n completions: z.number().int().nonnegative(),\n /** Share of viewers who did not complete this step, 0–1. */\n dropRate: z.number().min(0).max(1).nullable(),\n /**\n * Visits that converted AT this step — the node kind's own outcome trigger\n * fired here, or the Form was submitted here.\n *\n * Usually zero on every step but one. That is the point: a Form has one\n * conversion, and knowing which step owns it is what stops a drop-off chart\n * treating the booking widget's \"profile viewed\" as a funnel stage.\n */\n convertedSessions: z.number().int().nonnegative(),\n /**\n * This step is where the Form ends — a confirmation screen, not a loss.\n *\n * Stated rather than left to be inferred. The dashboard used to read it off\n * `dropRate === null`, which is also how a step nobody viewed arrives, so a\n * converting link node in the middle of a Form was labelled its ending.\n */\n isEnding: z.boolean(),\n});\nexport type AnalyticsFunnelStep = z.infer<typeof analyticsFunnelStepSchema>;\n\nexport const analyticsFunnelSchema = z.object({\n flowSlug: z.string(),\n /**\n * Visits that saw the Form at all — the denominator for every share here.\n *\n * Read this, not `starts`, whenever the question is \"out of how many\". A\n * start is recorded at the visitor's FIRST INTERACTION, so the two differ by\n * exactly the people who looked at the Form and never touched it — usually\n * most of them, and always the group a drop-off chart exists to find.\n *\n * Every count in this object is distinct visits, including each step's\n * `views` and `completions`. That is load-bearing rather than incidental:\n * the screen divides these by each other, and while the steps counted events\n * a Form four people had opened reported one step reached fifteen times and\n * fourteen people lost.\n */\n visits: z.number().int().nonnegative(),\n /** Visits that interacted with the Form at all — typed, picked, or advanced. */\n starts: z.number().int().nonnegative(),\n submits: z.number().int().nonnegative(),\n /** Conversions per visit that opened the Form, 0–1. */\n completionRate: z.number().min(0).max(1).nullable(),\n /** The step losing the most people. The single most useful field here. */\n worstStep: analyticsFunnelStepSchema.nullable(),\n steps: z.array(analyticsFunnelStepSchema),\n /**\n * Visits that converted, counted once each at whichever node did it.\n *\n * Read this rather than `submits` whenever the two disagree. `submits` only\n * fires when a flow runs out of nodes, so a Form that books a call on a\n * mid-flow Calendly node reports zero submits and every one of its real\n * bookings here.\n */\n convertedSessions: z.number().int().nonnegative(),\n});\nexport type AnalyticsFunnel = z.infer<typeof analyticsFunnelSchema>;\n\n/**\n * One campaign's paid performance.\n *\n * `clickPlatform` is derived from whichever click id the session arrived with,\n * and is the column to trust when the campaign label is missing or wrong —\n * which, on hand-built tracking templates, is often. `delivered` counts leads\n * whose server-side dispatch succeeded, so it can legitimately be far below\n * `conversions`; that gap IS the finding.\n */\nexport const analyticsPaidRowSchema = z.object({\n source: z.string(),\n medium: z.string(),\n campaign: z.string(),\n content: z.string(),\n term: z.string(),\n /** `google` | `meta` | `tiktok` | `x` | `linkedin` | `microsoft` | `\"\"`. */\n clickPlatform: z.string(),\n sessions: z.number().int().nonnegative(),\n convertingSessions: z.number().int().nonnegative(),\n conversions: z.number().int().nonnegative(),\n /** Leads whose server-side delivery reported success. */\n delivered: z.number().int().nonnegative(),\n /** Leads whose delivery reported failure. Non-zero here is a broken integration. */\n failedDeliveries: z.number().int().nonnegative(),\n /** Distinct people, across devices — only countable where visitors identified themselves. */\n identifiedPeople: z.number().int().nonnegative(),\n /** Converting sessions per session, 0–1. */\n conversionRate: z.number().min(0).max(1).nullable(),\n /** Delivered leads per conversion, 0–1. Below 1 means leads are being lost after submit. */\n deliveryRate: z.number().min(0).max(1).nullable(),\n});\nexport type AnalyticsPaidRow = z.infer<typeof analyticsPaidRowSchema>;\n\n/** One submission, and every destination it reached. */\nexport const analyticsDeliveryRowSchema = z.object({\n eventId: z.string(),\n submittedAt: z.string(),\n /**\n * The visit the submission was made in, so a lead can be opened as a story.\n *\n * The whole reason a delivery row is worth clicking. Without it a submission\n * is a timestamp and a CRM outcome, and the questions anybody actually has —\n * which ad brought them, what they read first, how long they took — are all\n * on the other side of this one join.\n */\n sessionId: z.string(),\n /** Lowercase hex digest of who submitted, when the page could identify them. */\n identityHash: z.string(),\n flowSlug: z.string(),\n stepId: z.string(),\n landingId: z.string(),\n destinations: z.array(z.string()),\n dispatched: z.number().int().nonnegative(),\n succeeded: z.number().int().nonnegative(),\n failed: z.number().int().nonnegative(),\n /** Dispatched and never settled. Distinguishing this from failed is \"wait\" vs \"act\". */\n pending: z.number().int().nonnegative(),\n /**\n * The ids the destinations gave back.\n *\n * A submission that succeeded everywhere and carries none of these is a real\n * and previously invisible failure: the integration accepted the call and\n * dropped the record.\n */\n providerRefs: z.array(z.string()),\n sampleError: z.string(),\n});\nexport type AnalyticsDeliveryRow = z.infer<typeof analyticsDeliveryRowSchema>;\n\nexport const analyticsTriggerRowSchema = z.object({\n flowSlug: z.string(),\n /** `link`, `customForm`, `iframe`, `hubspotMeeting`, … */\n nodeType: z.string(),\n /** The trigger, verbatim: `link.onClick`, `iframe.checkout_completed`. */\n triggerId: z.string(),\n fires: z.number().int().nonnegative(),\n sessions: z.number().int().nonnegative(),\n /** How many of those fires produced a side effect. Often zero, on purpose. */\n sideEffectsFired: z.number().int().nonnegative(),\n});\nexport type AnalyticsTriggerRow = z.infer<typeof analyticsTriggerRowSchema>;\n\nexport const analyticsReleaseRowSchema = z.object({\n /** The commit the build came from. */\n release: z.string(),\n firstSeen: z.string(),\n lastSeen: z.string(),\n sessions: z.number().int().nonnegative(),\n pageViews: z.number().int().nonnegative(),\n conversions: z.number().int().nonnegative(),\n /** Visits that converted on this build at least once. */\n convertingSessions: z.number().int().nonnegative(),\n /** Converting visits per session, 0–1. Null below a usable sample. */\n conversionRate: z.number().min(0).max(1).nullable(),\n});\nexport type AnalyticsReleaseRow = z.infer<typeof analyticsReleaseRowSchema>;\n\nexport const analyticsGeoRowSchema = z.object({\n /** The place itself, at whatever grain was asked for. */\n place: z.string(),\n /** Always carried: a postcode without its country is ambiguous, a city twice over. */\n country: z.string(),\n region: z.string(),\n sessions: z.number().int().nonnegative(),\n conversions: z.number().int().nonnegative(),\n /** Conversions per session, 0–1. Null below a usable sample. */\n conversionRate: z.number().min(0).max(1).nullable(),\n /** People rather than browsers, where the visit identified itself. */\n identifiedPeople: z.number().int().nonnegative(),\n});\nexport type AnalyticsGeoRow = z.infer<typeof analyticsGeoRowSchema>;\n\nexport const analyticsBotRowSchema = z.object({\n /** The crawler's name, or `unidentified` for a machine we could not name. */\n bot: z.string(),\n /** `search` | `ai` | `social` | `seo` | `monitoring` | `tool` | `automation`. */\n category: z.string(),\n hits: z.number().int().nonnegative(),\n pages: z.number().int().nonnegative(),\n lastSeen: z.string(),\n samplePaths: z.array(z.string()),\n});\nexport type AnalyticsBotRow = z.infer<typeof analyticsBotRowSchema>;\n\n/**\n * What the raw feed left off as machine traffic, under the feed's own filters.\n *\n * Beacon rows only — a headless browser running a page, which is what an\n * agent's own check of a site is — never a crawler's page serve, which `bots`\n * already counts. Stored like any row and hidden from every report, so a\n * reader who just tested a page and sees nothing is told it arrived.\n */\nexport const analyticsHiddenMachinesSchema = z.object({\n events: z.number().int().nonnegative(),\n /** Which signal decided, most first: `ua` is a browser that said it is headless. */\n reasons: z.array(\n z.object({\n reason: z.string(),\n events: z.number().int().nonnegative(),\n lastSeen: z.string(),\n }),\n ),\n});\nexport type AnalyticsHiddenMachines = z.infer<typeof analyticsHiddenMachinesSchema>;\n\n/**\n * One company that visited, named by its own network at the edge. A company,\n * never a person: counts, the pages read and when — no visitor in it.\n */\nexport const analyticsVisitorCompanyRowSchema = z.object({\n /** The network's registered name, as Cloudflare reports it. */\n name: z.string(),\n /** Null in v1 — Cloudflare names a network, never its website. */\n domain: z.string().nullable(),\n /** `high`: the name reads as an organisation. `low`: it only cleared the ISP/cloud/VPN deny-list. */\n confidence: z.enum([\"high\", \"low\"]),\n visits: z.number().int().nonnegative(),\n pageViews: z.number().int().nonnegative(),\n /** Distinct pages read. */\n pages: z.number().int().nonnegative(),\n /** Up to ten of them. */\n topPages: z.array(z.string()),\n firstSeen: z.string(),\n lastSeen: z.string(),\n});\nexport type AnalyticsVisitorCompanyRow = z.infer<typeof analyticsVisitorCompanyRowSchema>;\n\nexport const analyticsWarningSchema = z.object({\n /**\n * `AD_ROLE_UNMAPPED` is the one that reports a *question* rather than the\n * data. A role resolves to the parameter names that carry it, and when a\n * company has mapped none the list is empty — which the filter reads as\n * \"arrived carrying none of these\" and answers with a confident zero across\n * the whole report. Zero is a far more expensive conclusion than \"nobody has\n * said which parameter that is\", so the narrowing is dropped and this says so.\n */\n code: z.enum([\"LOW_SAMPLE\", \"NO_DATA\", \"BEYOND_RETENTION\", \"AD_ROLE_UNMAPPED\"]),\n message: z.string(),\n});\nexport type AnalyticsWarning = z.infer<typeof analyticsWarningSchema>;\n\n// ---------------------------------------------------------------------------\n// Response\n// ---------------------------------------------------------------------------\n\n/**\n * One shape for every preset, with the irrelevant blocks omitted. A single\n * shape means the CLI renders and the agent reads one thing rather than five,\n * which is the whole point of `overview` answering a question in one call.\n */\n/**\n * One Form, summarised — and specifically, summarised on the number that was\n * previously unobtainable.\n *\n * `convertedSessions` counts visits that reached the outcome, wherever the\n * outcome lives. `submits` counts the flow running out of nodes. On a\n * customForm they are the same number; on a booking Form they are 19 and 0,\n * and the 0 is what every report showed before the conversion trigger was\n * named.\n */\nexport const analyticsFlowSummarySchema = z.object({\n flowSlug: z.string(),\n sessions: z.number().int().nonnegative(),\n starts: z.number().int().nonnegative(),\n convertedSessions: z.number().int().nonnegative(),\n submits: z.number().int().nonnegative(),\n stepViews: z.number().int().nonnegative(),\n /** Distinct steps anyone reached, across all visits. */\n stepsSeen: z.number().int().nonnegative(),\n deepestStepIndex: z.number().int().nonnegative(),\n /** Steps reached per visit, averaged — \"how much of this Form do people read\". */\n avgStepsReached: z.number().nonnegative(),\n /** Conversions per visit that started, 0–1. */\n conversionRate: z.number().min(0).max(1).nullable(),\n /** Which node ids produced the conversions. Usually one; two means two offers. */\n conversionSteps: z.array(z.string()),\n /** And by what — `calendlyEvent.eventScheduled`, `customForm.onFormSubmitted`. */\n conversionTriggers: z.array(z.string()),\n conversionNodeTypes: z.array(z.string()),\n lastSeen: z.string(),\n});\nexport type AnalyticsFlowSummary = z.infer<typeof analyticsFlowSummarySchema>;\n\n/**\n * How many visits moved from one step to the next.\n *\n * `__flow_start__`, `__flow_converted__` and `__flow_exit__` are synthetic\n * endpoints so the graph is closed: every node's inbound total equals its\n * outbound total, and abandonment is an edge rather than a number missing from\n * the diagram. A real `stepId` is a node id from the Form's `_data.json` and\n * never takes that shape.\n */\nexport const analyticsFlowEdgeSchema = z.object({\n flowSlug: z.string(),\n fromStep: z.string(),\n toStep: z.string(),\n sessions: z.number().int().nonnegative(),\n});\nexport type AnalyticsFlowEdge = z.infer<typeof analyticsFlowEdgeSchema>;\n\n/** The three synthetic nodes that bracket every path. */\nexport const ANALYTICS_FLOW_START = \"__flow_start__\";\nexport const ANALYTICS_FLOW_CONVERTED = \"__flow_converted__\";\nexport const ANALYTICS_FLOW_EXIT = \"__flow_exit__\";\n\n/**\n * One value of an ad dimension, and what the people who arrived on it did.\n *\n * `bouncedSessions` is the column that justifies the whole feature: a keyword\n * with traffic, no engagement and no conversions is a keyword buying the wrong\n * intent, and nothing in the ad platform's own reporting can say so.\n */\n/**\n * One conversion this company counts, by the name somebody gave it.\n *\n * Present even when it has produced nothing — a definition at zero is the most\n * actionable row here, because it usually means the event is real but this\n * page never reaches it, which nothing else surfaces.\n *\n * Keyed by NAME, not by event: two definitions sharing a name are one row, so\n * \"Booked a call\" across three Forms is the single number anybody wants.\n * `eventKeys` is what it is made of — parse them with `parseEventKey` to say\n * where each one happens.\n */\nexport const analyticsConversionRowSchema = z.object({\n /** What a person calls this outcome. */\n name: z.string(),\n /** Every event counted under this name. One entry unless the name is shared. */\n eventKeys: z.array(z.string()),\n /**\n * `once_per_visit` | `every_time` — how repeats inside one visit are counted.\n *\n * `mixed` when a shared name spans both, which is a real state and an editing\n * accident; saying so is what makes it findable.\n */\n countMode: z.string(),\n /** Counted the way each definition asked to be counted, and credited to a landing. */\n conversions: z.number(),\n /**\n * Times it happened that no landing earned: by people none of the company's\n * pages saw, within 90 days. Zero under a landing or an ad scope, where the\n * gap would mean something else. Absent on a row built before it existed.\n */\n unplaced: z.number().default(0),\n /** Distinct visits that produced it, never mode-adjusted. The gap between the two is the repeat behaviour. */\n convertingVisits: z.number(),\n /** Null when it has never fired. */\n lastSeen: z.string().nullable(),\n /**\n * The doors its events came in by in the window, in\n * {@link ANALYTICS_EVENT_DOORS} order. Empty when it has not fired.\n */\n doors: z.array(z.enum(ANALYTICS_EVENT_DOORS)).default([]),\n /** Proposed in this chat: counted from when it is published, so every report reads it as nothing until then. */\n staged: z.boolean().optional(),\n});\nexport type AnalyticsConversionRow = z.infer<typeof analyticsConversionRowSchema>;\n\n/**\n * One distinct thing that happened on these pages, whether or not it counts.\n *\n * What the \"add a conversion\" picker is made of. Discovered from real traffic\n * rather than declared, because nobody can mark an outcome they cannot see —\n * before this, naming a conversion meant knowing a node's UUID and its trigger\n * id, and 7 companies out of 45 ever did.\n */\nexport const analyticsEventCatalogRowSchema = z.object({\n /** `form:…` | `submit:…` | `page:…` | `exit:…` — parse with `parseEventKey`. */\n eventKey: z.string(),\n /**\n * Which collector produced this key — and `both` when more than one did.\n *\n * On the row rather than in the key, which is the whole shape of the change\n * that removed the `server:` family: a picker can say where an event came\n * from without a filter round-trip, and a report can still separate the two\n * populations at any grain, but nothing downstream has to learn a second\n * vocabulary to count one.\n */\n origin: z.enum(ANALYTICS_EVENT_ORIGINS),\n events: z.number(),\n sessions: z.number(),\n /** The Form node's kind, for a `form:` key. Empty otherwise. */\n nodeType: z.string(),\n /** The Form this happened in, for a `form:` or `submit:` key. Empty otherwise. */\n flowSlug: z.string(),\n /** Null when it has never fired — only possible for a row we already count. */\n lastSeen: z.string().nullable(),\n /** Already counted as a conversion. Shown, never hidden: a row that vanished on being counted reads as a bug. */\n isCounted: z.boolean(),\n /** The name it is counted under, when it is. */\n countedAs: z.string(),\n /**\n * The doors on Sources this key's events came through, by name — the\n * `baker_source` each event carries. Empty for a key nothing on Sources\n * sent; more than one when two doors used one name.\n */\n sources: z.array(z.string()),\n /**\n * The doors this key's events came in by, in {@link ANALYTICS_EVENT_DOORS}\n * order, each with its own volume — only the doors it actually used.\n *\n * `origin` says site or systems; this says which of the four, which is the\n * question every picker is read to answer. Defaulted, so a store that\n * predates the column parses as \"no door known\" rather than failing.\n */\n doors: z\n .array(z.object({ door: z.enum(ANALYTICS_EVENT_DOORS), events: z.number(), sessions: z.number() }))\n .default([]),\n});\nexport type AnalyticsEventCatalogRow = z.infer<typeof analyticsEventCatalogRowSchema>;\n\nexport const analyticsAdDimensionRowSchema = z.object({\n /** The dimension's value. Empty means \"arrived carrying none of these\". */\n value: z.string(),\n /** Which platform stamped the click, from the click id rather than a label. */\n platform: z.string(),\n sessions: z.number(),\n convertingSessions: z.number(),\n conversions: z.number(),\n bouncedSessions: z.number(),\n identifiedPeople: z.number(),\n pageViews: z.number(),\n engagementMs: z.number(),\n /** Conversions per session, or null when nothing arrived. */\n conversionRate: z.number().nullable(),\n /** Bounces per session, or null when nothing arrived. */\n bounceRate: z.number().nullable(),\n});\nexport type AnalyticsAdDimensionRow = z.infer<typeof analyticsAdDimensionRowSchema>;\n\n/**\n * One campaign parameter seen on this company's URLs.\n *\n * The inventory the mapping tool is built on. `role` is what we currently read\n * it as — absent when nothing does, which is precisely the row a person needs\n * to act on.\n */\nexport const analyticsAdParamRowSchema = z.object({\n name: z.string(),\n /** Whether the value is stored, or only the fact that the name appeared. */\n captured: z.boolean(),\n /** The role in force for the platform asked about, built-in or configured. */\n role: adParamRoleSchema.optional(),\n /**\n * True when this company said what it means, rather than Baker knowing it.\n *\n * The difference between a row that can be reset to a sensible default and\n * one where clearing it means \"nobody has said\".\n */\n configured: z.boolean(),\n /**\n * Visits carrying this parameter **that arrived from the platform asked\n * about**, not visits carrying it anywhere.\n *\n * This used to be the second: one inventory of every parameter on every\n * visit, shown identically under each platform tab. That is wrong on the\n * facts, and confidently so. A visit arrives from one platform — it has one\n * click id — so `gclid` traffic cannot also be Meta traffic, and a Meta tab\n * listing `gad_campaignid` on 1,204 visits invited somebody to name a Google\n * parameter in Meta's vocabulary and then wonder why no report ever used it.\n * The count belongs to the platform or it is not a count of anything.\n */\n sessions: z.number(),\n events: z.number(),\n /** Up to five distinct values. Always empty for an uncaptured parameter. */\n sampleValues: z.array(z.string()),\n lastSeen: z.string().optional(),\n /**\n * True when this row is here because somebody configured it, not because it\n * arrived.\n *\n * The row a mapping made before its first click. A tracking setup is\n * routinely written the week before the campaign runs, and a tool that can\n * only describe traffic it has already seen cannot be used at the one moment\n * somebody sits down to do this properly. Such a row has no visits and no\n * values, and says so rather than reading as a parameter that stopped\n * arriving.\n */\n awaitingTraffic: z.boolean().optional(),\n});\nexport type AnalyticsAdParamRow = z.infer<typeof analyticsAdParamRowSchema>;\n\n/**\n * How much traffic each ad platform actually brought in this window.\n *\n * The number that makes the platform tabs honest. Nine tabs all offering to\n * configure something read as nine things left undone; nine tabs where six say\n * \"no visits\" read as an account running three platforms, which is the truth\n * and is what the reader already knows. Attribution is the generous one — the\n * click id, and failing that `utm_source` — because somebody who picks the\n * Google tab means the traffic they buy from Google, half of which arrives with\n * autotagging off or the click id stripped.\n */\n/**\n * What one Form has demonstrably reached, as opposed to what its picture says.\n *\n * Small on purpose: it exists to answer \"is this Form wired to anything\" from\n * evidence, and nothing more. The counts of what happened live on the\n * submission rows.\n */\nexport const analyticsFlowDestinationsSchema = z.object({\n flowSlug: z.string(),\n /** Destination types seen — `meta`, `httpWebhook`, `hubspotForm`. */\n destinations: z.array(z.string()),\n lastSeen: z.string().optional(),\n});\nexport type AnalyticsFlowDestinations = z.infer<typeof analyticsFlowDestinationsSchema>;\n\nexport const analyticsAdPlatformTrafficSchema = z.object({\n platform: z.string(),\n sessions: z.number(),\n conversions: z.number(),\n /** How many of those visits carried any campaign parameter at all. */\n taggedSessions: z.number(),\n});\nexport type AnalyticsAdPlatformTraffic = z.infer<typeof analyticsAdPlatformTrafficSchema>;\n\n/**\n * Which page of a list report this is, and whether there is another.\n *\n * `hasMore` is derived by asking the store for one row more than the page holds\n * and throwing it away, rather than by a second counting query. That answers\n * the only question a pager actually has — is the \"next\" button live — for the\n * price of one row, where a total count is a second full scan of the window for\n * a number that is stale before it is rendered.\n */\nexport const analyticsPageInfoSchema = z.object({\n /** Which field of the response was paged. */\n block: z.enum([\"visitors\", \"submissions\", \"deliveries\", \"stream\"]),\n page: z.number().int(),\n pageSize: z.number().int(),\n returned: z.number().int(),\n hasMore: z.boolean(),\n});\nexport type AnalyticsPageInfo = z.infer<typeof analyticsPageInfoSchema>;\n\n/**\n * One thing that happened in a visit, in the order it happened.\n *\n * Deliberately one flat row type for every kind of event rather than a union:\n * a replay is read top to bottom, and a reader who has to know which of nine\n * shapes each line is has been handed the raw store back. The fields that do\n * not apply come back empty, which is also how the store holds them.\n */\nexport const analyticsTimelineRowSchema = z.object({\n timestamp: z.string(),\n /**\n * The visit this event belongs to, and the browser that made it.\n *\n * Empty on anything that belongs to neither — a settlement, and an event a\n * client's own server posted about somebody who was never on a page. Carried\n * because the raw feed (`stream`) is a company-wide list rather than one\n * visit's, so a row has to say who it was about before a reader can get from\n * it to the person. On a replay both are constant and simply confirm it.\n */\n sessionId: z.string(),\n anonId: z.string(),\n /**\n * Where the producer put this event in its own order, from 1. `0` means it\n * did not number this one.\n *\n * The tie-break that makes the replay a replay. The timestamp is\n * milliseconds and the Form engine emits several events in one synchronous\n * block, so a step completing, the identities it revealed and the next step\n * coming into view routinely share one — and sorted on the timestamp alone\n * they came back in an order that matched nothing that happened.\n */\n eventSeq: z.number().int().nonnegative(),\n /** This event's identity, so two rows can be said to be the same event. */\n eventUid: z.string(),\n /** `page_view`, `form_step_view`, `side_effect_dispatched`, … */\n eventType: z.string(),\n /** `edge`, `beacon`, `server` — where the row was written from. */\n source: z.string(),\n /**\n * `baker`, `external`, `export` — where the page was, which `source` does\n * not say. The two together are what decide the row's door; see\n * `ANALYTICS_EVENT_DOORS`.\n *\n * Defaulted rather than required: the column arrived after the corpus did\n * and carries no default of its own, so a row older than it reads `\"\"`, and\n * that has to mean \"Baker's, as everything was then\" rather than fail the\n * whole row's parse.\n */\n deployment: z.string().default(\"\"),\n environment: z.string(),\n /** The build the page was published from. */\n release: z.string(),\n path: z.string(),\n host: z.string(),\n landingId: z.string(),\n flowSlug: z.string(),\n stepId: z.string(),\n stepIndex: z.number().int().nonnegative(),\n nodeType: z.string(),\n triggerId: z.string(),\n /** A page-defined event's own name, when this row is one. */\n eventName: z.string(),\n /** Whatever the page attached to a custom event. Strings only, by design. */\n properties: z.record(z.string(), z.string()),\n /** Where an outbound click or a link node was going, in full. */\n targetUrl: z.string(),\n /** A number the page reported alongside the event. */\n value: z.number(),\n /**\n * The currency that number is in, when it has one.\n *\n * Only a server-sent event carries it — a page reports a `value` on its own\n * terms, a CRM reports a deal in euros — and without it the replay prints a\n * bare `4200.5` beside a closed deal, which is a number nobody can act on.\n */\n valueCurrency: z.string().default(\"\"),\n /** The visitor's digest, when they had identified themselves by this point. */\n identityHash: z.string(),\n /**\n * The browser to attribute this row to, once the identity has been resolved.\n *\n * `anonId` when the row has one, and otherwise the browser {@link identityHash}\n * resolves to through `identities`. Empty when neither exists.\n *\n * It is a separate field rather than a coalesce into `anonId` because the two\n * claims are not the same: `anonId` is the cookie the row was written with,\n * and a server-sent event was written with none and must not start claiming\n * one. This is the *read's* answer to \"who was that\", and it is retroactive\n * and cross-device by construction — a \"Deal won\" posted in March attaches to\n * the person the moment they type that address into a Form in April, and to\n * every browser they have used here.\n *\n * Only `event_stream` fills it. Empty everywhere else, which is honest: a\n * session replay deliberately does not resolve, because an event that\n * happened during no visit must not be placed inside one.\n */\n personAnonId: z.string().default(\"\"),\n /**\n * What this row knows the person by, when it does not know their browser:\n * `email` | `phone` | `external` | `click` | `cookie`, or empty.\n *\n * The feed's Who column reads it. A row naming a person by a means that has\n * no face here yet — a digest nobody has typed into a Form, an ad click, one\n * of Meta's cookies — is not a row about nobody, and rendering it as one is\n * what made the server-sent half of the feed look unattached to the rest.\n */\n knownBy: z.string().default(\"\"),\n /**\n * What this row counts as under the company's own conversion definitions, or\n * empty when it counts as nothing.\n *\n * The store's own key, projected rather than rebuilt here, so the feed and\n * the reports cannot disagree about what one event was. Two rows carrying the\n * same non-empty key are **one** conversion — the second is stored, listed,\n * and marked as already counted rather than hidden, because a sender who\n * posted a deal twice needs to see that the second request arrived.\n *\n * Only `event_stream` fills it; empty everywhere else.\n */\n conversionKey: z.string().default(\"\"),\n /** Which signal took this row for a machine (`ua`, `datacenter`, …). Empty on every human row. */\n botReason: z.string().optional(),\n /**\n * The browser, OS, device and country the row came from. Only the feed fills\n * them: how somebody who just tested a page finds their own visit on it\n * without having typed an email.\n */\n browser: z.string().optional(),\n os: z.string().optional(),\n deviceType: z.string().optional(),\n country: z.string().optional(),\n /**\n * Which lines of a people search this row belongs to, by position in\n * `peopleSearch`. Empty when nobody was searched for.\n */\n personTerms: z.array(z.number()).optional(),\n /**\n * `email` | `phone` — which of the two they gave.\n *\n * Load-bearing rather than trivia: a visitor who fills in both fields\n * produces two `identify` rows in the same millisecond, and without the kind\n * a replay renders the same sentence twice and reads as a duplicated event.\n */\n identityKind: z.string(),\n /**\n * The `user_data` block this event carried, exactly as it arrived.\n *\n * The contact digests (`email_sha256`, `phone_sha256`, `external_id_sha256`),\n * the click ids, Meta's cookies, and the advanced-matching /\n * enhanced-conversions fields — first name, city, postcode, country. Empty on\n * everything a page produced, which is most rows.\n *\n * **Per event, and deliberately not merged** — unlike the person card, which\n * merges the same fields newest-wins across everything anyone ever sent. The\n * question one event answers is \"what did *this request* contain\", and the\n * answer has to be allowed to be *worse* than what is known about the person\n * overall. That is how a sender finds the field they forgot to map: the\n * person has a postcode, and this event does not.\n */\n userData: z.record(z.string(), z.string()).default({}),\n sideEffectType: z.string(),\n sideEffectName: z.string(),\n sideEffectId: z.string(),\n tagId: z.string(),\n messageId: z.string(),\n /**\n * The submission this row belongs to.\n *\n * What ties a browser pixel and a server dispatch of the SAME submit\n * together. Without it they read as two unrelated things that happened to\n * occur in the same second.\n */\n eventId: z.string(),\n runId: z.string(),\n engagementMs: z.number().int().nonnegative(),\n scrollDepth: z.number().int().nonnegative(),\n /**\n * The A/B test this row was stamped into, and which side the edge served —\n * `a` for the page as it is, `b` for the new variant.\n *\n * Set only on rows a page under test produced, which is how a person's story\n * can say which version they were shown. Empty on a server-sent row: a CRM\n * knows nothing about the split, and the conversion it posts is attributed\n * to a side by WHO did it (see `experiment_conversions`), never by a stamp.\n */\n experimentId: z.string().default(\"\"),\n variant: z.string().default(\"\"),\n /** `success` | `failed` | `\"\"` for a dispatch that has not settled yet. */\n outcome: z.string(),\n /** What the destination said when it refused. Empty on success. */\n error: z.string(),\n /** The destination's own id for the record it created, when it gave one. */\n providerRef: z.string(),\n /** When the destination answered, so \"it took eleven seconds\" is sayable. */\n settledAt: z.string(),\n});\nexport type AnalyticsTimelineRow = z.infer<typeof analyticsTimelineRowSchema>;\n\n/**\n * One attempt at a Form — one visitor, one Form, one visit.\n *\n * The unit a person means by \"a submission\": they filled it in once. It exists\n * whether they finished, gave up halfway, or completed a Form wired to nothing\n * — which is the case the delivery list structurally could not show, because\n * that list is built from dispatches and a Form with no destination makes none.\n */\nexport const analyticsSubmissionRowSchema = z.object({\n sessionId: z.string(),\n flowSlug: z.string(),\n startedAt: z.string(),\n lastAt: z.string(),\n anonId: z.string(),\n identityHash: z.string(),\n identityKind: z.string(),\n landingId: z.string(),\n path: z.string(),\n /** Steps they saw. The denominator for \"how far did they get\". */\n stepsReached: z.number().int().nonnegative(),\n /** Steps they actually handed something over on. Zero means no attempt. */\n stepsSubmitted: z.number().int().nonnegative(),\n submittedStepIds: z.array(z.string()),\n /** The furthest step they reached, for naming where they stopped. */\n lastStepId: z.string(),\n /** Whether the Form reached an ending, by any of its three routes. */\n finished: z.boolean(),\n conversions: z.number().int().nonnegative(),\n destinations: z.array(z.string()),\n /** Destinations that fired as a pixel in the page. */\n browserDestinations: z.array(z.string()),\n /** Destinations Baker dispatched from the server. */\n serverDestinations: z.array(z.string()),\n /**\n * Server-side dispatch runs — the counterpart of `serverDestinations`, NOT of\n * `destinations`.\n *\n * Zero is not \"this went nowhere\", and reading it that way is a mistake\n * already made once: it is zero for an attempt abandoned before the step that\n * fires a destination, and zero for a Form whose only destination is a\n * browser pixel. `destinations` is the union of both sides and is the column\n * that answers \"did this reach anything at all\".\n */\n dispatched: z.number().int().nonnegative(),\n succeeded: z.number().int().nonnegative(),\n failed: z.number().int().nonnegative(),\n /** Dispatched and never settled — \"wait\", as against \"act\". */\n pending: z.number().int().nonnegative(),\n sampleError: z.string(),\n providerRefs: z.array(z.string()),\n /** The submission ids, which is how the panel opens the sealed payload. */\n eventIds: z.array(z.string()),\n});\nexport type AnalyticsSubmissionRow = z.infer<typeof analyticsSubmissionRowSchema>;\n\n/** One earlier visit by the same person, resolved across their devices. */\nexport const analyticsVisitorSessionSchema = z.object({\n sessionId: z.string(),\n startedAt: z.string(),\n endedAt: z.string(),\n entryPath: z.string(),\n entryLandingId: z.string(),\n utmCampaign: z.string(),\n referrerSource: z.string(),\n deviceType: z.string(),\n pageViews: z.number().int().nonnegative(),\n conversions: z.number().int().nonnegative(),\n /**\n * True for the visits made BEFORE we knew who this was.\n *\n * Shown rather than smoothed over: a history without it invites the reading\n * that we recognised them all along, when in fact these visits were only\n * attributed backwards through the browser after they identified themselves.\n */\n wasAnonymous: z.boolean(),\n engagementMs: z.number().int().nonnegative(),\n});\nexport type AnalyticsVisitorSession = z.infer<typeof analyticsVisitorSessionSchema>;\n\n/**\n * Who made one visit — the header of a lead, not a row in a table.\n *\n * Everything here is one session's own facts, and it is the answer to the\n * question a conversion count cannot touch: a lead arrives, and before anything\n * else the reader wants to know whether this is a phone in Valencia off a\n * Google Ads keyword or a laptop in Berlin off a newsletter. That judgement is\n * made in a second from a flag, a device and a campaign, and it is made before\n * anybody reads a single delivery outcome.\n *\n * ## What is deliberately not here\n *\n * A name, an email, a phone number. `identityHash` is a digest computed in the\n * browser and the plaintext behind it never reaches the store — that is the\n * whole design of the lead vault, and it is why a person is shown as a stable\n * short code rather than as a person. Opening what they actually typed is a\n * separate, audited act with its own permission, and it stays that way.\n *\n * `campaignParams` is the honest substitute for a vendor's \"custom\n * properties\": the values the click itself carried, which for a paid visit is\n * usually the keyword, the ad and the campaign. Those are not personal data and\n * they are frequently the most useful thing on the panel.\n */\nexport const analyticsVisitProfileSchema = z.object({\n sessionId: z.string(),\n startedAt: z.string(),\n endedAt: z.string(),\n entryPath: z.string(),\n entryLandingId: z.string(),\n country: z.string(),\n region: z.string(),\n city: z.string(),\n timezone: z.string(),\n /** The network the visit came from. What separates a datacentre from a person. */\n asOrganization: z.string(),\n deviceType: z.string(),\n browser: z.string(),\n os: z.string(),\n referrerSource: z.string(),\n referrerDomain: z.string(),\n utmSource: z.string(),\n utmMedium: z.string(),\n utmCampaign: z.string(),\n /**\n * Which platform the click id proves, in the `AD_PLATFORMS` vocabulary —\n * `google` | `meta` | `tiktok` | `x` | `linkedin` | `microsoft` | `\"\"`.\n *\n * The same spelling every mark, mapping and report uses. It briefly said\n * `google_ads` / `meta_ads` here alone, which meant every logo lookup on the\n * People screen missed and a paid visit drew a neutral globe.\n */\n clickPlatform: z.string(),\n /** Every ValueTrack-style parameter the URL carried, unresolved. */\n campaignParams: z.record(z.string(), z.string()),\n /** The browser's own cookie id. One person can have several. */\n anonId: z.string(),\n identityHash: z.string(),\n /** What the digest was made of — `email`, `phone`. Never the value. */\n identityKind: z.string(),\n pageViews: z.number().int().nonnegative(),\n conversions: z.number().int().nonnegative(),\n engagementMs: z.number().int().nonnegative(),\n});\nexport type AnalyticsVisitProfile = z.infer<typeof analyticsVisitProfileSchema>;\n\n/**\n * One person in the visitors list.\n *\n * The same facts as {@link AnalyticsVisitProfile}, rolled up across every visit\n * that browser made instead of describing one. Two of the rollups are not\n * symmetrical and the asymmetry is deliberate: acquisition is first-touch,\n * because it records how somebody was found and the direct visit they made\n * afterwards must not take the credit; geography and device are last-touch,\n * because they answer \"what are they on\" and the current answer is the useful\n * one.\n *\n * No name and no email, for the reason given on the profile: the identity a\n * page sends is hashed in the browser. `anonId` is what the list is keyed and\n * labelled by, and a stable nickname and avatar are derived from it so a person\n * is recognisable between the list and their panel without ever being named.\n */\n/**\n * One key a person is known by.\n *\n * This used to be a pairing of a contact detail and a browser, and those were\n * the only two things it could ever be. A person is nameable in seven ways —\n * an email, a phone number, the sender's own contact id, the browser cookie,\n * an ad click, and either of Meta's two cookies — and six of them had nowhere\n * to appear, so \"how we recognise them\" answered \"by an email, or not at all\"\n * for a customer whose CRM identifies them perfectly well by something else.\n *\n * A digest never resolves to a value: it is a one-way hash and the value lives\n * in the sealed vault, so a row here is what \"we recognise this person\"\n * consists of and all of it.\n */\nexport const analyticsPersonKeySchema = z.object({\n /** `email` | `phone` | `external` | `browser` | a click-id name | `fbp` | `fbc`. */\n kind: z.string(),\n /** A digest for a contact detail, the value itself for a cookie or a click id. */\n value: z.string(),\n firstSeen: z.string(),\n lastSeen: z.string(),\n /** How many times it was observed beside another key — every submission repeats it. */\n observations: z.number().int().nonnegative(),\n sessions: z.number().int().nonnegative(),\n /** `client` | `edge` | `server` | `ingest` — which collector first saw it. */\n source: z.string(),\n /** The Form it was first seen on, where it came from one. */\n flowSlug: z.string(),\n landingId: z.string(),\n});\nexport type AnalyticsPersonKey = z.infer<typeof analyticsPersonKeySchema>;\n\n/**\n * The card: one person, rolled up across every key and every device.\n *\n * Distinct from the visitor row beside it, and the distinction is the whole\n * point. A visitor row is one **browser** — that is what the list is, because\n * most people never identify themselves and a list keyed on a person would be\n * a list of leads. Once a panel is open the unit has to change: the human who\n * read on a phone and converted on a laptop is one person, and their totals\n * have to say so or the panel contradicts the replay under it.\n */\nexport const analyticsPersonProfileSchema = z.object({\n firstSeen: z.string(),\n lastSeen: z.string(),\n sessions: z.number().int().nonnegative(),\n /** How many browsers resolved to this person. More than one is cross-device. */\n devices: z.number().int().nonnegative(),\n pageViews: z.number().int().nonnegative(),\n conversions: z.number().int().nonnegative(),\n /**\n * What they are worth, per currency — `{ EUR: 4753 }`.\n *\n * A map rather than an amount and a code, because the two-column shape had\n * no honest answer for somebody with a deal in each of two currencies: it\n * summed them and labelled the total with whichever code came back first.\n * Only a server event ever carries money, and a retried post is counted\n * once, on `event_uid`.\n */\n money: z.record(z.string(), z.number()),\n engagementMs: z.number().int().nonnegative(),\n /** How many of their events a client's own server posted. */\n serverEvents: z.number().int().nonnegative(),\n country: z.string(),\n region: z.string(),\n city: z.string(),\n deviceType: z.string(),\n browser: z.string(),\n os: z.string(),\n utmSource: z.string(),\n utmMedium: z.string(),\n utmCampaign: z.string(),\n referrerSource: z.string(),\n entryLandingId: z.string(),\n entryPath: z.string(),\n /**\n * The customer-matching block, merged newest-wins across everything anyone\n * has ever told us about them — `city`, `postal_code`, `country`,\n * `first_name_sha256`, `fbp`. Keyed exactly as the store holds it, so a key\n * ending `_sha256` says its value is a digest and cannot be shown.\n */\n matchKeys: z.record(z.string(), z.string()),\n});\nexport type AnalyticsPersonProfile = z.infer<typeof analyticsPersonProfileSchema>;\n\nexport const analyticsVisitorRowSchema = z.object({\n anonId: z.string(),\n firstSeen: z.string(),\n lastSeen: z.string(),\n sessions: z.number().int().nonnegative(),\n pageViews: z.number().int().nonnegative(),\n conversions: z.number().int().nonnegative(),\n formTriggers: z.number().int().nonnegative(),\n outboundClicks: z.number().int().nonnegative(),\n engagementMs: z.number().int().nonnegative(),\n /** The visit to open by default — the most recent one. */\n lastSessionId: z.string(),\n entryPath: z.string(),\n entryLandingId: z.string(),\n lastPath: z.string(),\n referrerSource: z.string(),\n referrerDomain: z.string(),\n utmSource: z.string(),\n utmMedium: z.string(),\n utmCampaign: z.string(),\n clickPlatform: z.string(),\n campaignParams: z.record(z.string(), z.string()),\n /**\n * The click ids this person arrived on, by their own names — `gclid`,\n * `fbclid`, `msclkid`, `li_fat_id`, `ttclid`, `twclid`, `wbraid`, `gbraid`,\n * `dclid`, `rdt_cid`, `epik`, `sccid`, `oppref`.\n *\n * Only the ones that were present. The endpoint has always returned all eight\n * columns and nothing above it carried them, so the one screen whose subject\n * is a person could name the platform that paid for the click and not the\n * click — which is the value a platform's own reporting is matched on, and\n * therefore the value somebody is looking for when a conversion did not show\n * up on the other side.\n */\n clickIds: z.record(z.string(), z.string()),\n country: z.string(),\n region: z.string(),\n city: z.string(),\n timezone: z.string(),\n asOrganization: z.string(),\n deviceType: z.string(),\n browser: z.string(),\n os: z.string(),\n identityHash: z.string(),\n identityKind: z.string(),\n});\nexport type AnalyticsVisitorRow = z.infer<typeof analyticsVisitorRowSchema>;\n\n/**\n * What the website tag is measuring, inferred from the rows it sent.\n *\n * Inferred and not stored: `data-baker-measure` is baked into a snippet\n * somebody pastes into their own tag manager, so nothing here records what is\n * actually live — and a stored copy would say what we told them to install,\n * not what they did. See `pixel_measure_scope` in `tinybird/definitions.ts`.\n *\n * `unknown` is the honest answer when the tag has sent nothing in this window:\n * not installed, not firing, or simply no traffic. It is not `campaigns`.\n */\nexport const analyticsMeasureScopeSchema = z.object({\n scope: z.enum([\"campaigns\", \"outcomes\", \"all\", \"unknown\"]),\n /** Every event the tag on the client's own site sent in this window. */\n websiteEvents: z.number(),\n /** Browser ids it minted off-Baker. Any at all means the tag is on `all`. */\n websiteNewVisitors: z.number(),\n});\nexport type AnalyticsMeasureScope = z.infer<typeof analyticsMeasureScopeSchema>;\n\nexport const analyticsQueryDataSchema = z.object({\n preset: analyticsPresetSchema,\n window: analyticsWindowSchema,\n totals: analyticsTotalsSchema,\n trend: z.array(analyticsTrendPointSchema).optional(),\n topPages: z.array(analyticsPageRowSchema).optional(),\n topSources: z.array(analyticsSourceRowSchema).optional(),\n topCampaigns: z.array(analyticsCampaignRowSchema).optional(),\n countries: z.array(analyticsDimensionRowSchema).optional(),\n devices: z.array(analyticsDimensionRowSchema).optional(),\n funnels: z.array(analyticsFunnelSchema).optional(),\n flowSummary: z.array(analyticsFlowSummarySchema).optional(),\n flowPaths: z.array(analyticsFlowEdgeSchema).optional(),\n triggers: z.array(analyticsTriggerRowSchema).optional(),\n releases: z.array(analyticsReleaseRowSchema).optional(),\n paid: z.array(analyticsPaidRowSchema).optional(),\n measureScope: analyticsMeasureScopeSchema.optional(),\n deliveries: z.array(analyticsDeliveryRowSchema).optional(),\n submissions: z.array(analyticsSubmissionRowSchema).optional(),\n geo: z.array(analyticsGeoRowSchema).optional(),\n bots: z.array(analyticsBotRowSchema).optional(),\n visitorCompanies: z.array(analyticsVisitorCompanyRowSchema).optional(),\n landings: z.array(analyticsLandingRowSchema).optional(),\n tags: z.array(analyticsTagRowSchema).optional(),\n customEvents: z.array(analyticsCustomEventRowSchema).optional(),\n eventBreakdown: z.array(analyticsEventBreakdownRowSchema).optional(),\n customFunnel: z.array(analyticsCustomFunnelStepSchema).optional(),\n deviceBreakdown: z.array(analyticsDeviceRowSchema).optional(),\n realtime: analyticsRealtimeSchema.optional(),\n adDimensions: z.array(analyticsAdDimensionRowSchema).optional(),\n adParams: z.array(analyticsAdParamRowSchema).optional(),\n /**\n * Parameters arriving on visits no platform can be named for.\n *\n * Kept apart rather than folded into the platform's own list, and kept rather\n * than dropped. Folding them in is what made every tab identical; dropping\n * them would lose a parameter that arrives on hand-tagged links, on an email\n * campaign, or on paid traffic whose click id an ad blocker removed — and\n * losing it silently, which is the one outcome worse than showing it in the\n * wrong place. They are evidence, not a to-do list: there is no platform to\n * map them under, so a reader who recognises one adds it by name to the\n * platform they know it belongs to.\n */\n adParamsUnattributed: z.array(analyticsAdParamRowSchema).optional(),\n adPlatformTraffic: z.array(analyticsAdPlatformTrafficSchema).optional(),\n flowDestinations: z.array(analyticsFlowDestinationsSchema).optional(),\n /** Present only on a report that was asked for by the page. */\n pageInfo: analyticsPageInfoSchema.optional(),\n conversions: z.array(analyticsConversionRowSchema).optional(),\n /** Every event these pages produced, counted or not. The \"add a conversion\" picker. */\n eventCatalog: z.array(analyticsEventCatalogRowSchema).optional(),\n timeline: z.array(analyticsTimelineRowSchema).optional(),\n /**\n * The raw feed. Same row shape as a replay, because it is the same rows —\n * what changes is that this one is bounded by a window rather than by a visit.\n */\n stream: z.array(analyticsTimelineRowSchema).optional(),\n /** How each line of a people search on `stream` was read — what `personTerms` points at. */\n peopleSearch: z.array(peopleSearchTermSchema).optional(),\n /** `stream` only: what that list left off as machine traffic. Absent when it is narrowed past what a count can follow. */\n hiddenMachines: analyticsHiddenMachinesSchema.optional(),\n visitorSessions: z.array(analyticsVisitorSessionSchema).optional(),\n /** Every key this person is known by — contact details, browsers, clicks, cookies. */\n personKeys: z.array(analyticsPersonKeySchema).optional(),\n /** The same person, rolled up: their totals across every device, and what is known about them. */\n personProfile: analyticsPersonProfileSchema.optional(),\n visitProfile: analyticsVisitProfileSchema.optional(),\n visitors: z.array(analyticsVisitorRowSchema).optional(),\n /**\n * The same totals for the window immediately before this one, when asked for.\n *\n * The comparison window is returned alongside rather than reduced to a set of\n * deltas, so a caller can show either side of it and does not have to trust\n * our arithmetic to render \"1,240 vs 980\".\n */\n previous: z\n .object({\n window: analyticsWindowSchema,\n totals: analyticsTotalsSchema,\n })\n .optional(),\n});\nexport type AnalyticsQueryData = z.infer<typeof analyticsQueryDataSchema>;\n\nexport const analyticsQueryResponseSchema = z.object({\n ok: z.literal(true),\n data: analyticsQueryDataSchema,\n warnings: z.array(analyticsWarningSchema).optional(),\n hints: z.array(z.string()).optional(),\n});\nexport type AnalyticsQueryResponse = z.infer<typeof analyticsQueryResponseSchema>;\n\n// ---------------------------------------------------------------------------\n// What counts as a conversion\n// ---------------------------------------------------------------------------\n\n/**\n * Changing what this company counts as a conversion.\n *\n * Operations rather than the whole list, for the parameter mapping's reason: an\n * agent that has just decided one outcome matters would otherwise have to read\n * the list first, and a dropped read would silently wipe every other outcome —\n * including the ones a person set in the dashboard an hour ago.\n *\n * With neither field set this is a read, which is what makes\n * `baker analytics conversions` with no flags show what is counted rather than\n * erroring.\n */\nexport const analyticsConversionsRequestSchema = z.object({\n set: z\n .array(\n z.object({\n /** `form:<flow>:<step>:<trigger>` | `submit:<flow>` | `page:<name>` | `exit:<host>`. */\n eventKey: z.string().max(320),\n name: z.string().max(MAX_CONVERSION_NAME),\n countMode: z.enum(CONVERSION_COUNT_MODES).optional(),\n }),\n )\n .max(60)\n .optional(),\n remove: z.array(z.string().max(320)).max(60).optional(),\n /** Every key counted under `from` moves to `to`, and the sends under the name go with it. */\n rename: z\n .object({ from: z.string().min(1).max(MAX_CONVERSION_NAME), to: z.string().min(1).max(MAX_CONVERSION_NAME) })\n .optional(),\n});\nexport type AnalyticsConversionsRequest = z.infer<typeof analyticsConversionsRequestSchema>;\n\nexport const analyticsConversionsResponseSchema = z.object({\n ok: z.literal(true),\n data: z.object({\n /**\n * What the company counts — inside a chat, with this chat's proposal on\n * top: `staged` marks a definition that applies when the chat is published.\n */\n definitions: z.array(\n z.object({\n eventKey: z.string(),\n name: z.string(),\n countMode: z.string(),\n updatedAt: z.number(),\n staged: z.boolean().optional(),\n }),\n ),\n /** This chat's proposal, when it has one: counted from Publish, and gone if the chat is discarded. */\n staged: z\n .object({\n set: z.array(z.object({ eventKey: z.string(), name: z.string() })),\n remove: z.array(z.string()),\n })\n .optional(),\n }),\n hints: z.array(z.string()).optional(),\n});\nexport type AnalyticsConversionsResponse = z.infer<typeof analyticsConversionsResponseSchema>;\n\n// ---------------------------------------------------------------------------\n// The parameter mapping\n// ---------------------------------------------------------------------------\n\n/**\n * Changing how this company's URLs are understood.\n *\n * Expressed as operations rather than as the whole mapping, because the caller\n * is usually an agent that has just been told about one parameter. Making it\n * send the complete map back would mean reading it first and would turn a\n * dropped read into a silent wipe of every other entry.\n *\n * With neither field set this is a read, which is what makes `baker analytics\n * map` with no flags show the mapping in force rather than erroring.\n */\nexport const analyticsMappingRequestSchema = z.object({\n /**\n * Which platform these operations are about. Required for a change.\n *\n * There is no cross-platform entry to write: the platforms do not describe\n * the same world, so `cid` is TikTok's ad and half the internet's campaign,\n * and a keyword on Meta traffic is not a thing that can exist. Omit it only\n * to read the mapping back.\n */\n platform: z.enum(AD_PLATFORMS).optional(),\n /** Parameter name → role, on `platform`. Merged over what is stored. */\n set: z.record(z.string().max(64), adParamRoleSchema).optional(),\n /** Parameter names to stop reading, on `platform`. Applied after `set`. */\n remove: z.array(z.string().max(64)).max(64).optional(),\n});\nexport type AnalyticsMappingRequest = z.infer<typeof analyticsMappingRequestSchema>;\n\nexport const analyticsMappingResponseSchema = z.object({\n ok: z.literal(true),\n data: z.object({\n /**\n * This company's own entries after the change, keyed `<platform>/<name>`.\n *\n * Every platform's, not only the one the request named: a caller that has\n * just mapped `cid` on TikTok is usually about to be asked the same\n * question about Google, and the answer there is different.\n */\n params: z.record(z.string(), adParamRoleSchema),\n /**\n * Names that are understood without being configured.\n *\n * Returned so a caller can tell \"already handled\" from \"not mapped\" without\n * shipping a copy of the built-in list, and so an agent does not spend a\n * turn configuring `keyword` to mean keyword.\n */\n builtIn: z.array(z.string()),\n /**\n * What the client's own ad account says about each parameter just staged.\n *\n * Present only when a Session staged a `set` on a platform whose tracking\n * configuration Baker can read. `contradicted` is the entry worth stopping\n * for: the account fills that name with a placeholder meaning something\n * else, so the answer just given would produce a report that is wrong and\n * looks entirely plausible.\n */\n evidence: z\n .record(\n z.string(),\n z.object({\n status: z.enum([\"confirmed\", \"contradicted\", \"unseen\", \"not_read\"]),\n macro: z.string().optional(),\n suggestedRole: adParamRoleSchema.optional(),\n source: z.string().optional(),\n reason: z.string().optional(),\n }),\n )\n .optional(),\n updatedAt: z.number().nullable(),\n }),\n hints: z.array(z.string()).optional(),\n});\nexport type AnalyticsMappingResponse = z.infer<typeof analyticsMappingResponseSchema>;\n\n// ---------------------------------------------------------------------------\n// Whether the ads are tagged well enough to report on\n// ---------------------------------------------------------------------------\n\n/**\n * A read, always — nothing here changes an ad account or a mapping.\n *\n * It answers the question an audit opens with and the mapping screen cannot:\n * given what this company's ad URLs carry and what Baker understands of it,\n * which of the dimensions any budget decision needs will actually reach a\n * report. Omitting `platform` checks the ones worth checking rather than all\n * eight — see `platformsToCheck`.\n */\nexport const analyticsTrackingRequestSchema = z.object({\n platform: z.enum(AD_PLATFORMS).optional(),\n days: z.number().int().min(1).max(365).optional(),\n});\nexport type AnalyticsTrackingRequest = z.infer<typeof analyticsTrackingRequestSchema>;\n\nconst trackingDimensionSchema = z.object({\n role: adParamRoleSchema,\n /** The platform's own macro for it — what goes in the URL. */\n macro: z.string(),\n /** The account's own spelling where it has one, else the canonical name. */\n name: z.string(),\n /**\n * What has to happen for this dimension to reach a report.\n *\n * Four states rather than a boolean, because two of them need opposite fixes\n * and one of them is not a finding at all. `unmapped` is one\n * `baker analytics map` call and repairs the history with it; `missing` is a\n * change to the ad account and everything before it stays unattributable;\n * `unknown` means no visit arrived and the account could not be read, which\n * must never be reported as untagged.\n */\n state: z.enum([\"covered\", \"unmapped\", \"missing\", \"unknown\"]),\n evidence: z.string().optional(),\n note: z.string().optional(),\n});\n\nexport const analyticsTrackingPlatformSchema = z.object({\n platform: z.enum(AD_PLATFORMS),\n label: z.string(),\n sessions: z.number(),\n /** Whether the ad account itself could be read, and why not when it could not. */\n account: z.object({ read: z.boolean(), templates: z.number(), reason: z.string().optional() }),\n dimensions: z.array(trackingDimensionSchema),\n /** Names the URLs already carry that Baker does not read yet. */\n unmapped: z.record(z.string(), adParamRoleSchema),\n mapCommand: z.string(),\n unexplained: z.array(z.object({ name: z.string(), value: z.string() })),\n /** The pairs to append to the account's tagging, or `\"\"`. */\n suffix: z.string(),\n whereItGoes: z.string(),\n /** Which level to put them at, who can write it, and how an existing value is treated. */\n fix: z.string(),\n});\nexport type AnalyticsTrackingPlatform = z.infer<typeof analyticsTrackingPlatformSchema>;\n\nexport const analyticsTrackingResponseSchema = z.object({\n ok: z.literal(true),\n data: z.object({ days: z.number(), platforms: z.array(analyticsTrackingPlatformSchema) }),\n hints: z.array(z.string()).optional(),\n});\nexport type AnalyticsTrackingResponse = z.infer<typeof analyticsTrackingResponseSchema>;\n\n// ---------------------------------------------------------------------------\n// Keeping a copy of a submission\n// ---------------------------------------------------------------------------\n\n/**\n * What a page posts when somebody finishes a step of a Form.\n *\n * Not an analytics event and deliberately not shaped like one. The event that\n * says the step was finished has already gone to the collector; this is the\n * copy of what was typed, on its way to be sealed with a key only Convex holds.\n * The two travel separately because only one of them can ever be read back\n * without a permission check and an audit entry.\n *\n * Every field here is a reference the copy has to carry to be findable later —\n * which submission, which Form, which step, which page, which build, and which\n * person by digest. `answers` is the only field that holds anything a visitor\n * wrote, and it is the only one that is encrypted.\n */\n/**\n * A reference field, cut to length rather than rejected over it.\n *\n * `answers` was not the only field that could refuse a whole copy: every\n * `.max()` on the object below failed the entire `safeParse` too, and one of\n * them is reachable. `pageUrl` is `window.location.href`, so an ad URL whose\n * query string runs past 2048 characters discarded the answers along with it —\n * the same silent loss, arriving by a different field.\n *\n * Cutting keeps the useful half of every one of these, because what overruns\n * is always the tail: a URL's identity is its host and path, and its\n * parameters are what grow without bound.\n */\nconst boundedReference = (max: number) => z.string().transform((value) => cutToLength(value, max));\n\nexport const submissionRecordRequestSchema = z.object({\n /**\n * Injected by the Worker from the host or the site key. Never from the page.\n *\n * One of the two fields that is still required, because a copy that cannot\n * be filed under a company cannot be found again by anybody. Bounding it\n * would produce a row belonging to a company that does not exist, which is\n * worse than refusing — this is the case where refusing is right.\n */\n companyId: z.string().min(1),\n /** Shared with every destination this submit was sent to, and with its event. */\n submissionId: z.string().min(1).pipe(boundedReference(128)),\n /**\n * The Form execution this step belongs to, or `\"\"` from a page published\n * before attempts existed.\n *\n * `submissionId` is minted per node, so two steps of one filling produce two\n * copies with nothing joining them. This is that join. It changes nothing\n * else here: a sealed copy is still per step, and still the only payload\n * anybody keeps.\n */\n attemptId: boundedReference(128).default(\"\"),\n flowSlug: boundedReference(120).default(\"\"),\n nodeId: boundedReference(120).default(\"\"),\n nodeType: boundedReference(40).default(\"\"),\n landingId: boundedReference(120).default(\"\"),\n release: boundedReference(64).default(\"\"),\n pageUrl: boundedReference(2048).default(\"\"),\n /**\n * A SHA-256 digest, or absent. Never an address.\n *\n * The join from a recovered copy to every session the same person had,\n * without anybody having to open the copy to get there.\n */\n identityHash: boundedReference(128).optional(),\n /**\n * Degraded rather than refused, like everything else here: an unrecognised\n * kind costs the copy one label, and refusing it would cost the copy.\n */\n identityKind: z.enum([\"email\", \"phone\"]).optional().catch(undefined),\n /** Injected by the Worker's edge. Kept exactly here and as a /24 everywhere else. */\n clientIp: boundedReference(64).nullish(),\n /**\n * Every answer on the step, free text included. Sealed on arrival.\n *\n * Bounded rather than length-checked, and that is the whole of it: a `max()`\n * here refused the **entire copy** over one long answer, so a visitor who\n * pasted a paragraph into a free-text field lost the email and the budget\n * beside it too. See {@link boundSubmissionAnswers}.\n */\n answers: z.record(z.string(), z.string()).transform(boundSubmissionAnswers),\n});\nexport type SubmissionRecordRequest = z.infer<typeof submissionRecordRequestSchema>;\n","/**\n * What counts as a conversion: what the Form's author marked, and nothing else.\n *\n * Baker used to answer this with one built-in rule for everybody — on each node\n * kind, one trigger is the outcome, so any `customForm` submit counted and any\n * booking counted. It was removed, and this is why: a guess about the number\n * every other number on the screen is a ratio of is wrong in two ways it cannot\n * fix from inside, and both of them are invisible.\n *\n * - **A Form has more than one ending.** A qualification branch that sends the\n * visitor to a \"not a fit\" screen ends in a `customForm.onFormSubmitted`\n * exactly like the branch that books a call. One rule per node kind counted\n * both, so the disqualified were a conversion.\n * - **Not every submit is the outcome.** A Form that qualifies on step one and\n * books on step four raises two submits; the first is a screening question\n * and only the second is worth money.\n *\n * Neither produces an empty report or an error. It produces a *plausible* wrong\n * number, which is the expensive kind — a rate somebody sets a budget by. So the\n * definition lives in the Form, next to the side effects, where the person who\n * built it already knows which node is the point of it, and a Form nobody has\n * marked counts **zero**. Zero is visible; it is warned about on the Form card,\n * in the Forms list, on the report and in the client's own verify run; and it is\n * the one state that cannot be mistaken for a measurement.\n *\n * ## Resolved when a report is read, never stamped at ingest\n *\n * The same decision the ad-parameter mapping made, for the same reason:\n * marking a node as a conversion has to fix the *past*, not only the future.\n * A company that publishes its Forms today and marks the conversion next week\n * would otherwise have a week of traffic that can never be scored, and the\n * warning that asks them to mark it would be asking them to accept a hole.\n *\n * Definitions therefore mirror to the `conversion_definitions` dimension and\n * the endpoints join them onto history. The page never asserts its own conversion\n * — same rule that refuses an unhashed `identity_hash`, and for the same\n * reason: the conversion count is the number somebody would want to inflate.\n */\n\n/**\n * How often one definition may count inside a single visit.\n *\n * The question is not academic: a \"book a call\" button that a visitor clicks,\n * comes back to, and clicks again is one booking; a \"download\" button on a\n * resources page genuinely converts each time it is pressed. Guessing either\n * way is wrong for the other, and the person who built the Form is the only\n * one who knows which it is.\n */\nexport const CONVERSION_COUNT_MODES = [\"once_per_visit\", \"every_time\"] as const;\nexport type ConversionCountMode = (typeof CONVERSION_COUNT_MODES)[number];\n\n/**\n * The safe default, and why it is the default.\n *\n * A repeated click is far more often the same intent expressed twice than two\n * outcomes. Over-counting is also the more expensive mistake here: conversions\n * feed the rate on every screen, and an inflated rate makes a campaign look\n * profitable enough to spend more on.\n */\nexport const DEFAULT_CONVERSION_COUNT_MODE: ConversionCountMode = \"once_per_visit\";\n\nexport function isConversionCountMode(value: unknown): value is ConversionCountMode {\n return typeof value === \"string\" && (CONVERSION_COUNT_MODES as readonly string[]).includes(value);\n}\n\n/** One outcome a Form can produce, as its author marked it. */\nexport interface ConversionDefinition {\n /** The node the trigger fires on — `events.step_id`. */\n nodeId: string;\n /** The trigger that is the outcome — `events.trigger_id`. */\n triggerId: string;\n countMode: ConversionCountMode;\n /**\n * What a person calls this outcome.\n *\n * Optional in the file and never empty here, because a Form with three\n * conversions reports three rows and `node-7 / calendlyEvent.eventScheduled`\n * is not a row anybody can read. Falls back to the node's name.\n */\n name: string;\n}\n\n/**\n * Which triggers each node kind can actually raise.\n *\n * Mirrors the `triggers()` map on each dashboard node definition\n * (`apps/dashboard/src/lib/flow/definitions/**`), which is where the flow\n * editor gets its picker from. Copied rather than imported because this module\n * is read by the Worker and by a standalone `.mjs` validator in every client\n * repo; `conversions.test.ts` reads those definitions and fails on drift.\n *\n * The point of having it at all is that a conversion on a trigger the node\n * cannot raise is invisible: it does not error, it does not warn, it simply\n * never counts, and the Form reports zero for as long as nobody checks. That\n * is the one failure in this feature worth blocking a publish for.\n *\n * `iframe` is deliberately absent rather than empty — its triggers are\n * `iframe.<name>` where the name is whatever the embedded page posts, invented\n * per client, so there is no list to check against and anything is allowed.\n */\nexport const FLOW_NODE_TRIGGERS: Readonly<Record<string, readonly string[]>> = {\n calendly: [\n \"calendlyEvent.eventScheduled\",\n \"calendlyEvent.dateAndTimeSelected\",\n \"calendlyEvent.eventTypeViewed\",\n \"calendlyEvent.profilePageViewed\",\n ],\n customForm: [\"customForm.onFormSubmitted\"],\n highlevel: [\"highlevelEvent.appointmentScheduled\", \"highlevelEvent.calendarLoaded\"],\n highlevelForm: [\"highlevelFormEvent.formSubmitted\", \"highlevelFormEvent.formLoaded\"],\n hubspot: [\n \"hubspotForm.onFormSubmitted\",\n \"hubspotForm.onBeforeFormSubmit\",\n \"hubspotForm.onFormReady\",\n \"hubspotForm.onBeforeFormInit\",\n ],\n hubspotMeeting: [\"hubspotMeeting.onMeetingScheduled\"],\n link: [\"link.onClick\"],\n savvycal: [\"savvycalEvent.scheduled\"],\n text: [\"text.onTextDisplayed\", \"text.onButtonClicked\"],\n};\n\n/**\n * The one trigger on each node kind that means \"the visitor did the thing\".\n *\n * What the star on a node card marks, and the reason a mark needs no further\n * input: a node kind has exactly one event that is its point, and the rest are\n * lifecycle noise — a form that finished loading, a calendar page that was\n * looked at. Asking which of four `hubspotForm` events to count is asking a\n * marketer to know an embed's API.\n *\n * Every kind that raises anything a person could mean is here, including the\n * two — a link click and a button press — that no rule could ever have counted\n * on its own: a link click is the whole point of one Form and a \"back to the\n * homepage\" in the middle of another. Nothing here counts until it is marked,\n * so the map can afford to offer them.\n *\n * `iframe` is absent for the same reason it is absent from\n * {@link FLOW_NODE_TRIGGERS}: its events are named by whatever the embedded\n * page posts. The editor falls back to the node's own first trigger there.\n */\nexport const PRIMARY_CONVERSION_TRIGGER: Readonly<Record<string, string>> = {\n calendly: \"calendlyEvent.eventScheduled\",\n customForm: \"customForm.onFormSubmitted\",\n highlevel: \"highlevelEvent.appointmentScheduled\",\n highlevelForm: \"highlevelFormEvent.formSubmitted\",\n hubspot: \"hubspotForm.onFormSubmitted\",\n hubspotMeeting: \"hubspotMeeting.onMeetingScheduled\",\n link: \"link.onClick\",\n savvycal: \"savvycalEvent.scheduled\",\n text: \"text.onButtonClicked\",\n};\n\n/** The outcome trigger for a node kind, or `null` when only the node itself can say. */\nexport function primaryConversionTrigger(nodeType: string): string | null {\n return PRIMARY_CONVERSION_TRIGGER[nodeType] ?? null;\n}\n\n/**\n * The node kinds whose outcome trigger IS that step being submitted.\n *\n * Not a conversion rule — nothing here counts anything. It answers a narrower\n * question two places need: \"did this trigger fire because the visitor pressed\n * the button that completed the step?\" The replay folds such a trigger into the\n * completion beside it (one press, one line), and the scaffold's flow engine\n * files a node's side effects under it.\n *\n * Derived from {@link PRIMARY_CONVERSION_TRIGGER} rather than written out, so\n * the two cannot drift. The exclusions are the kinds whose primary trigger is\n * something the visitor did *instead of* submitting: a link click leaves the\n * page, and a text screen's button press advances a screen that collected\n * nothing.\n */\nconst NON_SUBMIT_NODE_TYPES: readonly string[] = [\"link\", \"text\"];\n\nexport const STEP_SUBMIT_TRIGGERS: Readonly<Record<string, string>> = Object.fromEntries(\n Object.entries(PRIMARY_CONVERSION_TRIGGER).filter(([nodeType]) => !NON_SUBMIT_NODE_TYPES.includes(nodeType)),\n);\n\n/** Node kinds whose trigger names are defined by the client's own embed. */\nconst DYNAMIC_TRIGGER_NODE_TYPES: readonly string[] = [\"iframe\"];\n\n/**\n * Can this node kind raise this trigger?\n *\n * Unknown node kinds answer `true`. A node type this list has not heard of is\n * a scaffold that is newer than the checker, and refusing a publish over that\n * would freeze the client on their current build — the expensive direction of\n * a check that exists to catch typos.\n */\nexport function nodeCanRaiseTrigger(nodeType: string, triggerId: string): boolean {\n if (DYNAMIC_TRIGGER_NODE_TYPES.includes(nodeType)) return triggerId.startsWith(\"iframe.\");\n const allowed = FLOW_NODE_TRIGGERS[nodeType];\n if (!allowed) return true;\n return allowed.includes(triggerId);\n}\n\n/**\n * How long the \"why this Form has no conversion\" note may be.\n *\n * Longer than a conversion name because it is a sentence rather than a label —\n * it is printed in place of a warning and has to say where the outcome went —\n * and still bounded, because it reaches a dashboard badge and a Convex row.\n */\nexport const MAX_NO_CONVERSION_REASON = 200;\n\n/** A Form's declaration that it has no outcome to measure. See `FlowNoConversion` in the scaffold's types. */\nexport interface FlowNoConversion {\n reason: string;\n}\n\n/**\n * The Form's \"no conversion here, on purpose\" declaration, or `null`.\n *\n * Read from the ROOT node only. `_data.json` has no wrapper around its tree, so\n * a Form-level fact lives on the root exactly as `globalVariables` does — and\n * reading it anywhere else would let one branch speak for the whole Form.\n * `validate-flow-conversions` errors on a misplaced one rather than silently\n * honouring it, so this reader does not have to go looking.\n *\n * A declaration with no usable reason is not a declaration: it would silence\n * the warning while telling a reader nothing, which is worse than the warning.\n * Dropped here and reported by the validator, the same way a malformed mark is.\n */\nexport function readFlowNoConversion(tree: unknown): FlowNoConversion | null {\n if (typeof tree !== \"object\" || tree === null) return null;\n const declared = (tree as { noConversion?: unknown }).noConversion;\n if (typeof declared !== \"object\" || declared === null) return null;\n const reason = (declared as { reason?: unknown }).reason;\n if (typeof reason !== \"string\") return null;\n const trimmed = reason.trim();\n return trimmed === \"\" ? null : { reason: trimmed.slice(0, MAX_NO_CONVERSION_REASON) };\n}\n\n/** How long a conversion's display name may be. Bounded before it reaches the dimension. */\nexport const MAX_CONVERSION_NAME = 80;\n\ninterface RawNode {\n id?: unknown;\n name?: unknown;\n nodeData?: { type?: unknown } | null;\n conversions?: unknown;\n children?: unknown;\n}\n\nfunction readOne(node: RawNode, raw: unknown): ConversionDefinition | null {\n if (typeof raw !== \"object\" || raw === null) return null;\n const entry = raw as { triggerId?: unknown; countMode?: unknown; name?: unknown };\n const nodeId = typeof node.id === \"string\" ? node.id : \"\";\n const triggerId = typeof entry.triggerId === \"string\" ? entry.triggerId.trim() : \"\";\n if (nodeId === \"\" || triggerId === \"\") return null;\n const given = typeof entry.name === \"string\" ? entry.name.trim() : \"\";\n const nodeName = typeof node.name === \"string\" ? node.name.trim() : \"\";\n return {\n nodeId,\n triggerId,\n countMode: isConversionCountMode(entry.countMode) ? entry.countMode : DEFAULT_CONVERSION_COUNT_MODE,\n name: (given || nodeName || triggerId).slice(0, MAX_CONVERSION_NAME),\n };\n}\n\n/**\n * Every conversion a flow tree declares, in tree order.\n *\n * Tolerant by construction — it reads a `_data.json` that a person or an agent\n * hand-edited, and one malformed entry must not cost the other three. What it\n * cannot read it drops; `validate-flow-conversions` is what tells the author.\n */\nexport function readFlowConversions(tree: unknown): ConversionDefinition[] {\n const found: ConversionDefinition[] = [];\n const seen = new Set<string>();\n for (const node of flowNodes(tree)) {\n if (!Array.isArray(node.conversions)) continue;\n for (const raw of node.conversions) {\n const definition = readOne(node, raw);\n if (!definition) continue;\n // One node cannot count the same trigger twice. Two entries for one pair\n // are an editing accident, and keeping both would double every number\n // they produce.\n const key = `${definition.nodeId} ${definition.triggerId}`;\n if (seen.has(key)) continue;\n seen.add(key);\n found.push(definition);\n }\n }\n return found;\n}\n\n/**\n * Every node in a flow tree, depth first.\n *\n * Shared by both readers, so the traversal — and its tolerance of whatever\n * shape a hand-edited `_data.json` turns out to be — exists once.\n */\nfunction* flowNodes(value: unknown): Generator<RawNode> {\n if (typeof value !== \"object\" || value === null) return;\n const node = value as RawNode;\n yield node;\n if (!Array.isArray(node.children)) return;\n for (const child of node.children) yield* flowNodes(child);\n}\n\n/** The node kind each node id in a tree carries, for checking a trigger against it. */\nexport function readFlowNodeTypes(tree: unknown): Map<string, string> {\n const types = new Map<string, string>();\n for (const node of flowNodes(tree)) {\n const id = typeof node.id === \"string\" ? node.id : \"\";\n const type = typeof node.nodeData?.type === \"string\" ? node.nodeData.type : \"\";\n if (id !== \"\" && type !== \"\") types.set(id, type);\n }\n return types;\n}\n","/**\n * What survives of one step's answers on the way into the sealed copy.\n *\n * The copy of a submission is a safety net — the thing somebody restores from\n * when a CRM was down for a week, and the only record at all of a Form wired to\n * nothing. So the one behaviour it must never have is *all or nothing*, and\n * that is exactly the behaviour it had: the answers arrived under\n * `z.record(z.string().max(120), z.string().max(4096))`, a visitor pasted a\n * long paragraph into a free-text field, and the route logged\n *\n * [analytics:submission] refused a malformed copy\n * { issue: 'Too big: expected string to have <=4096 characters' }\n *\n * and kept **nothing** — not the paragraph, not the email beside it, not the\n * budget or the headcount on the same step. The page is told `ok` either way\n * and holds no second copy, so the whole submission was gone, and the log named\n * no company, no Form and no field to find it by.\n *\n * A textarea has no length limit unless the Form's author set `maxLength`, so\n * this is a visitor holding down a key, not a malformed body. The same\n * submission delivered to a CRM was kept: that path caps the payload with a\n * `slice` and stores the row (`MAX_PROPERTIES_CHARS`). Retention was the strict\n * one, which is backwards — a delivery can be retried and a copy cannot.\n *\n * So a copy is bounded, never refused. Nothing here can fail, which is the\n * property that matters: the caps are what the store can hold, not a judgement\n * about the body, and a body that genuinely makes no sense is still the\n * schema's to reject.\n *\n * ## Storage is not what bounds this, and neither was 4096\n *\n * Worth stating, because 4096 looked like a storage limit and was not one.\n * Nothing in the store is near it: `properties_sealed` is a ClickHouse\n * `String`, AES-GCM has no length to speak of, and Convex's request body and\n * Tinybird's ingest are both orders of magnitude above anything a Form\n * collects. The number was a guess, and it was low enough that an ordinary\n * visitor writing at length could reach it, which is how a guess ended up\n * deciding whether a lead was kept.\n *\n * Encoding the answers to make them smaller would be answering the wrong\n * question at this end. Base64 makes them larger; compression only pays\n * against a limit, and it would cost the property this store is built on,\n * that a sealed row opens to plain JSON with no scheme to know about.\n *\n * ## The ceiling that does bind is the browser, and it is one hop earlier\n *\n * `recordSubmission` posts with `keepalive: true`, and a keepalive request\n * body over **64 KiB fails as a network error** — a budget shared across every\n * keepalive request the page has in flight, which the scaffold already notes\n * where the CRM dispatches use it (`flow-engine/side-effects.ts`). So the\n * copy that most needs bounding — a visitor pasting a document — never\n * reaches this module to be bounded, and `recordSubmission` swallows the\n * failure by design. **Nothing in this file can fix that**; the bound has to\n * be applied in the page, before the body is serialised, and until it is, the\n * caps below cover everything except the largest case of all.\n *\n * The caps exist so one pathological answer cannot crowd out the fifteen\n * ordinary ones beside it. That is their whole job.\n */\n\n/**\n * How much of one answer is kept.\n *\n * Generous on purpose — the sealed copy is the *full* version of what the event\n * stream already carries at 300 characters, so cutting it short here would\n * leave the long answer nowhere at all. Around 2,500 words: past this it is a\n * document rather than an answer, and four of them still fit inside one step's\n * budget, which is the ratio that matters. The answer that started all of this\n * would have been kept whole.\n */\nexport const MAX_ANSWER_CHARS = 16_384;\n\n/**\n * How much of a field's name is kept.\n *\n * A name is written by the Form's author or handed over by a widget's own\n * vocabulary; neither produces a long one. Bounded rather than refused for the\n * same reason as the value: losing the answer because its label was odd is a\n * worse outcome than a label that reads short.\n */\nexport const MAX_ANSWER_NAME_CHARS = 120;\n\n/**\n * Cut to length, saying so where something was cut.\n *\n * The ellipsis is the marker the reported half already uses, so \"this was\n * truncated\" reads the same way on the replay and in the opened copy.\n */\nconst TRUNCATED = \"…\";\n\n/**\n * What stands where an answer did not fit the step's budget at all.\n *\n * Words rather than a glyph, and that is the whole of the choice. The masked\n * sentinel beside this one is `•••`, and its module states the rule it was\n * learned from: a sentinel only some surfaces recognise \"prints three dots and\n * looks like a bug\" (`apps/dashboard/src/features/leads/sealedAnswer.ts`). The\n * sealed copy is read by the submission panel, by a lead export, and by\n * whoever is recovering a lead with a query — so a marker here has to survive\n * a reader that has never heard of it, which a bracketed sentence does and a\n * fourth glyph does not.\n *\n * It cannot be confused with a truncated answer either: that one is the\n * visitor's own text with `…` on the end, and this replaces the text entirely.\n */\nexport const OMITTED_ANSWER = \"[too long to keep]\";\n\n/**\n * Cut to `max`, marking the cut, without splitting a character in half.\n *\n * `slice` counts UTF-16 code units, so a cut landing between the halves of an\n * emoji leaves a lone surrogate on the end. It survives storage intact —\n * `JSON.stringify` escapes it to `\\ud83d`, which is ASCII by the time the row\n * is sealed and round-trips exactly — so nothing is corrupted. It is the\n * *reader* who pays: the panel renders the answer ending in a broken glyph.\n * Iterating by code point costs an array on a path that runs once per\n * over-long answer, which is a fair price for not handing somebody half a\n * character back as their own words.\n */\nexport function cutToLength(value: string, max: number): string {\n if (value.length <= max) return value;\n return `${[...value].slice(0, max - 1).join(\"\")}${TRUNCATED}`;\n}\n\n/**\n * Every answer on the step, each bounded to what the copy can hold.\n *\n * Two names sharing a 120-character prefix would collide here and the second\n * would overwrite the first — the same silent loss this module exists to end,\n * narrowed to a trigger nothing plausible reaches. A field name is written by\n * a Form's author or fixed by a widget's own vocabulary; none of them is\n * 120 characters, let alone two of them agreeing for 120 and differing after.\n * Left as it is deliberately: de-duplicating would mean inventing a name no\n * reader would recognise, to save a case that cannot arise from the producers\n * this endpoint has.\n */\nexport function boundSubmissionAnswers(answers: Record<string, string>): Record<string, string> {\n const kept: Record<string, string> = {};\n for (const [name, value] of Object.entries(answers)) {\n kept[cutToLength(name, MAX_ANSWER_NAME_CHARS)] = cutToLength(value, MAX_ANSWER_CHARS);\n }\n return kept;\n}\n\n/**\n * The step's answers, fitted to what one row of the store is given.\n *\n * Separate from the per-answer cap above because it answers a different\n * question — that one is \"how long may an answer be\", this is \"how much may a\n * step hold\" — and because the budget belongs to the store rather than to the\n * answer, so its caller passes it in.\n *\n * The route used to do this by slicing the serialised JSON, which is what the\n * delivered-payload path does. That is defensible there (its own comment says\n * a capped row may not parse, and recovery tooling reads it as text) and it is\n * a poor trade here: a copy is opened by somebody trying to recover a lead, and\n * handing them a truncated blob loses the answers that *did* fit along with the\n * one that did not. Dropping whole entries instead keeps the row parseable, so\n * everything inside the budget survives intact.\n *\n * An answer past the budget keeps its name and loses its value, for the reason\n * a masked answer does: \"this step asked for a phone number\" is a fact worth\n * reading even when the answer itself is not there. It says so in words — see\n * {@link OMITTED_ANSWER}.\n */\nexport function fitAnswersToBudget(answers: Record<string, string>, budget: number): Record<string, string> {\n const kept: Record<string, string> = {};\n let spent = \"{}\".length;\n for (const [name, value] of Object.entries(answers)) {\n // What this entry adds to the serialised row, escaping included — measured\n // rather than estimated, because a paragraph of quotes and newlines costs\n // considerably more than its character count.\n const cost = JSON.stringify(name).length + JSON.stringify(value).length + \":,\".length;\n if (spent + cost > budget) {\n kept[name] = OMITTED_ANSWER;\n spent += JSON.stringify(name).length + JSON.stringify(OMITTED_ANSWER).length + \":,\".length;\n continue;\n }\n kept[name] = value;\n spent += cost;\n }\n return kept;\n}\n","/**\n * What everybody says when the analytics store is at its compute limit.\n *\n * The store bills reads against a rolling per-minute allowance and refuses a\n * query outright once a burst has spent it. That refusal is not a failure of\n * anything: no data is lost, no query was wrong, and the same question answered\n * a moment later returns the same numbers. It is the one store error whose\n * correct response is to wait.\n *\n * Three readers have to agree about it and each used to guess separately — the\n * backend throwing it, the dashboard deciding whether to try again, and the CLI\n * deciding whether an agent should abandon a report or carry on. So the\n * sentence is written once here and recognised by comparing against it, rather\n * than by three regexes over prose that one of them will reword.\n *\n * The words are the marketer's, not the store's. \"vCPU usage limit exceeded\" is\n * the truth and is unreadable; a reader told their query failed goes looking\n * for a problem they do not have.\n */\nexport const ANALYTICS_BUSY_MESSAGE =\n \"Analytics is busy right now. The numbers will be there in a moment — try again shortly.\";\n\n/**\n * Whether a failed read was a refusal to wait out.\n *\n * Substring rather than equality: the message crosses a Convex action boundary\n * on the way to both the dashboard and the CLI, and Convex prefixes its own\n * wrapper onto a thrown message as it goes.\n */\nexport function isAnalyticsBusy(message: string | undefined | null): boolean {\n return typeof message === \"string\" && message.includes(ANALYTICS_BUSY_MESSAGE);\n}\n\n/**\n * What an agent should do about it, in the `error.fix` an agent actually reads.\n *\n * A report is usually one step of a longer job, and abandoning the job over a\n * refusal that clears in seconds is the expensive mistake here — the whole\n * point of saying this rather than returning a bare failure.\n */\nexport const ANALYTICS_BUSY_FIX =\n \"Nothing went wrong and nothing was lost — the analytics store was briefly at capacity. \" +\n \"Wait a few seconds and ask for the same report again. If you were in the middle of a larger \" +\n \"job, carry on with the rest of it and come back to this read; do not abandon the job.\";\n\n/**\n * How long a reader waits before asking once more after a busy refusal. The\n * backend has already waited out the store's own suggestion once\n * (`lib/tinybirdQuery.ts`), so a refusal that reaches a reader is a burst of a\n * few seconds at least: waiting a little longer than that, once, clears most.\n */\nexport const ANALYTICS_BUSY_RETRY_MS = 5_000;\n\n/**\n * One read, and one more after `ANALYTICS_BUSY_RETRY_MS` when the store was\n * busy — never a loop, and never for any other failure. Shared by the\n * dashboard's Ads reports and the CLI's, so neither refires into a full budget.\n */\nexport async function retryOnceIfBusy<T>(\n read: () => Promise<T>,\n messageOf: (caught: unknown) => string,\n sleep: (ms: number) => Promise<void> = (ms) => new Promise((resolve) => setTimeout(resolve, ms)),\n): Promise<T> {\n try {\n return await read();\n } catch (caught) {\n if (!isAnalyticsBusy(messageOf(caught))) throw caught;\n await sleep(ANALYTICS_BUSY_RETRY_MS);\n return await read();\n }\n}\n","/**\n * The name two conversions share when they are the same outcome.\n *\n * Baker counts by NAME: two definitions given the same name are one row and one\n * number, which is how \"booked a call\" across three Forms becomes the single\n * figure anybody wants. That makes the name the whole grouping mechanism — and\n * it makes a near-miss the most expensive typo in the feature. \"Booked a call\"\n * and \"Booked a Call\" are two rows, two numbers, and one headline that is quietly\n * split; nothing errors, and both numbers look plausible.\n *\n * So a name is compared the way a person reads it rather than the way a string\n * compares: case, accents, punctuation and a trailing plural are not the\n * difference between two outcomes.\n *\n * Shared rather than CLI-local because both doors write these names — the agent\n * through `baker analytics conversions` and a person through the dashboard's\n * Conversions screen. Today only the CLI refuses a near-miss, and that is where\n * it matters most: the agent is writing into a list it cannot see, while the\n * dashboard shows the two rows one under the other. Keeping the comparison here\n * means the screen can adopt the same rule without inventing a second one that\n * disagrees.\n */\n\n/**\n * A name reduced to what it means, for comparison only. Never displayed: the\n * name a person typed is the name every report shows.\n *\n * The plural rule is per word and deliberately naive — `leads` and `lead`, or\n * `reservas` and `reserva`, are one outcome in every language this product is\n * used in. It over-reaches on a word that genuinely ends in `s` (`proceso` is\n * untouched, `access` becomes `acces`), which costs nothing: both sides of\n * every comparison are reduced the same way.\n */\nexport function normalizeConversionName(name: string): string {\n return name\n .normalize(\"NFD\")\n .replace(/[\\u0300-\\u036f]/g, \"\")\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \" \")\n .trim()\n .split(\" \")\n .filter((word) => word !== \"\")\n .map((word) => (word.length > 3 && word.endsWith(\"s\") ? word.slice(0, -1) : word))\n .join(\" \");\n}\n\n/**\n * The existing name this one would have been split from, or `null`.\n *\n * `null` for an exact match too: reusing a name on purpose is the whole point\n * of the mechanism, so it is never something to warn about. Only a name that\n * *reads* the same and *stores* differently is a finding.\n */\nexport function findNameCollision(name: string, existing: readonly string[]): string | null {\n const normalized = normalizeConversionName(name);\n return existing.find((candidate) => candidate !== name && normalizeConversionName(candidate) === normalized) ?? null;\n}\n\n/**\n * Where to start when a company counts nothing yet.\n *\n * Not a closed set and not enforced — a company's outcome is whatever its\n * business does, and an enforced vocabulary would make somebody name a\n * WhatsApp click \"Lead\" because the list had no better row. It exists so the\n * first outcome in a company is one of a handful of recognisable words rather\n * than a phrase somebody invented, which is what makes two accounts comparable\n * at all.\n *\n * **Keep the company's language.** Once a company has named one outcome, the\n * rule that matters is reuse: read what it already counts and use that name\n * again. A Spanish account whose outcomes read \"Reserva\" and \"Presupuesto\" is\n * standardised; the same account with \"Reserva\" and \"Booking\" is not.\n */\nexport const STANDARD_CONVERSION_NAMES: readonly { name: string; meaning: string }[] = [\n { name: \"Lead\", meaning: \"Somebody handed over contact details\" },\n { name: \"Booked a call\", meaning: \"A meeting or demo was scheduled\" },\n { name: \"Quote requested\", meaning: \"Somebody asked what it would cost\" },\n { name: \"Purchase\", meaning: \"Money changed hands\" },\n { name: \"Signed up\", meaning: \"An account or trial was created\" },\n { name: \"Subscribed\", meaning: \"Somebody opted in to hear from them again\" },\n { name: \"Downloaded\", meaning: \"A guide, price list or asset was taken\" },\n { name: \"Contacted us\", meaning: \"A call, WhatsApp or email started from the page\" },\n];\n","/**\n * What a delivery's destination is called, wherever it is reported.\n *\n * A `side_effect_type` is an internal slug and it reaches a person on four\n * surfaces: the Events feed, the visit replay, the lead panel, and every\n * `baker analytics` report an agent reads and then repeats to a marketer. Each\n * of those had its own answer, and two of them had no answer at all — so one\n * account read **\"Meta Pixel\"** where its tag is configured, **\"Meta\"** where\n * its uploads are listed, and **`metaCapi`** on the row our own server wrote,\n * one line apart, for the same Meta account.\n *\n * ## The three spellings one destination arrives in\n *\n * 1. **The tag or the Form action** — `meta`, `googleAds`, `httpWebhook`. What\n * the builder configured.\n * 2. **The server-side sender** — `metaCapi`, `ga4Mp`, `googleAdsCapi`,\n * `linkedinCapi`, `twitterAdsCapi`. The *dispatcher's* own name, written\n * when a tag carrying its credential fires from our server rather than from\n * the visitor's browser (`isCapiConfigured`). These names exist nowhere a\n * marketer can see them and nothing resolved them, so the feed printed the\n * slug and drew its \"unknown destination\" glyph — on 34% of one production\n * company's events, and on the two destination types carrying the largest\n * number of silent upload failures on the platform.\n * 3. **A send's upload** — `conversionSend:<platform>`. The platform is the\n * part that matters.\n *\n * All three resolve to one name here, and the server-side senders resolve\n * *through* the tag they belong to rather than to names of their own: that is\n * what makes it impossible for a delivery and the tag that caused it to drift\n * apart. Whether it went from the browser or from our server is not lost by\n * this — it is a different column's job, and the feed has one.\n *\n * ## Why this is in `@baker/api` and not in the dashboard\n *\n * Because the CLI reports the same rows to an agent, and an agent that reads\n * `metaCapi` writes `metaCapi` into a chat message. Every name a person can\n * see has to have exactly one owner, and a table only the dashboard can import\n * is not one.\n *\n * Kept in step with `computeSigForTag` in `convex/tags/utils.ts` — where a tag\n * type becomes a dispatcher name — by `destinations.parity.test.ts`, which\n * reads that function, `DISPATCHERS`, and the Tinybird generator's own copy of\n * the alias table. It said so before it did so: the test only checked that the\n * builder's catalogue was named, so the sixth server-side sender would have\n * been added in one place with everything green.\n */\n\nimport { isSendPlatform, SEND_PLATFORM_SPECS } from \"./sends\";\n\n/** A send's upload is logged as `conversionSend:<platform>`. */\nexport const CONVERSION_SEND_PREFIX = \"conversionSend:\";\n\n/** Whether a destination type is an upload a conversion's send made, rather than a Form's side effect. */\nexport function isConversionSend(type: string): boolean {\n return type.startsWith(CONVERSION_SEND_PREFIX);\n}\n\n/**\n * The tag each server-side sender belongs to.\n *\n * The dispatcher names are the ones `convex/sideEffects/utils.ts` registers in\n * `DISPATCHERS` and `convex/tags/utils.ts` signs with. `posthog` and\n * `datafast` are deliberately absent: their dispatcher name IS their tag name,\n * so they need no alias.\n *\n * Mirrored in `tinybird/definitions.ts` as `SERVER_SIDE_SENDERS`, which is\n * what `sent_to` narrows through — the generator runs in the Tinybird CLI's\n * own runtime and cannot import this. Held by the same test.\n */\nexport const SERVER_SIDE_SENDER_TAG: Readonly<Record<string, string>> = {\n metaCapi: \"meta\",\n chatgptAdsCapi: \"chatgptAds\",\n googleAdsCapi: \"googleAds\",\n linkedinCapi: \"linkedinInsightTag\",\n twitterAdsCapi: \"twitterAds\",\n ga4Mp: \"googleAnalytics\",\n};\n\n/**\n * Every destination Baker delivers to, by the name a person is shown.\n *\n * The five platforms a conversion send can reach are **not** listed here: they\n * are named by {@link SEND_PLATFORM_SPECS}, which is Sources' own vocabulary\n * and the one a marketer has already met on the screen where the send is set\n * up. Listing them twice is how \"Meta\" and \"Meta Pixel\" happened.\n *\n * Everything else keeps the builder's own title, down to the word — \"HubSpot\n * Events\" and \"HubSpot Form\" are two different destinations on the same\n * platform, and shortening both to \"HubSpot\" would make a refusal name\n * something the reader cannot act on. Only the five need shortening, because\n * only they arrive in three spellings and have to read as one account.\n *\n * `destinations.parity.test.ts` fails if the dashboard's builder catalogue\n * grows a destination this does not name, because that is precisely the\n * direction the drift runs: a destination is added where it is configured, and\n * the four places that *report* it find out later or never.\n */\nconst DESTINATION_NAMES: Readonly<Record<string, string>> = {\n email: \"Email\",\n httpWebhook: \"HTTP Webhook\",\n zapier: \"Zapier\",\n tiktok: \"TikTok Pixel\",\n pinterest: \"Pinterest Tag\",\n googleTagManager: \"Google Tag Manager\",\n amplitude: \"Amplitude\",\n hotjar: \"Hotjar\",\n clarity: \"Microsoft Clarity\",\n hubspot: \"HubSpot Events\",\n vwo: \"VWO\",\n posthog: \"PostHog\",\n datafast: \"DataFast\",\n code: \"Custom Code\",\n pipedrive: \"Pipedrive\",\n googleSpreadsheet: \"Google Sheets\",\n hubspotForm: \"HubSpot Form\",\n goHighlevelContact: \"GoHighLevel\",\n shopifyCustomer: \"Shopify\",\n crmble: \"Crmble\",\n activeCampaignContact: \"ActiveCampaign\",\n};\n/**\n * The catalogue key behind a destination type, whichever spelling it arrived in.\n *\n * Exported because the dashboard resolves a *logo* by the same key and must\n * not answer that question a second way — a row reading \"Meta\" under Google's\n * mark is worse than either name alone.\n */\nexport function destinationKey(type: string): string {\n const platform = isConversionSend(type) ? type.slice(CONVERSION_SEND_PREFIX.length) : type;\n return SERVER_SIDE_SENDER_TAG[platform] ?? platform;\n}\n\n/**\n * What to call this destination, or the type itself when we do not know it.\n *\n * The fallback is the raw type on purpose. A destination nobody has named is a\n * gap to fill in {@link DESTINATION_NAMES}, and showing the slug is what makes\n * the gap visible; inventing a title from the slug is how `httpWebhook`\n * becomes \"Http Webhook\" on one screen and \"HTTP Webhook\" on the next.\n */\nexport function destinationName(type: string): string {\n if (type === \"\") return \"\";\n const key = destinationKey(type);\n if (isSendPlatform(key)) return SEND_PLATFORM_SPECS[key].title;\n return DESTINATION_NAMES[key] ?? type;\n}\n","/**\n * The row shape written to the `events` Tinybird datasource, and the mapping\n * from a semantic event onto it.\n *\n * One shape for everything a visit produces, whichever side it happened on —\n * the Worker serving HTML, the browser scrolling and stepping through a Form,\n * a pixel firing in the page, or Convex dispatching a webhook and later\n * hearing how it went. `source` says which.\n *\n * Deliberately free of `zod` and every other import: `apps/worker` ships this\n * to the edge on the hot path of every published page, and the Worker is\n * otherwise dependency-free. Validation of untrusted beacon input lives in the\n * Worker (hand-rolled, cheap); this module only decides what a row looks like.\n *\n * `events.ts` layers the zod schemas on top for the CLI and backend.\n *\n * ## Column names are the contract\n *\n * Every key here matches a column in `tinybird/definitions.ts`. Tinybird\n * quarantines a row whose fields do not match the datasource schema rather\n * than rejecting the batch, so a rename here that is not mirrored there fails\n * silently into `events_quarantine` — loudly enough to find, but\n * only if someone looks. Change the two together.\n */\n\nimport { CONSENT_STATES, type ConsentState, rowAllows } from \"../event-ingest/consent\";\nimport { isExperimentId, isExperimentVariant } from \"../experiments/routing\";\nimport {\n type ClickIdFields,\n normalizeHost,\n normalizeIdentityHash,\n normalizePath,\n readCampaignParamsFromUrl,\n readClickIdsFromUrl,\n readUnmappedParamNames,\n readUtmFromUrl,\n truncateIp,\n type UtmFields,\n} from \"./normalize\";\nimport { MAX_REFUSAL_CHARS, refusalText } from \"./refusalText\";\n\n/**\n * Where an event happened, and therefore which columns mean anything — and, for\n * the fourth value, *who wrote it*.\n *\n * `ingest` is a client's own server posting to `POST /v1/events`. It is a\n * fourth source rather than a flavour of `server` because the first question\n * asked about any number that moved is which collector moved it, and the three\n * that existed before were all ours. Folding a third party's writes into\n * `server` would make \"our own dispatch rows\" and \"whatever a client's CRM\n * decided to send\" one undifferentiated population.\n *\n * It carries that question on its own now, because the event *type* no longer\n * does: a server-sent named event is a `custom` event like any other, and this\n * column is what a report filters on to separate what a page or the pixel saw from what\n * the API or an integration sent. That is the whole reason it must never be settable by a\n * browser — see the guard in `apps/worker/src/published-site/analytics.ts`.\n */\nexport const ANALYTICS_SOURCES = [\"edge\", \"client\", \"server\", \"ingest\"] as const;\nexport type AnalyticsSource = (typeof ANALYTICS_SOURCES)[number];\n\nexport const ANALYTICS_EVENT_TYPES = [\n /** Server-side page serve, written by the Worker. Includes bots. */\n \"page_view_edge\",\n /** Client-confirmed page view. A human with JavaScript on. */\n \"page_view\",\n /** First page of a new session, for this company. */\n \"session_start\",\n /** First ever page for this browser, for this company. */\n \"visitor_new\",\n /** Sent on page hide with time-on-page and scroll depth. */\n \"engagement\",\n /** First interaction with a form. */\n \"form_start\",\n /** A flow step became visible. */\n \"form_step_view\",\n /** A flow step was completed and the visitor moved on. */\n \"form_step_complete\",\n /** The flow reached its last node. Not a conversion on its own — only a mark on a step is. */\n \"form_submit\",\n /** Click on a link leaving the site. */\n \"outbound_click\",\n /** A pixel that fired in the browser (Meta, GA4, TikTok, …). */\n \"side_effect_fired\",\n /** Server side: work handed to the retrier or a workflow. */\n \"side_effect_dispatched\",\n /** Server side: what that work's onComplete reported. */\n \"side_effect_settled\",\n /**\n * A Form trigger fired — `link.onClick`, `calendlyEvent.dateAndTimeSelected`,\n * `iframe.<anything>`. Reported whether or not a side effect is attached to\n * it, because the trigger is the visitor's behaviour and the side effect is\n * only what we chose to do about it.\n *\n * The trigger's own name is in `trigger_id`, never in `event_type`: the\n * `iframe.${string}` family is open-ended per company, and an unbounded\n * `event_type` would both defeat its `LowCardinality` encoding and break\n * every endpoint that filters on a known set.\n */\n \"form_trigger\",\n /**\n * A visitor became recognisable — they gave an identity we can hash.\n *\n * Its own event rather than a flag on `form_submit`, because identification\n * and conversion are not the same moment: a Form can identify on step one\n * and convert on step four, and a returning visitor can be recognised\n * without converting at all. Collapsing them would make \"when did we first\n * know who this was\" unanswerable.\n */\n \"identify\",\n /**\n * A named event: an escape hatch a page defines, or one a client's own server\n * posted to `POST /v1/events`.\n *\n * One type for both, deliberately. There was briefly a `server_event`, and\n * the type was the mistake: every report that shows named events whitelists\n * `custom`, so a server-sent \"Deal won\" was structurally invisible on the\n * Page events card, in Top pages and in the events catalogue — the three\n * screens somebody would look at after being told it had arrived. GA4 makes\n * the same call for the same reason: a Measurement Protocol event is just an\n * event, and the transport is a property.\n *\n * Which collector wrote it is {@link ANALYTICS_SOURCES} — `client` for a\n * page, `ingest` for a third party's server — and every report that needs to\n * tell them apart filters on that.\n */\n \"custom\",\n /**\n * A fact about the business that names no person — a product changed, a\n * variant went out of stock.\n *\n * Only a commerce door writes it (`ingestShopifyFacts`), and it is a type of\n * its own rather than a `custom` event with no identity for one reason:\n * every report that lists named events whitelists `custom`, so an\n * operational fact would otherwise appear in the conversion picker, the\n * funnel and the sends. Here it reaches the raw feed and the agents that\n * listen for its name, and no person-level report. The row builder blanks\n * every identity column on it, whatever it carried.\n */\n \"operational\",\n] as const;\nexport type AnalyticsEventType = (typeof ANALYTICS_EVENT_TYPES)[number];\n\n/**\n * Who vouches for an event, as the door it came through decided — never its\n * sender. `authoritative`: a store's signed delivery or its own API.\n * `browser`: a script in a shopper's browser, which anyone can forge. A row\n * with neither was written by a door that predates the distinction. See\n * `events.trust` in `tinybird/definitions.ts`.\n */\nexport const EVENT_TRUST_CLASSES = [\"authoritative\", \"browser\"] as const;\nexport type EventTrust = (typeof EVENT_TRUST_CLASSES)[number];\n\n/**\n * Lines of an order kept on one row. More is not refused — the order's\n * `value` still carries all of its money — the rest are dropped and the count\n * dropped is said in `properties.items_omitted` by the writer that cut them.\n */\nexport const EVENT_ITEMS_MAX = 250;\n\n/** One line of what was bought. `price` is the unit price in the event's currency. */\nexport interface AnalyticsItem {\n productId?: string;\n variantId?: string;\n feedId?: string;\n quantity?: number;\n price?: number;\n}\n\nexport const ANALYTICS_DEVICE_TYPES = [\"desktop\", \"mobile\", \"tablet\", \"bot\", \"unknown\"] as const;\nexport type AnalyticsDeviceType = (typeof ANALYTICS_DEVICE_TYPES)[number];\n\n/**\n * Which signal decided a request was not a person.\n *\n * A closed set, ordered by how certain each one is, because the order is also\n * the order they are evaluated in and \"the first reason that fired\" is only a\n * useful answer if the cheapest and most certain go first.\n *\n * - `optout` — the request asked not to be counted. Not really a bot, but\n * it is the same exclusion, and giving it its own reason is\n * what keeps our own smoke tests out of `is_bot` counts *and*\n * distinguishable from real crawlers.\n * - `head` — a `HEAD` request. A browser rendering a page never sends one.\n * - `signed` — the client signed itself as an automated agent, per Web Bot\n * Auth. Nothing but an agent has a reason to send that header,\n * so its presence is conclusive and its absence means nothing.\n * - `ua` — the user-agent string matched `isbot`'s maintained pattern\n * list. A claim, and the floor: it decides most rows.\n * - `referrer` — the referrer is an email-security scanner. These fetch every\n * link in every message and look exactly like a person: real\n * browser UA, every header a browser sends, one page view,\n * instant leave. On a product whose follow-up emails contain\n * landing links, this is the single largest category no other\n * signal catches.\n * - `asn` — a known crawler operator's network, with a user agent that\n * did not admit it. Cheap because Cloudflare resolves the ASN\n * for free; the same coverage a hand-maintained list of CIDR\n * blocks buys, without the list.\n * - `headers` — the request contradicts the browser it claims to be: a Chrome\n * user agent with no client hints, no `Accept-Language`, an\n * `Accept` that never asked for a document. Two independent\n * contradictions are required. This is what replaced the\n * Cloudflare bot score, and it is narrower: it catches a\n * careless scraper, not a careful one.\n * - `datacenter` — a rent-by-the-hour hosting provider, by network name. Last,\n * because it is the only one whose false positive is a person.\n *\n * **`verified` and `score` used to be here and are gone.** Both read\n * `request.cf.botManagement`, an Enterprise Bot Management field that arrives\n * absent on every request Baker serves — so neither ever fired, `bot_verified`\n * was `0` on every row ever written, and the crawl report's \"verified\" column\n * and `only_verified` filter were answering a question no row could answer yes\n * to. A column that is structurally constant is worse than a missing one: it\n * reads as a measurement.\n */\nexport const ANALYTICS_BOT_REASONS = [\n \"optout\",\n \"head\",\n \"signed\",\n \"ua\",\n \"referrer\",\n \"asn\",\n \"headers\",\n \"datacenter\",\n] as const;\nexport type AnalyticsBotReason = (typeof ANALYTICS_BOT_REASONS)[number];\n\n/**\n * Where the page that produced this event is being served from.\n *\n * Not cosmetic, and not derivable from `host`: the three differ in **which\n * events can physically exist**, so a report that compares them without\n * knowing which is which draws a false conclusion.\n *\n * - `baker` — Baker's own Worker and R2. The only one that can produce\n * `page_view_edge`, the un-blockable traffic floor.\n * - `external` — built by Baker, served from the client's own infrastructure\n * (their cloud, their CDN). No edge rows, ever: nothing of ours\n * is in the request path. Beacon events only.\n * - `export` — a static bundle handed to the client as a zip. Same as\n * `external`, except we do not even know the host in advance.\n *\n * Without this column, moving a site off Baker's hosting reads in every report\n * as traffic collapsing by however much the edge collector was contributing —\n * a number that looks exactly like a broken campaign.\n */\nexport const ANALYTICS_DEPLOYMENTS = [\"baker\", \"external\", \"export\"] as const;\nexport type AnalyticsDeployment = (typeof ANALYTICS_DEPLOYMENTS)[number];\n\n/**\n * What kind of identifier an `identity_hash` was computed from.\n *\n * All three are SHA-256 of a normalized value, so they are indistinguishable\n * once hashed — and a person who gives an email on one Form and a phone number\n * on another produces two unrelated digests. Recording which is which is what\n * lets the identity map unify them through the browser they share.\n *\n * `external` is a sender's own id for a person — a CRM contact key, hashed the\n * same way and for the same reason Meta hashes `external_id`. Only the server\n * ingest produces it, and it joins that sender's events to each other rather\n * than to a browser, which is worth saying out loud: it is the weakest of the\n * three and the label on it has to let a reader see that.\n */\nexport const ANALYTICS_IDENTITY_KINDS = [\"email\", \"phone\", \"external\"] as const;\nexport type AnalyticsIdentityKind = (typeof ANALYTICS_IDENTITY_KINDS)[number];\n\n/**\n * Events batch so a page hide can flush engagement alongside whatever else is\n * pending in one `sendBeacon`, and so the Worker posts once rather than once\n * per event.\n */\nexport const ANALYTICS_BEACON_BATCH_MAX = 50;\n\n/** How long `events` keeps a row, matching the datasource TTL. */\nexport const ANALYTICS_RETENTION_DAYS = 400;\n\n/** The datasource every event is written to. */\nexport const ANALYTICS_DATASOURCE = \"events\";\n\n/** The companion datasource holding lead payloads, keyed by `run_id`. */\nexport const ANALYTICS_PAYLOAD_DATASOURCE = \"event_payloads\";\n\n/**\n * The `side_effect_type` on a payload kept because it was **submitted**, not\n * because it was sent anywhere.\n *\n * Not a side effect, which is worth saying out loud. Every other row in\n * `event_payloads` is a by-product of dispatching a lead somewhere, and that\n * used to be the only way a submission was ever kept — so a Form wired to\n * nothing kept nothing, and a visitor who abandoned before the step that fires\n * the CRM kept nothing either. Baker now posts a copy of every step submit on\n * its own account, and this marker is how that copy is told apart from a\n * delivery: in a column that already exists, with no schema change to\n * quarantine live rows between two deploys.\n *\n * Its twin is `SUBMISSION_TYPE` in `tinybird/definitions.ts`, which decides\n * whether these rows are part of lead recovery at all — that file is compiled\n * from the repo root by the Tinybird CLI and cannot import this one.\n * `submissionPayloadType.test.ts` fails if the two stop agreeing.\n */\nexport const SUBMISSION_PAYLOAD_TYPE = \"submission\";\n\n/** The datasource mapping an identity digest to the browsers that used it. */\nexport const ANALYTICS_IDENTITY_DATASOURCE = \"identities\";\n\n/**\n * One row of `events`.\n *\n * Every field is required and non-null: ClickHouse has no concept of an absent\n * column in a `MergeTree` row, and an empty string reads back identically to a\n * null while sorting and grouping predictably. Callers fill the ones that do\n * not apply with `\"\"` or `0` — see {@link buildAnalyticsRow}.\n */\nexport interface AnalyticsEventRow {\n /** `YYYY-MM-DD HH:MM:SS.mmm`, UTC. Stamped by the Worker, never by the page. */\n timestamp: string;\n /**\n * What makes two rows the same event.\n *\n * A MergeTree appends, so the only protection against a row arriving twice is\n * being able to tell that it did. Every producer that can retry mints this\n * deterministically — the browser once per event before it is ever sent, the\n * server as `${kind}:${runId}` — so a re-post carries the id the first\n * attempt did, and `uniqExact(event_uid)` counts the event once.\n *\n * The retry that makes this necessary is real and already shipped: the outbox\n * drains by deleting only what Tinybird accepted, so a POST that lands and\n * then loses its response is re-sent in full on the next cron cycle.\n */\n event_uid: string;\n /**\n * The producer's own emission order, monotonic per page load.\n *\n * `timestamp` is milliseconds and the Form engine emits several events in one\n * synchronous block, so ties are the normal case rather than the exception.\n * Sorting a replay by `(timestamp, event_seq)` is what makes it read in the\n * order things happened; timestamp alone cannot, at any precision, because\n * the events genuinely occupy the same tick.\n *\n * `0` where the producer does not number — the edge page view and every\n * server row, which are one per request and have nothing to tie with.\n */\n event_seq: number;\n company_id: string;\n source: AnalyticsSource;\n event_type: AnalyticsEventType;\n /** `production` | `development` | `preview`. Stamped at the edge. */\n environment: string;\n /**\n * The commit the page was built from, so behaviour can be attributed to a\n * publish. Empty on `page_view_edge` and on server rows that never saw a\n * page: the edge would need an extra KV read per request to learn it, and\n * the beacon already carries it on every event a visitor generates.\n */\n release: string;\n /** Empty on a settled row, which correlates by `run_id` instead. */\n session_id: string;\n anon_id: string;\n /**\n * The Form execution this event belongs to, minted by the page when the Form\n * is wired. Empty on everything that is not part of one.\n *\n * The grain between the session and the submission: it is what says \"these\n * six events are one attempt at one Form\", which neither `session_id` (one\n * session routinely holds several fillings) nor the per-node `submissionId`\n * (a Form whose second and fourth steps each send something mints two\n * unrelated ids) can say. Never re-minted between steps — a field that looked\n * like this but re-minted would be worse than absent, because every\n * per-attempt count drawn from it would still look correct.\n */\n attempt_id: string;\n /**\n * Which landing this happened on, by the id the page carries in its own\n * definition (`internalId` in `_definition.md`).\n *\n * The reference, where `path` is only a description. `landings.bulkReplace`\n * matches an incoming landing to its Convex row **by this id**, so it maps\n * one-to-one onto a `landings._id` and — unlike the path — survives the page\n * being moved or renamed. Without it, \"how did this page do before and after\n * we renamed it\" is two unrelated sets of rows.\n *\n * Empty on `page_view_edge`: the Worker resolves a request to an R2 object by\n * host and path and never reads the page, so learning the id would cost a KV\n * lookup on every HTML serve.\n */\n landing_id: string;\n /**\n * The page, as a path and nothing else — no origin, no query, no fragment,\n * no trailing slash. Normalized on the way in by {@link normalizePath} so\n * that every writer spells one page the same way; see that function for what\n * this column used to look like and why it could not be reported on.\n */\n path: string;\n /** The site the page was served from, lowercased and without its port. */\n host: string;\n referrer_source: string;\n referrer_medium: string;\n referrer_domain: string;\n utm_source: string;\n utm_medium: string;\n utm_campaign: string;\n utm_content: string;\n utm_term: string;\n\n /**\n * The click identifiers, one column each.\n *\n * A campaign name describes a visit; a click id *is* the visit, resolvable\n * to one click in one ad account. They are columns rather than entries in\n * {@link AnalyticsEventRow.campaign_params} because \"find the session this\n * click became\" is a lookup, and only a column can carry the data-skipping\n * index that makes one cheap.\n */\n gclid: string;\n wbraid: string;\n gbraid: string;\n dclid: string;\n fbclid: string;\n ttclid: string;\n twclid: string;\n li_fat_id: string;\n msclkid: string;\n rdt_cid: string;\n epik: string;\n sccid: string;\n oppref: string;\n /**\n * Everything else the ad platform put on the URL — ValueTrack keyword,\n * match type, device, placement, ad id.\n *\n * A map because the list grows every year and a column per parameter would\n * mean a schema migration each time, for values that are broken *down* by\n * far more often than they are filtered *on*.\n */\n campaign_params: Record<string, string>;\n /**\n * The names of query parameters we saw and could not read. No values.\n *\n * The discovery half of the parameter mapping: a company cannot map what it\n * does not know is arriving, and asking a marketer to recite their own\n * tracking template is how a mapping tool goes unfinished. This is what lets\n * the tool open with \"we have seen `kw` on your traffic — what is it?\".\n *\n * Names only, and that is a boundary rather than an economy. A parameter\n * name is a label the advertiser chose; a parameter value is where a magic\n * link puts an email address, and keeping unrecognised values out of\n * `events` is the reason capture was ever a closed list. Because nothing\n * identifying can reach this column, discovery is on for everybody rather\n * than something each company has to switch on before it is any use.\n */\n unmapped_params: string[];\n\n country: string;\n /**\n * Cloudflare's name for the subdivision — `Catalonia`, `California`.\n *\n * A display name, and therefore not a key: it is localized and it changes.\n * {@link AnalyticsEventRow.region_code} is the stable half.\n */\n region: string;\n /** ISO 3166-2 subdivision code (`CA`, `B`). What to group and join on. */\n region_code: string;\n /** Cloudflare's city for the request. Coarse by construction. */\n city: string;\n /**\n * Postcode, as Cloudflare resolved it.\n *\n * The finest geography available without touching the address, and the one\n * that matches how local campaigns are actually bought — a radius around a\n * branch is a set of postcodes, never a set of cities. Coarse by\n * construction and never derived from anything the visitor typed.\n */\n postal_code: string;\n /** `EU`, `NA`, `AS`. Cheap continental rollup without a country lookup table. */\n continent: string;\n /**\n * US DMA code, where the request is in one.\n *\n * Present because it is a *buying* unit, not a geographic one: Google and\n * Meta both target US campaigns by DMA, so it is the only geo column that\n * can be put beside a campaign's own targeting without a conversion table.\n * Empty everywhere outside the US.\n */\n metro_code: string;\n /** IANA zone from Cloudflare, e.g. `Europe/Madrid`. */\n timezone: string;\n /** The network the request came from. Zero where unknown, and on server rows. */\n asn: number;\n /**\n * Who that network belongs to — `Vodafone ES`, `Amazon.com, Inc.`.\n *\n * The readable half of {@link AnalyticsEventRow.asn}, and the one that makes\n * the bot columns actionable: traffic that passes every user-agent check and\n * arrives from a cloud provider's network is a datacentre, whatever it calls\n * itself.\n */\n as_organization: string;\n /**\n * The company the visit came from, when its own network named one.\n *\n * Read off `as_organization` at the edge, after a deny-list has removed every\n * network that names somebody other than the reader — ISPs, carriers, clouds,\n * hosts, VPN exits (`apps/worker/src/published-site/visitor-company.ts`). A\n * company, never a person: the address that produced it is not stored here\n * or anywhere else for this purpose.\n *\n * Empty unless three things all held: the company switched visitor companies\n * on, it measures in `full`, and the visitor's stored choice allows marketing.\n */\n visitor_company_name: string;\n /**\n * The company's website, when it could be derived. Always empty in v1 —\n * Cloudflare names a network, not its domain — and kept so a later\n * IP-to-company lookup can fill it without a schema change.\n */\n visitor_company_domain: string;\n /**\n * `high` when the name reads as an organisation in its own right (a legal\n * suffix, a bank, a university), `low` when it only cleared the deny-list.\n * Empty when no company was named.\n */\n visitor_company_confidence: string;\n /**\n * The visitor's address, truncated to a network (`/24`, `/48`).\n *\n * The exact address is deliberately absent from this table and present in\n * `event_payloads`: see {@link truncateIp}. Storing the precise one here\n * would turn every routine page-views query into a read of personal data.\n */\n ip_truncated: string;\n /**\n * `\"\"` on a server row, which is not a device at all. Deliberately not\n * `unknown`, which means \"a visitor whose user agent we could not classify\" —\n * collapsing the two would put every side-effect dispatch into that bucket.\n */\n device_type: AnalyticsDeviceType | \"\";\n browser: string;\n os: string;\n /**\n * The narrower client description, for the questions `browser` alone cannot\n * answer.\n *\n * \"Is the hero broken on Safari?\" is not the same question as \"is it broken\n * on Safari 17?\", and the second one is the one a fix gets tested against.\n * Same for the OS. All four are parsed from the same user-agent string the\n * coarse columns come from, so they cost nothing extra to collect and are\n * only ever as truthful as that string.\n *\n * `engine_name` rather than `engine`: `engine` is the SDK's table-engine\n * builder in `tinybird/definitions.ts`, and a column that shadows it there\n * reads as a mistake even where it would compile.\n */\n browser_version: string;\n os_version: string;\n engine_name: string;\n engine_version: string;\n /** `Apple`, `Samsung`, … and the model where the UA states it. Mostly mobile. */\n device_vendor: string;\n device_model: string;\n /**\n * The visitor's viewport in CSS pixels, `0` where it was not reported.\n *\n * The only honest input to a responsive-breakpoint decision. `device_type`\n * says a phone was used; it does not say that a third of the traffic is\n * under 380px wide, which is the number that decides whether the hero's two\n * columns can stay. Reported by the beacon, so it is `0` on every edge and\n * server row by construction.\n */\n viewport_width: number;\n viewport_height: number;\n /**\n * Which Form, by the slug that IS its identity: `flows` rows are matched to\n * the repo by `(companyId, slug)`, so this is the reference, not a label.\n * Present on server-side side-effect rows too — the browser sends it with the\n * dispatch — so a failed CRM delivery names the Form that produced it.\n */\n flow_slug: string;\n /** The `FlowNode.id` in `_data.json` — which step of that Form. */\n step_id: string;\n step_index: number;\n /** The Form node's kind — `link`, `customForm`, `iframe`, `hubspotMeeting`, … */\n node_type: string;\n /** The trigger that fired, verbatim (`link.onClick`, `iframe.checkout_done`). */\n trigger_id: string;\n /**\n * What specifically this was, for the two event types that need to say.\n *\n * `custom` carries the name the page chose; `outbound_click` carries the\n * destination host. Empty everywhere else. Kept apart from `event_type` for\n * the same reason `trigger_id` is: a page can invent names, and an\n * `event_type` that grows with them stops being a closed set.\n */\n event_name: string;\n /**\n * Where a click was actually going, in full.\n *\n * `event_name` holds only the destination *host* — it is `LowCardinality`,\n * and a column of full URLs would defeat that encoding outright. But a host\n * cannot answer the question anyone actually asks: a page with two booking\n * links to `calendly.com` reports both as the same exit, so \"which offer did\n * they leave through\" had no answer at all.\n *\n * Set on `outbound_click` and on a Form link node's trigger. Empty elsewhere.\n */\n target_url: string;\n /** The destination — `metaCapi`, `hubspotForm`, `email`, … */\n side_effect_type: string;\n /**\n * Which configured side effect fired, by its id in the Form definition\n * (`se-meta-001`). This is the join back to what we control: the config that\n * produced this call, not what the visitor typed. Payload values live in\n * `event_payloads` and never here.\n */\n side_effect_id: string;\n /** The event name sent to the destination — `Lead`, `Purchase`, `PageView`. */\n side_effect_name: string;\n /** The Baker tag the side effect was routed through, when it names one. */\n tag_id: string;\n /** Ties a dispatch to its settlement and to its payload. */\n run_id: string;\n /** The shared submit id every destination of one submission carries. */\n event_id: string;\n message_id: string;\n /** Settled rows only: `success` | `failed` | `canceled`. */\n outcome: string;\n /** Settled rows only, truncated. */\n error: string;\n\n /**\n * Who this was, as a SHA-256 digest of a normalized email or phone number.\n *\n * The only column that survives a visitor changing device, and therefore the\n * only way to answer \"is this the same person who came back yesterday\". The\n * cookie id cannot: it is per browser by construction.\n *\n * Never plaintext. {@link normalizeIdentityHash} refuses anything that is not\n * a 64-character hex digest, so a producer that forgets to hash writes an\n * empty column rather than an email address into the stream.\n *\n * Pseudonymous personal data, not anonymous: it is a stable pointer to a\n * person for as long as something maps it back. That mapping lives in the\n * `identities` datasource, and erasing a person means deleting there and in\n * `event_payloads` — after which what is left here is a digest with nothing\n * to resolve it against.\n */\n identity_hash: string;\n /** `email` | `phone` | `external`, so two digests of one person can be told apart. */\n identity_kind: string;\n /**\n * What a server-sent event said about the person, for matching them at a\n * destination: `city`, `postal_code`, `country`, `first_name_sha256`, `fbp`.\n *\n * Meta's advanced matching and Google's enhanced conversions, in one\n * vocabulary — `@baker/api/event-ingest`'s `./matching` owns the mapping and\n * the rule that decides which of these are held hashed. Nothing reports on\n * this; it exists so that an event can be forwarded later without asking the\n * client to send everything a second time.\n *\n * Written only by `POST /v1/events`. Empty for every browser-side producer,\n * which is honest rather than a gap: a landing page has no postcode.\n */\n match_keys: Record<string, string>;\n\n /**\n * What the destination called this record in ITS system — a HubSpot contact\n * id, a Go High Level contact id, a Calendly invitee URI.\n *\n * The missing half of delivery. `outcome = success` says we were not\n * refused; it does not say what was created, so \"the lead is in Baker but\n * the client cannot find it in their CRM\" was previously two systems with no\n * shared key. This is that key.\n */\n provider_ref: string;\n /**\n * Every id the destination returned, including the ones that are not the\n * primary record: `fbtrace_id` for a Meta support ticket, `events_received`,\n * a Calendly event URI alongside its invitee URI.\n *\n * A map because the shape is genuinely per-provider and there is no useful\n * union of it. Identity only — never a value the visitor typed.\n */\n provider_ids: Record<string, string>;\n\n /** `baker` | `external` | `export`, or `\"\"` where the writer cannot know. */\n deployment: string;\n\n engagement_ms: number;\n scroll_depth: number;\n value: number;\n /** ISO-4217, or `\"\"` where the producer had no currency to name. */\n value_currency: string;\n /**\n * What a page-defined event was about, as flat key/value strings.\n *\n * The reason a custom event is worth having at all. `event_name` says\n * `section_view`; without this there is no way to say *which* section, and\n * every page ends up encoding the answer into the name — `section_view_hero`,\n * `section_view_pricing` — which destroys the grouping the name existed to\n * provide and grows `event_name`'s `LowCardinality` dictionary without bound.\n *\n * Bounded on the way in by {@link stringMap}, and for a sharper reason here\n * than for the other maps: the keys come from a landing page, so a page that\n * generated them (`item_${id}`) would write an unbounded dictionary into a\n * `LowCardinality` key column. The cap is the only thing standing between a\n * page author and that.\n *\n * Strings only, deliberately. A number typed by a page is a number of\n * unknown units; the one numeric fact worth aggregating is {@link value},\n * which has a column and a meaning.\n */\n properties: Record<string, string>;\n\n /**\n * The A/B test this visitor was assigned to, and which side of it they saw.\n *\n * Empty on the overwhelming majority of rows, which is why they are two\n * columns rather than entries in {@link AnalyticsEventRow.properties}: a test\n * is read by GROUPing on the variant and FILTERing on the id, and only a\n * column carries the `LowCardinality` encoding that makes those cheap on a\n * table this wide.\n *\n * ## Stamped on every event of an assigned visit, not just the page view\n *\n * This is the decision that makes a pluggable goal possible at all. The edge\n * decides the assignment while serving the HTML and the page echoes it back\n * on every event it later sends, so a scroll, a Form step, a page-defined\n * `request_demo` click and a CRM delivery all carry the same two values.\n *\n * The alternative — a dedicated \"experiment conversion\" event — would have\n * required knowing, at page-build time, which metric a future test would be\n * run against. Every goal an experiment can take (`experiments/goal.ts` in\n * `@baker/api`) is therefore just a predicate over rows that already exist,\n * and adding a new kind of goal needs no change here, in the page, or at the\n * edge.\n *\n * `experiment_id` is eight hex characters; `variant` is `a` for the control\n * and `b` for the variant, and never anything else — a row carrying a third\n * value is a visitor in a bucket nobody sized, so both producers validate\n * rather than pass through.\n */\n experiment_id: string;\n variant: string;\n\n /**\n * Whether this was a machine. The one column every report filters on.\n *\n * Non-human traffic is **stored, never dropped**. A crawler hit is a real\n * fact about a page — it is how the page gets indexed, how a link preview\n * gets its image, and how an AI answer engine learns the offer exists — so\n * discarding it at write time would trade a question we can still ask for a\n * number we could have computed anyway.\n */\n is_bot: number;\n /**\n * Which crawler, by name — `Googlebot`, `GPTBot`, `facebookexternalhit`.\n *\n * `is_bot` alone can only ever exclude. This is what makes the same rows\n * answerable: \"is Google still crawling the page we republished\", \"did Meta\n * fetch the preview for the link we are about to spend on\", \"how much of the\n * traffic is AI answer engines\". Empty when the client is human, and empty\n * for a machine we recognise as one but cannot name.\n */\n bot_name: string;\n /**\n * What kind of machine — `search`, `ai`, `social`, `seo`, `monitoring`,\n * `preview`, `tool`. A class, because the useful questions are asked of\n * classes and the roster inside each one changes constantly.\n */\n bot_category: string;\n /**\n * Retired. Written as `0` on every row, and read by nothing.\n *\n * Both came from Cloudflare Bot Management, which Baker's plan does not\n * include — so they were `0` on every row ever written, which is why they are\n * going. They are still *sent* because the producer and the schema deploy\n * independently: see the constant in `buildAnalyticsRow`, which explains the\n * three-step removal these are in the middle of.\n */\n bot_verified: number;\n bot_score: number;\n /**\n * *How* we decided, out of {@link ANALYTICS_BOT_REASONS}.\n *\n * The other columns say what the machine is; none of them says which signal\n * fired, and without that a misclassification is undebuggable. \"This real\n * customer is marked as a bot\" has eight possible causes with eight different\n * fixes — a pattern in the `isbot` list that is too broad, a header check\n * tripped by an unusual browser, a hosting network that is somebody's\n * employer's VPN — and from the stored row alone they are indistinguishable.\n * This is the only column that separates them, which is why it survived the\n * two that did not.\n *\n * First reason wins, cheapest and most certain first, so this is the signal\n * that *decided* rather than every signal that agreed. Empty on a human.\n */\n bot_reason: string;\n\n /**\n * The commerce columns. Optional, and written only when set: a producer\n * that sets none of them writes exactly the row it always did, and the\n * store defaults each (see `tinybird/definitions.ts`).\n */\n trust?: EventTrust;\n consent_analytics?: ConsentState;\n consent_marketing?: ConsentState;\n commerce_store?: string;\n commerce_customer?: string;\n commerce_order?: string;\n item_product_ids?: string[];\n item_variant_ids?: string[];\n item_feed_ids?: string[];\n item_quantities?: number[];\n item_prices?: number[];\n}\n\n/**\n * Everything the writer knows about the client, shared by every collector.\n *\n * Named for the edge because that is where most of it is measured, but Convex\n * fills it too: a side-effect dispatch carries the visitor's Cloudflare geo on\n * its enriched event, and a delivery that cannot say which country it came\n * from is a lead nobody can put beside the campaign that bought it.\n */\nexport interface AnalyticsEdgeContext {\n country: string;\n region: string;\n /** Cloudflare's city, IANA timezone and network. Empty on a server row. */\n city?: string;\n timezone?: string;\n asn?: number;\n /**\n * The rest of what Cloudflare resolved for free.\n *\n * `postalCode` is the only one a server-side dispatch can fill: the browser\n * reads it back out of the `__cf_geo` cookie the edge already sets, and that\n * cookie carries nothing else in this list. The others stay empty on a\n * server row and are recovered by joining to the session, exactly as `asn`\n * is — the alternative is a wider cookie on every request to save a join\n * nobody is waiting on.\n */\n postalCode?: string;\n regionCode?: string;\n continent?: string;\n metroCode?: string;\n asOrganization?: string;\n /**\n * The company the network names, already resolved and already gated by the\n * edge. Absent whenever the edge did not resolve one — which is every writer\n * but the Worker, and the Worker whenever consent, the company's mode or its\n * switch said no.\n */\n visitorCompany?: { name: string; domain: string; confidence: \"high\" | \"low\" };\n /**\n * The visitor's address, exactly as received.\n *\n * Passed in raw and truncated by {@link buildAnalyticsRow}, not by the\n * caller — the same reason `path` is normalized there. A caller that had to\n * remember to truncate is a caller that will eventually forget, and the\n * mistake would be invisible until someone audited the table.\n */\n ip?: string;\n /**\n * `\"\"` where the writer is not looking at a device — a server-side dispatch.\n * Deliberately not `unknown`, which means \"a visitor we could not classify\".\n */\n deviceType: AnalyticsDeviceType | \"\";\n browser: string;\n os: string;\n /** The narrower client description, where the user agent stated it. */\n browserVersion?: string;\n osVersion?: string;\n engineName?: string;\n engineVersion?: string;\n deviceVendor?: string;\n deviceModel?: string;\n /**\n * Whether this is a machine, from every signal the caller has.\n *\n * Deliberately still a single boolean at this boundary. The detail below\n * says *which* machine and *how we know*, but every report needs one column\n * to filter on, and a report that had to reconstruct \"is this a human\" from\n * four fields would reconstruct it differently in each place.\n */\n isBot: boolean;\n /** The crawler's name and class, where it could be identified. */\n botName?: string;\n botCategory?: string;\n /** Which signal decided it, out of {@link ANALYTICS_BOT_REASONS}. */\n botReason?: AnalyticsBotReason | \"\";\n}\n\n/** The semantic parts of an event, before edge context is folded in. */\nexport interface AnalyticsEventInput {\n eventType: AnalyticsEventType;\n /**\n * The event's identity, if the producer has one it can reproduce. Left unset\n * only where a retry is impossible — the edge page view, which is written\n * once or not at all.\n */\n eventUid?: string;\n /**\n * Where the producer put this event in its own order. See `event_seq`.\n *\n * Untrusted like every other page-supplied number, and clamped rather than\n * rejected: a bad value costs nothing worse than a replay ordering two\n * same-millisecond rows the way it used to.\n */\n eventSeq?: number;\n /**\n * When the page says this happened, in epoch ms — unbounded and untrusted.\n *\n * Carried on the input rather than folded into `timestamp` because deciding\n * whether to believe it needs a second value the row builder does not have:\n * when the batch arrived. The Worker's `occurredAt` bounds the two together\n * and passes the result as `nowMs`.\n */\n clientTsMs?: number;\n source?: AnalyticsSource;\n /**\n * Either a path or the visitor's full href — the Worker holds one, a\n * side-effect dispatch holds the other. {@link buildAnalyticsRow} reduces it\n * to a path and recovers the host and the campaign from it when they were\n * only ever carried inside the URL.\n */\n path: string;\n host?: string;\n /** `internalId` from the landing's `_definition.md`. */\n landingId?: string;\n referrerSource?: string;\n referrerMedium?: string;\n referrerDomain?: string;\n utmSource?: string;\n utmMedium?: string;\n utmCampaign?: string;\n utmContent?: string;\n utmTerm?: string;\n /**\n * The click ids, when the producer holds them as fields rather than only\n * inside the URL. The browser does — it recovers them from its own cookies,\n * so they survive to page three of a visit; the URL only carries them on the\n * landing hit.\n */\n clickIds?: ClickIdFields;\n /** Long-tail campaign parameters the producer already parsed. */\n campaignParams?: Record<string, string>;\n /**\n * Names of parameters the producer saw and could not read. No values.\n *\n * Passed explicitly rather than derived from `path`, because the two\n * producers hold different things: the edge has the whole landing URL, while\n * the beacon sends a bare pathname and reads the query itself. Deriving it\n * here would work for one of them and silently return nothing for the other.\n */\n unmappedParams?: readonly string[];\n /** Where an `outbound_click` or a link node was actually going. */\n targetUrl?: string;\n /** SHA-256 of a normalized email or phone. Refused unless it is a digest. */\n identityHash?: string;\n identityKind?: AnalyticsIdentityKind;\n /**\n * The customer-matching set a server-sent event carried, keyed by how it is\n * held — `city`, `postal_code`, `first_name_sha256`.\n *\n * Only `POST /v1/events` writes it, and only so that an offline-conversion\n * upload to Meta or Google can be built from the event later. The keys are\n * `@baker/api/event-ingest`'s `./matching`, which is also where the rule that\n * decides hashed-or-not is written down. Nothing reports on it.\n */\n matchKeys?: Record<string, string>;\n /** The destination's own id for what it created, and the rest of its ids. */\n providerRef?: string;\n providerIds?: Record<string, string>;\n /**\n * Where the page is served from. Defaults to `baker`.\n *\n * `\"\"` is a distinct, meaningful third answer: a Convex dispatch never sees\n * the page, so it cannot know. Defaulting THAT to `baker` would make an\n * external site's deliveries indistinguishable from Baker-hosted ones, which\n * is the very confusion this column exists to remove. The session reducer\n * fills it in from the client rows of the same visit, which do know.\n */\n deployment?: AnalyticsDeployment | \"\";\n flowSlug?: string;\n stepId?: string;\n stepIndex?: number;\n nodeType?: string;\n triggerId?: string;\n eventName?: string;\n environment?: string;\n release?: string;\n anonId?: string;\n sessionId?: string;\n /** The Form execution this event belongs to, when it is part of one. */\n attemptId?: string;\n sideEffectType?: string;\n sideEffectId?: string;\n sideEffectName?: string;\n tagId?: string;\n runId?: string;\n eventId?: string;\n messageId?: string;\n outcome?: string;\n error?: string;\n engagementMs?: number;\n scrollDepth?: number;\n value?: number;\n /** The currency {@link value} is in, ISO-4217. Only a producer that has one sends it. */\n valueCurrency?: string;\n /** Flat key/value detail for a page-defined event. Bounded on the way in. */\n properties?: Record<string, string>;\n /**\n * The A/B assignment this visit is carrying, if any.\n *\n * Supplied by whichever collector knows it: the edge, which made the\n * assignment while serving the page, or the beacon, which read it back out of\n * the cookie the edge set. Refused rather than trusted — see\n * {@link buildAnalyticsRow}.\n */\n experimentId?: string;\n variant?: string;\n /** The visitor's viewport in CSS pixels, from the beacon only. */\n viewportWidth?: number;\n viewportHeight?: number;\n /** Set by a commerce door, never by a sender. See {@link EVENT_TRUST_CLASSES}. */\n trust?: EventTrust;\n /** What the person allowed, as the door recorded it. Enforced by the builder: see `withConsent`. */\n consentAnalytics?: ConsentState;\n consentMarketing?: ConsentState;\n /** The store, customer and order a commerce fact is about — numeric ids, so a privacy request can reach it. */\n commerceStore?: string;\n commerceCustomer?: string;\n commerceOrder?: string;\n /** What was bought. Cut to {@link EVENT_ITEMS_MAX} lines. */\n items?: readonly AnalyticsItem[];\n}\n\n/**\n * Render an instant the way the `timestamp DateTime64(3)` column reads it.\n *\n * Measured against a real instance, not assumed: the Events API accepts\n * `YYYY-MM-DD HH:MM:SS.mmm`, the ISO form with its `T` and `Z`, and an integer\n * of epoch milliseconds, and stores all three as the same instant. The integer\n * is only unambiguous because the column's precision is 3 — on a plain\n * `DateTime` the same number would read as epoch *seconds* — so this returns\n * the explicit form and leaves nothing resting on that precision.\n */\nexport function toClickHouseTimestamp(msSinceEpoch: number): string {\n return new Date(msSinceEpoch).toISOString().replace(\"T\", \" \").replace(\"Z\", \"\");\n}\n\n/**\n * The widest an event's id may be: the store cuts it here, and the cut is\n * silent, so anything that builds one has to fit inside it or two events\n * become one. `uniqExact(event_uid)` is how an event is counted once and how\n * a send is claimed, so a collision loses an event and an upload alike.\n */\nexport const EVENT_UID_MAX = 64;\n\nfunction text(value: string | undefined, max: number): string {\n if (typeof value !== \"string\" || value === \"\") return \"\";\n return value.length > max ? value.slice(0, max) : value;\n}\n\nfunction whole(value: number | undefined, max: number): number {\n if (typeof value !== \"number\" || !Number.isFinite(value) || value < 0) return 0;\n return Math.min(Math.round(value), max);\n}\n\n/**\n * The campaign fields the caller set explicitly, with the unset ones removed.\n *\n * Spreading the input directly would let an absent field overwrite one\n * recovered from the URL with `undefined`, so the browser's own campaign\n * columns — which are the authoritative ones, held for the whole session —\n * have to be filtered before they win.\n */\nfunction definedUtm(input: AnalyticsEventInput): UtmFields {\n const utm: UtmFields = {};\n if (input.utmSource) utm.utmSource = input.utmSource;\n if (input.utmMedium) utm.utmMedium = input.utmMedium;\n if (input.utmCampaign) utm.utmCampaign = input.utmCampaign;\n if (input.utmContent) utm.utmContent = input.utmContent;\n if (input.utmTerm) utm.utmTerm = input.utmTerm;\n return utm;\n}\n\n/**\n * The click ids the caller set explicitly, with the unset ones removed.\n *\n * Same hazard as {@link definedUtm}: spreading the input directly would let an\n * absent field overwrite one recovered from the URL with `undefined`. Here it\n * matters more, because the browser's copy is the *better* one — it comes from\n * a cookie that outlives the landing URL — so an undefined must never win.\n */\nfunction definedClickIds(input: AnalyticsEventInput): ClickIdFields {\n const ids: ClickIdFields = {};\n const source = input.clickIds;\n if (!source) return ids;\n if (source.gclid) ids.gclid = source.gclid;\n if (source.wbraid) ids.wbraid = source.wbraid;\n if (source.gbraid) ids.gbraid = source.gbraid;\n if (source.dclid) ids.dclid = source.dclid;\n if (source.fbclid) ids.fbclid = source.fbclid;\n if (source.ttclid) ids.ttclid = source.ttclid;\n if (source.twclid) ids.twclid = source.twclid;\n if (source.liFatId) ids.liFatId = source.liFatId;\n if (source.msclkid) ids.msclkid = source.msclkid;\n if (source.rdtCid) ids.rdtCid = source.rdtCid;\n if (source.epik) ids.epik = source.epik;\n if (source.sccid) ids.sccid = source.sccid;\n if (source.oppref) ids.oppref = source.oppref;\n return ids;\n}\n\n/** Matches the width the map's values are stored at, and caps how many there are. */\nconst MAX_MAP_ENTRIES = 24;\nconst MAX_MAP_VALUE = 255;\n\n/**\n * A string map, bounded on the way in.\n *\n * `campaign_params` comes off a URL a stranger controls and `provider_ids`\n * comes off a third party's response body, so neither is trusted to be small.\n * An unbounded map is a row of unbounded width, and ClickHouse would take it.\n */\nfunction stringMap(value: Record<string, string> | undefined): Record<string, string> {\n if (!value) return {};\n const out: Record<string, string> = {};\n let count = 0;\n for (const [key, entry] of Object.entries(value)) {\n if (count >= MAX_MAP_ENTRIES) break;\n if (typeof entry !== \"string\" || entry === \"\" || key === \"\") continue;\n out[key.slice(0, 64)] = entry.slice(0, MAX_MAP_VALUE);\n count += 1;\n }\n return out;\n}\n\n/** Fold an event and the edge's view of the client into one storable row. */\nexport interface AnalyticsRowOptions {\n /**\n * The company's own parameter spellings, from its mapping.\n *\n * Passed in rather than read here because this module has no access to\n * anything company-scoped by design — it ships to the edge. The Worker gets\n * them alongside the collection policy it already reads from KV, so a\n * mapping saved in the dashboard widens capture within seconds instead of at\n * the next publish.\n */\n adParamNames?: readonly string[];\n}\n\n/**\n * The assignment pair, kept together so a row can never carry half of one.\n *\n * An id with no variant cannot be counted into an arm, and a variant with no id\n * does not say which test it belongs to. Either alone is a row that joins to\n * nothing and inflates nothing — but it also reads as a bug forever, because\n * nothing downstream would ever look at it. Dropping both is unambiguous.\n */\nfunction experimentColumns(\n experimentId: string | undefined,\n variant: string | undefined,\n): { experiment_id: string; variant: string } {\n const empty = { experiment_id: \"\", variant: \"\" };\n if (typeof experimentId !== \"string\" || typeof variant !== \"string\") return empty;\n if (!isExperimentId(experimentId) || !isExperimentVariant(variant)) return empty;\n return { experiment_id: experimentId, variant };\n}\n\n/** A value from a closed set, or nothing — like {@link experimentColumns}, a forged value is dropped rather than stored. */\nfunction oneOf<T extends string>(value: string | undefined, allowed: readonly T[]): T | undefined {\n return (allowed as readonly string[]).includes(value ?? \"\") ? (value as T) : undefined;\n}\n\n/**\n * The commerce columns, each only when it has something to say. Every item\n * array is written from the one list, so they stay aligned by position.\n */\nfunction commerceColumns(input: AnalyticsEventInput): Partial<AnalyticsEventRow> {\n const columns: Partial<AnalyticsEventRow> = {};\n const trust = oneOf(input.trust, EVENT_TRUST_CLASSES);\n if (trust) columns.trust = trust;\n const analytics = oneOf(input.consentAnalytics, CONSENT_STATES);\n if (analytics) columns.consent_analytics = analytics;\n const marketing = oneOf(input.consentMarketing, CONSENT_STATES);\n if (marketing) columns.consent_marketing = marketing;\n const store = text(input.commerceStore, 255);\n if (store) columns.commerce_store = store;\n const customer = text(input.commerceCustomer, 255);\n if (customer) columns.commerce_customer = customer;\n const order = text(input.commerceOrder, 255);\n if (order) columns.commerce_order = order;\n const items = (input.items ?? []).slice(0, EVENT_ITEMS_MAX);\n if (items.length > 0) {\n columns.item_product_ids = items.map((item) => text(item.productId, 255));\n columns.item_variant_ids = items.map((item) => text(item.variantId, 255));\n columns.item_feed_ids = items.map((item) => text(item.feedId, 255));\n columns.item_quantities = items.map((item) => whole(item.quantity, 1_000_000));\n columns.item_prices = items.map((item) =>\n typeof item.price === \"number\" && Number.isFinite(item.price) && item.price >= 0 ? item.price : 0,\n );\n }\n return columns;\n}\n\nconst NOBODY = {\n anon_id: \"\",\n session_id: \"\",\n identity_hash: \"\",\n identity_kind: \"\",\n gclid: \"\",\n wbraid: \"\",\n gbraid: \"\",\n dclid: \"\",\n fbclid: \"\",\n ttclid: \"\",\n twclid: \"\",\n li_fat_id: \"\",\n msclkid: \"\",\n rdt_cid: \"\",\n epik: \"\",\n sccid: \"\",\n match_keys: {},\n} satisfies Partial<AnalyticsEventRow>;\n\n/**\n * What the person allowed, applied to the finished row — the builder's last\n * step, after every field has been derived, so nothing it recovered on the way\n * (a click id read off the URL, above all) can put back a person who did not\n * allow it.\n *\n * - Analytics refused, or an operational row: nobody — no browser, no visit,\n * no digest, no click id, no match keys. The campaign stays: it describes\n * the ad, not the person, and a sale must stay attributable to one.\n * - Marketing refused (or unsaid on a store's row): no match keys — the\n * contact digests and Meta's cookies an upload would be matched on. Baker's\n * own analytics of who bought, and from which click, stays.\n *\n * A row with no consent and no store ref passes untouched, which is every row\n * every older producer writes.\n */\nfunction withConsent(row: AnalyticsEventRow): AnalyticsEventRow {\n if (!rowAllows(row, \"identify\")) return { ...row, ...NOBODY };\n if (!rowAllows(row, \"link\")) return { ...row, match_keys: {} };\n return row;\n}\n\nexport function buildAnalyticsRow(\n companyId: string,\n input: AnalyticsEventInput,\n edge: AnalyticsEdgeContext,\n nowMs: number,\n options: AnalyticsRowOptions = {},\n): AnalyticsEventRow {\n return withConsent(assembleRow(companyId, input, edge, nowMs, options));\n}\n\nfunction assembleRow(\n companyId: string,\n input: AnalyticsEventInput,\n edge: AnalyticsEdgeContext,\n nowMs: number,\n options: AnalyticsRowOptions,\n): AnalyticsEventRow {\n // The three page fields are derived together, because a producer that holds\n // a full href holds all three of them at once and used to store only the\n // first, misspelled. Doing it here rather than in each caller is what makes\n // it impossible to add a fourth writer that gets it wrong again.\n const path = normalizePath(input.path);\n const host = normalizeHost(input.host) || normalizeHost(input.path);\n const utm = { ...readUtmFromUrl(input.path), ...definedUtm(input) };\n // The URL is the floor, not the ceiling. It is the only source a server-side\n // dispatch has, and the only one at all on the very first request of a visit\n // — but the browser's cookie copy outlives the landing URL, so it wins where\n // it exists. Same precedence as the campaign above, for the same reason.\n const clickIds = { ...readClickIdsFromUrl(input.path), ...definedClickIds(input) };\n const adParamNames = options.adParamNames ?? [];\n const campaignParams = {\n ...readCampaignParamsFromUrl(input.path, adParamNames),\n ...stringMap(input.campaignParams),\n };\n const identityHash = normalizeIdentityHash(input.identityHash);\n\n return {\n timestamp: toClickHouseTimestamp(nowMs),\n event_uid: text(input.eventUid, EVENT_UID_MAX),\n // Capped at the column's own width. A page that manages to emit four\n // billion events has other problems, and saturating is better than\n // wrapping: a wrapped counter would sort the newest event first.\n event_seq: whole(input.eventSeq, 4_294_967_295),\n company_id: companyId,\n // Beacon and edge events are the client and edge sides; anything naming a\n // side-effect run came from the server.\n source: input.source ?? (input.eventType === \"page_view_edge\" ? \"edge\" : \"client\"),\n event_type: input.eventType,\n environment: text(input.environment, 20),\n release: text(input.release, 64),\n session_id: text(input.sessionId, 64),\n anon_id: text(input.anonId, 64),\n attempt_id: text(input.attemptId, 128),\n landing_id: text(input.landingId, 120),\n path,\n host,\n referrer_source: text(input.referrerSource, 120),\n referrer_medium: text(input.referrerMedium, 60),\n referrer_domain: normalizeHost(input.referrerDomain),\n utm_source: text(utm.utmSource, 255),\n utm_medium: text(utm.utmMedium, 255),\n utm_campaign: text(utm.utmCampaign, 255),\n utm_content: text(utm.utmContent, 255),\n utm_term: text(utm.utmTerm, 255),\n gclid: text(clickIds.gclid, 255),\n wbraid: text(clickIds.wbraid, 255),\n gbraid: text(clickIds.gbraid, 255),\n dclid: text(clickIds.dclid, 255),\n fbclid: text(clickIds.fbclid, 255),\n ttclid: text(clickIds.ttclid, 255),\n twclid: text(clickIds.twclid, 255),\n li_fat_id: text(clickIds.liFatId, 255),\n msclkid: text(clickIds.msclkid, 255),\n rdt_cid: text(clickIds.rdtCid, 255),\n epik: text(clickIds.epik, 255),\n sccid: text(clickIds.sccid, 255),\n oppref: text(clickIds.oppref, 255),\n campaign_params: campaignParams,\n // Read off the URL rather than diffed against what was captured: a name\n // the beacon sent under `campaignParams` was recognised by the page, and\n // reporting it as undiscovered would send the company chasing a mapping\n // that already exists.\n unmapped_params: input.unmappedParams\n ? [...new Set(input.unmappedParams)].slice(0, 12).map((name) => name.slice(0, 40))\n : readUnmappedParamNames(input.path, adParamNames),\n country: text(edge.country, 8),\n region: text(edge.region, 120),\n region_code: text(edge.regionCode, 16),\n city: text(edge.city, 120),\n postal_code: text(edge.postalCode, 24),\n continent: text(edge.continent, 4),\n metro_code: text(edge.metroCode, 8),\n timezone: text(edge.timezone, 60),\n asn: whole(edge.asn, 4_294_967_295),\n as_organization: text(edge.asOrganization, 120),\n visitor_company_name: text(edge.visitorCompany?.name, 120),\n visitor_company_domain: text(edge.visitorCompany?.domain, 255),\n visitor_company_confidence: edge.visitorCompany ? edge.visitorCompany.confidence : \"\",\n // Truncated here rather than by the caller, so no producer can write an\n // exact address into this table by forgetting to.\n ip_truncated: truncateIp(edge.ip),\n device_type: edge.deviceType,\n browser: text(edge.browser, 60),\n os: text(edge.os, 60),\n browser_version: text(edge.browserVersion, 24),\n os_version: text(edge.osVersion, 24),\n engine_name: text(edge.engineName, 24),\n engine_version: text(edge.engineVersion, 24),\n device_vendor: text(edge.deviceVendor, 32),\n device_model: text(edge.deviceModel, 60),\n // Clamped well above any real display and well below anything that would\n // make the column meaningless. A browser cannot be trusted to send a sane\n // number — the beacon reads it from `window`, but the Worker's validator\n // accepts whatever arrives, and this is the last place to bound it.\n viewport_width: whole(input.viewportWidth, 16_384),\n viewport_height: whole(input.viewportHeight, 16_384),\n flow_slug: text(input.flowSlug, 120),\n step_id: text(input.stepId, 120),\n step_index: whole(input.stepIndex, 1000),\n node_type: text(input.nodeType, 40),\n trigger_id: text(input.triggerId, 120),\n event_name: text(input.eventName, 120),\n target_url: text(input.targetUrl, 2048),\n side_effect_type: text(input.sideEffectType, 60),\n side_effect_id: text(input.sideEffectId, 120),\n side_effect_name: text(input.sideEffectName, 120),\n tag_id: text(input.tagId, 64),\n run_id: text(input.runId, 128),\n event_id: text(input.eventId, 128),\n message_id: text(input.messageId, 255),\n outcome: text(input.outcome, 20),\n // The destination's own words, and only those. Producers reach this column\n // with whatever their runtime threw — Convex wraps an action's throw as\n // `Uncaught Error: … at …` — and two dashboard surfaces print this field to\n // a marketer verbatim, so the wrapper and its stack frames come off here,\n // where every writer of the column passes, rather than at each writer.\n error: text(refusalText(input.error), MAX_REFUSAL_CHARS),\n // Refused unless it really is a digest: a producer that forgets to hash\n // writes nothing rather than an email address.\n identity_hash: identityHash,\n // Never a kind without a digest to describe: a row saying \"this was an\n // email\" with nothing hashed is a row that claims an identity it does not\n // have, and every count of identified visitors would include it.\n identity_kind: identityHash === \"\" ? \"\" : text(input.identityKind, 10),\n // A map rather than a column each, for `campaign_params`' reason: nothing\n // filters or groups on a postcode, it is read back whole when an event is\n // forwarded, and a column per matching field would be a migration every\n // time a destination invents one.\n match_keys: stringMap(input.matchKeys),\n provider_ref: text(input.providerRef, 255),\n provider_ids: stringMap(input.providerIds),\n // Defaults to Baker's own hosting, which is what every existing build is\n // and what every existing row was written from.\n deployment: input.deployment ?? \"baker\",\n engagement_ms: whole(input.engagementMs, 86_400_000),\n scroll_depth: whole(input.scrollDepth, 100),\n // The one field that may legitimately be negative — a refund, a negative\n // margin — so it does not go through `whole`.\n value: typeof input.value === \"number\" && Number.isFinite(input.value) ? input.value : 0,\n // Uppercased and bounded rather than checked against ISO-4217 here: this\n // builder has no vocabulary and every writer that has a currency to send\n // has already validated it (`validateEventValue` on the server ingest). A\n // page-defined `value` has no currency at all and writes \"\".\n value_currency: text(input.valueCurrency?.toUpperCase(), 3),\n properties: stringMap(input.properties),\n // Both or neither, and only in the exact shapes the assignment can take.\n //\n // Validated here rather than trusted for the same reason `identity_hash`\n // refuses a non-digest: the collector is reachable by anything that can\n // POST, and these two columns decide which arm a conversion is counted in.\n // A forged or corrupted pair does not merely add noise — it moves real\n // conversions between arms, which is the one kind of error the statistics\n // downstream cannot detect or survive. Writing an empty pair instead leaves\n // the visit out of the test, which costs the test some traffic and costs\n // the result nothing.\n ...experimentColumns(input.experimentId, input.variant),\n is_bot: edge.isBot ? 1 : 0,\n // Only ever set alongside `is_bot`. A name on a row that reports itself as\n // human is a contradiction the reports would have to arbitrate, and the\n // arbitration would differ per report; refusing it here means there is\n // nothing to arbitrate.\n bot_name: edge.isBot ? text(edge.botName, 60) : \"\",\n bot_category: edge.isBot ? text(edge.botCategory, 20) : \"\",\n // Retired, still emitted, deliberately constant.\n //\n // Both read Cloudflare Bot Management, which our plan does not include, so\n // both were already `0` on every row ever written. Nothing computes them\n // any more — but the Worker and Tinybird deploy from two workflows that\n // fire on the same push to `main` and do not wait for each other, so a\n // release that stopped sending them could reach production before the\n // release that gives the columns a default. Every edge row written in that\n // window would be quarantined for omitting a column with no `DEFAULT`.\n //\n // A constant costs one key per row and removes the ordering question\n // entirely. Delete these once the `.default(0)` is live, and drop the\n // columns themselves after that — three steps, because two of them race.\n bot_verified: 0,\n bot_score: 0,\n // Tied to the flag for the same reason the name is: a reason on a row that\n // reports itself as human describes a decision that was not taken.\n bot_reason: edge.isBot ? text(edge.botReason, 12) : \"\",\n ...commerceColumns(input),\n };\n}\n\n/** Tinybird's Events API takes newline-delimited JSON, one row per line. */\nexport function rowsToNdjson(rows: readonly AnalyticsEventRow[]): string {\n return rows.map((row) => JSON.stringify(row)).join(\"\\n\");\n}\n\n/**\n * One observation that a browser and an identity belong together.\n *\n * Separate from `events` for the same reason `event_payloads` is: this is the\n * table that makes a digest re-identifiable, and it is therefore the table an\n * erasure request has to empty. Keep it apart and erasure is a delete here\n * plus a delete in `event_payloads`, after which the digests left on the event\n * stream point at nobody. Fold it into `events` and erasure means rewriting\n * the stream, which a MergeTree does not do.\n *\n * Append-only and deliberately not deduplicated on write: the same pair is\n * observed on every submission, and collapsing at read (`GROUP BY`) is cheap\n * where collapsing at write would need a `ReplacingMergeTree` and a `FINAL`.\n * The repetition is also the signal — it dates the relationship.\n */\nexport interface AnalyticsIdentityRow {\n /** `YYYY-MM-DD HH:MM:SS.mmm`, UTC. */\n timestamp: string;\n company_id: string;\n /** Lowercase hex SHA-256 of a normalized email or phone number. */\n identity_hash: string;\n /** `email` | `phone`. */\n identity_kind: string;\n /**\n * Which normalization rule produced {@link identity_hash} — `1` or `2`.\n *\n * A digest cannot be recomputed, so a change to the rule splits one person\n * into two digests with nothing recording which rule made which. v1 is the\n * beacon's original rule and v2 is the strict one; `@baker/api/identity`\n * owns both. Every row written so far is v1, which is why the column\n * defaults to 1 rather than to 0.\n */\n normalization_version: number;\n /**\n * The same identity hashed the way Meta, LinkedIn and Twitter hash it, or\n * `\"\"` when it is not known.\n *\n * A provider form, not a version: a second digest of the *same* canonical\n * value, which cannot be derived from {@link identity_hash} because deriving\n * it would mean unhashing. Nothing sends it yet — the column exists so the\n * deploy that adds it is separate from the deploy that starts filling it.\n */\n meta_sha256: string;\n /**\n * The same identity hashed the way Google Ads hashes it, or `\"\"` when it is\n * not known. Differs from {@link meta_sha256} by the `+` on a phone and by\n * the Gmail canonical form on an address. Nothing sends it yet.\n */\n google_sha256: string;\n /**\n * The browser that gave it.\n *\n * This is the whole point of the table. Every anonymous session that browser\n * produced before it identified itself becomes attributable to a person by\n * joining `events.anon_id` through here — which is the only way to answer\n * \"how long did they research before they converted\".\n */\n anon_id: string;\n session_id: string;\n /** `client` or `server`, matching the event stream's vocabulary. */\n source: string;\n /** Where the identification happened, so it can be attributed like anything else. */\n landing_id: string;\n flow_slug: string;\n environment: string;\n}\n\nexport interface AnalyticsIdentityInput {\n identityHash: string;\n identityKind?: AnalyticsIdentityKind;\n /** Defaults to 1 — the rule every digest observed so far was made with. */\n normalizationVersion?: 1 | 2;\n /** Must already be a digest; anything else is dropped, never stored. */\n metaSha256?: string;\n /** Must already be a digest; anything else is dropped, never stored. */\n googleSha256?: string;\n anonId?: string;\n sessionId?: string;\n source?: AnalyticsSource;\n landingId?: string;\n flowSlug?: string;\n environment?: string;\n}\n\n/**\n * Build an identity observation, or refuse to.\n *\n * Returns `null` unless there is both a real digest and a browser to tie it\n * to: a row with one and not the other maps nothing to nothing and would only\n * inflate the counts of how many people we can recognise.\n */\nexport function buildIdentityRow(\n companyId: string,\n input: AnalyticsIdentityInput,\n nowMs: number,\n): AnalyticsIdentityRow | null {\n const identityHash = normalizeIdentityHash(input.identityHash);\n const anonId = text(input.anonId, 64);\n if (identityHash === \"\" || anonId === \"\") return null;\n\n return {\n timestamp: toClickHouseTimestamp(nowMs),\n company_id: companyId,\n identity_hash: identityHash,\n identity_kind: text(input.identityKind, 10),\n normalization_version: input.normalizationVersion ?? 1,\n // Through the same guard as `identity_hash`: a producer that forgets to\n // hash writes an empty column rather than an address into the store.\n meta_sha256: normalizeIdentityHash(input.metaSha256),\n google_sha256: normalizeIdentityHash(input.googleSha256),\n anon_id: anonId,\n session_id: text(input.sessionId, 64),\n source: input.source ?? \"client\",\n landing_id: text(input.landingId, 120),\n flow_slug: text(input.flowSlug, 120),\n environment: text(input.environment, 20),\n };\n}\n\n/** Same wire format as {@link rowsToNdjson}, for the identity datasource. */\nexport function identityRowsToNdjson(rows: readonly AnalyticsIdentityRow[]): string {\n return rows.map((row) => JSON.stringify(row)).join(\"\\n\");\n}\n","/**\n * What each Form trigger means, said as an outcome rather than as an API event.\n *\n * The ONE answer, shared. It used to live in the dashboard, which was right\n * until a second package had to say the same thing: `experiments/goal.ts`\n * renders a test's goal for the same marketer, on a card that sits beside the\n * Conversions screen, and a private copy there produced a third vocabulary for\n * the same events — \"step reached\" against \"Screen shown\", \"booking made\"\n * against \"Call booked\" — plus a de-camelCase for everything the copy had not\n * thought of. That de-camelCase is exactly what this table was written to\n * replace, so the copy reintroduced the defect it was modelled on.\n *\n * Free of every import, so the Convex runtime, the CLI and the dashboard all\n * read one table.\n */\n\nexport interface TriggerDescription {\n what: string;\n meaning: string;\n /** Fires on its own, without the visitor deciding anything. */\n lifecycle?: true;\n}\n\n/**\n * What each trigger means, said as an outcome rather than as an API event.\n *\n * \"Form submitted\", not `customForm.onFormSubmitted`. A marketer choosing what\n * to count should never have to know an embed's API, which is the reason the\n * previous design's per-trigger checkbox list needed a paragraph of explanation\n * above it.\n *\n * `lifecycle` is the field that earns this table its second column. Four of\n * HubSpot's events are named almost identically and only one of them is an\n * outcome; renaming them more carefully was tried and does not work, because\n * \"Form about to submit\" and \"Form submitted\" are genuinely a hair apart in\n * English and a chasm apart in meaning. The sentence says which is which, and\n * the flag lets a picker put the other three away.\n *\n * Anything absent falls back to the raw trigger id with no sentence. That is\n * deliberate and not a gap: the `iframe.<name>` family is named by whatever a\n * client's embedded page posts, so the raw name IS the best available\n * description of it, and inventing a friendlier one would be inventing a\n * meaning.\n */\nconst TRIGGERS: Readonly<Record<string, TriggerDescription>> = {\n \"calendlyEvent.eventScheduled\": {\n what: \"Call booked\",\n meaning: \"Somebody picked a slot and confirmed it. This is the booking.\",\n },\n \"calendlyEvent.dateAndTimeSelected\": {\n what: \"Time picked\",\n meaning: \"Somebody chose a slot but has not confirmed the booking yet.\",\n },\n \"calendlyEvent.eventTypeViewed\": {\n what: \"Calendar opened\",\n meaning: \"The calendar appeared. Nobody has picked a time yet.\",\n lifecycle: true,\n },\n \"calendlyEvent.profilePageViewed\": {\n what: \"Booking page shown\",\n meaning: \"Calendly's own page appeared. Nobody has picked a time yet.\",\n lifecycle: true,\n },\n \"customForm.onFormSubmitted\": {\n what: \"Form submitted\",\n meaning: \"Somebody filled this step in and sent it.\",\n },\n \"highlevelEvent.appointmentScheduled\": {\n what: \"Appointment booked\",\n meaning: \"Somebody picked a slot and confirmed it. This is the booking.\",\n },\n \"highlevelEvent.calendarLoaded\": {\n what: \"Calendar finished loading\",\n meaning: \"The calendar appeared. Nobody has picked a time yet.\",\n lifecycle: true,\n },\n \"highlevelFormEvent.formSubmitted\": {\n what: \"Form submitted\",\n meaning: \"Somebody filled the form in and sent it.\",\n },\n \"highlevelFormEvent.formLoaded\": {\n what: \"Form finished loading\",\n meaning: \"The form appeared. Nobody has typed anything yet.\",\n lifecycle: true,\n },\n \"hubspotForm.onFormSubmitted\": {\n what: \"Form submitted\",\n meaning: \"Somebody filled the form in and sent it. This is the one that means a lead.\",\n },\n \"hubspotForm.onBeforeFormSubmit\": {\n what: \"Form about to be sent\",\n meaning: \"Fires an instant before the form is sent — not the same as it arriving.\",\n lifecycle: true,\n },\n \"hubspotForm.onFormReady\": {\n what: \"Form finished loading\",\n meaning: \"The form appeared. Nobody has typed anything yet.\",\n lifecycle: true,\n },\n \"hubspotForm.onBeforeFormInit\": {\n what: \"Form starting to load\",\n meaning: \"Fires before the form has even drawn. It only means the page was opened.\",\n lifecycle: true,\n },\n \"hubspotMeeting.onMeetingScheduled\": {\n what: \"Meeting booked\",\n meaning: \"Somebody picked a slot and confirmed it. This is the booking.\",\n },\n \"link.onClick\": {\n what: \"Link clicked\",\n meaning: \"Somebody clicked the link on this step and left for wherever it points.\",\n },\n \"savvycalEvent.scheduled\": {\n what: \"Call booked\",\n meaning: \"Somebody picked a slot and confirmed it. This is the booking.\",\n },\n \"text.onTextDisplayed\": {\n what: \"Screen shown\",\n meaning: \"This screen appeared. On a thank-you screen that is the outcome; elsewhere it is not.\",\n },\n \"text.onButtonClicked\": {\n what: \"Button pressed\",\n meaning: \"Somebody pressed the button on this screen.\",\n },\n};\n\n/**\n * One trigger, described — for the surfaces that hold a trigger id rather than\n * an event key.\n *\n * The replay and the events feed both had their own answer before this: a\n * `triggerLabel` that de-camelCased the raw id, so `hubspotForm.onBeforeFormInit`\n * reached a marketer's screen as **\"Before form init\"** and\n * `text.onTextDisplayed` as **\"Text displayed\"**. Both are engineering internals\n * in the product's voice, and — worse — they were a *second* vocabulary: the\n * Conversions picker called the same event \"Form starting to load\" on the\n * screen next door.\n *\n * So the table is exported and there is one answer. The mechanical fallback\n * stays where it belongs, at the call site, for `iframe.<whatever the embed\n * posts>`: there the raw name really is the best description there is.\n */\nexport function triggerDescription(triggerId: string): TriggerDescription | undefined {\n return TRIGGERS[triggerId];\n}\n","/**\n * The rows the store keeps so a *report* can be computed, which are not events.\n *\n * ## Why this is shared and not a dashboard detail\n *\n * Two readers ask the same question of the same rows. The Events tab shows\n * them to a marketer; `baker analytics feed` hands them to an agent, which\n * reads them and then tells a marketer what happened. Only the first one had\n * this rule, so the feed a person saw and the feed an agent saw were different\n * lists — and the agent's was the noisier one, in a context window where noise\n * is paid for by the token and crowds out the rows it was called to find.\n *\n * Measured on one production account over 25 days: **559 of its 573 trigger\n * rows** were an embedded HubSpot form announcing that it had drawn, against\n * **7** where somebody actually filled one in. An agent asked \"did anything\n * arrive today\" read eighty rows of housekeeping for every real answer.\n *\n * ## What counts as bookkeeping, and why each one\n *\n * None of these is an event somebody sent or did. Each is either a restatement\n * of the row beside it or a fact about the machinery:\n *\n * - `visitor_new` / `session_start` — written in the same millisecond as the\n * page view that carried them, about the same page view.\n * - `identify` — what the submit beside it *revealed*, not a separate act.\n * - `form_step_view` — a Form reporting that it wired itself up. Every Form on\n * a page emits one on load.\n * - `side_effect_settled` — the destination's answer, already folded onto the\n * dispatch row that asked the question (`event_stream` joins it).\n * - a **submit trigger** — `onFormSubmitted` and its siblings fire because the\n * visitor pressed the button, and the step completion beside them fires\n * because they pressed the same button. Two rows, one press.\n * - an **embed lifecycle trigger** — \"Form starting to load\", \"Form finished\n * loading\", \"Calendar opened\". `triggerDescription` has always known which\n * these are, and the Conversions picker has always used that flag to put\n * them away; the two feeds did not.\n *\n * ## Hidden, not dropped — and that is load-bearing\n *\n * Nothing here is unreachable. Both readers say how many rows they are holding\n * back and both can ask for them (`?raw=1`, `--everything`), because the\n * moment somebody is debugging the collector rather than their integration\n * these are the only rows that matter: an embed that draws and never fires\n * again is a real failure, and its lifecycle rows are the only evidence of it.\n */\n\nimport { STEP_SUBMIT_TRIGGERS } from \"./conversions\";\nimport { triggerDescription } from \"./triggers\";\n\n/** Row shape this rule needs. Deliberately narrower than `AnalyticsTimelineRow`, so a caller can pass either spelling. */\nexport interface FeedNoiseRow {\n eventType: string;\n triggerId: string;\n}\n\nconst BOOKKEEPING_TYPES: ReadonlySet<string> = new Set([\n \"visitor_new\",\n \"session_start\",\n \"identify\",\n \"form_step_view\",\n \"side_effect_settled\",\n]);\n\n/** The triggers a visitor raises BY submitting a step, which the completion beside them reports. */\nconst SUBMIT_TRIGGERS: ReadonlySet<string> = new Set(Object.values(STEP_SUBMIT_TRIGGERS));\n\n/**\n * A trigger nobody raised: the embed reporting on itself.\n *\n * Read through {@link triggerDescription} rather than from a list here,\n * because that table already carries the flag and a second list would be a\n * second thing to remember when an embed's vocabulary grows.\n */\nexport function isEmbedLifecycle(row: FeedNoiseRow): boolean {\n return row.eventType === \"form_trigger\" && triggerDescription(row.triggerId)?.lifecycle === true;\n}\n\n/** Whether this row is machinery rather than something somebody sent or did. */\nexport function isBookkeepingRow(row: FeedNoiseRow): boolean {\n if (BOOKKEEPING_TYPES.has(row.eventType)) return true;\n if (isEmbedLifecycle(row)) return true;\n return row.eventType === \"form_trigger\" && SUBMIT_TRIGGERS.has(row.triggerId);\n}\n","/**\n * Every way one event could be matched to a person — the whole ladder, not the\n * top rung.\n *\n * ## The reading this replaces\n *\n * A stored row reports **one** key: `known_by` is the store's answer to \"what\n * is the best thing on this row\", and by construction it cannot say there were\n * four. Every surface read it as if it were the whole truth, so an event\n * carrying an address, a phone number, the CRM's own contact id *and* the\n * Google click that produced it was reported as **\"By email\"**, and a page view\n * carrying two Google click ids was reported as **\"Not identified\"**.\n *\n * Both readings are wrong in the same direction: they say matching is one\n * question with one answer, when it is a ladder walked in order. The email if\n * there is one, the phone if there is not, the contact id, then the click that\n * brought them, then the platform cookie. The count is the fact that matters —\n * *\"there is more than one way we will find this person again\"* — and it is\n * the difference between an event that survives a missing field and one that\n * is lost by it. Measured on one production account: every deal event read\n * \"By email\" and every one of them carried four keys.\n *\n * ## Why it is here rather than in the dashboard\n *\n * Two readers, and the second is the expensive one. The Events tab shows this\n * in a column; `baker analytics feed` hands the same rows to an agent, which\n * reasons about them and then tells a marketer what happened. An agent given\n * one key concludes the event is fragile, or that two rows are two strangers —\n * the same wrong conclusion the column invited, reached somewhere nobody can\n * see it.\n *\n * ## Derived, never restated\n *\n * The list is built from {@link INGEST_CLICK_ID_PARAMS} and\n * {@link MATCH_COOKIES} rather than written out, because this is the fifth\n * copy of that vocabulary in the repo and the other four have already proved\n * that a hand-written one drifts: `vocabulary.parity.test.ts` exists because\n * an omission here is silent in the worst way — the key arrived, we stored it,\n * and every screen reports that we did not have it.\n */\n\nimport { MATCH_COOKIES } from \"../event-ingest/matching\";\nimport { INGEST_CLICK_ID_PARAMS } from \"../event-ingest/vocabulary\";\n\n/**\n * The keys that name a *person* rather than a browser or a click, best first.\n *\n * `baker_id` is Baker's own visitor id as a sender may pass it back, which is\n * why it is a contact key and not a cookie: a sender that has it took it from\n * a page and is telling us which visitor this is.\n */\nconst CONTACT_KEYS = [\"email\", \"phone\", \"external_id\", \"baker_id\"] as const;\n\n/**\n * Every field a row can be matched on, in the order matching walks them.\n *\n * Contact details first, then the ad click that brought them, then the\n * platform cookies — the same precedence `knownBySql` and `personRowKeySql`\n * apply in the store, so the first entry of this list is the key the store\n * would actually have resolved the row by. A summary that led with a different\n * one would describe a resolution that did not happen.\n */\nexport const MATCH_KEY_FIELDS: readonly string[] = [...CONTACT_KEYS, ...INGEST_CLICK_ID_PARAMS, ...MATCH_COOKIES];\n\n/**\n * What each key is called where a person reads it.\n *\n * The click ids are named by their platform and not by their parameter —\n * \"we know them by their Google Ads click\" is an answer and \"we know them by\n * a `gbraid`\" is a support ticket. The parameter survives in the field name\n * beside it, for anyone debugging the payload rather than reading the row.\n *\n * `external_id` and `baker_id` keep their contract spellings, the same\n * decision `USER_DATA_FIELDS` made and for the same reason: these two are\n * field names a sender types into their own integration, and a friendlier\n * label leaves them checking a screen that never mentions what they sent.\n */\nexport const MATCH_KEY_LABELS: Readonly<Record<string, string>> = {\n email: \"email\",\n phone: \"phone\",\n external_id: \"external ID\",\n baker_id: \"Baker ID\",\n gclid: \"Google click\",\n wbraid: \"Google click (wbraid)\",\n gbraid: \"Google click (gbraid)\",\n dclid: \"Google Display click\",\n fbclid: \"Meta click\",\n ttclid: \"TikTok click\",\n twclid: \"X click\",\n li_fat_id: \"LinkedIn click\",\n msclkid: \"Microsoft click\",\n rdt_cid: \"Reddit click\",\n epik: \"Pinterest click\",\n sccid: \"Snapchat click\",\n oppref: \"ChatGPT Ads click\",\n fbp: \"Meta browser cookie\",\n fbc: \"Meta click cookie\",\n};\n\n/**\n * One key a row carries: the field it arrived under, and what to call it.\n *\n * `RowMatchKey` rather than `MatchKey`, which is taken and means something\n * narrower: {@link MATCH_KEYS} in `sendMatch.ts` is the subset an *ad\n * platform* will accept, and this is every key *Baker* can recognise a row by.\n * The two overlap and must never disagree about a spelling, which\n * `matchKeys.parity.test.ts` holds.\n */\nexport interface RowMatchKey {\n field: string;\n label: string;\n}\n\n/**\n * The three spellings one key arrives in, all of which mean \"present\".\n *\n * A `user_data` map carries a field in the clear, as `<field>_sha256` when it\n * is held as a digest, or as `<field>_preview` when a masked copy was kept for\n * a person to recognise. All three say the key was there; only the first says\n * what it was. Reading just the plain name was how a row holding five digests\n * reported that it held nothing.\n */\nfunction carries(userData: Readonly<Record<string, string>>, field: string): boolean {\n for (const key of [field, `${field}_sha256`, `${field}_preview`]) {\n const value = userData[key];\n if (typeof value === \"string\" && value !== \"\") return true;\n }\n return false;\n}\n\n/**\n * Every key this row could be matched on, in precedence order.\n *\n * Read off the row's own `user_data` — which the store builds by merging\n * `match_keys` with the click-id columns (`userDataSql`) — rather than off\n * `known_by`, which holds one.\n */\nexport function rowMatchKeys(userData: Readonly<Record<string, string>> | undefined): RowMatchKey[] {\n if (userData === undefined) return [];\n return MATCH_KEY_FIELDS.filter((field) => carries(userData, field)).map((field) => ({\n field,\n label: MATCH_KEY_LABELS[field] ?? field,\n }));\n}\n\n/**\n * The keys as one line: the best one, and how many more there are.\n *\n * The first key and a count, never the whole list. A row with an address, a\n * phone and three click ids is one line of a table, and spelling it out pushes\n * the event's own name off the screen on the surface that exists to show it.\n * The count carries the meaning on its own, and the list is one hover away.\n */\nexport function rowMatchSummary(keys: readonly RowMatchKey[], userData?: Readonly<Record<string, string>>): string {\n const [first] = keys;\n if (first === undefined) return \"\";\n const more = keys.length === 1 ? \"\" : ` +${keys.length - 1}`;\n const shown = userData === undefined ? undefined : matchKeyPreview(userData, first.field);\n return shown === undefined ? `By ${first.label}${more}` : `${shown}${more}`;\n}\n\n/**\n * The best key as something a person can recognise: the masked copy the store\n * keeps beside a digest (`m***@icloud.com`), or the value itself for an id\n * the sender posted in the clear. Nothing for a digest with no masked copy,\n * and nothing for a click id or a platform cookie — sixty characters of\n * `gclid` name nobody.\n */\nexport function matchKeyPreview(userData: Readonly<Record<string, string>>, field: string): string | undefined {\n const preview = userData[`${field}_preview`];\n if (typeof preview === \"string\" && preview !== \"\") return preview;\n if (field !== \"external_id\" && field !== \"baker_id\") return undefined;\n const plain = userData[field];\n return typeof plain === \"string\" && plain !== \"\" ? plain : undefined;\n}\n","/**\n * What a campaign URL already says it carries, read back as ad dimensions.\n *\n * A tracking template is the client's own answer to \"what do you want to know\n * about a click\", written in the ad platform's placeholder vocabulary:\n * `?kw={keyword}&mt={matchtype}` says the URLs carry a keyword under `kw` and a\n * match type under `mt`. Baker cannot read either until it is told, and until\n * it is told the values are not stored at all — only the fact that the name\n * appeared.\n *\n * So the answer is already written down, in a place Baker can read, and the\n * step that gets missed is copying it across. This turns the template into the\n * `baker analytics map` call that would make it readable.\n *\n * It proposes nothing about the ad account itself. Every role here is derived\n * from a placeholder the account is *already* sending, which is the difference\n * between naming what arrives and telling a client to retag.\n */\nimport type { AdParamRole, AdPlatform } from \"./adParams\";\nimport { AD_PLATFORM_DEFINITIONS, builtinAdParamMapping, RESERVED_PARAM_NAMES } from \"./adParams\";\n\n/**\n * A placeholder, reduced to the part that identifies it.\n *\n * The platforms delimit their macros three different ways — `{keyword}`,\n * `{{ad.name}}`, `__CID__` — and two of them disagree with themselves about\n * case (`{{adSet.id}}` resolves, `{{adset.id}}` does not). Comparing the raw\n * spelling would therefore need a per-platform parser to say that a template is\n * carrying the macro the platform documents. Stripping the delimiters and the\n * case leaves exactly the identifying part, and it is only ever compared within\n * one platform's own list, so the flattening cannot collide across vocabularies.\n */\nfunction placeholderKey(raw: string): string {\n return raw\n .replace(/[{}]/g, \"\")\n .replace(/^_+|_+$/g, \"\")\n .trim()\n .toLowerCase();\n}\n\n/**\n * Extra Google spellings for placeholders that already have a canonical macro.\n *\n * `AD_PLATFORM_DEFINITIONS` names ONE macro per dimension, because it is shown\n * to a reader as evidence of what the platform can send and a list of four\n * synonyms would be worse evidence, not better. Reading a template is the\n * opposite job: whatever the account actually wrote has to be recognised. Only\n * Google needs this, because only Google has several placeholders resolving to\n * one dimension.\n */\nconst EXTRA_PLACEHOLDER_ROLES: Partial<Record<AdPlatform, Readonly<Record<string, AdParamRole>>>> = {\n google: {\n merchant_id: \"product_id\",\n product_partition_id: \"product_id\",\n },\n};\n\n/**\n * This platform's placeholders, and the dimension each one carries.\n *\n * Derived from the platform's own dimension list rather than kept as a second\n * copy, because the two would drift and the drift is invisible: a macro added\n * to the definitions and not here reads as \"no placeholder explains this\",\n * which is the message that tells somebody not to map their own parameter.\n *\n * A placeholder outside the list is surfaced as unexplained rather than forced\n * into `other`. `{adposition}` and `{loc_physical_ms}` are real Google\n * placeholders with no role in this vocabulary, and a proposal that has to be\n * corrected is worse than one that is not made.\n */\nfunction placeholderRoles(platform: AdPlatform): Readonly<Record<string, AdParamRole>> {\n const roles: Record<string, AdParamRole> = {};\n for (const dimension of AD_PLATFORM_DEFINITIONS[platform].dimensions) {\n roles[placeholderKey(dimension.macro)] = dimension.role;\n }\n return { ...roles, ...(EXTRA_PLACEHOLDER_ROLES[platform] ?? {}) };\n}\n\nexport interface TemplateParam {\n name: string;\n /** The placeholder or literal on the right of the `=`, verbatim. */\n value: string;\n}\n\nexport interface TemplateMappingProposal {\n /** Parameter name → the role its placeholder says it carries. */\n proposed: Record<string, AdParamRole>;\n /**\n * Parameters carrying something no placeholder explains — a literal, or a\n * placeholder outside the vocabulary.\n *\n * Reported rather than guessed at. `agency=acme` is the same shape as\n * `kw={keyword}` and means something entirely different, and only somebody\n * who knows the account can say which of `other` and `ignore` it is.\n */\n unexplained: TemplateParam[];\n}\n\n/**\n * The parameters a template or suffix carries.\n *\n * Everything before a `?` is dropped — a tracking template starts with a\n * redirect and `{lpurl}`, neither of which is a parameter — and a bare suffix\n * with no `?` is read as the query string it is.\n */\nexport function paramsFromTemplate(template: string): TemplateParam[] {\n const start = template.indexOf(\"?\");\n const query = start === -1 ? template : template.slice(start + 1);\n const params: TemplateParam[] = [];\n for (const pair of query.split(\"&\")) {\n const at = pair.indexOf(\"=\");\n if (at < 1) continue;\n const name = pair.slice(0, at).trim();\n if (name === \"\") continue;\n params.push({ name, value: pair.slice(at + 1).trim() });\n }\n return params;\n}\n\n/**\n * What `baker analytics map` would have to be told for this template to be read.\n *\n * Silent about two kinds of parameter, both deliberately. The five UTMs cannot\n * be mapped — they have their own columns and their own report. Anything the\n * platform is already understood to send is already readable, and proposing it\n * again spends a turn to change nothing.\n */\nexport function proposeMappingFromTemplate(template: string, platform: AdPlatform): TemplateMappingProposal {\n const builtIn = builtinAdParamMapping(platform);\n const vocabulary = placeholderRoles(platform);\n const proposed: Record<string, AdParamRole> = {};\n const unexplained: TemplateParam[] = [];\n\n for (const param of paramsFromTemplate(template)) {\n if (RESERVED_PARAM_NAMES.includes(param.name)) continue;\n if (param.name in builtIn) continue;\n const role = vocabulary[placeholderKey(param.value)];\n if (role) {\n proposed[param.name] = role;\n continue;\n }\n unexplained.push(param);\n }\n\n return { proposed, unexplained };\n}\n\n/** The proposal as the command that applies it, or `\"\"` when there is nothing to apply. */\nexport function mappingCommandFor(platform: AdPlatform, proposed: Record<string, AdParamRole>): string {\n const pairs = Object.entries(proposed).map(([name, role]) => `${name}=${role}`);\n if (pairs.length === 0) return \"\";\n return `baker analytics map --platform ${platform} --set ${pairs.join(\",\")}`;\n}\n"],"mappings":"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","names":["z","z","z","z","z","z","z"]}