tapimo 0.9.0 → 0.10.1

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 (319) 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 +4292 -3708
  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 +21 -20
  12. package/dist/Webhooks.d.ts.map +1 -1
  13. package/dist/Webhooks.js +66 -23
  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 +26 -17
  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/apps/verified-tokens.d.ts +168 -168
  24. package/dist/admin/ui.generated.d.ts +1 -1
  25. package/dist/admin/ui.generated.js +69 -69
  26. package/dist/admin/ui.generated.js.map +1 -1
  27. package/dist/analytics/Analytics.d.ts.map +1 -1
  28. package/dist/analytics/Analytics.js +26 -0
  29. package/dist/analytics/Analytics.js.map +1 -1
  30. package/dist/apps/Zones.d.ts +4280 -84
  31. package/dist/apps/Zones.d.ts.map +1 -1
  32. package/dist/apps/Zones.js +30 -10
  33. package/dist/apps/Zones.js.map +1 -1
  34. package/dist/apps/data/App.d.ts +5799 -4929
  35. package/dist/apps/data/App.d.ts.map +1 -1
  36. package/dist/apps/data/FxOracle.d.ts +4 -5
  37. package/dist/apps/data/FxOracle.d.ts.map +1 -1
  38. package/dist/apps/data/FxOracle.js +19 -4
  39. package/dist/apps/data/FxOracle.js.map +1 -1
  40. package/dist/apps/data/routes/activities.d.ts +3391 -492
  41. package/dist/apps/data/routes/activities.d.ts.map +1 -1
  42. package/dist/apps/data/routes/activities.js +372 -29
  43. package/dist/apps/data/routes/activities.js.map +1 -1
  44. package/dist/apps/data/routes/addresses.d.ts +853 -339
  45. package/dist/apps/data/routes/addresses.d.ts.map +1 -1
  46. package/dist/apps/data/routes/balances.d.ts +10 -10
  47. package/dist/apps/data/routes/balances.d.ts.map +1 -1
  48. package/dist/apps/data/routes/balances.js +145 -83
  49. package/dist/apps/data/routes/balances.js.map +1 -1
  50. package/dist/apps/data/routes/blocks.d.ts +123 -123
  51. package/dist/apps/data/routes/coingecko.d.ts +169 -169
  52. package/dist/apps/data/routes/earn.d.ts +455 -1
  53. package/dist/apps/data/routes/earn.d.ts.map +1 -1
  54. package/dist/apps/data/routes/earn.js +369 -45
  55. package/dist/apps/data/routes/earn.js.map +1 -1
  56. package/dist/apps/data/routes/exchanges.d.ts.map +1 -1
  57. package/dist/apps/data/routes/exchanges.js +114 -118
  58. package/dist/apps/data/routes/exchanges.js.map +1 -1
  59. package/dist/apps/data/routes/fee-amm.d.ts +24 -24
  60. package/dist/apps/data/routes/indexer.d.ts +35 -34
  61. package/dist/apps/data/routes/indexer.d.ts.map +1 -1
  62. package/dist/apps/data/routes/indexer.js +3 -2
  63. package/dist/apps/data/routes/indexer.js.map +1 -1
  64. package/dist/apps/data/routes/receipts.d.ts.map +1 -1
  65. package/dist/apps/data/routes/receipts.js +68 -47
  66. package/dist/apps/data/routes/receipts.js.map +1 -1
  67. package/dist/apps/data/routes/rpc.d.ts +31 -31
  68. package/dist/apps/data/routes/rpc.d.ts.map +1 -1
  69. package/dist/apps/data/routes/rpc.js +5 -1
  70. package/dist/apps/data/routes/rpc.js.map +1 -1
  71. package/dist/apps/data/routes/tokenlist.d.ts +12 -12
  72. package/dist/apps/data/routes/tokens.d.ts +461 -459
  73. package/dist/apps/data/routes/tokens.d.ts.map +1 -1
  74. package/dist/apps/data/routes/transactions.d.ts +313 -305
  75. package/dist/apps/data/routes/transactions.d.ts.map +1 -1
  76. package/dist/apps/data/routes/transactions.js +25 -23
  77. package/dist/apps/data/routes/transactions.js.map +1 -1
  78. package/dist/apps/data/routes/transfers.d.ts +108 -86
  79. package/dist/apps/data/routes/transfers.d.ts.map +1 -1
  80. package/dist/apps/data/routes/transfers.js +618 -105
  81. package/dist/apps/data/routes/transfers.js.map +1 -1
  82. package/dist/apps/data/routes/valuation.d.ts +21 -21
  83. package/dist/apps/data/routes/valuation.d.ts.map +1 -1
  84. package/dist/apps/data/routes/valuation.js +2 -2
  85. package/dist/apps/data/routes/valuation.js.map +1 -1
  86. package/dist/apps/data/routes/webhooks.d.ts +519 -868
  87. package/dist/apps/data/routes/webhooks.d.ts.map +1 -1
  88. package/dist/apps/data/routes/webhooks.js +642 -353
  89. package/dist/apps/data/routes/webhooks.js.map +1 -1
  90. package/dist/apps/data/routes/zones.d.ts +31 -31
  91. package/dist/apps/data/routes/zones.d.ts.map +1 -1
  92. package/dist/apps/data/routes/zones.js +4 -2
  93. package/dist/apps/data/routes/zones.js.map +1 -1
  94. package/dist/apps/funding/routes/deposit-addresses.js +1 -1
  95. package/dist/apps/funding/routes/deposit-addresses.js.map +1 -1
  96. package/dist/apps/funding/routes/providers.d.ts +13 -13
  97. package/dist/apps/funding/routes/quotes.d.ts +89 -89
  98. package/dist/apps/funding/routes/transfers.d.ts +36 -36
  99. package/dist/apps/funding/routes/transfers.js +1 -1
  100. package/dist/apps/funding/routes/transfers.js.map +1 -1
  101. package/dist/apps/management/App.d.ts +11 -309
  102. package/dist/apps/management/App.d.ts.map +1 -1
  103. package/dist/apps/management/routes/api-keys.d.ts +74 -74
  104. package/dist/apps/management/routes/billing.js +1 -1
  105. package/dist/apps/management/routes/billing.js.map +1 -1
  106. package/dist/apps/management/routes/faucet.d.ts +35 -35
  107. package/dist/apps/management/routes/faucet.js +2 -2
  108. package/dist/apps/management/routes/faucet.js.map +1 -1
  109. package/dist/apps/management/routes/invitations.d.ts +134 -134
  110. package/dist/apps/management/routes/invite-links.d.ts +80 -80
  111. package/dist/apps/management/routes/members.d.ts +69 -69
  112. package/dist/apps/management/routes/orgs.d.ts +98 -98
  113. package/dist/apps/management/routes/projects.d.ts +60 -60
  114. package/dist/apps/management/routes/usage.d.ts +72 -72
  115. package/dist/apps/management/routes/verified-token-requests.d.ts +83 -83
  116. package/dist/apps/management/routes/webhooks.d.ts +203 -618
  117. package/dist/apps/management/routes/webhooks.d.ts.map +1 -1
  118. package/dist/apps/mcp.d.ts.map +1 -1
  119. package/dist/apps/mcp.js +4 -1
  120. package/dist/apps/mcp.js.map +1 -1
  121. package/dist/apps/mpp/App.d.ts +56 -44
  122. package/dist/apps/mpp/App.d.ts.map +1 -1
  123. package/dist/apps/mpp/Mpp.d.ts +3 -0
  124. package/dist/apps/mpp/Mpp.d.ts.map +1 -1
  125. package/dist/apps/mpp/Mpp.js +3 -0
  126. package/dist/apps/mpp/Mpp.js.map +1 -1
  127. package/dist/apps/mpp/internal/Adapter.d.ts.map +1 -1
  128. package/dist/apps/mpp/internal/Adapter.js +9 -2
  129. package/dist/apps/mpp/internal/Adapter.js.map +1 -1
  130. package/dist/db/Db.d.ts +4 -2
  131. package/dist/db/Db.d.ts.map +1 -1
  132. package/dist/db/Db.js +75 -15
  133. package/dist/db/Db.js.map +1 -1
  134. package/dist/db/Schema.d.ts +19 -0
  135. package/dist/db/Schema.d.ts.map +1 -1
  136. package/dist/db/Schema.js +24 -0
  137. package/dist/db/Schema.js.map +1 -1
  138. package/dist/db/tables/sponsoredTransactions.d.ts.map +1 -1
  139. package/dist/db/tables/sponsoredTransactions.js +4 -1
  140. package/dist/db/tables/sponsoredTransactions.js.map +1 -1
  141. package/dist/db/tables/webhookQueueEvents.d.ts +122 -0
  142. package/dist/db/tables/webhookQueueEvents.d.ts.map +1 -0
  143. package/dist/db/tables/webhookQueueEvents.js +160 -0
  144. package/dist/db/tables/webhookQueueEvents.js.map +1 -0
  145. package/dist/db/tables/webhookSubscriptions.d.ts +58 -3
  146. package/dist/db/tables/webhookSubscriptions.d.ts.map +1 -1
  147. package/dist/db/tables/webhookSubscriptions.js +86 -3
  148. package/dist/db/tables/webhookSubscriptions.js.map +1 -1
  149. package/dist/internal/Auth.d.ts +3 -0
  150. package/dist/internal/Auth.d.ts.map +1 -1
  151. package/dist/internal/Auth.js +6 -4
  152. package/dist/internal/Auth.js.map +1 -1
  153. package/dist/internal/MetricSink.d.ts +139 -3
  154. package/dist/internal/MetricSink.d.ts.map +1 -1
  155. package/dist/internal/MetricSink.js +144 -4
  156. package/dist/internal/MetricSink.js.map +1 -1
  157. package/dist/internal/RateLimit.d.ts +4 -6
  158. package/dist/internal/RateLimit.d.ts.map +1 -1
  159. package/dist/internal/RateLimit.js +15 -14
  160. package/dist/internal/RateLimit.js.map +1 -1
  161. package/dist/internal/Schema.js +1 -1
  162. package/dist/internal/Schema.js.map +1 -1
  163. package/dist/internal/Store.d.ts +2 -1
  164. package/dist/internal/Store.d.ts.map +1 -1
  165. package/dist/internal/Store.js +57 -18
  166. package/dist/internal/Store.js.map +1 -1
  167. package/dist/internal/Tidx.d.ts +1 -3
  168. package/dist/internal/Tidx.d.ts.map +1 -1
  169. package/dist/internal/Tidx.js +1 -3
  170. package/dist/internal/Tidx.js.map +1 -1
  171. package/dist/internal/Value.d.ts +2 -0
  172. package/dist/internal/Value.d.ts.map +1 -1
  173. package/dist/internal/Value.js +25 -0
  174. package/dist/internal/Value.js.map +1 -1
  175. package/dist/internal/Viem.d.ts.map +1 -1
  176. package/dist/internal/Viem.js +2 -1
  177. package/dist/internal/Viem.js.map +1 -1
  178. package/dist/internal/WebhookDestination.d.ts +2 -0
  179. package/dist/internal/WebhookDestination.d.ts.map +1 -1
  180. package/dist/internal/WebhookDestination.js +11 -0
  181. package/dist/internal/WebhookDestination.js.map +1 -1
  182. package/dist/internal/WebhookTransfer.d.ts +75 -0
  183. package/dist/internal/WebhookTransfer.d.ts.map +1 -0
  184. package/dist/internal/WebhookTransfer.js +299 -0
  185. package/dist/internal/WebhookTransfer.js.map +1 -0
  186. package/dist/internal/Webhooks.d.ts +176 -0
  187. package/dist/internal/Webhooks.d.ts.map +1 -1
  188. package/dist/internal/Webhooks.js +216 -20
  189. package/dist/internal/Webhooks.js.map +1 -1
  190. package/dist/internal/funding/Provider.d.ts +4 -0
  191. package/dist/internal/funding/Provider.d.ts.map +1 -1
  192. package/dist/internal/funding/Provider.js +11 -7
  193. package/dist/internal/funding/Provider.js.map +1 -1
  194. package/dist/internal/funding/Reconciliation.d.ts.map +1 -1
  195. package/dist/internal/funding/Reconciliation.js +128 -0
  196. package/dist/internal/funding/Reconciliation.js.map +1 -1
  197. package/dist/internal/funding/providers/relay.d.ts +6 -4
  198. package/dist/internal/funding/providers/relay.d.ts.map +1 -1
  199. package/dist/internal/funding/providers/relay.js +71 -9
  200. package/dist/internal/funding/providers/relay.js.map +1 -1
  201. package/package.json +4 -4
  202. package/src/ApiKeys.test.ts +20 -0
  203. package/src/ApiKeys.ts +28 -1
  204. package/src/App.test.ts +10 -8
  205. package/src/App.ts +1 -0
  206. package/src/Client.test-d.ts +61 -5
  207. package/src/Webhooks.bench.ts +714 -0
  208. package/src/Webhooks.memory.bench.ts +250 -0
  209. package/src/Webhooks.test.ts +77 -6
  210. package/src/Webhooks.ts +85 -29
  211. package/src/admin/apps/api-keys.test.ts +9 -0
  212. package/src/admin/apps/api-keys.ts +15 -0
  213. package/src/admin/ui/src/lib/query-client.ts +3 -2
  214. package/src/admin/ui/src/routes/api-keys.tsx +31 -9
  215. package/src/admin/ui.generated.ts +69 -69
  216. package/src/analytics/Analytics.test.ts +8 -0
  217. package/src/analytics/Analytics.ts +26 -0
  218. package/src/apps/Zones.test.ts +23 -5
  219. package/src/apps/Zones.ts +31 -10
  220. package/src/apps/composition.test.ts +1 -0
  221. package/src/apps/data/App.test.ts +2 -2
  222. package/src/apps/data/FxOracle.test.ts +18 -0
  223. package/src/apps/data/FxOracle.ts +28 -5
  224. package/src/apps/data/routes/activities.bench.ts +41 -0
  225. package/src/apps/data/routes/activities.test.ts +122 -2
  226. package/src/apps/data/routes/activities.ts +435 -32
  227. package/src/apps/data/routes/balances.bench.ts +21 -0
  228. package/src/apps/data/routes/balances.test.ts +119 -10
  229. package/src/apps/data/routes/balances.ts +164 -97
  230. package/src/apps/data/routes/blocks.bench.ts +34 -0
  231. package/src/apps/data/routes/blocks.test.ts +12 -6
  232. package/src/apps/data/routes/coingecko.bench.ts +53 -0
  233. package/src/apps/data/routes/earn.bench.ts +71 -0
  234. package/src/apps/data/routes/earn.test.ts +197 -0
  235. package/src/apps/data/routes/earn.ts +487 -45
  236. package/src/apps/data/routes/exchanges.bench.ts +85 -0
  237. package/src/apps/data/routes/exchanges.test.ts +7 -6
  238. package/src/apps/data/routes/exchanges.ts +124 -117
  239. package/src/apps/data/routes/fee-amm.bench.ts +13 -0
  240. package/src/apps/data/routes/indexer.bench.ts +8 -0
  241. package/src/apps/data/routes/indexer.test.ts +2 -1
  242. package/src/apps/data/routes/indexer.ts +3 -2
  243. package/src/apps/data/routes/receipts.bench.ts +39 -0
  244. package/src/apps/data/routes/receipts.test.ts +24 -7
  245. package/src/apps/data/routes/receipts.ts +67 -50
  246. package/src/apps/data/routes/rpc.bench.ts +14 -0
  247. package/src/apps/data/routes/rpc.test.ts +27 -0
  248. package/src/apps/data/routes/rpc.ts +5 -1
  249. package/src/apps/data/routes/tokenlist.bench.ts +3 -0
  250. package/src/apps/data/routes/tokens.bench.ts +76 -0
  251. package/src/apps/data/routes/tokens.test.ts +51 -0
  252. package/src/apps/data/routes/transactions.bench.ts +40 -0
  253. package/src/apps/data/routes/transactions.test.ts +42 -0
  254. package/src/apps/data/routes/transactions.ts +44 -39
  255. package/src/apps/data/routes/transfers.bench.ts +28 -0
  256. package/src/apps/data/routes/transfers.test.ts +402 -24
  257. package/src/apps/data/routes/transfers.ts +708 -101
  258. package/src/apps/data/routes/valuation.bench.ts +14 -0
  259. package/src/apps/data/routes/valuation.test.ts +26 -0
  260. package/src/apps/data/routes/valuation.ts +6 -2
  261. package/src/apps/data/routes/verified-tokens.bench.ts +8 -0
  262. package/src/apps/data/routes/webhooks.bench.ts +26 -0
  263. package/src/apps/data/routes/webhooks.scan.test.ts +326 -0
  264. package/src/apps/data/routes/webhooks.test.ts +466 -135
  265. package/src/apps/data/routes/webhooks.ts +820 -429
  266. package/src/apps/data/routes/zones.bench.ts +32 -0
  267. package/src/apps/data/routes/zones.test.ts +1 -1
  268. package/src/apps/data/routes/zones.ts +7 -2
  269. package/src/apps/funding/routes/chains.test.ts +1 -1
  270. package/src/apps/funding/routes/deposit-addresses.test.ts +1 -1
  271. package/src/apps/funding/routes/deposit-addresses.ts +1 -1
  272. package/src/apps/funding/routes/providers.test.ts +1 -1
  273. package/src/apps/funding/routes/quotes.test.ts +2 -2
  274. package/src/apps/funding/routes/transfers.test.ts +3 -6
  275. package/src/apps/funding/routes/transfers.ts +1 -1
  276. package/src/apps/management/routes/billing.ts +1 -1
  277. package/src/apps/management/routes/faucet.ts +2 -2
  278. package/src/apps/management/routes/me.test.ts +31 -0
  279. package/src/apps/mcp.ts +4 -1
  280. package/src/apps/mpp/Mpp.test.ts +13 -1
  281. package/src/apps/mpp/Mpp.ts +8 -0
  282. package/src/apps/mpp/internal/Adapter.ts +10 -2
  283. package/src/db/Db.test.ts +40 -6
  284. package/src/db/Db.ts +114 -19
  285. package/src/db/Schema.ts +32 -0
  286. package/src/db/tables/sponsoredTransactions.ts +9 -2
  287. package/src/db/tables/webhookQueueEvents.ts +285 -0
  288. package/src/db/tables/webhookSubscriptions.ts +131 -4
  289. package/src/internal/Auth.test.ts +61 -50
  290. package/src/internal/Auth.ts +9 -4
  291. package/src/internal/EarnVaults.test.ts +2 -2
  292. package/src/internal/MetricSink.test.ts +575 -0
  293. package/src/internal/MetricSink.ts +325 -10
  294. package/src/internal/RateLimit.test.ts +23 -18
  295. package/src/internal/RateLimit.ts +23 -15
  296. package/src/internal/Schema.ts +1 -1
  297. package/src/internal/Store.test.ts +99 -13
  298. package/src/internal/Store.ts +52 -17
  299. package/src/internal/Tidx.test.ts +4 -4
  300. package/src/internal/Tidx.ts +1 -3
  301. package/src/internal/Value.test.ts +25 -0
  302. package/src/internal/Value.ts +29 -0
  303. package/src/internal/Viem.test.ts +8 -0
  304. package/src/internal/Viem.ts +2 -1
  305. package/src/internal/WebhookDestination.test.ts +20 -0
  306. package/src/internal/WebhookDestination.ts +10 -0
  307. package/src/internal/WebhookTransfer.test.ts +89 -0
  308. package/src/internal/WebhookTransfer.ts +430 -0
  309. package/src/internal/Webhooks.test.ts +616 -0
  310. package/src/internal/Webhooks.ts +412 -24
  311. package/src/internal/funding/Provider.test.ts +48 -8
  312. package/src/internal/funding/Provider.ts +12 -7
  313. package/src/internal/funding/Reconciliation.local.test.ts +3 -1
  314. package/src/internal/funding/Reconciliation.test.ts +1240 -0
  315. package/src/internal/funding/Reconciliation.ts +136 -0
  316. package/src/internal/funding/SourceObservation.test.ts +1 -0
  317. package/src/internal/funding/providers/index.test.ts +73 -3
  318. package/src/internal/funding/providers/relay.test.ts +2 -9
  319. package/src/internal/funding/providers/relay.ts +72 -13
@@ -0,0 +1,250 @@
1
+ /** @module-tag localnet */
2
+ /**
3
+ * Measures retained database-factory memory and dense transfer-scan allocations.
4
+ * Set `WEBHOOK_MEMORY_SNAPSHOT_DIR` to write V8 heap snapshots for retained objects.
5
+ */
6
+ import * as fs from 'node:fs'
7
+ import * as path from 'node:path'
8
+ import * as v8 from 'node:v8'
9
+ import { nanoid } from 'nanoid'
10
+ import { sendTransactionSync } from 'viem/actions'
11
+ import { Actions, Addresses } from 'viem/tempo'
12
+ import { Webhooks } from 'tapimo'
13
+ import { bench } from 'vite-plus/test'
14
+
15
+ import * as Db from './db/Db.js'
16
+ import * as Cursor from './internal/Cursor.js'
17
+ import * as Runtime from '../test/runtime.js'
18
+ import * as TestApp from '../test/App.js'
19
+ import * as Tempo from '../test/Tempo.js'
20
+ import * as TestViem from '../test/Viem.js'
21
+
22
+ const batchSize = 200
23
+ const endOfBlockIndex = 2_147_483_647
24
+ const factoryTicks = positiveInteger('WEBHOOK_MEMORY_FACTORY_TICKS', 25)
25
+ const mebibyte = 1024 * 1024
26
+ const runtime = Runtime.get()
27
+ const transferCallsPerTransaction = 32
28
+ const transferCount = positiveInteger('WEBHOOK_MEMORY_TRANSFER_COUNT', 3_600)
29
+
30
+ type Memory = {
31
+ arrayBuffers: number
32
+ external: number
33
+ heapUsed: number
34
+ rss: number
35
+ }
36
+
37
+ type Report = {
38
+ after: Memory
39
+ afterOperation: Memory
40
+ afterOperationDelta: Memory
41
+ before: Memory
42
+ delta: Memory
43
+ details?: Record<string, number> | undefined
44
+ label: string
45
+ }
46
+
47
+ const reports: Report[] = []
48
+
49
+ beforeAll(() => {
50
+ if (runtime.mode !== 'localnet')
51
+ throw new Error('Run webhook memory benchmarks with TEMPO_ENV=localnet.')
52
+ if (!globalThis.gc)
53
+ throw new Error('Run webhook memory benchmarks with NODE_OPTIONS=--expose-gc.')
54
+ })
55
+
56
+ afterAll(() => {
57
+ process.stdout.write(`WEBHOOK_MEMORY_REPORT ${JSON.stringify(reports)}\n`)
58
+ })
59
+
60
+ describe('webhook poller memory', () => {
61
+ bench(
62
+ `factory-created databases / ${factoryTicks} ticks`,
63
+ async () => {
64
+ reports.push(await measureFactoryRetention())
65
+ },
66
+ { iterations: 1, time: 0, warmupIterations: 0, warmupTime: 0 },
67
+ )
68
+
69
+ bench(
70
+ `transfer firehose / ${transferCount} generated transfers`,
71
+ async () => {
72
+ reports.push(await measureTransferFirehose())
73
+ },
74
+ { iterations: 1, time: 0, warmupIterations: 0, warmupTime: 0 },
75
+ )
76
+ })
77
+
78
+ async function measureFactoryRetention(): Promise<Report> {
79
+ const databases: Db.Db[] = []
80
+ const schema = `memory_${nanoid()}`
81
+ const database = Db.postgres({ connectionString: Runtime.postgresUrl, schema })
82
+ await database.migrate()
83
+ await Webhooks.createSubscription(database, {
84
+ chainId: runtime.chainId,
85
+ destination: { type: 'url', url: 'https://hooks.example.com/bench/memory/factory' },
86
+ eventType: 'block:created',
87
+ owner: { orgId: 'org_benchmark', type: 'api_key' },
88
+ })
89
+ const headBlockNumber = Number(await TestViem.getClient().getBlockNumber())
90
+ const poller = Webhooks.createPoller({
91
+ db: () => {
92
+ const database = Db.postgres({ connectionString: Runtime.postgresUrl, schema })
93
+ databases.push(database)
94
+ return database
95
+ },
96
+ defaultChainId: runtime.chainId,
97
+ dispatchBatch: async () => ({ ok: true }),
98
+ rpc: TestApp.rpc,
99
+ })
100
+
101
+ try {
102
+ const report = await measure('factory-retained', async () => {
103
+ for (let index = 0; index < factoryTicks; index++)
104
+ await poller.tick({
105
+ confirmations: 0,
106
+ eventTypes: ['block:created'],
107
+ headBlockNumber,
108
+ maxBlocks: 1,
109
+ })
110
+ })
111
+ writeHeapSnapshot('factory-retained')
112
+ return { ...report, details: { databases: databases.length, ticks: factoryTicks } }
113
+ } finally {
114
+ await Promise.allSettled(databases.map((database) => database.close()))
115
+ await database.close()
116
+ }
117
+ }
118
+
119
+ async function measureTransferFirehose(): Promise<Report> {
120
+ const database = TestApp.database()
121
+ const client = Tempo.getClient({ account: Tempo.accounts[0] })
122
+ const startBlockNumber = Number(await TestViem.getClient().getBlockNumber())
123
+
124
+ try {
125
+ for (let offset = 0; offset < transferCount; offset += transferCallsPerTransaction) {
126
+ const length = Math.min(transferCallsPerTransaction, transferCount - offset)
127
+ await sendTransactionSync(client, {
128
+ account: Tempo.accounts[0],
129
+ calls: Array.from({ length }, () =>
130
+ Actions.token.transfer.call(client, {
131
+ amount: 1n,
132
+ to: Tempo.accounts[1].address,
133
+ token: Addresses.pathUsd,
134
+ }),
135
+ ),
136
+ })
137
+ }
138
+
139
+ const headBlockNumber = Number(await TestViem.getClient().getBlockNumber())
140
+ const subscription = await Webhooks.createSubscription(database, {
141
+ chainId: runtime.chainId,
142
+ destination: { type: 'url', url: 'https://hooks.example.com/bench/memory/firehose' },
143
+ eventType: 'token:transfer',
144
+ filters: {},
145
+ owner: { orgId: 'org_benchmark', type: 'api_key' },
146
+ })
147
+ await Webhooks.setCursor(
148
+ database,
149
+ subscription.id,
150
+ Cursor.encode([startBlockNumber, endOfBlockIndex]),
151
+ )
152
+
153
+ let dispatched = 0
154
+ let serializedBytes = 0
155
+ const poller = Webhooks.createPoller({
156
+ db: database,
157
+ defaultChainId: runtime.chainId,
158
+ dispatchBatch: async (dispatchables) => {
159
+ dispatched += dispatchables.length
160
+ for (const body of dispatchables)
161
+ serializedBytes += new TextEncoder().encode(JSON.stringify({ body })).byteLength
162
+ return { ok: true }
163
+ },
164
+ rpc: TestApp.rpc,
165
+ })
166
+
167
+ const report = await measure('transfer-firehose', async () => {
168
+ const result = await poller.tick({
169
+ batchSize,
170
+ confirmations: 0,
171
+ eventTypes: ['token:transfer'],
172
+ headBlockNumber,
173
+ maxBlocks: 500,
174
+ })
175
+ if (result.dispatched === 0 || result.dispatched !== dispatched)
176
+ throw new Error(`Unexpected dispatch count: ${result.dispatched} / ${dispatched}.`)
177
+ if (result.dispatched > batchSize)
178
+ throw new Error(`Dispatch count ${result.dispatched} exceeded batch size ${batchSize}.`)
179
+ if (serializedBytes === 0) throw new Error('Expected serialized webhook queue messages.')
180
+ })
181
+ writeHeapSnapshot('transfer-firehose-retained')
182
+ return {
183
+ ...report,
184
+ details: { dispatched, serializedBytes, transferCount },
185
+ }
186
+ } finally {
187
+ await database.close()
188
+ }
189
+ }
190
+
191
+ async function measure(label: string, fn: () => Promise<void>): Promise<Report> {
192
+ collectGarbage()
193
+ const before = memoryUsage()
194
+ await fn()
195
+ const afterOperation = memoryUsage()
196
+ collectGarbage()
197
+ const after = memoryUsage()
198
+ return {
199
+ after,
200
+ afterOperation,
201
+ afterOperationDelta: subtract(afterOperation, before),
202
+ before,
203
+ delta: subtract(after, before),
204
+ label,
205
+ }
206
+ }
207
+
208
+ function collectGarbage(): void {
209
+ globalThis.gc?.()
210
+ globalThis.gc?.()
211
+ }
212
+
213
+ function memoryUsage(): Memory {
214
+ const usage = process.memoryUsage()
215
+ return {
216
+ arrayBuffers: mebibytes(usage.arrayBuffers),
217
+ external: mebibytes(usage.external),
218
+ heapUsed: mebibytes(usage.heapUsed),
219
+ rss: mebibytes(usage.rss),
220
+ }
221
+ }
222
+
223
+ function mebibytes(bytes: number): number {
224
+ return Math.round((bytes / mebibyte) * 100) / 100
225
+ }
226
+
227
+ function positiveInteger(name: string, fallback: number): number {
228
+ const input = process.env[name]
229
+ if (!input) return fallback
230
+ const value = Number(input)
231
+ if (!Number.isSafeInteger(value) || value < 1)
232
+ throw new Error(`${name} must be a positive integer.`)
233
+ return value
234
+ }
235
+
236
+ function subtract(after: Memory, before: Memory): Memory {
237
+ return {
238
+ arrayBuffers: after.arrayBuffers - before.arrayBuffers,
239
+ external: after.external - before.external,
240
+ heapUsed: after.heapUsed - before.heapUsed,
241
+ rss: after.rss - before.rss,
242
+ }
243
+ }
244
+
245
+ function writeHeapSnapshot(label: string): void {
246
+ const directory = process.env.WEBHOOK_MEMORY_SNAPSHOT_DIR
247
+ if (!directory) return
248
+ fs.mkdirSync(directory, { recursive: true })
249
+ v8.writeHeapSnapshot(path.join(directory, `${label}.heapsnapshot`))
250
+ }
@@ -1,4 +1,4 @@
1
- import { Webhooks } from 'tapimo'
1
+ import { Metrics, Webhooks } from 'tapimo'
2
2
  import * as TestApp from '../test/App.js'
3
3
 
4
4
  describe('createPoller', () => {
@@ -25,9 +25,8 @@ describe('createPoller', () => {
25
25
 
26
26
  // A brand-new subscription anchors at the confirmed head on its first tick
27
27
  // (no backfill), so this exercises the createPoller → poll wiring (db
28
- // binding, default chain id, injected head, result shape) without touching
29
- // TIDX. Detailed poll/getIndexedHead behavior lives in
30
- // src/apps/webhooks.test.ts.
28
+ // binding, default chain id, injected head, and result shape) without an
29
+ // upstream request. Detailed scanner behavior lives in the route test.
31
30
  const result = await poller.tick({ headBlockNumber: 200 })
32
31
 
33
32
  expect(result).toMatchInlineSnapshot(`
@@ -35,19 +34,70 @@ describe('createPoller', () => {
35
34
  "chainId": 42431,
36
35
  "dispatched": 0,
37
36
  "failed": 0,
38
- "safeBlock": 198,
37
+ "safeBlock": 199,
39
38
  "scanned": 0,
40
39
  "subscriptions": 1,
41
40
  }
42
41
  `)
43
42
  expect(decodeCursor(await Webhooks.getCursor(db, subscription.id))).toMatchInlineSnapshot(`
44
43
  [
45
- 198,
44
+ 199,
46
45
  2147483647,
47
46
  ]
48
47
  `)
49
48
  })
50
49
 
50
+ test('records active subscriptions before scanning', async () => {
51
+ const db = TestApp.database()
52
+ await Webhooks.createSubscription(db, {
53
+ chainId: 42431,
54
+ destination: { type: 'url', url: 'https://hooks.example.com/x' },
55
+ eventType: 'block:created',
56
+ owner: { orgId: 'org_test', type: 'api_key' },
57
+ })
58
+ const gauges: { name: string; tags: Metrics.Tags; value: number }[] = []
59
+ const poller = Webhooks.createPoller({
60
+ db,
61
+ defaultChainId: 42431,
62
+ metrics: Metrics.from({
63
+ count() {},
64
+ flush() {},
65
+ gauge(name, value, tags = {}) {
66
+ gauges.push({ name, tags, value })
67
+ },
68
+ histogram() {},
69
+ }),
70
+ })
71
+
72
+ await poller.tick({ eventTypes: ['block:created'], headBlockNumber: 200 })
73
+
74
+ expect(gauges).toMatchInlineSnapshot(`
75
+ [
76
+ {
77
+ "name": "webhook_poller_active_subscription_count",
78
+ "tags": {
79
+ "chain_id": "42431",
80
+ },
81
+ "value": 1,
82
+ },
83
+ {
84
+ "name": "webhook_subscription_cursor_lag_blocks",
85
+ "tags": {
86
+ "chain_id": "42431",
87
+ },
88
+ "value": 0,
89
+ },
90
+ {
91
+ "name": "webhook_poller_safe_block",
92
+ "tags": {
93
+ "chain_id": "42431",
94
+ },
95
+ "value": 199,
96
+ },
97
+ ]
98
+ `)
99
+ })
100
+
51
101
  test('clamps a creation checkpoint to genesis', async () => {
52
102
  const db = TestApp.database()
53
103
  const subscription = await Webhooks.createSubscription(
@@ -72,4 +122,25 @@ describe('createPoller', () => {
72
122
 
73
123
  expect(decodeCursor(await Webhooks.getCursor(db, subscription.id))).toEqual([0, 2_147_483_647])
74
124
  })
125
+
126
+ test('keeps a factory-created database open after each tick', async () => {
127
+ const db = TestApp.database()
128
+ await Webhooks.createSubscription(db, {
129
+ chainId: 42431,
130
+ destination: { type: 'url', url: 'https://hooks.example.com/x' },
131
+ eventType: 'block:created',
132
+ owner: { orgId: 'org_test', type: 'api_key' },
133
+ })
134
+ const poller = Webhooks.createPoller({
135
+ db: () => db,
136
+ defaultChainId: 42431,
137
+ })
138
+
139
+ await poller.tick({
140
+ eventTypes: ['block:created'],
141
+ headBlockNumber: 1,
142
+ })
143
+
144
+ await expect(Webhooks.maxCursorBlock(db, 42431)).resolves.toBe(0)
145
+ })
75
146
  })
package/src/Webhooks.ts CHANGED
@@ -1,11 +1,14 @@
1
1
  import * as AppWebhooks from './apps/data/routes/webhooks.js'
2
2
  import * as Db from './db/Db.js'
3
+ import * as core_Webhooks from './internal/Webhooks.js'
3
4
  import * as MetricSink from './internal/MetricSink.js'
4
5
  import * as Store from './internal/Store.js'
5
6
  import type * as Metrics from './Metrics.js'
6
- import * as Tidx from './internal/Tidx.js'
7
7
  import * as Viem from './internal/Viem.js'
8
8
 
9
+ /** How often a poller reclaims expired rows, independent of tick cadence. */
10
+ const pruneIntervalMs = 60_000
11
+
9
12
  // Re-export the runtime-agnostic primitives (subscriptions, signing, delivery,
10
13
  // `deliverAndRecord`, cursors, …) so consumers reach everything through one
11
14
  // `Webhooks` namespace. The apps-layer poller stays private behind
@@ -19,33 +22,29 @@ export * from './internal/Webhooks.js'
19
22
  export { eventTypes, schema } from './apps/data/routes/webhooks.js'
20
23
 
21
24
  /**
22
- * Builds a scheduled webhook poller bound to a state store and chain config.
25
+ * Builds a webhook poller bound to a state store and chain config.
23
26
  *
24
- * This is the entry point a host runtime drives on a timer (a Cloudflare
25
- * `scheduled` cron handler, a self-host `setInterval`, …). Each {@link
26
- * Poller.tick} scans active subscriptions from their cursors up to `head -
27
- * confirmations` and dispatches matching events.
27
+ * This is the entry point a host runtime drives from chain-head notifications
28
+ * or a timer. Each {@link Poller.tick} scans active subscriptions from their
29
+ * cursors up to `head - confirmations` and dispatches matching events.
28
30
  *
29
- * The monitoring event types (`log:emitted`, `block:created`,
30
- * `transaction:included`) scan a confirmed window directly over JSON-RPC, so the
31
- * poller builds memoized viem clients (via {@link Viem.createGetClient}); the legacy
32
- * `token:transfer` type still reads from TIDX, so it
33
- * also builds memoized TIDX clients (via {@link Tidx.createGetClient}) when a
34
- * `tidx` config is supplied. The poller keeps `App.create` a pure Hono app: it
35
- * constructs its own clients instead of reaching into app internals, and owns no
36
- * request state.
31
+ * Every onchain event type scans a confirmed window directly over JSON-RPC, so
32
+ * the poller builds memoized viem clients via {@link Viem.createGetClient}. The
33
+ * poller keeps `App.create` a pure Hono app: it constructs its own clients
34
+ * instead of reaching into app internals, and owns no request state.
37
35
  */
38
36
  export function createPoller(options: createPoller.Options): createPoller.Poller {
39
37
  // Persistent memoize cache for token metadata, shared across ticks (the
40
38
  // webhook state store used to double-duty as this cache).
41
- const cache = Store.memory()
42
- const getTidx = options.tidx
43
- ? Tidx.createGetClient({ defaultChainId: options.defaultChainId, tidx: options.tidx })
44
- : undefined
39
+ const cache = options.cache ?? Store.memory()
45
40
  const getClient = Viem.createGetClient({
46
41
  defaultChainId: options.defaultChainId,
47
42
  rpc: options.rpc,
48
43
  })
44
+ // Chain-head ticks run about once per block, so the janitor is throttled here
45
+ // rather than issuing deletes per block. Reads filter expiry at query time, so
46
+ // reclaiming late only costs space.
47
+ let prunedAt: number | undefined
49
48
 
50
49
  const metrics = options.metrics ? MetricSink.webhooks(options.metrics) : undefined
51
50
 
@@ -58,6 +57,8 @@ export function createPoller(options: createPoller.Options): createPoller.Poller
58
57
  // returns the underlying result unchanged and is a no-op when no metrics
59
58
  // backend is configured.
60
59
  const inner = input.dispatch ?? options.dispatch
60
+ const innerBatch =
61
+ input.dispatchBatch ?? (input.dispatch === undefined ? options.dispatchBatch : undefined)
61
62
  const dispatch =
62
63
  metrics && inner
63
64
  ? async (dispatchable: AppWebhooks.poll.Dispatchable) => {
@@ -73,8 +74,37 @@ export function createPoller(options: createPoller.Options): createPoller.Poller
73
74
  return result
74
75
  }
75
76
  : inner
77
+ const dispatchBatch = innerBatch
78
+ ? async (
79
+ dispatchables: readonly AppWebhooks.poll.Dispatchable[],
80
+ context: AppWebhooks.poll.DispatchContext,
81
+ ) => {
82
+ const start = Date.now()
83
+ const result = await innerBatch(dispatchables, context)
84
+ const durationMs = Date.now() - start
85
+ for (const dispatchable of dispatchables)
86
+ metrics?.record({
87
+ chainId: dispatchable.envelope.chainId,
88
+ durationMs,
89
+ eventType: dispatchable.envelope.type,
90
+ ok: result.ok,
91
+ type: 'webhook:enqueue',
92
+ })
93
+ return result
94
+ }
95
+ : undefined
96
+
97
+ // Resolve per tick so Workers factories build a fresh Hyperdrive-pooled
98
+ // connection each run.
99
+ const db = Db.get(options.db)
100
+ const now = input.now ?? options.now
101
+ if (prunedAt === undefined || Date.now() - prunedAt >= pruneIntervalMs) {
102
+ prunedAt = Date.now()
103
+ await core_Webhooks.pruneExpired(db, { now }).catch(() => {})
104
+ }
76
105
 
77
106
  const start = Date.now()
107
+ let cursorLagBlocks: number | undefined
78
108
  try {
79
109
  const result = await AppWebhooks.poll({
80
110
  batchSize: input.batchSize ?? options.batchSize,
@@ -82,19 +112,37 @@ export function createPoller(options: createPoller.Options): createPoller.Poller
82
112
  chainId,
83
113
  client: getClient(chainId),
84
114
  confirmations: input.confirmations ?? options.confirmations,
85
- // Resolve per tick so Workers factories build a fresh
86
- // Hyperdrive-pooled connection each run.
87
- db: Db.get(options.db),
115
+ db,
88
116
  dispatch,
117
+ dispatchBatch,
89
118
  eventTypes: input.eventTypes ?? options.eventTypes,
90
119
  ...(input.headBlockNumber === undefined
91
120
  ? {}
92
121
  : { headBlockNumber: input.headBlockNumber }),
93
122
  maxBlocks: input.maxBlocks ?? options.maxBlocks,
94
- now: input.now ?? options.now,
95
- ...(getTidx ? { tidx: getTidx(chainId) } : {}),
123
+ now,
124
+ signal: input.signal,
125
+ ...(metrics
126
+ ? {
127
+ onCursorLag(blocks: number) {
128
+ cursorLagBlocks = blocks
129
+ },
130
+ onScanCheckpoint(checkpoint: AppWebhooks.poll.ScanCheckpoint) {
131
+ metrics.record({ chainId, type: 'webhook:poll-scan', ...checkpoint })
132
+ // Ship the start boundary before the RPC allocation can terminate the isolate.
133
+ metrics.flush()
134
+ },
135
+ onStage(stage: AppWebhooks.poll.Stage) {
136
+ metrics.record({ chainId, type: 'webhook:poll', ...stage })
137
+ },
138
+ onSubscriptions(count: number) {
139
+ metrics.record({ chainId, count, type: 'webhook:poll-subscriptions' })
140
+ },
141
+ }
142
+ : {}),
96
143
  })
97
144
  metrics?.record({
145
+ cursorLagBlocks,
98
146
  meta: { durationMs: Date.now() - start, outcome: 'ok' },
99
147
  result,
100
148
  type: 'webhook:tick',
@@ -102,6 +150,7 @@ export function createPoller(options: createPoller.Options): createPoller.Poller
102
150
  return result
103
151
  } catch (error) {
104
152
  metrics?.record({
153
+ cursorLagBlocks,
105
154
  meta: { chainId, durationMs: Date.now() - start, outcome: 'error' },
106
155
  result: undefined,
107
156
  type: 'webhook:tick',
@@ -121,6 +170,9 @@ export declare namespace createPoller {
121
170
  /** Delivery transport: hands a matched event to a queue or delivers inline. */
122
171
  type Dispatch = NonNullable<AppWebhooks.poll.Options['dispatch']>
123
172
 
173
+ /** Batch delivery transport: confirms all rows together or leaves their cursor unchanged. */
174
+ type DispatchBatch = NonNullable<AppWebhooks.poll.Options['dispatchBatch']>
175
+
124
176
  /** Summary counters returned by {@link Poller.tick}. */
125
177
  type Result = AppWebhooks.poll.Result
126
178
 
@@ -128,13 +180,15 @@ export declare namespace createPoller {
128
180
  type Defaults = {
129
181
  /** Max matching rows scanned per subscription per tick (default 200). */
130
182
  batchSize?: number | undefined
131
- /** Reorg-safety window: only emit blocks this far behind head (default 2). */
183
+ /** Reorg-safety window: only emit blocks this far behind head (default 1). */
132
184
  confirmations?: number | undefined
133
185
  /**
134
186
  * Delivery transport. Defaults to inline `deliverAndRecord` (self-host); on
135
187
  * Cloudflare pass a Queue producer so retry/backoff/DLQ are native.
136
188
  */
137
189
  dispatch?: Dispatch | undefined
190
+ /** Batch delivery transport used when the runtime can enqueue multiple rows atomically. */
191
+ dispatchBatch?: DispatchBatch | undefined
138
192
  /** Event types to poll (default all). */
139
193
  eventTypes?: AppWebhooks.poll.Options['eventTypes']
140
194
  /** Backfill cap: most blocks an RPC scanner inspects per tick (default 1000). */
@@ -145,16 +199,16 @@ export declare namespace createPoller {
145
199
 
146
200
  /** Options for {@link createPoller}. */
147
201
  type Options = Defaults & {
202
+ /** Cache for token metadata shared across ticks. */
203
+ cache?: Store.Store | undefined
148
204
  /** Database holding subscriptions + cursors, or a factory resolved per tick (Workers/Hyperdrive). */
149
205
  db: Db.Source
150
206
  /** Tempo chain id polled when a tick omits one. */
151
207
  defaultChainId?: Viem.ChainId | undefined
152
208
  /** Metrics backend; emits `webhook:tick` per tick and `webhook:enqueue` per dispatch. */
153
209
  metrics?: Metrics.Metrics | undefined
154
- /** RPC options for the viem clients the monitoring scanners read from. */
210
+ /** RPC options for the viem clients that scan chain events. */
155
211
  rpc?: Viem.getClient.Rpc | undefined
156
- /** TIDX query client options for the legacy `token:transfer` type. */
157
- tidx?: Tidx.getClient.Tidx | undefined
158
212
  }
159
213
 
160
214
  /** Per-tick overrides; falls back to the {@link Options} defaults. */
@@ -163,11 +217,13 @@ export declare namespace createPoller {
163
217
  chainId?: Viem.ChainId | undefined
164
218
  /** Chain head to work back from; resolved from RPC when omitted. */
165
219
  headBlockNumber?: bigint | number | undefined
220
+ /** Stops scanning and dispatch when the caller loses its execution lease. */
221
+ signal?: AbortSignal | undefined
166
222
  }
167
223
 
168
- /** A scheduled webhook poller. */
224
+ /** A chain-head-driven webhook poller. */
169
225
  type Poller = {
170
- /** Runs one poll tick, resolving the indexed head when not supplied. */
226
+ /** Runs one poll tick, resolving the RPC head when not supplied. */
171
227
  tick(options?: TickOptions): Promise<Result>
172
228
  }
173
229
  }
@@ -105,6 +105,15 @@ describe('POST /api-keys', () => {
105
105
  expect((await core_ApiKeys.list(store))[0]?.expiresAt).toBe(expiresAt)
106
106
  })
107
107
 
108
+ test('rejects an expiry without the minimum storage lead time', async () => {
109
+ const { client } = await setup()
110
+ const response = await client['api-keys'].$post({
111
+ json: { ...validBody, expiresAt: new Date(Date.now() + 60_000).toISOString() },
112
+ })
113
+
114
+ expect(response.status).toBe(400)
115
+ })
116
+
108
117
  test('rejects an unknown scope with 400', async () => {
109
118
  const { client } = await setup()
110
119
  const response = await client['api-keys'].$post({
@@ -15,6 +15,9 @@ import type * as App from '../App.js'
15
15
 
16
16
  /** Zod schemas owned by the admin API-keys handler. */
17
17
  export namespace schema {
18
+ // Leave time for validation and database lookups before KV writes its minimum 60-second TTL.
19
+ const minimumExpiryLeadMs = 2 * 60_000
20
+
18
21
  const Name = z
19
22
  .pipe(
20
23
  z.string(),
@@ -27,6 +30,17 @@ export namespace schema {
27
30
  z.meta({ examples: ['Explorer testnet'] }),
28
31
  )
29
32
 
33
+ const ExpiresAt = z.optional(z.iso.datetime()).check(
34
+ z.refine(
35
+ (value) => value === undefined || Date.parse(value) >= Date.now() + minimumExpiryLeadMs,
36
+ {
37
+ error: 'Expiry must be at least two minutes in the future.',
38
+ },
39
+ ),
40
+ z.describe('ISO 8601 expiry timestamp. Omit for a non-expiring key.'),
41
+ z.meta({ examples: ['2100-01-01T00:00:00.000Z'] }),
42
+ )
43
+
30
44
  const ApiKey = Schema.describe(
31
45
  z.extend(ApiKeys.schema.Record, {
32
46
  createdByLabel: z
@@ -59,6 +73,7 @@ export namespace schema {
59
73
  */
60
74
  export const Body = Schema.describe(
61
75
  z.extend(z.omit(ApiKeys.schema.MintInput, { createdBy: true, name: true }), {
76
+ expiresAt: ExpiresAt,
62
77
  name: Name,
63
78
  scopes: z
64
79
  .readonly(
@@ -17,12 +17,13 @@ const persister = createAsyncStoragePersister({
17
17
  })
18
18
 
19
19
  export const persistOptions: Omit<PersistQueryClientOptions, 'queryClient'> = {
20
- maxAge: 1000 * 60 * 60 * 24,
21
- persister,
20
+ buster: 'rate-limit-periods',
22
21
  dehydrateOptions: {
23
22
  shouldDehydrateQuery: (query) =>
24
23
  query.queryKey[0] !== 'config' &&
25
24
  query.queryKey[0] !== 'organizations' &&
26
25
  defaultShouldDehydrateQuery(query),
27
26
  },
27
+ maxAge: 1000 * 60 * 60 * 24,
28
+ persister,
28
29
  }