tapimo 0.10.2 → 0.12.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 (386) hide show
  1. package/dist/App.d.ts +4896 -2166
  2. package/dist/App.d.ts.map +1 -1
  3. package/dist/App.js +2 -2
  4. package/dist/App.js.map +1 -1
  5. package/dist/Assets.d.ts +15 -0
  6. package/dist/Assets.d.ts.map +1 -1
  7. package/dist/Assets.js +7 -0
  8. package/dist/Assets.js.map +1 -1
  9. package/dist/Webhooks.d.ts +0 -75
  10. package/dist/Webhooks.d.ts.map +1 -1
  11. package/dist/Webhooks.js +1 -143
  12. package/dist/Webhooks.js.map +1 -1
  13. package/dist/admin/App.d.ts +127 -78
  14. package/dist/admin/App.d.ts.map +1 -1
  15. package/dist/admin/apps/earn-vaults.d.ts +115 -1
  16. package/dist/admin/apps/earn-vaults.d.ts.map +1 -1
  17. package/dist/admin/apps/earn-vaults.js +59 -6
  18. package/dist/admin/apps/earn-vaults.js.map +1 -1
  19. package/dist/admin/apps/organizations.d.ts +3 -3
  20. package/dist/admin/apps/organizations.d.ts.map +1 -1
  21. package/dist/admin/apps/organizations.js +4 -2
  22. package/dist/admin/apps/organizations.js.map +1 -1
  23. package/dist/admin/apps/verified-tokens.d.ts +83 -83
  24. package/dist/admin/ui.generated.d.ts +1 -1
  25. package/dist/admin/ui.generated.d.ts.map +1 -1
  26. package/dist/admin/ui.generated.js +151 -151
  27. package/dist/admin/ui.generated.js.map +1 -1
  28. package/dist/apps/data/App.d.ts +5924 -4310
  29. package/dist/apps/data/App.d.ts.map +1 -1
  30. package/dist/apps/data/App.js +61 -9
  31. package/dist/apps/data/App.js.map +1 -1
  32. package/dist/apps/data/ZoneSelection.d.ts +62 -0
  33. package/dist/apps/data/ZoneSelection.d.ts.map +1 -0
  34. package/dist/apps/data/ZoneSelection.js +44 -0
  35. package/dist/apps/data/ZoneSelection.js.map +1 -0
  36. package/dist/apps/data/routes/activities.d.ts +2368 -5
  37. package/dist/apps/data/routes/activities.d.ts.map +1 -1
  38. package/dist/apps/data/routes/activities.js +423 -114
  39. package/dist/apps/data/routes/activities.js.map +1 -1
  40. package/dist/apps/data/routes/addresses.d.ts +131 -0
  41. package/dist/apps/data/routes/addresses.d.ts.map +1 -1
  42. package/dist/apps/data/routes/balances.d.ts +25 -25
  43. package/dist/apps/data/routes/balances.js +1 -1
  44. package/dist/apps/data/routes/balances.js.map +1 -1
  45. package/dist/apps/data/routes/blocks.d.ts +1 -1
  46. package/dist/apps/data/routes/blocks.js +1 -1
  47. package/dist/apps/data/routes/blocks.js.map +1 -1
  48. package/dist/apps/data/routes/earn.d.ts +135 -10
  49. package/dist/apps/data/routes/earn.d.ts.map +1 -1
  50. package/dist/apps/data/routes/earn.js +157 -25
  51. package/dist/apps/data/routes/earn.js.map +1 -1
  52. package/dist/apps/data/routes/exchanges.d.ts +829 -123
  53. package/dist/apps/data/routes/exchanges.d.ts.map +1 -1
  54. package/dist/apps/data/routes/exchanges.js +16 -13
  55. package/dist/apps/data/routes/exchanges.js.map +1 -1
  56. package/dist/apps/data/routes/fee-amm.d.ts +26 -26
  57. package/dist/apps/data/routes/fee-amm.d.ts.map +1 -1
  58. package/dist/apps/data/routes/fee-amm.js +9 -11
  59. package/dist/apps/data/routes/fee-amm.js.map +1 -1
  60. package/dist/apps/data/routes/receipts.d.ts +54 -54
  61. package/dist/apps/data/routes/receipts.js +1 -1
  62. package/dist/apps/data/routes/receipts.js.map +1 -1
  63. package/dist/apps/data/routes/rpc.js +24 -10
  64. package/dist/apps/data/routes/rpc.js.map +1 -1
  65. package/dist/apps/data/routes/tokenlist.d.ts +12 -12
  66. package/dist/apps/data/routes/tokenlist.d.ts.map +1 -1
  67. package/dist/apps/data/routes/tokenlist.js +16 -15
  68. package/dist/apps/data/routes/tokenlist.js.map +1 -1
  69. package/dist/apps/data/routes/tokens.d.ts +17 -3
  70. package/dist/apps/data/routes/tokens.d.ts.map +1 -1
  71. package/dist/apps/data/routes/tokens.js +34 -15
  72. package/dist/apps/data/routes/tokens.js.map +1 -1
  73. package/dist/apps/data/routes/transactions.d.ts +1 -1
  74. package/dist/apps/data/routes/transactions.js +1 -1
  75. package/dist/apps/data/routes/transactions.js.map +1 -1
  76. package/dist/apps/data/routes/transfers.d.ts +13 -13
  77. package/dist/apps/data/routes/transfers.d.ts.map +1 -1
  78. package/dist/apps/data/routes/transfers.js +7 -9
  79. package/dist/apps/data/routes/transfers.js.map +1 -1
  80. package/dist/apps/data/routes/valuation.d.ts +21 -21
  81. package/dist/apps/data/routes/verified-tokens.d.ts +5 -5
  82. package/dist/apps/data/routes/webhooks.d.ts +1011 -151
  83. package/dist/apps/data/routes/webhooks.d.ts.map +1 -1
  84. package/dist/apps/data/routes/webhooks.js +126 -739
  85. package/dist/apps/data/routes/webhooks.js.map +1 -1
  86. package/dist/apps/data/routes/zones.d.ts +15 -15
  87. package/dist/apps/funding/App.d.ts +27 -27
  88. package/dist/apps/funding/routes/chains.d.ts.map +1 -1
  89. package/dist/apps/funding/routes/chains.js +5 -4
  90. package/dist/apps/funding/routes/chains.js.map +1 -1
  91. package/dist/apps/funding/routes/deposit-addresses.d.ts +18 -18
  92. package/dist/apps/funding/routes/deposit-addresses.d.ts.map +1 -1
  93. package/dist/apps/funding/routes/deposit-addresses.js +8 -8
  94. package/dist/apps/funding/routes/deposit-addresses.js.map +1 -1
  95. package/dist/apps/funding/routes/providers.d.ts.map +1 -1
  96. package/dist/apps/funding/routes/providers.js +3 -2
  97. package/dist/apps/funding/routes/providers.js.map +1 -1
  98. package/dist/apps/funding/routes/quotes.d.ts.map +1 -1
  99. package/dist/apps/funding/routes/quotes.js +2 -2
  100. package/dist/apps/funding/routes/quotes.js.map +1 -1
  101. package/dist/apps/funding/routes/transfers.d.ts +33 -33
  102. package/dist/apps/management/App.d.ts +1433 -322
  103. package/dist/apps/management/App.d.ts.map +1 -1
  104. package/dist/apps/management/Billing.d.ts.map +1 -1
  105. package/dist/apps/management/Billing.js +19 -6
  106. package/dist/apps/management/Billing.js.map +1 -1
  107. package/dist/apps/management/routes/api-keys.d.ts +154 -94
  108. package/dist/apps/management/routes/api-keys.d.ts.map +1 -1
  109. package/dist/apps/management/routes/api-keys.js +76 -30
  110. package/dist/apps/management/routes/api-keys.js.map +1 -1
  111. package/dist/apps/management/routes/billing.d.ts +103 -30
  112. package/dist/apps/management/routes/billing.d.ts.map +1 -1
  113. package/dist/apps/management/routes/billing.js +91 -32
  114. package/dist/apps/management/routes/billing.js.map +1 -1
  115. package/dist/apps/management/routes/faucet.d.ts +12 -12
  116. package/dist/apps/management/routes/faucet.d.ts.map +1 -1
  117. package/dist/apps/management/routes/faucet.js +23 -12
  118. package/dist/apps/management/routes/faucet.js.map +1 -1
  119. package/dist/apps/management/routes/invitations.d.ts +46 -13
  120. package/dist/apps/management/routes/invitations.d.ts.map +1 -1
  121. package/dist/apps/management/routes/invitations.js +79 -29
  122. package/dist/apps/management/routes/invitations.js.map +1 -1
  123. package/dist/apps/management/routes/invite-links.d.ts +128 -83
  124. package/dist/apps/management/routes/invite-links.d.ts.map +1 -1
  125. package/dist/apps/management/routes/invite-links.js +100 -41
  126. package/dist/apps/management/routes/invite-links.js.map +1 -1
  127. package/dist/apps/management/routes/me.d.ts +5 -5
  128. package/dist/apps/management/routes/me.d.ts.map +1 -1
  129. package/dist/apps/management/routes/me.js +14 -10
  130. package/dist/apps/management/routes/me.js.map +1 -1
  131. package/dist/apps/management/routes/members.d.ts +68 -47
  132. package/dist/apps/management/routes/members.d.ts.map +1 -1
  133. package/dist/apps/management/routes/members.js +37 -18
  134. package/dist/apps/management/routes/members.js.map +1 -1
  135. package/dist/apps/management/routes/orgs.d.ts +48 -8
  136. package/dist/apps/management/routes/orgs.d.ts.map +1 -1
  137. package/dist/apps/management/routes/orgs.js +48 -19
  138. package/dist/apps/management/routes/orgs.js.map +1 -1
  139. package/dist/apps/management/routes/projects.d.ts +130 -68
  140. package/dist/apps/management/routes/projects.d.ts.map +1 -1
  141. package/dist/apps/management/routes/projects.js +72 -21
  142. package/dist/apps/management/routes/projects.js.map +1 -1
  143. package/dist/apps/management/routes/scopes.d.ts.map +1 -1
  144. package/dist/apps/management/routes/scopes.js +8 -5
  145. package/dist/apps/management/routes/scopes.js.map +1 -1
  146. package/dist/apps/management/routes/usage.d.ts +38 -38
  147. package/dist/apps/management/routes/usage.d.ts.map +1 -1
  148. package/dist/apps/management/routes/usage.js +41 -31
  149. package/dist/apps/management/routes/usage.js.map +1 -1
  150. package/dist/apps/management/routes/verified-token-requests.d.ts +34 -6
  151. package/dist/apps/management/routes/verified-token-requests.d.ts.map +1 -1
  152. package/dist/apps/management/routes/verified-token-requests.js +36 -17
  153. package/dist/apps/management/routes/verified-token-requests.js.map +1 -1
  154. package/dist/apps/management/routes/webhooks.d.ts +1186 -197
  155. package/dist/apps/management/routes/webhooks.d.ts.map +1 -1
  156. package/dist/apps/management/routes/webhooks.js +106 -38
  157. package/dist/apps/management/routes/webhooks.js.map +1 -1
  158. package/dist/apps/mpp/internal/Adapter.d.ts +1 -1
  159. package/dist/apps/mpp/internal/Adapter.d.ts.map +1 -1
  160. package/dist/apps/mpp/internal/Adapter.js +12 -0
  161. package/dist/apps/mpp/internal/Adapter.js.map +1 -1
  162. package/dist/db/Db.d.ts +2 -0
  163. package/dist/db/Db.d.ts.map +1 -1
  164. package/dist/db/Db.js +61 -0
  165. package/dist/db/Db.js.map +1 -1
  166. package/dist/db/Schema.d.ts +30 -2
  167. package/dist/db/Schema.d.ts.map +1 -1
  168. package/dist/db/Schema.js +28 -3
  169. package/dist/db/Schema.js.map +1 -1
  170. package/dist/db/tables/earnVaults.d.ts +5 -1
  171. package/dist/db/tables/earnVaults.d.ts.map +1 -1
  172. package/dist/db/tables/earnVaults.js +5 -0
  173. package/dist/db/tables/earnVaults.js.map +1 -1
  174. package/dist/db/tables/webhookDeliveries.d.ts.map +1 -1
  175. package/dist/db/tables/webhookDeliveries.js +10 -0
  176. package/dist/db/tables/webhookDeliveries.js.map +1 -1
  177. package/dist/db/tables/webhookQueueCompletions.d.ts +15 -0
  178. package/dist/db/tables/webhookQueueCompletions.d.ts.map +1 -0
  179. package/dist/db/tables/webhookQueueCompletions.js +32 -0
  180. package/dist/db/tables/webhookQueueCompletions.js.map +1 -0
  181. package/dist/db/tables/webhookQueueEvents.d.ts +16 -20
  182. package/dist/db/tables/webhookQueueEvents.d.ts.map +1 -1
  183. package/dist/db/tables/webhookQueueEvents.js +68 -43
  184. package/dist/db/tables/webhookQueueEvents.js.map +1 -1
  185. package/dist/db/tables/webhookSubscriptions.d.ts +47 -72
  186. package/dist/db/tables/webhookSubscriptions.d.ts.map +1 -1
  187. package/dist/db/tables/webhookSubscriptions.js +92 -105
  188. package/dist/db/tables/webhookSubscriptions.js.map +1 -1
  189. package/dist/internal/Auth.d.ts +8 -1
  190. package/dist/internal/Auth.d.ts.map +1 -1
  191. package/dist/internal/Auth.js +13 -3
  192. package/dist/internal/Auth.js.map +1 -1
  193. package/dist/internal/Cache.d.ts.map +1 -1
  194. package/dist/internal/Cache.js +11 -7
  195. package/dist/internal/Cache.js.map +1 -1
  196. package/dist/internal/EarnVaults.d.ts +41 -2
  197. package/dist/internal/EarnVaults.d.ts.map +1 -1
  198. package/dist/internal/EarnVaults.js +167 -0
  199. package/dist/internal/EarnVaults.js.map +1 -1
  200. package/dist/internal/MetricSink.d.ts +35 -125
  201. package/dist/internal/MetricSink.d.ts.map +1 -1
  202. package/dist/internal/MetricSink.js +50 -121
  203. package/dist/internal/MetricSink.js.map +1 -1
  204. package/dist/internal/OpenApi.d.ts +26 -22
  205. package/dist/internal/OpenApi.d.ts.map +1 -1
  206. package/dist/internal/OpenApi.js +128 -39
  207. package/dist/internal/OpenApi.js.map +1 -1
  208. package/dist/internal/Response.d.ts +13 -8
  209. package/dist/internal/Response.d.ts.map +1 -1
  210. package/dist/internal/Response.js +16 -5
  211. package/dist/internal/Response.js.map +1 -1
  212. package/dist/internal/Schema.d.ts +8 -8
  213. package/dist/internal/Schema.d.ts.map +1 -1
  214. package/dist/internal/Schema.js +10 -9
  215. package/dist/internal/Schema.js.map +1 -1
  216. package/dist/internal/Store.d.ts +15 -6
  217. package/dist/internal/Store.d.ts.map +1 -1
  218. package/dist/internal/Store.js +45 -15
  219. package/dist/internal/Store.js.map +1 -1
  220. package/dist/internal/Tidx.d.ts +2 -0
  221. package/dist/internal/Tidx.d.ts.map +1 -1
  222. package/dist/internal/Tidx.js +16 -4
  223. package/dist/internal/Tidx.js.map +1 -1
  224. package/dist/internal/WebhookTransfer.d.ts +1 -55
  225. package/dist/internal/WebhookTransfer.d.ts.map +1 -1
  226. package/dist/internal/WebhookTransfer.js +1 -239
  227. package/dist/internal/WebhookTransfer.js.map +1 -1
  228. package/dist/internal/Webhooks.d.ts +112 -56
  229. package/dist/internal/Webhooks.d.ts.map +1 -1
  230. package/dist/internal/Webhooks.js +124 -93
  231. package/dist/internal/Webhooks.js.map +1 -1
  232. package/dist/internal/funding/Deposit.d.ts +2 -2
  233. package/dist/internal/funding/Deposit.d.ts.map +1 -1
  234. package/dist/internal/funding/Deposit.js +9 -8
  235. package/dist/internal/funding/Deposit.js.map +1 -1
  236. package/dist/internal/funding/DepositAddress.d.ts +4 -4
  237. package/dist/internal/funding/DepositAddress.d.ts.map +1 -1
  238. package/dist/internal/funding/DepositAddress.js +9 -8
  239. package/dist/internal/funding/DepositAddress.js.map +1 -1
  240. package/dist/internal/funding/DepositWebhook.d.ts +25 -0
  241. package/dist/internal/funding/DepositWebhook.d.ts.map +1 -0
  242. package/dist/internal/funding/DepositWebhook.js +74 -0
  243. package/dist/internal/funding/DepositWebhook.js.map +1 -0
  244. package/dist/internal/funding/Provider.d.ts.map +1 -1
  245. package/dist/internal/funding/Provider.js +13 -12
  246. package/dist/internal/funding/Provider.js.map +1 -1
  247. package/dist/internal/funding/Reconciliation.d.ts +6 -0
  248. package/dist/internal/funding/Reconciliation.d.ts.map +1 -1
  249. package/dist/internal/funding/Reconciliation.js +42 -10
  250. package/dist/internal/funding/Reconciliation.js.map +1 -1
  251. package/dist/internal/funding/index.d.ts +1 -0
  252. package/dist/internal/funding/index.d.ts.map +1 -1
  253. package/dist/internal/funding/index.js +1 -0
  254. package/dist/internal/funding/index.js.map +1 -1
  255. package/dist/internal/funding/providers/stargate.d.ts +19 -0
  256. package/dist/internal/funding/providers/stargate.d.ts.map +1 -0
  257. package/dist/internal/funding/providers/stargate.js +143 -0
  258. package/dist/internal/funding/providers/stargate.js.map +1 -0
  259. package/dist/internal/webhookDestinations/slack.d.ts.map +1 -1
  260. package/dist/internal/webhookDestinations/slack.js +16 -0
  261. package/dist/internal/webhookDestinations/slack.js.map +1 -1
  262. package/dist/providers/index.d.ts +7 -0
  263. package/dist/providers/index.d.ts.map +1 -1
  264. package/dist/providers/index.js +3 -0
  265. package/dist/providers/index.js.map +1 -1
  266. package/package.json +2 -2
  267. package/src/App.test.ts +48 -0
  268. package/src/App.ts +7 -2
  269. package/src/Assets.test.ts +18 -0
  270. package/src/Assets.ts +22 -0
  271. package/src/Client.test-d.ts +287 -17
  272. package/src/Webhooks.ts +1 -220
  273. package/src/admin/apps/earn-vaults.test.ts +143 -0
  274. package/src/admin/apps/earn-vaults.ts +83 -6
  275. package/src/admin/apps/organizations.ts +6 -2
  276. package/src/admin/ui/src/routes/api-keys.tsx +36 -0
  277. package/src/admin/ui.generated.ts +151 -189
  278. package/src/apps/data/App.test.ts +31 -0
  279. package/src/apps/data/App.ts +64 -10
  280. package/src/apps/data/ZoneSelection.test.ts +196 -0
  281. package/src/apps/data/ZoneSelection.ts +108 -0
  282. package/src/apps/data/routes/activities.test.ts +435 -12
  283. package/src/apps/data/routes/activities.ts +562 -125
  284. package/src/apps/data/routes/balances.ts +1 -1
  285. package/src/apps/data/routes/blocks.ts +1 -1
  286. package/src/apps/data/routes/earn.test.ts +183 -23
  287. package/src/apps/data/routes/earn.ts +225 -29
  288. package/src/apps/data/routes/earn.zone-access.test.ts +46 -0
  289. package/src/apps/data/routes/exchanges.test.ts +31 -1
  290. package/src/apps/data/routes/exchanges.ts +19 -15
  291. package/src/apps/data/routes/fee-amm.ts +18 -11
  292. package/src/apps/data/routes/receipts.ts +1 -1
  293. package/src/apps/data/routes/rpc.test.ts +75 -0
  294. package/src/apps/data/routes/rpc.ts +23 -9
  295. package/src/apps/data/routes/tokenlist.ts +21 -18
  296. package/src/apps/data/routes/tokens.test.ts +29 -0
  297. package/src/apps/data/routes/tokens.ts +38 -15
  298. package/src/apps/data/routes/transactions.ts +1 -1
  299. package/src/apps/data/routes/transfers.test.ts +3 -3
  300. package/src/apps/data/routes/transfers.ts +12 -9
  301. package/src/apps/data/routes/webhooks.test.ts +179 -1497
  302. package/src/apps/data/routes/webhooks.ts +156 -976
  303. package/src/apps/data/routes/zones.test.ts +19 -13
  304. package/src/apps/funding/routes/chains.test.ts +211 -7
  305. package/src/apps/funding/routes/chains.ts +19 -12
  306. package/src/apps/funding/routes/deposit-addresses.test.ts +394 -0
  307. package/src/apps/funding/routes/deposit-addresses.ts +84 -72
  308. package/src/apps/funding/routes/providers.ts +9 -5
  309. package/src/apps/funding/routes/quotes.test.ts +46 -0
  310. package/src/apps/funding/routes/quotes.ts +8 -5
  311. package/src/apps/funding/routes/transfers.test.ts +35 -32
  312. package/src/apps/management/Billing.test.ts +26 -0
  313. package/src/apps/management/Billing.ts +24 -7
  314. package/src/apps/management/routes/api-keys.test.ts +178 -0
  315. package/src/apps/management/routes/api-keys.ts +203 -144
  316. package/src/apps/management/routes/billing.test.ts +209 -0
  317. package/src/apps/management/routes/billing.ts +254 -177
  318. package/src/apps/management/routes/faucet.test.ts +39 -0
  319. package/src/apps/management/routes/faucet.ts +75 -41
  320. package/src/apps/management/routes/invitations.test.ts +216 -0
  321. package/src/apps/management/routes/invitations.ts +186 -109
  322. package/src/apps/management/routes/invite-links.test.ts +263 -1
  323. package/src/apps/management/routes/invite-links.ts +295 -214
  324. package/src/apps/management/routes/me.test.ts +49 -0
  325. package/src/apps/management/routes/me.ts +42 -32
  326. package/src/apps/management/routes/members.test.ts +124 -1
  327. package/src/apps/management/routes/members.ts +81 -51
  328. package/src/apps/management/routes/orgs.test.ts +176 -0
  329. package/src/apps/management/routes/orgs.ts +101 -59
  330. package/src/apps/management/routes/projects.test.ts +183 -0
  331. package/src/apps/management/routes/projects.ts +124 -60
  332. package/src/apps/management/routes/scopes.test.ts +44 -0
  333. package/src/apps/management/routes/scopes.ts +37 -28
  334. package/src/apps/management/routes/usage.test.ts +92 -0
  335. package/src/apps/management/routes/usage.ts +186 -151
  336. package/src/apps/management/routes/verified-token-requests.test.ts +114 -0
  337. package/src/apps/management/routes/verified-token-requests.ts +166 -133
  338. package/src/apps/management/routes/webhooks.test.ts +313 -1
  339. package/src/apps/management/routes/webhooks.ts +220 -71
  340. package/src/apps/mpp/internal/Adapter.ts +13 -1
  341. package/src/db/Db.test.ts +42 -5
  342. package/src/db/Db.ts +85 -0
  343. package/src/db/Schema.ts +41 -3
  344. package/src/db/tables/earnVaults.test.ts +22 -0
  345. package/src/db/tables/earnVaults.ts +11 -1
  346. package/src/db/tables/organizations.test.ts +5 -2
  347. package/src/db/tables/webhookDeliveries.ts +13 -0
  348. package/src/db/tables/webhookQueueCompletions.ts +55 -0
  349. package/src/db/tables/webhookQueueEvents.ts +97 -57
  350. package/src/db/tables/webhookSubscriptions.ts +134 -144
  351. package/src/internal/Auth.ts +23 -5
  352. package/src/internal/Cache.ts +14 -7
  353. package/src/internal/EarnVaults.capabilities.test.ts +33 -0
  354. package/src/internal/EarnVaults.test.ts +79 -0
  355. package/src/internal/EarnVaults.ts +277 -0
  356. package/src/internal/MetricSink.test.ts +41 -373
  357. package/src/internal/MetricSink.ts +92 -273
  358. package/src/internal/OpenApi.ts +165 -49
  359. package/src/internal/Response.test.ts +95 -0
  360. package/src/internal/Response.ts +40 -2
  361. package/src/internal/Schema.test.ts +34 -0
  362. package/src/internal/Schema.ts +20 -9
  363. package/src/internal/Store.test.ts +186 -0
  364. package/src/internal/Store.ts +57 -17
  365. package/src/internal/Tidx.test.ts +19 -0
  366. package/src/internal/Tidx.ts +16 -4
  367. package/src/internal/WebhookTransfer.ts +1 -351
  368. package/src/internal/Webhooks.test.ts +222 -145
  369. package/src/internal/Webhooks.ts +234 -135
  370. package/src/internal/funding/Deposit.ts +71 -64
  371. package/src/internal/funding/DepositAddress.ts +50 -43
  372. package/src/internal/funding/DepositWebhook.test.ts +131 -0
  373. package/src/internal/funding/DepositWebhook.ts +101 -0
  374. package/src/internal/funding/Provider.ts +164 -142
  375. package/src/internal/funding/Reconciliation.test.ts +14 -0
  376. package/src/internal/funding/Reconciliation.ts +161 -86
  377. package/src/internal/funding/index.test.ts +1 -0
  378. package/src/internal/funding/index.ts +1 -0
  379. package/src/internal/funding/providers/index.test.ts +185 -0
  380. package/src/internal/funding/providers/stargate.ts +184 -0
  381. package/src/internal/webhookDestinations/slack.ts +15 -0
  382. package/src/providers/index.test.ts +1 -0
  383. package/src/providers/index.ts +9 -0
  384. package/src/Webhooks.bench.ts +0 -714
  385. package/src/Webhooks.memory.bench.ts +0 -250
  386. package/src/Webhooks.test.ts +0 -146
package/src/db/Schema.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import * as z from 'zod/mini'
2
2
  import type { Address } from 'viem'
3
3
 
4
+ import type * as EarnVaults from '../internal/EarnVaults.js'
4
5
  import type * as funding_Catalog from '../internal/funding/Catalog.js'
5
6
  import type * as funding_Deposit from '../internal/funding/Deposit.js'
6
7
  import type * as funding_DepositAddress from '../internal/funding/DepositAddress.js'
@@ -106,6 +107,9 @@ export const EarnVault = z
106
107
  slug: z.string().check(z.describe('Stable URL slug assigned when the vault is registered.')),
107
108
  updatedAt: z.string().check(z.describe('When the vault registration last changed (ISO 8601).')),
108
109
  vaultAddress: z.custom<Address>().check(z.describe('Earn vault address (lowercase).')),
110
+ zones: z
111
+ .custom<readonly EarnVaults.ZoneRoute[]>()
112
+ .check(z.describe('Curated private Zone routes (`jsonb`).')),
109
113
  })
110
114
  .check(z.describe('One curated Earn vault row.'))
111
115
 
@@ -792,6 +796,9 @@ export const WebhookSubscription = z.object({
792
796
  destination: z
793
797
  .custom<Webhooks.Destination>()
794
798
  .check(z.describe('Delivery destination (`jsonb`).')),
799
+ environment: z
800
+ .nullable(z.enum(['production', 'sandbox']))
801
+ .check(z.describe('API-key environment for private resource events, or null.')),
795
802
  eventType: z
796
803
  .custom<Webhooks.EventType>()
797
804
  .check(z.describe('Event type the subscription listens to.')),
@@ -820,6 +827,9 @@ export const WebhookSubscription = z.object({
820
827
  pollerCursor: z
821
828
  .nullable(z.string())
822
829
  .check(z.describe('Poller keyset cursor, or null before the first tick.')),
830
+ projectId: z
831
+ .nullable(z.string())
832
+ .check(z.describe('API-key project attribution for private resource events, or null.')),
823
833
  secret: z.string().check(z.describe('HMAC signing secret.')),
824
834
  status: z.custom<Webhooks.Status>().check(z.describe('Lifecycle status.')),
825
835
  updatedAt: z
@@ -875,6 +885,23 @@ export const WebhookDelivery = z.object({
875
885
  /** Columns of the `webhook_deliveries` table. */
876
886
  export type WebhookDelivery = z.output<typeof WebhookDelivery>
877
887
 
888
+ /** An insert-only dedupe record marking a delivery obligation terminal. */
889
+ export const WebhookQueueCompletion = z.object({
890
+ eventId: z.string().check(z.describe('Idempotent event id (`evt_...`).')),
891
+ expiresAt: z
892
+ .string()
893
+ .check(z.describe('ISO dedupe retention deadline; rows past it are invisible and pruned.')),
894
+ status: z
895
+ .enum(['failed', 'skipped', 'succeeded'])
896
+ .check(z.describe('Terminal delivery-obligation outcome.')),
897
+ subscriptionId: z
898
+ .string()
899
+ .check(z.describe('Owning subscription id (`wh_...`); FK with `ON DELETE CASCADE`.')),
900
+ })
901
+
902
+ /** Columns of the `webhook_queue_completions` table. */
903
+ export type WebhookQueueCompletion = z.output<typeof WebhookQueueCompletion>
904
+
878
905
  /** A durable webhook envelope staged before its compact Queue reference is admitted. */
879
906
  export const WebhookQueueEvent = z.object({
880
907
  attemptCount: z.number().check(z.describe('Delivery attempts claimed so far.')),
@@ -887,18 +914,29 @@ export const WebhookQueueEvent = z.object({
887
914
  .check(z.describe('The full webhook envelope retained outside Cloudflare Queues.')),
888
915
  eventId: z.string().check(z.describe('Idempotent event id (`evt_...`).')),
889
916
  expiresAt: z
890
- .string()
917
+ .nullable(z.string())
891
918
  .check(
892
919
  z.describe(
893
- 'ISO dedupe retention deadline, restarted at terminal transitions; inert while pending.',
920
+ 'Unwritten since completions took over dedupe; non-null only on rows staged before the split.',
894
921
  ),
895
922
  ),
896
923
  nextAttemptAt: z
897
924
  .nullable(z.string())
898
925
  .check(z.describe('ISO retry due time, or null when due immediately.')),
926
+ observedAt: z
927
+ .nullable(z.string())
928
+ .check(
929
+ z.describe(
930
+ 'ISO timestamp the coordinator observed the head owing this event, or null on replays.',
931
+ ),
932
+ ),
899
933
  status: z
900
934
  .enum(['failed', 'pending', 'skipped', 'succeeded'])
901
- .check(z.describe('Delivery-obligation lifecycle status.')),
935
+ .check(
936
+ z.describe(
937
+ 'Always `pending` since completions took over terminal state; other values occur only on rows completed before the split.',
938
+ ),
939
+ ),
902
940
  subscriptionId: z
903
941
  .string()
904
942
  .check(z.describe('Owning subscription id (`wh_...`); FK with `ON DELETE CASCADE`.')),
@@ -11,6 +11,13 @@ const input = {
11
11
  ],
12
12
  privateOutputTokens: ['0x20c00000000000000000000057F242B21a53ae4D'],
13
13
  vaultAddress: '0x2f05f4d3625B2eFFb383eFbEDAFd2D8F04371636',
14
+ zones: [
15
+ {
16
+ chainId: 1_424_310_001,
17
+ deploymentBlock: 31_039_238,
18
+ earnRouter: '0x9144086A79dea966d8bf1B52125eBED8919fC45c',
19
+ },
20
+ ],
14
21
  } as const satisfies EarnVaults.upsert.Options
15
22
 
16
23
  describe('upsert', () => {
@@ -36,6 +43,13 @@ describe('upsert', () => {
36
43
  ],
37
44
  "updatedAt": "2026-01-01T00:00:00.000Z",
38
45
  "vaultAddress": "0x2f05f4d3625b2effb383efbedafd2d8f04371636",
46
+ "zones": [
47
+ {
48
+ "chainId": 1424310001,
49
+ "deploymentBlock": 31039238,
50
+ "earnRouter": "0x9144086a79dea966d8bf1b52125ebed8919fc45c",
51
+ },
52
+ ],
39
53
  }
40
54
  `)
41
55
  } finally {
@@ -68,6 +82,7 @@ describe('upsert', () => {
68
82
  privateInputTokens: stored.privateInputTokens,
69
83
  privateOutputTokens: stored.privateOutputTokens,
70
84
  updatedAt: stored.updatedAt,
85
+ zones: stored.zones,
71
86
  }).toMatchInlineSnapshot(`
72
87
  {
73
88
  "createdAt": "2026-01-01T00:00:00.000Z",
@@ -76,6 +91,13 @@ describe('upsert', () => {
76
91
  "privateInputTokens": [],
77
92
  "privateOutputTokens": [],
78
93
  "updatedAt": "2026-01-02T00:00:00.000Z",
94
+ "zones": [
95
+ {
96
+ "chainId": 1424310001,
97
+ "deploymentBlock": 31039238,
98
+ "earnRouter": "0x9144086a79dea966d8bf1b52125ebed8919fc45c",
99
+ },
100
+ ],
79
101
  }
80
102
  `)
81
103
 
@@ -10,7 +10,7 @@ const slugSuffix = customAlphabet('0123456789abcdefghijklmnopqrstuvwxyz', 8)
10
10
  /** Columns of the `earn_vaults` table. */
11
11
  export type Table = Omit<
12
12
  db_Schema.EarnVault,
13
- 'chainId' | 'privateInputTokens' | 'privateOutputTokens'
13
+ 'chainId' | 'privateInputTokens' | 'privateOutputTokens' | 'zones'
14
14
  > & {
15
15
  /** Bigint chain id, read as a string from pg and written as a number. */
16
16
  chainId: ColumnType<string, number, number>
@@ -18,6 +18,8 @@ export type Table = Omit<
18
18
  privateInputTokens: JSONColumnType<db_Schema.EarnVault['privateInputTokens'], string, string>
19
19
  /** Tokens supported for private redemptions. */
20
20
  privateOutputTokens: JSONColumnType<db_Schema.EarnVault['privateOutputTokens'], string, string>
21
+ /** Curated private Zone routes. */
22
+ zones: JSONColumnType<db_Schema.EarnVault['zones'], string, string>
21
23
  }
22
24
 
23
25
  /** A stored curated Earn vault. */
@@ -76,6 +78,7 @@ export async function upsert(db: Db.Db, options: upsert.Options): Promise<Record
76
78
  slug,
77
79
  updatedAt: now,
78
80
  vaultAddress: normalize(options.vaultAddress),
81
+ zones: JSON.stringify(normalizeZones(options.zones)),
79
82
  })
80
83
  .onConflict((oc) =>
81
84
  oc.columns(['chainId', 'vaultAddress']).doUpdateSet({
@@ -85,6 +88,7 @@ export async function upsert(db: Db.Db, options: upsert.Options): Promise<Record
85
88
  privateOutputTokens: JSON.stringify(options.privateOutputTokens),
86
89
  ...(options.slug !== undefined ? { slug } : {}),
87
90
  updatedAt: now,
91
+ zones: JSON.stringify(normalizeZones(options.zones)),
88
92
  }),
89
93
  )
90
94
  .returningAll()
@@ -109,6 +113,8 @@ export declare namespace upsert {
109
113
  slug?: string | undefined
110
114
  /** Earn vault address. */
111
115
  vaultAddress: Address
116
+ /** Curated private Zone routes. */
117
+ zones: db_Schema.EarnVault['zones']
112
118
  }
113
119
  }
114
120
 
@@ -149,3 +155,7 @@ function toRecord(row: Selectable<Table>): Record {
149
155
  function normalize(address: string): Address {
150
156
  return address.toLowerCase() as Address
151
157
  }
158
+
159
+ function normalizeZones(zones: db_Schema.EarnVault['zones']): db_Schema.EarnVault['zones'] {
160
+ return zones.map((zone) => ({ ...zone, earnRouter: normalize(zone.earnRouter) }))
161
+ }
@@ -363,8 +363,11 @@ describe('deleteOrganization', () => {
363
363
  expect(await Organizations.deleteOrganization(db, 'org_1')).toBe(true)
364
364
 
365
365
  expect(await Webhooks.listSubscriptions(db, deletedOrg)).toEqual([])
366
- const active = await Webhooks.listActive(db, 4217, 'token:transfer')
367
- expect(active.map(({ id }) => id).sort()).toEqual(
366
+ const active = await Webhooks.listActiveForChain(db, {
367
+ chainId: 4217,
368
+ eventTypes: ['token:transfer'],
369
+ })
370
+ expect(active.map(({ subscription }) => subscription.id).sort()).toEqual(
368
371
  [keptApiKey.id, keptMppSubscription.id].sort(),
369
372
  )
370
373
  await db.close()
@@ -225,6 +225,10 @@ export async function get(
225
225
  return row ? toDelivery(row) : null
226
226
  }
227
227
 
228
+ // Per-statement delete bound: prune runs every minute, so a backlog drains in
229
+ // a few ticks instead of one unbounded DELETE churning the origin.
230
+ const pruneBatchLimit = 50_000
231
+
228
232
  /**
229
233
  * Deletes deliveries past their retention deadline. Called best-effort from
230
234
  * the poller tick.
@@ -236,7 +240,16 @@ export async function get(
236
240
  export async function pruneExpired(db: Db.Db, now: string): Promise<number> {
237
241
  const result = await db.kysely
238
242
  .deleteFrom('webhook_deliveries')
243
+ // Repeated on the outer delete: the row recheck under concurrency re-runs
244
+ // only this predicate, so a row renewed mid-statement survives.
239
245
  .where('expiresAt', '<=', now)
246
+ .where('id', 'in', (eb) =>
247
+ eb
248
+ .selectFrom('webhook_deliveries')
249
+ .select('id')
250
+ .where('expiresAt', '<=', now)
251
+ .limit(pruneBatchLimit),
252
+ )
240
253
  .executeTakeFirst()
241
254
  return Number(result.numDeletedRows)
242
255
  }
@@ -0,0 +1,55 @@
1
+ import type * as Webhooks from '../../internal/Webhooks.js'
2
+ import type * as Db from '../Db.js'
3
+ import type * as db_Schema from '../Schema.js'
4
+
5
+ /** Columns of the `webhook_queue_completions` table, derived from `Schema.WebhookQueueCompletion`. */
6
+ export type Table = db_Schema.WebhookQueueCompletion
7
+
8
+ /** A terminal delivery-obligation outcome. */
9
+ export type TerminalStatus = db_Schema.WebhookQueueCompletion['status']
10
+
11
+ /**
12
+ * Reads the unexpired completion for a reference, if any. Rows are written by
13
+ * the queue-event terminal transition and only ever read for dedupe.
14
+ */
15
+ export async function get(
16
+ db: Db.Db,
17
+ reference: Webhooks.QueueReference,
18
+ now: string,
19
+ ): Promise<Table | null> {
20
+ return (
21
+ (await db.kysely
22
+ .selectFrom('webhook_queue_completions')
23
+ .selectAll()
24
+ .where('eventId', '=', reference.eventId)
25
+ .where('subscriptionId', '=', reference.subscriptionId)
26
+ .where('expiresAt', '>', now)
27
+ .executeTakeFirst()) ?? null
28
+ )
29
+ }
30
+
31
+ // Per-statement delete bound: prune runs every minute, so a backlog drains in
32
+ // a few ticks instead of one unbounded DELETE churning the origin.
33
+ const pruneBatchLimit = 50_000
34
+
35
+ /** Deletes expired completions, bounded per call; expiry is filtered at query time, so pruning is space-only. */
36
+ export async function pruneExpired(db: Db.Db, now: string): Promise<number> {
37
+ const result = await db.kysely
38
+ .deleteFrom('webhook_queue_completions')
39
+ // Repeated on the outer delete: the row recheck under concurrency re-runs
40
+ // only this predicate, so a completion renewed mid-statement survives.
41
+ .where('expiresAt', '<=', now)
42
+ .where(({ eb, refTuple, selectFrom }) =>
43
+ eb(
44
+ refTuple('subscriptionId', 'eventId'),
45
+ 'in',
46
+ selectFrom('webhook_queue_completions')
47
+ .select(['subscriptionId', 'eventId'])
48
+ .where('expiresAt', '<=', now)
49
+ .limit(pruneBatchLimit)
50
+ .$asTuple('subscriptionId', 'eventId'),
51
+ ),
52
+ )
53
+ .executeTakeFirst()
54
+ return Number(result.numDeletedRows)
55
+ }
@@ -2,19 +2,24 @@ import { type ExpressionBuilder, type Generated, type Insertable, type Selectabl
2
2
 
3
3
  import type * as Webhooks from '../../internal/Webhooks.js'
4
4
  import type * as Db from '../Db.js'
5
+ import * as WebhookQueueCompletions from './webhookQueueCompletions.js'
5
6
  import type * as db_Schema from '../Schema.js'
6
7
 
7
8
  /** Columns of the `webhook_queue_events` table, derived from `Schema.WebhookQueueEvent`. */
8
9
  export type Table = Omit<
9
10
  db_Schema.WebhookQueueEvent,
10
- 'attemptCount' | 'attemptingAt' | 'nextAttemptAt' | 'status'
11
+ 'attemptCount' | 'attemptingAt' | 'expiresAt' | 'nextAttemptAt' | 'observedAt' | 'status'
11
12
  > & {
12
13
  /** Delivery attempts claimed so far; database default 0. */
13
14
  attemptCount: Generated<number>
14
15
  /** ISO timestamp of the current claim; database default null. */
15
16
  attemptingAt: Generated<string | null>
17
+ /** Legacy dedupe deadline; unwritten since completions took over. */
18
+ expiresAt: Generated<string | null>
16
19
  /** ISO retry due time; database default null (due immediately). */
17
20
  nextAttemptAt: Generated<string | null>
21
+ /** ISO head-observation stamp; database default null (replays carry none). */
22
+ observedAt: Generated<string | null>
18
23
  /** Delivery-obligation lifecycle status; database default `pending`. */
19
24
  status: Generated<db_Schema.WebhookQueueEvent['status']>
20
25
  }
@@ -22,12 +27,9 @@ export type Table = Omit<
22
27
  /** A stored webhook Queue event. */
23
28
  export type Record = Selectable<Table>
24
29
 
25
- /** A terminal delivery-obligation outcome. */
26
- export type TerminalStatus = Exclude<db_Schema.WebhookQueueEvent['status'], 'pending'>
27
-
28
30
  /**
29
- * Reads one pending envelope. Terminal rows read as absent so no consumer
30
- * can redeliver a completed obligation or disturb its dedupe row.
31
+ * Reads one pending envelope. The status filter only ever excludes rows
32
+ * completed before the completions split; completions now leave the table.
31
33
  */
32
34
  export async function get(
33
35
  db: Db.Db,
@@ -45,38 +47,56 @@ export async function get(
45
47
  }
46
48
 
47
49
  /**
48
- * Stages envelopes as fresh obligations. Pending conflicts keep their first
49
- * envelope and backoff position; terminal conflicts inside the dedupe window
50
- * suppress the replay; terminal conflicts past it are replaced, so an expired
51
- * dedupe row pruning has not yet reclaimed never blocks a replay.
50
+ * Stages envelopes as fresh obligations. Unexpired completions and any
51
+ * existing row suppress the replay; a replay stages only when neither exists,
52
+ * which a completion past its dedupe window guarantees for completed work.
52
53
  */
53
54
  export async function ensure(db: Db.Db, rows: readonly Insertable<Table>[]): Promise<number> {
54
55
  if (rows.length === 0) return 0
56
+ const values = sql.join(
57
+ rows.map(
58
+ (row) =>
59
+ sql`(${row.subscriptionId}::text, ${row.eventId}::text, ${JSON.stringify(row.envelope)}::jsonb, ${row.createdAt}::text, ${row.nextAttemptAt ?? null}::text, ${row.observedAt ?? null}::text)`,
60
+ ),
61
+ )
62
+ // The VALUES source is raw because kysely has no table constructor for it,
63
+ // but both real tables stay in the builder so WithSchemaPlugin qualifies
64
+ // them (previews isolate by schema; raw identifiers would escape it). The
65
+ // completion check must ride the insert: a separate read would let a
66
+ // completion commit between the statements and re-stage just-finished work.
67
+ // `column1..6` are Postgres's default VALUES names, in the row order above;
68
+ // the window comparison uses each row's own staging time, not a shared clock.
69
+ // Conflicts only ever hit pending rows (or pre-split terminal rows during
70
+ // the drain), and both suppress the replay; a legitimate post-expiry replay
71
+ // meets no conflict because its completed row was deleted.
55
72
  const result = await db.kysely
56
73
  .insertInto('webhook_queue_events')
57
- .values([...rows])
58
- .onConflict((oc) =>
59
- oc
60
- .columns(['subscriptionId', 'eventId'])
61
- .doUpdateSet((eb) => ({
62
- attemptCount: 0,
63
- attemptingAt: null,
64
- createdAt: eb.ref('excluded.createdAt'),
65
- envelope: eb.ref('excluded.envelope'),
66
- expiresAt: eb.ref('excluded.expiresAt'),
67
- nextAttemptAt: eb.ref('excluded.nextAttemptAt'),
68
- status: 'pending',
69
- }))
70
- .where((eb) =>
71
- eb.and([
72
- eb('webhook_queue_events.status', '!=', 'pending'),
73
- eb('webhook_queue_events.expiresAt', '<=', eb.ref('excluded.createdAt')),
74
- ]),
74
+ .columns(['subscriptionId', 'eventId', 'envelope', 'createdAt', 'nextAttemptAt', 'observedAt'])
75
+ .expression((eb) =>
76
+ eb
77
+ .selectFrom(sql`(VALUES ${values})`.as('v'))
78
+ .select([
79
+ sql`v.column1`.as('subscriptionId'),
80
+ sql`v.column2`.as('eventId'),
81
+ sql`v.column3`.as('envelope'),
82
+ sql`v.column4`.as('createdAt'),
83
+ sql`v.column5`.as('nextAttemptAt'),
84
+ sql`v.column6`.as('observedAt'),
85
+ ])
86
+ .where(({ exists, not, selectFrom }) =>
87
+ not(
88
+ exists(
89
+ selectFrom('webhook_queue_completions')
90
+ .select('eventId')
91
+ .where('subscriptionId', '=', sql<string>`v.column1`)
92
+ .where('eventId', '=', sql<string>`v.column2`)
93
+ .where('expiresAt', '>', sql<string>`v.column4`),
94
+ ),
95
+ ),
75
96
  ),
76
97
  )
98
+ .onConflict((oc) => oc.columns(['subscriptionId', 'eventId']).doNothing())
77
99
  .executeTakeFirst()
78
- // The command tag counts inserted rows plus replaced expired rows; live
79
- // conflicts fall out of the conditional update and are not counted.
80
100
  return Number(result.numInsertedOrUpdatedRows ?? 0n)
81
101
  }
82
102
 
@@ -108,13 +128,23 @@ export async function claim(
108
128
  .executeTakeFirst()
109
129
  if (claimed) return { record: claimed, type: 'claimed' }
110
130
 
131
+ // Queue first, completions second: a complete() landing between the reads
132
+ // then still surfaces its completion, so a finished obligation never reads
133
+ // as missing, the one claim outcome alerts treat as a fault.
111
134
  const existing = await db.kysely
112
135
  .selectFrom('webhook_queue_events')
113
136
  .select(['attemptingAt', 'nextAttemptAt', 'status'])
114
137
  .where('subscriptionId', '=', reference.subscriptionId)
115
138
  .where('eventId', '=', reference.eventId)
116
139
  .executeTakeFirst()
117
- if (!existing) return { type: 'missing' }
140
+ if (!existing) {
141
+ // An unexpired completion reads as terminal so claim-outcome metrics keep
142
+ // their meaning; an expired one reads as missing, like a pruned row.
143
+ const completed = await WebhookQueueCompletions.get(db, reference, options.now)
144
+ if (completed) return { status: completed.status, type: 'terminal' }
145
+ return { type: 'missing' }
146
+ }
147
+ // Reachable only for rows completed before the completions split.
118
148
  if (existing.status !== 'pending') return { status: existing.status, type: 'terminal' }
119
149
  if (existing.attemptingAt !== null && existing.attemptingAt > options.staleBefore)
120
150
  return { type: 'attempting' }
@@ -136,12 +166,12 @@ export declare namespace claim {
136
166
  | { record: Record; type: 'claimed' }
137
167
  | { type: 'missing' }
138
168
  | { nextAttemptAt: string | null; type: 'scheduled' }
139
- | { status: TerminalStatus; type: 'terminal' }
169
+ | { status: WebhookQueueCompletions.TerminalStatus; type: 'terminal' }
140
170
  }
141
171
 
142
172
  /**
143
- * Marks a claimed obligation terminal, starting its dedupe retention window;
144
- * pending rows never expire, so this stamp is the only expiry that matters.
173
+ * Marks a claimed obligation terminal: one atomic statement deletes the queue
174
+ * row and writes the insert-only completion that starts the dedupe window.
145
175
  * The `attemptingAt` predicate fences stale claimants.
146
176
  */
147
177
  export async function complete(
@@ -150,14 +180,38 @@ export async function complete(
150
180
  options: complete.Options,
151
181
  ): Promise<boolean> {
152
182
  const result = await db.kysely
153
- .updateTable('webhook_queue_events')
154
- .set({ attemptingAt: null, expiresAt: options.expiresAt, status: options.status })
155
- .where('subscriptionId', '=', reference.subscriptionId)
156
- .where('eventId', '=', reference.eventId)
157
- .where('status', '=', 'pending')
158
- .where('attemptingAt', '=', options.claimedAt)
183
+ .with('done', (qb) =>
184
+ qb
185
+ .deleteFrom('webhook_queue_events')
186
+ .where('subscriptionId', '=', reference.subscriptionId)
187
+ .where('eventId', '=', reference.eventId)
188
+ .where('status', '=', 'pending')
189
+ .where('attemptingAt', '=', options.claimedAt)
190
+ .returning(['subscriptionId', 'eventId']),
191
+ )
192
+ .insertInto('webhook_queue_completions')
193
+ .columns(['subscriptionId', 'eventId', 'status', 'expiresAt'])
194
+ .expression((eb) =>
195
+ eb
196
+ .selectFrom('done')
197
+ .select((s) => [
198
+ 'subscriptionId',
199
+ 'eventId',
200
+ s.val(options.status).as('status'),
201
+ s.val(options.expiresAt).as('expiresAt'),
202
+ ]),
203
+ )
204
+ // Re-completing a replayed obligation: the latest delivery outcome wins
205
+ // and retention restarts, matching pre-split replay semantics; doNothing
206
+ // would also report zero rows and fail a fence that won.
207
+ .onConflict((oc) =>
208
+ oc.columns(['subscriptionId', 'eventId']).doUpdateSet((eb) => ({
209
+ expiresAt: eb.ref('excluded.expiresAt'),
210
+ status: eb.ref('excluded.status'),
211
+ })),
212
+ )
159
213
  .executeTakeFirst()
160
- return result.numUpdatedRows === 1n
214
+ return result.numInsertedOrUpdatedRows === 1n
161
215
  }
162
216
 
163
217
  export declare namespace complete {
@@ -165,10 +219,10 @@ export declare namespace complete {
165
219
  type Options = {
166
220
  /** Claim marker returned by {@link claim}; fences stale claimants. */
167
221
  claimedAt: string
168
- /** Replacement ISO retention deadline for the terminal dedupe row. */
222
+ /** ISO retention deadline for the dedupe completion. */
169
223
  expiresAt: string
170
224
  /** Terminal outcome. */
171
- status: TerminalStatus
225
+ status: WebhookQueueCompletions.TerminalStatus
172
226
  }
173
227
  }
174
228
 
@@ -269,17 +323,3 @@ export declare namespace pending {
269
323
  oldestDueAt: string | null
270
324
  }
271
325
  }
272
-
273
- /**
274
- * Deletes terminal dedupe rows past their retention deadline. Pending rows
275
- * never expire: owed work drains only through terminal transitions
276
- * (delivery, permanent failure, or the skip after auto-disable).
277
- */
278
- export async function pruneExpired(db: Db.Db, now: string): Promise<number> {
279
- const result = await db.kysely
280
- .deleteFrom('webhook_queue_events')
281
- .where('expiresAt', '<=', now)
282
- .where('status', '!=', 'pending')
283
- .executeTakeFirst()
284
- return Number(result.numDeletedRows)
285
- }