tapimo 0.10.0 → 0.10.2

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 (295) hide show
  1. package/dist/ApiKey.d.ts +2 -1
  2. package/dist/ApiKey.d.ts.map +1 -1
  3. package/dist/ApiKeys.d.ts +4 -2
  4. package/dist/ApiKeys.d.ts.map +1 -1
  5. package/dist/ApiKeys.js +26 -1
  6. package/dist/ApiKeys.js.map +1 -1
  7. package/dist/App.d.ts +6007 -5003
  8. package/dist/App.d.ts.map +1 -1
  9. package/dist/App.js +1 -0
  10. package/dist/App.js.map +1 -1
  11. package/dist/Webhooks.d.ts +1 -1
  12. package/dist/Webhooks.d.ts.map +1 -1
  13. package/dist/Webhooks.js +6 -6
  14. package/dist/Webhooks.js.map +1 -1
  15. package/dist/admin/App.d.ts +91 -84
  16. package/dist/admin/App.d.ts.map +1 -1
  17. package/dist/admin/apps/api-keys.d.ts +22 -13
  18. package/dist/admin/apps/api-keys.d.ts.map +1 -1
  19. package/dist/admin/apps/api-keys.js +6 -0
  20. package/dist/admin/apps/api-keys.js.map +1 -1
  21. package/dist/admin/apps/organizations.d.ts +4 -2
  22. package/dist/admin/apps/organizations.d.ts.map +1 -1
  23. package/dist/admin/ui.generated.d.ts +1 -1
  24. package/dist/admin/ui.generated.js +69 -69
  25. package/dist/admin/ui.generated.js.map +1 -1
  26. package/dist/analytics/Analytics.d.ts.map +1 -1
  27. package/dist/analytics/Analytics.js +232 -0
  28. package/dist/analytics/Analytics.js.map +1 -1
  29. package/dist/analytics/tables/requestEvents.d.ts +4 -0
  30. package/dist/analytics/tables/requestEvents.d.ts.map +1 -1
  31. package/dist/analytics/tables/requestEvents.js +2 -0
  32. package/dist/analytics/tables/requestEvents.js.map +1 -1
  33. package/dist/apps/Zones.d.ts +4266 -82
  34. package/dist/apps/Zones.d.ts.map +1 -1
  35. package/dist/apps/Zones.js +27 -8
  36. package/dist/apps/Zones.js.map +1 -1
  37. package/dist/apps/data/App.d.ts +3467 -2696
  38. package/dist/apps/data/App.d.ts.map +1 -1
  39. package/dist/apps/data/App.js +2 -0
  40. package/dist/apps/data/App.js.map +1 -1
  41. package/dist/apps/data/routes/activities.d.ts +2999 -100
  42. package/dist/apps/data/routes/activities.d.ts.map +1 -1
  43. package/dist/apps/data/routes/activities.js +340 -2
  44. package/dist/apps/data/routes/activities.js.map +1 -1
  45. package/dist/apps/data/routes/addresses.d.ts +514 -0
  46. package/dist/apps/data/routes/addresses.d.ts.map +1 -1
  47. package/dist/apps/data/routes/balances.d.ts +26 -26
  48. package/dist/apps/data/routes/balances.d.ts.map +1 -1
  49. package/dist/apps/data/routes/balances.js +7 -3
  50. package/dist/apps/data/routes/balances.js.map +1 -1
  51. package/dist/apps/data/routes/blocks.d.ts +4 -4
  52. package/dist/apps/data/routes/earn.d.ts +75 -75
  53. package/dist/apps/data/routes/earn.d.ts.map +1 -1
  54. package/dist/apps/data/routes/earn.js +65 -36
  55. package/dist/apps/data/routes/earn.js.map +1 -1
  56. package/dist/apps/data/routes/exchanges.d.ts +124 -120
  57. package/dist/apps/data/routes/exchanges.d.ts.map +1 -1
  58. package/dist/apps/data/routes/exchanges.js +115 -77
  59. package/dist/apps/data/routes/exchanges.js.map +1 -1
  60. package/dist/apps/data/routes/fee-amm.d.ts +24 -24
  61. package/dist/apps/data/routes/indexer.d.ts +2 -1
  62. package/dist/apps/data/routes/indexer.d.ts.map +1 -1
  63. package/dist/apps/data/routes/indexer.js +3 -2
  64. package/dist/apps/data/routes/indexer.js.map +1 -1
  65. package/dist/apps/data/routes/receipts.d.ts +4 -3
  66. package/dist/apps/data/routes/receipts.d.ts.map +1 -1
  67. package/dist/apps/data/routes/receipts.js +15 -14
  68. package/dist/apps/data/routes/receipts.js.map +1 -1
  69. package/dist/apps/data/routes/rpc.d.ts.map +1 -1
  70. package/dist/apps/data/routes/rpc.js +5 -1
  71. package/dist/apps/data/routes/rpc.js.map +1 -1
  72. package/dist/apps/data/routes/tokenlist.d.ts +12 -12
  73. package/dist/apps/data/routes/tokens.d.ts +1 -1
  74. package/dist/apps/data/routes/tokens.d.ts.map +1 -1
  75. package/dist/apps/data/routes/tokens.js +4 -3
  76. package/dist/apps/data/routes/tokens.js.map +1 -1
  77. package/dist/apps/data/routes/transfers.d.ts.map +1 -1
  78. package/dist/apps/data/routes/transfers.js +25 -7
  79. package/dist/apps/data/routes/transfers.js.map +1 -1
  80. package/dist/apps/data/routes/verified-tokens.d.ts +37 -37
  81. package/dist/apps/data/routes/webhooks.d.ts +71 -1
  82. package/dist/apps/data/routes/webhooks.d.ts.map +1 -1
  83. package/dist/apps/data/routes/webhooks.js +177 -23
  84. package/dist/apps/data/routes/webhooks.js.map +1 -1
  85. package/dist/apps/data/routes/zones.js +1 -1
  86. package/dist/apps/data/routes/zones.js.map +1 -1
  87. package/dist/apps/funding/App.d.ts +322 -89
  88. package/dist/apps/funding/App.d.ts.map +1 -1
  89. package/dist/apps/funding/routes/deposit-addresses.d.ts +375 -45
  90. package/dist/apps/funding/routes/deposit-addresses.d.ts.map +1 -1
  91. package/dist/apps/funding/routes/deposit-addresses.js +208 -12
  92. package/dist/apps/funding/routes/deposit-addresses.js.map +1 -1
  93. package/dist/apps/funding/routes/transfers.d.ts +32 -32
  94. package/dist/apps/funding/routes/transfers.js +1 -1
  95. package/dist/apps/funding/routes/transfers.js.map +1 -1
  96. package/dist/apps/management/App.d.ts +418 -418
  97. package/dist/apps/management/routes/api-keys.d.ts +50 -50
  98. package/dist/apps/management/routes/billing.d.ts +72 -72
  99. package/dist/apps/management/routes/billing.js +1 -1
  100. package/dist/apps/management/routes/billing.js.map +1 -1
  101. package/dist/apps/management/routes/faucet.d.ts +8 -8
  102. package/dist/apps/management/routes/faucet.js +2 -2
  103. package/dist/apps/management/routes/faucet.js.map +1 -1
  104. package/dist/apps/management/routes/invite-links.d.ts +42 -42
  105. package/dist/apps/management/routes/me.d.ts +6 -6
  106. package/dist/apps/management/routes/members.d.ts +14 -14
  107. package/dist/apps/management/routes/projects.d.ts +40 -40
  108. package/dist/apps/management/routes/usage.d.ts +8 -8
  109. package/dist/apps/management/routes/webhooks.d.ts +49 -49
  110. package/dist/apps/mcp.d.ts.map +1 -1
  111. package/dist/apps/mcp.js +4 -1
  112. package/dist/apps/mcp.js.map +1 -1
  113. package/dist/db/Db.d.ts +2 -0
  114. package/dist/db/Db.d.ts.map +1 -1
  115. package/dist/db/Db.js +90 -0
  116. package/dist/db/Db.js.map +1 -1
  117. package/dist/db/Schema.d.ts +19 -0
  118. package/dist/db/Schema.d.ts.map +1 -1
  119. package/dist/db/Schema.js +24 -0
  120. package/dist/db/Schema.js.map +1 -1
  121. package/dist/db/tables/fundingDepositAddresses.d.ts +20 -0
  122. package/dist/db/tables/fundingDepositAddresses.d.ts.map +1 -1
  123. package/dist/db/tables/fundingDepositAddresses.js +17 -0
  124. package/dist/db/tables/fundingDepositAddresses.js.map +1 -1
  125. package/dist/db/tables/fundingDeposits.d.ts +41 -0
  126. package/dist/db/tables/fundingDeposits.d.ts.map +1 -1
  127. package/dist/db/tables/fundingDeposits.js +69 -0
  128. package/dist/db/tables/fundingDeposits.js.map +1 -1
  129. package/dist/db/tables/webhookQueueEvents.d.ts +122 -0
  130. package/dist/db/tables/webhookQueueEvents.d.ts.map +1 -0
  131. package/dist/db/tables/webhookQueueEvents.js +160 -0
  132. package/dist/db/tables/webhookQueueEvents.js.map +1 -0
  133. package/dist/db/tables/webhookSubscriptions.d.ts +2 -0
  134. package/dist/db/tables/webhookSubscriptions.d.ts.map +1 -1
  135. package/dist/db/tables/webhookSubscriptions.js +10 -0
  136. package/dist/db/tables/webhookSubscriptions.js.map +1 -1
  137. package/dist/internal/Auth.d.ts +3 -26
  138. package/dist/internal/Auth.d.ts.map +1 -1
  139. package/dist/internal/Auth.js +6 -37
  140. package/dist/internal/Auth.js.map +1 -1
  141. package/dist/internal/Credential.d.ts +27 -0
  142. package/dist/internal/Credential.d.ts.map +1 -0
  143. package/dist/internal/Credential.js +34 -0
  144. package/dist/internal/Credential.js.map +1 -0
  145. package/dist/internal/EarnRates.js +11 -7
  146. package/dist/internal/EarnRates.js.map +1 -1
  147. package/dist/internal/Log.d.ts +8 -0
  148. package/dist/internal/Log.d.ts.map +1 -1
  149. package/dist/internal/Log.js +4 -0
  150. package/dist/internal/Log.js.map +1 -1
  151. package/dist/internal/MetricSink.d.ts +51 -0
  152. package/dist/internal/MetricSink.d.ts.map +1 -1
  153. package/dist/internal/MetricSink.js +54 -2
  154. package/dist/internal/MetricSink.js.map +1 -1
  155. package/dist/internal/RateLimit.d.ts +4 -6
  156. package/dist/internal/RateLimit.d.ts.map +1 -1
  157. package/dist/internal/RateLimit.js +15 -14
  158. package/dist/internal/RateLimit.js.map +1 -1
  159. package/dist/internal/Store.d.ts +2 -1
  160. package/dist/internal/Store.d.ts.map +1 -1
  161. package/dist/internal/Store.js +57 -18
  162. package/dist/internal/Store.js.map +1 -1
  163. package/dist/internal/Tidx.d.ts +4 -3
  164. package/dist/internal/Tidx.d.ts.map +1 -1
  165. package/dist/internal/Tidx.js +2 -2
  166. package/dist/internal/Tidx.js.map +1 -1
  167. package/dist/internal/Value.d.ts +2 -0
  168. package/dist/internal/Value.d.ts.map +1 -1
  169. package/dist/internal/Value.js +25 -0
  170. package/dist/internal/Value.js.map +1 -1
  171. package/dist/internal/Viem.d.ts +4 -3
  172. package/dist/internal/Viem.d.ts.map +1 -1
  173. package/dist/internal/Viem.js +3 -2
  174. package/dist/internal/Viem.js.map +1 -1
  175. package/dist/internal/WebhookTransfer.d.ts +20 -0
  176. package/dist/internal/WebhookTransfer.d.ts.map +1 -1
  177. package/dist/internal/WebhookTransfer.js +7 -4
  178. package/dist/internal/WebhookTransfer.js.map +1 -1
  179. package/dist/internal/Webhooks.d.ts +141 -0
  180. package/dist/internal/Webhooks.d.ts.map +1 -1
  181. package/dist/internal/Webhooks.js +139 -10
  182. package/dist/internal/Webhooks.js.map +1 -1
  183. package/dist/internal/funding/providers/relay.d.ts +3 -4
  184. package/dist/internal/funding/providers/relay.d.ts.map +1 -1
  185. package/dist/internal/funding/providers/relay.js +59 -8
  186. package/dist/internal/funding/providers/relay.js.map +1 -1
  187. package/dist/internal/index.d.ts +1 -0
  188. package/dist/internal/index.d.ts.map +1 -1
  189. package/dist/internal/index.js +1 -0
  190. package/dist/internal/index.js.map +1 -1
  191. package/package.json +3 -3
  192. package/src/ApiKeys.test.ts +20 -0
  193. package/src/ApiKeys.ts +28 -1
  194. package/src/App.test.ts +40 -8
  195. package/src/App.ts +1 -0
  196. package/src/Client.test-d.ts +34 -1
  197. package/src/Webhooks.ts +20 -18
  198. package/src/admin/apps/api-keys.test.ts +9 -0
  199. package/src/admin/apps/api-keys.ts +15 -0
  200. package/src/admin/ui/src/lib/query-client.ts +3 -2
  201. package/src/admin/ui/src/routes/api-keys.tsx +31 -9
  202. package/src/admin/ui.generated.ts +69 -69
  203. package/src/analytics/Analytics.test.ts +42 -0
  204. package/src/analytics/Analytics.ts +232 -0
  205. package/src/analytics/tables/requestEvents.ts +6 -0
  206. package/src/apps/Zones.test.ts +20 -3
  207. package/src/apps/Zones.ts +28 -8
  208. package/src/apps/composition.test.ts +2 -0
  209. package/src/apps/data/App.test.ts +2 -2
  210. package/src/apps/data/App.ts +2 -0
  211. package/src/apps/data/routes/activities.bench.ts +41 -0
  212. package/src/apps/data/routes/activities.test.ts +103 -2
  213. package/src/apps/data/routes/activities.ts +401 -2
  214. package/src/apps/data/routes/balances.bench.ts +21 -0
  215. package/src/apps/data/routes/balances.ts +7 -3
  216. package/src/apps/data/routes/blocks.bench.ts +34 -0
  217. package/src/apps/data/routes/coingecko.bench.ts +53 -0
  218. package/src/apps/data/routes/earn.bench.ts +71 -0
  219. package/src/apps/data/routes/earn.ts +66 -36
  220. package/src/apps/data/routes/exchanges.bench.ts +85 -0
  221. package/src/apps/data/routes/exchanges.test.ts +8 -3
  222. package/src/apps/data/routes/exchanges.ts +134 -82
  223. package/src/apps/data/routes/fee-amm.bench.ts +13 -0
  224. package/src/apps/data/routes/indexer.bench.ts +8 -0
  225. package/src/apps/data/routes/indexer.test.ts +2 -1
  226. package/src/apps/data/routes/indexer.ts +3 -2
  227. package/src/apps/data/routes/receipts.bench.ts +39 -0
  228. package/src/apps/data/routes/receipts.ts +16 -14
  229. package/src/apps/data/routes/rpc.bench.ts +14 -0
  230. package/src/apps/data/routes/rpc.test.ts +27 -0
  231. package/src/apps/data/routes/rpc.ts +5 -1
  232. package/src/apps/data/routes/tokenlist.bench.ts +3 -0
  233. package/src/apps/data/routes/tokens.bench.ts +76 -0
  234. package/src/apps/data/routes/tokens.ts +3 -2
  235. package/src/apps/data/routes/transactions.bench.ts +48 -0
  236. package/src/apps/data/routes/transfers.bench.ts +28 -0
  237. package/src/apps/data/routes/transfers.ts +36 -10
  238. package/src/apps/data/routes/valuation.bench.ts +14 -0
  239. package/src/apps/data/routes/verified-tokens.bench.ts +8 -0
  240. package/src/apps/data/routes/webhooks.bench.ts +26 -0
  241. package/src/apps/data/routes/webhooks.scan.test.ts +326 -0
  242. package/src/apps/data/routes/webhooks.test.ts +53 -6
  243. package/src/apps/data/routes/webhooks.ts +252 -24
  244. package/src/apps/data/routes/zones.bench.ts +32 -0
  245. package/src/apps/data/routes/zones.test.ts +1 -1
  246. package/src/apps/data/routes/zones.ts +1 -1
  247. package/src/apps/funding/routes/chains.test.ts +1 -1
  248. package/src/apps/funding/routes/deposit-addresses.test.ts +217 -3
  249. package/src/apps/funding/routes/deposit-addresses.ts +255 -19
  250. package/src/apps/funding/routes/providers.test.ts +1 -1
  251. package/src/apps/funding/routes/quotes.test.ts +1 -1
  252. package/src/apps/funding/routes/transfers.test.ts +20 -8
  253. package/src/apps/funding/routes/transfers.ts +1 -1
  254. package/src/apps/management/routes/billing.ts +1 -1
  255. package/src/apps/management/routes/faucet.ts +2 -2
  256. package/src/apps/management/routes/me.test.ts +31 -0
  257. package/src/apps/mcp.ts +4 -1
  258. package/src/db/Db.ts +117 -0
  259. package/src/db/Schema.ts +32 -0
  260. package/src/db/tables/fundingDepositAddresses.test.ts +28 -0
  261. package/src/db/tables/fundingDepositAddresses.ts +39 -0
  262. package/src/db/tables/fundingDeposits.test.ts +82 -0
  263. package/src/db/tables/fundingDeposits.ts +130 -0
  264. package/src/db/tables/webhookQueueEvents.ts +285 -0
  265. package/src/db/tables/webhookSubscriptions.ts +15 -0
  266. package/src/internal/Auth.test.ts +61 -98
  267. package/src/internal/Auth.ts +9 -63
  268. package/src/internal/Credential.test.ts +51 -0
  269. package/src/internal/Credential.ts +58 -0
  270. package/src/internal/EarnRates.ts +13 -7
  271. package/src/internal/EarnVaults.test.ts +2 -2
  272. package/src/internal/Log.test.ts +3 -1
  273. package/src/internal/Log.ts +14 -0
  274. package/src/internal/MetricSink.test.ts +108 -2
  275. package/src/internal/MetricSink.ts +112 -2
  276. package/src/internal/RateLimit.test.ts +23 -18
  277. package/src/internal/RateLimit.ts +23 -15
  278. package/src/internal/Store.test.ts +99 -13
  279. package/src/internal/Store.ts +52 -17
  280. package/src/internal/Tidx.test.ts +4 -4
  281. package/src/internal/Tidx.ts +5 -4
  282. package/src/internal/Value.test.ts +25 -0
  283. package/src/internal/Value.ts +29 -0
  284. package/src/internal/Viem.test.ts +4 -0
  285. package/src/internal/Viem.ts +6 -4
  286. package/src/internal/WebhookTransfer.test.ts +89 -0
  287. package/src/internal/WebhookTransfer.ts +11 -7
  288. package/src/internal/Webhooks.test.ts +488 -0
  289. package/src/internal/Webhooks.ts +291 -10
  290. package/src/internal/funding/Provider.test.ts +1 -1
  291. package/src/internal/funding/providers/index.test.ts +73 -3
  292. package/src/internal/funding/providers/relay.test.ts +1 -5
  293. package/src/internal/funding/providers/relay.ts +56 -12
  294. package/src/internal/index.test.ts +1 -0
  295. package/src/internal/index.ts +1 -0
@@ -0,0 +1,285 @@
1
+ import { type ExpressionBuilder, type Generated, type Insertable, type Selectable, sql } from 'kysely' // prettier-ignore
2
+
3
+ import type * as Webhooks from '../../internal/Webhooks.js'
4
+ import type * as Db from '../Db.js'
5
+ import type * as db_Schema from '../Schema.js'
6
+
7
+ /** Columns of the `webhook_queue_events` table, derived from `Schema.WebhookQueueEvent`. */
8
+ export type Table = Omit<
9
+ db_Schema.WebhookQueueEvent,
10
+ 'attemptCount' | 'attemptingAt' | 'nextAttemptAt' | 'status'
11
+ > & {
12
+ /** Delivery attempts claimed so far; database default 0. */
13
+ attemptCount: Generated<number>
14
+ /** ISO timestamp of the current claim; database default null. */
15
+ attemptingAt: Generated<string | null>
16
+ /** ISO retry due time; database default null (due immediately). */
17
+ nextAttemptAt: Generated<string | null>
18
+ /** Delivery-obligation lifecycle status; database default `pending`. */
19
+ status: Generated<db_Schema.WebhookQueueEvent['status']>
20
+ }
21
+
22
+ /** A stored webhook Queue event. */
23
+ export type Record = Selectable<Table>
24
+
25
+ /** A terminal delivery-obligation outcome. */
26
+ export type TerminalStatus = Exclude<db_Schema.WebhookQueueEvent['status'], 'pending'>
27
+
28
+ /**
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
+ */
32
+ export async function get(
33
+ db: Db.Db,
34
+ reference: Webhooks.QueueReference,
35
+ ): Promise<Webhooks.QueueEvent | null> {
36
+ return (
37
+ (await db.kysely
38
+ .selectFrom('webhook_queue_events')
39
+ .selectAll()
40
+ .where('eventId', '=', reference.eventId)
41
+ .where('status', '=', 'pending')
42
+ .where('subscriptionId', '=', reference.subscriptionId)
43
+ .executeTakeFirst()) ?? null
44
+ )
45
+ }
46
+
47
+ /**
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.
52
+ */
53
+ export async function ensure(db: Db.Db, rows: readonly Insertable<Table>[]): Promise<number> {
54
+ if (rows.length === 0) return 0
55
+ const result = await db.kysely
56
+ .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
+ ]),
75
+ ),
76
+ )
77
+ .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
+ return Number(result.numInsertedOrUpdatedRows ?? 0n)
81
+ }
82
+
83
+ /** Due, unclaimed pending work: shared by {@link claim} and {@link due}. */
84
+ function whereClaimable(
85
+ eb: ExpressionBuilder<Db.Database, 'webhook_queue_events'>,
86
+ options: { now: string; staleBefore: string },
87
+ ) {
88
+ return eb.and([
89
+ eb('status', '=', 'pending'),
90
+ eb.or([eb('nextAttemptAt', 'is', null), eb('nextAttemptAt', '<=', options.now)]),
91
+ eb.or([eb('attemptingAt', 'is', null), eb('attemptingAt', '<=', options.staleBefore)]),
92
+ ])
93
+ }
94
+
95
+ /** Claims one due pending obligation with a single atomic update. */
96
+ export async function claim(
97
+ db: Db.Db,
98
+ reference: Webhooks.QueueReference,
99
+ options: claim.Options,
100
+ ): Promise<claim.Result> {
101
+ const claimed = await db.kysely
102
+ .updateTable('webhook_queue_events')
103
+ .set((eb) => ({ attemptCount: eb('attemptCount', '+', 1), attemptingAt: options.now }))
104
+ .where('subscriptionId', '=', reference.subscriptionId)
105
+ .where('eventId', '=', reference.eventId)
106
+ .where((eb) => whereClaimable(eb, options))
107
+ .returningAll()
108
+ .executeTakeFirst()
109
+ if (claimed) return { record: claimed, type: 'claimed' }
110
+
111
+ const existing = await db.kysely
112
+ .selectFrom('webhook_queue_events')
113
+ .select(['attemptingAt', 'nextAttemptAt', 'status'])
114
+ .where('subscriptionId', '=', reference.subscriptionId)
115
+ .where('eventId', '=', reference.eventId)
116
+ .executeTakeFirst()
117
+ if (!existing) return { type: 'missing' }
118
+ if (existing.status !== 'pending') return { status: existing.status, type: 'terminal' }
119
+ if (existing.attemptingAt !== null && existing.attemptingAt > options.staleBefore)
120
+ return { type: 'attempting' }
121
+ return { nextAttemptAt: existing.nextAttemptAt, type: 'scheduled' }
122
+ }
123
+
124
+ export declare namespace claim {
125
+ /** Claim clock bounds. */
126
+ type Options = {
127
+ /** Current ISO timestamp; becomes the claim marker. */
128
+ now: string
129
+ /** ISO cutoff below which an existing claim counts as stale. */
130
+ staleBefore: string
131
+ }
132
+
133
+ /** Atomic claim outcome. */
134
+ type Result =
135
+ | { type: 'attempting' }
136
+ | { record: Record; type: 'claimed' }
137
+ | { type: 'missing' }
138
+ | { nextAttemptAt: string | null; type: 'scheduled' }
139
+ | { status: TerminalStatus; type: 'terminal' }
140
+ }
141
+
142
+ /**
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.
145
+ * The `attemptingAt` predicate fences stale claimants.
146
+ */
147
+ export async function complete(
148
+ db: Db.Db,
149
+ reference: Webhooks.QueueReference,
150
+ options: complete.Options,
151
+ ): Promise<boolean> {
152
+ 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)
159
+ .executeTakeFirst()
160
+ return result.numUpdatedRows === 1n
161
+ }
162
+
163
+ export declare namespace complete {
164
+ /** Fenced terminal transition. */
165
+ type Options = {
166
+ /** Claim marker returned by {@link claim}; fences stale claimants. */
167
+ claimedAt: string
168
+ /** Replacement ISO retention deadline for the terminal dedupe row. */
169
+ expiresAt: string
170
+ /** Terminal outcome. */
171
+ status: TerminalStatus
172
+ }
173
+ }
174
+
175
+ /** Releases a claim and schedules the next attempt; fenced like {@link complete}. */
176
+ export async function schedule(
177
+ db: Db.Db,
178
+ reference: Webhooks.QueueReference,
179
+ options: schedule.Options,
180
+ ): Promise<boolean> {
181
+ const result = await db.kysely
182
+ .updateTable('webhook_queue_events')
183
+ .set({ attemptingAt: null, nextAttemptAt: options.nextAttemptAt })
184
+ .where('subscriptionId', '=', reference.subscriptionId)
185
+ .where('eventId', '=', reference.eventId)
186
+ .where('status', '=', 'pending')
187
+ .where('attemptingAt', '=', options.claimedAt)
188
+ .executeTakeFirst()
189
+ return result.numUpdatedRows === 1n
190
+ }
191
+
192
+ export declare namespace schedule {
193
+ /** Fenced retry scheduling. */
194
+ type Options = {
195
+ /** Claim marker returned by {@link claim}; fences stale claimants. */
196
+ claimedAt: string
197
+ /** ISO due time of the next attempt. */
198
+ nextAttemptAt: string
199
+ }
200
+ }
201
+
202
+ /** Lists due pending references for the sweeper, most overdue first. */
203
+ export async function due(db: Db.Db, options: due.Options): Promise<Webhooks.QueueReference[]> {
204
+ return (
205
+ db.kysely
206
+ .selectFrom('webhook_queue_events')
207
+ .select(['eventId', 'subscriptionId'])
208
+ .where((eb) => whereClaimable(eb, options))
209
+ // Matches the pending partial index order so the scan needs no sort.
210
+ .orderBy(sql`next_attempt_at nulls first`)
211
+ .limit(options.limit)
212
+ .execute()
213
+ )
214
+ }
215
+
216
+ export declare namespace due {
217
+ /** Sweep bounds. */
218
+ type Options = {
219
+ /** Maximum references to return. */
220
+ limit: number
221
+ /** Current ISO timestamp. */
222
+ now: string
223
+ /** ISO cutoff below which an existing claim counts as stale. */
224
+ staleBefore: string
225
+ }
226
+ }
227
+
228
+ /** Returns the pending-obligation snapshot, split by due-and-unclaimed work. */
229
+ export async function pending(db: Db.Db, options: pending.Options): Promise<pending.Result> {
230
+ const row = await db.kysely
231
+ .selectFrom('webhook_queue_events')
232
+ .select((eb) => [
233
+ eb.fn.countAll<number>().as('count'),
234
+ eb.fn.min('createdAt').as('oldestAt'),
235
+ eb.fn
236
+ .countAll<number>()
237
+ .filterWhere((fb) => whereClaimable(fb, options))
238
+ .as('dueCount'),
239
+ eb.fn
240
+ // Null next attempts (migration-backfilled rows) are due immediately;
241
+ // coalesce to staging time so they age instead of vanishing from MIN.
242
+ .min(eb.fn.coalesce('nextAttemptAt', 'createdAt'))
243
+ .filterWhere((fb) => whereClaimable(fb, options))
244
+ .as('oldestDueAt'),
245
+ ])
246
+ .where('status', '=', 'pending')
247
+ .executeTakeFirstOrThrow()
248
+ return {
249
+ count: Number(row.count),
250
+ dueCount: Number(row.dueCount),
251
+ oldestAt: row.oldestAt,
252
+ oldestDueAt: row.oldestDueAt,
253
+ }
254
+ }
255
+
256
+ export declare namespace pending {
257
+ /** Snapshot clock bounds; the read-only subset of {@link claim.Options}. */
258
+ type Options = Pick<claim.Options, 'now' | 'staleBefore'>
259
+
260
+ /** Pending-work snapshot. */
261
+ type Result = {
262
+ /** Pending obligations, including in-flight claims and scheduled retries. */
263
+ count: number
264
+ /** Obligations due now with no live claim; the sweeper's backlog. */
265
+ dueCount: number
266
+ /** Oldest pending staging timestamp, or null when none. */
267
+ oldestAt: string | null
268
+ /** Oldest due-and-unclaimed retry time, or null when none; drives the stall alert. */
269
+ oldestDueAt: string | null
270
+ }
271
+ }
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
+ }
@@ -13,6 +13,21 @@ export type Table = db_Schema.WebhookSubscription
13
13
  /** A stored webhook-subscription row. */
14
14
  export type Record = Selectable<Table>
15
15
 
16
+ /** Reads one live subscription by id for an internal Queue delivery. */
17
+ export async function getById(
18
+ db: Db.Db,
19
+ id: string,
20
+ now: string,
21
+ ): Promise<Webhooks.Subscription | null> {
22
+ const row = await db.kysely
23
+ .selectFrom('webhook_subscriptions')
24
+ .selectAll()
25
+ .where('id', '=', id)
26
+ .where(live(now))
27
+ .executeTakeFirst()
28
+ return row ? toSubscription(row) : null
29
+ }
30
+
16
31
  /** Maps an owner union to its stored `ownerId` column value. */
17
32
  export function ownerId(owner: Webhooks.Owner): string {
18
33
  return owner.type === 'api_key' ? owner.orgId : owner.payer