tapimo 0.0.0 → 0.0.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 (422) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +119 -0
  3. package/dist/ApiKey.d.ts +65 -0
  4. package/dist/ApiKey.d.ts.map +1 -0
  5. package/dist/ApiKey.js +105 -0
  6. package/dist/ApiKey.js.map +1 -0
  7. package/dist/ApiKeySource.d.ts +166 -0
  8. package/dist/ApiKeySource.d.ts.map +1 -0
  9. package/dist/ApiKeySource.js +234 -0
  10. package/dist/ApiKeySource.js.map +1 -0
  11. package/dist/App.d.ts +19317 -0
  12. package/dist/App.d.ts.map +1 -0
  13. package/dist/App.js +612 -0
  14. package/dist/App.js.map +1 -0
  15. package/dist/Assets.d.ts +44 -0
  16. package/dist/Assets.d.ts.map +1 -0
  17. package/dist/Assets.js +23 -0
  18. package/dist/Assets.js.map +1 -0
  19. package/dist/Auth.d.ts +2 -0
  20. package/dist/Auth.d.ts.map +1 -0
  21. package/dist/Auth.js +2 -0
  22. package/dist/Auth.js.map +1 -0
  23. package/dist/Cli.d.ts +21 -0
  24. package/dist/Cli.d.ts.map +1 -0
  25. package/dist/Cli.js +13 -0
  26. package/dist/Cli.js.map +1 -0
  27. package/dist/Client.d.ts +20 -0
  28. package/dist/Client.d.ts.map +1 -0
  29. package/dist/Client.js +24 -0
  30. package/dist/Client.js.map +1 -0
  31. package/dist/Metrics.d.ts +70 -0
  32. package/dist/Metrics.d.ts.map +1 -0
  33. package/dist/Metrics.js +91 -0
  34. package/dist/Metrics.js.map +1 -0
  35. package/dist/Scope.d.ts +35 -0
  36. package/dist/Scope.d.ts.map +1 -0
  37. package/dist/Scope.js +38 -0
  38. package/dist/Scope.js.map +1 -0
  39. package/dist/Webhooks.d.ts +79 -0
  40. package/dist/Webhooks.d.ts.map +1 -0
  41. package/dist/Webhooks.js +107 -0
  42. package/dist/Webhooks.js.map +1 -0
  43. package/dist/admin/Access.d.ts +41 -0
  44. package/dist/admin/Access.d.ts.map +1 -0
  45. package/dist/admin/Access.js +75 -0
  46. package/dist/admin/Access.js.map +1 -0
  47. package/dist/admin/App.d.ts +1221 -0
  48. package/dist/admin/App.d.ts.map +1 -0
  49. package/dist/admin/App.js +113 -0
  50. package/dist/admin/App.js.map +1 -0
  51. package/dist/admin/apps/api-keys.d.ts +209 -0
  52. package/dist/admin/apps/api-keys.d.ts.map +1 -0
  53. package/dist/admin/apps/api-keys.js +129 -0
  54. package/dist/admin/apps/api-keys.js.map +1 -0
  55. package/dist/admin/apps/scopes.d.ts +46 -0
  56. package/dist/admin/apps/scopes.d.ts.map +1 -0
  57. package/dist/admin/apps/scopes.js +41 -0
  58. package/dist/admin/apps/scopes.js.map +1 -0
  59. package/dist/admin/apps/verified-tokens.d.ts +1206 -0
  60. package/dist/admin/apps/verified-tokens.d.ts.map +1 -0
  61. package/dist/admin/apps/verified-tokens.js +485 -0
  62. package/dist/admin/apps/verified-tokens.js.map +1 -0
  63. package/dist/admin/index.d.ts +3 -0
  64. package/dist/admin/index.d.ts.map +1 -0
  65. package/dist/admin/index.js +3 -0
  66. package/dist/admin/index.js.map +1 -0
  67. package/dist/admin/ui.generated.d.ts +11 -0
  68. package/dist/admin/ui.generated.d.ts.map +1 -0
  69. package/dist/admin/ui.generated.js +43 -0
  70. package/dist/admin/ui.generated.js.map +1 -0
  71. package/dist/apps/activities.d.ts +18725 -0
  72. package/dist/apps/activities.d.ts.map +1 -0
  73. package/dist/apps/activities.js +3138 -0
  74. package/dist/apps/activities.js.map +1 -0
  75. package/dist/apps/addresses.d.ts +3662 -0
  76. package/dist/apps/addresses.d.ts.map +1 -0
  77. package/dist/apps/addresses.js +22 -0
  78. package/dist/apps/addresses.js.map +1 -0
  79. package/dist/apps/balances.d.ts +342 -0
  80. package/dist/apps/balances.d.ts.map +1 -0
  81. package/dist/apps/balances.js +393 -0
  82. package/dist/apps/balances.js.map +1 -0
  83. package/dist/apps/blocks.d.ts +931 -0
  84. package/dist/apps/blocks.d.ts.map +1 -0
  85. package/dist/apps/blocks.js +689 -0
  86. package/dist/apps/blocks.js.map +1 -0
  87. package/dist/apps/coingecko.d.ts +1053 -0
  88. package/dist/apps/coingecko.d.ts.map +1 -0
  89. package/dist/apps/coingecko.js +1015 -0
  90. package/dist/apps/coingecko.js.map +1 -0
  91. package/dist/apps/exchanges.d.ts +3000 -0
  92. package/dist/apps/exchanges.d.ts.map +1 -0
  93. package/dist/apps/exchanges.js +3570 -0
  94. package/dist/apps/exchanges.js.map +1 -0
  95. package/dist/apps/fee-amm.d.ts +640 -0
  96. package/dist/apps/fee-amm.d.ts.map +1 -0
  97. package/dist/apps/fee-amm.js +815 -0
  98. package/dist/apps/fee-amm.js.map +1 -0
  99. package/dist/apps/indexer.d.ts +116 -0
  100. package/dist/apps/indexer.d.ts.map +1 -0
  101. package/dist/apps/indexer.js +270 -0
  102. package/dist/apps/indexer.js.map +1 -0
  103. package/dist/apps/receipts.d.ts +1438 -0
  104. package/dist/apps/receipts.d.ts.map +1 -0
  105. package/dist/apps/receipts.js +874 -0
  106. package/dist/apps/receipts.js.map +1 -0
  107. package/dist/apps/rpc.d.ts +173 -0
  108. package/dist/apps/rpc.d.ts.map +1 -0
  109. package/dist/apps/rpc.js +191 -0
  110. package/dist/apps/rpc.js.map +1 -0
  111. package/dist/apps/tokenlist.d.ts +192 -0
  112. package/dist/apps/tokenlist.d.ts.map +1 -0
  113. package/dist/apps/tokenlist.js +153 -0
  114. package/dist/apps/tokenlist.js.map +1 -0
  115. package/dist/apps/tokens.d.ts +2246 -0
  116. package/dist/apps/tokens.d.ts.map +1 -0
  117. package/dist/apps/tokens.js +1789 -0
  118. package/dist/apps/tokens.js.map +1 -0
  119. package/dist/apps/transactions.d.ts +2327 -0
  120. package/dist/apps/transactions.d.ts.map +1 -0
  121. package/dist/apps/transactions.js +975 -0
  122. package/dist/apps/transactions.js.map +1 -0
  123. package/dist/apps/transfers.d.ts +589 -0
  124. package/dist/apps/transfers.d.ts.map +1 -0
  125. package/dist/apps/transfers.js +769 -0
  126. package/dist/apps/transfers.js.map +1 -0
  127. package/dist/apps/verified-tokens.d.ts +544 -0
  128. package/dist/apps/verified-tokens.d.ts.map +1 -0
  129. package/dist/apps/verified-tokens.js +234 -0
  130. package/dist/apps/verified-tokens.js.map +1 -0
  131. package/dist/apps/webhooks.d.ts +7684 -0
  132. package/dist/apps/webhooks.d.ts.map +1 -0
  133. package/dist/apps/webhooks.js +2112 -0
  134. package/dist/apps/webhooks.js.map +1 -0
  135. package/dist/cloudflare.d.ts +32 -0
  136. package/dist/cloudflare.d.ts.map +1 -0
  137. package/dist/cloudflare.js +45 -0
  138. package/dist/cloudflare.js.map +1 -0
  139. package/dist/index.d.ts +16 -0
  140. package/dist/index.d.ts.map +1 -0
  141. package/dist/index.js +16 -0
  142. package/dist/index.js.map +1 -0
  143. package/dist/internal/Auth.d.ts +298 -0
  144. package/dist/internal/Auth.d.ts.map +1 -0
  145. package/dist/internal/Auth.js +618 -0
  146. package/dist/internal/Auth.js.map +1 -0
  147. package/dist/internal/Cache.d.ts +74 -0
  148. package/dist/internal/Cache.d.ts.map +1 -0
  149. package/dist/internal/Cache.js +215 -0
  150. package/dist/internal/Cache.js.map +1 -0
  151. package/dist/internal/Cursor.d.ts +79 -0
  152. package/dist/internal/Cursor.d.ts.map +1 -0
  153. package/dist/internal/Cursor.js +116 -0
  154. package/dist/internal/Cursor.js.map +1 -0
  155. package/dist/internal/EdgeCache.d.ts +59 -0
  156. package/dist/internal/EdgeCache.d.ts.map +1 -0
  157. package/dist/internal/EdgeCache.js +174 -0
  158. package/dist/internal/EdgeCache.js.map +1 -0
  159. package/dist/internal/Log.d.ts +104 -0
  160. package/dist/internal/Log.d.ts.map +1 -0
  161. package/dist/internal/Log.js +118 -0
  162. package/dist/internal/Log.js.map +1 -0
  163. package/dist/internal/MetricSink.d.ts +65 -0
  164. package/dist/internal/MetricSink.d.ts.map +1 -0
  165. package/dist/internal/MetricSink.js +146 -0
  166. package/dist/internal/MetricSink.js.map +1 -0
  167. package/dist/internal/Mpp.d.ts +71 -0
  168. package/dist/internal/Mpp.d.ts.map +1 -0
  169. package/dist/internal/Mpp.js +105 -0
  170. package/dist/internal/Mpp.js.map +1 -0
  171. package/dist/internal/OpenApi.d.ts +313 -0
  172. package/dist/internal/OpenApi.d.ts.map +1 -0
  173. package/dist/internal/OpenApi.fonts.d.ts +2 -0
  174. package/dist/internal/OpenApi.fonts.d.ts.map +1 -0
  175. package/dist/internal/OpenApi.fonts.js +39 -0
  176. package/dist/internal/OpenApi.fonts.js.map +1 -0
  177. package/dist/internal/OpenApi.js +335 -0
  178. package/dist/internal/OpenApi.js.map +1 -0
  179. package/dist/internal/OpenApi.logo.d.ts +6 -0
  180. package/dist/internal/OpenApi.logo.d.ts.map +1 -0
  181. package/dist/internal/OpenApi.logo.js +19 -0
  182. package/dist/internal/OpenApi.logo.js.map +1 -0
  183. package/dist/internal/Path.d.ts +5 -0
  184. package/dist/internal/Path.d.ts.map +1 -0
  185. package/dist/internal/Path.js +16 -0
  186. package/dist/internal/Path.js.map +1 -0
  187. package/dist/internal/RateLimit.d.ts +60 -0
  188. package/dist/internal/RateLimit.d.ts.map +1 -0
  189. package/dist/internal/RateLimit.js +48 -0
  190. package/dist/internal/RateLimit.js.map +1 -0
  191. package/dist/internal/Response.d.ts +132 -0
  192. package/dist/internal/Response.d.ts.map +1 -0
  193. package/dist/internal/Response.js +72 -0
  194. package/dist/internal/Response.js.map +1 -0
  195. package/dist/internal/Schema.d.ts +257 -0
  196. package/dist/internal/Schema.d.ts.map +1 -0
  197. package/dist/internal/Schema.js +360 -0
  198. package/dist/internal/Schema.js.map +1 -0
  199. package/dist/internal/Store.d.ts +204 -0
  200. package/dist/internal/Store.d.ts.map +1 -0
  201. package/dist/internal/Store.js +291 -0
  202. package/dist/internal/Store.js.map +1 -0
  203. package/dist/internal/Tidx.d.ts +120 -0
  204. package/dist/internal/Tidx.d.ts.map +1 -0
  205. package/dist/internal/Tidx.js +156 -0
  206. package/dist/internal/Tidx.js.map +1 -0
  207. package/dist/internal/Timing.d.ts +22 -0
  208. package/dist/internal/Timing.d.ts.map +1 -0
  209. package/dist/internal/Timing.js +32 -0
  210. package/dist/internal/Timing.js.map +1 -0
  211. package/dist/internal/Ttl.d.ts +25 -0
  212. package/dist/internal/Ttl.d.ts.map +1 -0
  213. package/dist/internal/Ttl.js +49 -0
  214. package/dist/internal/Ttl.js.map +1 -0
  215. package/dist/internal/Value.d.ts +47 -0
  216. package/dist/internal/Value.d.ts.map +1 -0
  217. package/dist/internal/Value.js +122 -0
  218. package/dist/internal/Value.js.map +1 -0
  219. package/dist/internal/VerifiedTokens.d.ts +228 -0
  220. package/dist/internal/VerifiedTokens.d.ts.map +1 -0
  221. package/dist/internal/VerifiedTokens.js +450 -0
  222. package/dist/internal/VerifiedTokens.js.map +1 -0
  223. package/dist/internal/Viem.d.ts +2082 -0
  224. package/dist/internal/Viem.d.ts.map +1 -0
  225. package/dist/internal/Viem.js +63 -0
  226. package/dist/internal/Viem.js.map +1 -0
  227. package/dist/internal/WebhookDestination.d.ts +198 -0
  228. package/dist/internal/WebhookDestination.d.ts.map +1 -0
  229. package/dist/internal/WebhookDestination.js +170 -0
  230. package/dist/internal/WebhookDestination.js.map +1 -0
  231. package/dist/internal/Webhooks.d.ts +433 -0
  232. package/dist/internal/Webhooks.d.ts.map +1 -0
  233. package/dist/internal/Webhooks.js +489 -0
  234. package/dist/internal/Webhooks.js.map +1 -0
  235. package/dist/internal/webhookDestinations/betterStack.d.ts +26 -0
  236. package/dist/internal/webhookDestinations/betterStack.d.ts.map +1 -0
  237. package/dist/internal/webhookDestinations/betterStack.js +76 -0
  238. package/dist/internal/webhookDestinations/betterStack.js.map +1 -0
  239. package/dist/internal/webhookDestinations/slack.d.ts +59 -0
  240. package/dist/internal/webhookDestinations/slack.d.ts.map +1 -0
  241. package/dist/internal/webhookDestinations/slack.js +422 -0
  242. package/dist/internal/webhookDestinations/slack.js.map +1 -0
  243. package/dist/internal/webhookDestinations/url.d.ts +17 -0
  244. package/dist/internal/webhookDestinations/url.d.ts.map +1 -0
  245. package/dist/internal/webhookDestinations/url.js +130 -0
  246. package/dist/internal/webhookDestinations/url.js.map +1 -0
  247. package/dist/openrpc.json +5302 -0
  248. package/package.json +46 -4
  249. package/seeds/31318/icons/0x20c0000000000000000000000000000000000000.svg +1 -0
  250. package/seeds/31318/icons/0x20c0000000000000000000000000000000000001.svg +1 -0
  251. package/seeds/31318/icons/0x20c0000000000000000000000000000000000002.svg +1 -0
  252. package/seeds/31318/icons/0x20c0000000000000000000000000000000000003.svg +1 -0
  253. package/seeds/31318/verified-tokens.json +30 -0
  254. package/seeds/4217/icons/0x20c0000000000000000000000000000000000000.svg +25 -0
  255. package/seeds/4217/icons/0x20c000000000000000000000048c8f36df1c9a4a.svg +1 -0
  256. package/seeds/4217/icons/0x20c0000000000000000000000520792dcccccccc.svg +1 -0
  257. package/seeds/4217/icons/0x20c0000000000000000000000a6da882d075a4c3.svg +4 -0
  258. package/seeds/4217/icons/0x20c000000000000000000000111111111e910f0f.svg +30 -0
  259. package/seeds/4217/icons/0x20c00000000000000000000014f22ca97301eb73.svg +1 -0
  260. package/seeds/4217/icons/0x20c0000000000000000000001621e21f71cf12fb.svg +1 -0
  261. package/seeds/4217/icons/0x20c0000000000000000000002f52d5cc21a3207b.svg +1 -0
  262. package/seeds/4217/icons/0x20c0000000000000000000003158081efd85bfc2.svg +11 -0
  263. package/seeds/4217/icons/0x20c0000000000000000000003554d28269e0f3c2.svg +1 -0
  264. package/seeds/4217/icons/0x20c000000000000000000000383a23bacb546ab9.svg +1 -0
  265. package/seeds/4217/icons/0x20c00000000000000000000042109aef2f8b28e1.png +0 -0
  266. package/seeds/4217/icons/0x20c0000000000000000000005c0bac7cef389a11.svg +1 -0
  267. package/seeds/4217/icons/0x20c0000000000000000000006fd9a167923ba194.png +0 -0
  268. package/seeds/4217/icons/0x20c0000000000000000000007f7ba549dd0251b9.svg +1 -0
  269. package/seeds/4217/icons/0x20c0000000000000000000008191667423f70e67.svg +5 -0
  270. package/seeds/4217/icons/0x20c0000000000000000000008ee4fcff88888888.svg +1 -0
  271. package/seeds/4217/icons/0x20c0000000000000000000009a4a4b17e0dc6651.svg +1 -0
  272. package/seeds/4217/icons/0x20c000000000000000000000ab02d39df30bd17e.svg +1 -0
  273. package/seeds/4217/icons/0x20c000000000000000000000ae247a1130450f09.svg +1 -0
  274. package/seeds/4217/icons/0x20c000000000000000000000aeed2ec36a54d0e5.svg +1 -0
  275. package/seeds/4217/icons/0x20c000000000000000000000b9537d11c60e8b50.svg +1 -0
  276. package/seeds/4217/icons/0x20c000000000000000000000bd95bfb69fbe6ce3.svg +1 -0
  277. package/seeds/4217/icons/0x20c000000000000000000000c412ec89d0c08be5.svg +1 -0
  278. package/seeds/4217/icons/0x20c000000000000000000000f047dd7018e50367.png +0 -0
  279. package/seeds/4217/verified-tokens.json +177 -0
  280. package/seeds/42431/icons/0x20c0000000000000000000000000000000000000.svg +25 -0
  281. package/seeds/42431/icons/0x20c0000000000000000000000000000000000001.svg +1 -0
  282. package/seeds/42431/icons/0x20c0000000000000000000000000000000000002.svg +1 -0
  283. package/seeds/42431/icons/0x20c0000000000000000000000000000000000003.svg +1 -0
  284. package/seeds/42431/icons/0x20c0000000000000000000009e8d7eb59b783726.svg +1 -0
  285. package/seeds/42431/icons/0x20c000000000000000000000d72572838bbee59c.svg +1 -0
  286. package/seeds/42431/verified-tokens.json +44 -0
  287. package/seeds/openrpc/README.md +27 -0
  288. package/seeds/openrpc/execution-apis.json +8989 -0
  289. package/src/ApiKey.test.ts +91 -0
  290. package/src/ApiKey.ts +122 -0
  291. package/src/ApiKeySource.test-d.ts +11 -0
  292. package/src/ApiKeySource.test.ts +295 -0
  293. package/src/ApiKeySource.ts +303 -0
  294. package/src/App.test-d.ts +38 -0
  295. package/src/App.test.ts +65927 -0
  296. package/src/App.ts +855 -0
  297. package/src/Assets.ts +58 -0
  298. package/src/Auth.ts +1 -0
  299. package/src/Cli.test.ts +81 -0
  300. package/src/Cli.ts +38 -0
  301. package/src/Client.test-d.ts +1438 -0
  302. package/src/Client.test.ts +134 -0
  303. package/src/Client.ts +45 -0
  304. package/src/Metrics.ts +154 -0
  305. package/src/Scope.ts +50 -0
  306. package/src/Webhooks.test.ts +49 -0
  307. package/src/Webhooks.ts +172 -0
  308. package/src/admin/Access.test.ts +130 -0
  309. package/src/admin/Access.ts +87 -0
  310. package/src/admin/App.test.ts +225 -0
  311. package/src/admin/App.ts +216 -0
  312. package/src/admin/apps/api-keys.test.ts +156 -0
  313. package/src/admin/apps/api-keys.ts +157 -0
  314. package/src/admin/apps/scopes.test.ts +52 -0
  315. package/src/admin/apps/scopes.ts +47 -0
  316. package/src/admin/apps/verified-tokens.test.ts +415 -0
  317. package/src/admin/apps/verified-tokens.ts +566 -0
  318. package/src/admin/index.ts +2 -0
  319. package/src/admin/ui/plugin.ts +135 -0
  320. package/src/admin/ui/src/components/Checkbox.tsx +22 -0
  321. package/src/admin/ui/src/components/Dialog.tsx +32 -0
  322. package/src/admin/ui/src/components/MobileProfile.tsx +31 -0
  323. package/src/admin/ui/src/components/Page.tsx +53 -0
  324. package/src/admin/ui/src/components/TempoLogo.tsx +37 -0
  325. package/src/admin/ui/src/fonts/Pilat-Demi.woff2 +0 -0
  326. package/src/admin/ui/src/fonts/Pilat-Regular.woff2 +0 -0
  327. package/src/admin/ui/src/lib/api-keys.ts +63 -0
  328. package/src/admin/ui/src/lib/client.ts +15 -0
  329. package/src/admin/ui/src/lib/config.ts +57 -0
  330. package/src/admin/ui/src/lib/query-client.ts +26 -0
  331. package/src/admin/ui/src/lib/scopes.ts +25 -0
  332. package/src/admin/ui/src/lib/verified-tokens.ts +89 -0
  333. package/src/admin/ui/src/lib/wagmi.ts +18 -0
  334. package/src/admin/ui/src/routeTree.gen.ts +104 -0
  335. package/src/admin/ui/src/router.tsx +25 -0
  336. package/src/admin/ui/src/routes/__root.tsx +219 -0
  337. package/src/admin/ui/src/routes/api-keys.tsx +329 -0
  338. package/src/admin/ui/src/routes/index.tsx +7 -0
  339. package/src/admin/ui/src/routes/verified-tokens.tsx +331 -0
  340. package/src/admin/ui/src/styles.css +20 -0
  341. package/src/admin/ui/tsconfig.json +16 -0
  342. package/src/admin/ui/vite.config.ts +29 -0
  343. package/src/admin/ui.generated.ts +60 -0
  344. package/src/apps/activities.test.ts +1020 -0
  345. package/src/apps/activities.ts +3934 -0
  346. package/src/apps/addresses.ts +24 -0
  347. package/src/apps/balances.test.ts +274 -0
  348. package/src/apps/balances.ts +533 -0
  349. package/src/apps/blocks.test.ts +385 -0
  350. package/src/apps/blocks.ts +896 -0
  351. package/src/apps/coingecko.test.ts +218 -0
  352. package/src/apps/coingecko.ts +1383 -0
  353. package/src/apps/exchanges.test.ts +958 -0
  354. package/src/apps/exchanges.ts +4773 -0
  355. package/src/apps/fee-amm.test.ts +321 -0
  356. package/src/apps/fee-amm.ts +1121 -0
  357. package/src/apps/indexer.test.ts +153 -0
  358. package/src/apps/indexer.ts +330 -0
  359. package/src/apps/receipts.test.ts +285 -0
  360. package/src/apps/receipts.ts +1193 -0
  361. package/src/apps/rpc.test.ts +302 -0
  362. package/src/apps/rpc.ts +222 -0
  363. package/src/apps/tokenlist.test.ts +80 -0
  364. package/src/apps/tokenlist.ts +207 -0
  365. package/src/apps/tokens.test.ts +1062 -0
  366. package/src/apps/tokens.ts +2292 -0
  367. package/src/apps/transactions.test.ts +408 -0
  368. package/src/apps/transactions.ts +1363 -0
  369. package/src/apps/transfers.test.ts +371 -0
  370. package/src/apps/transfers.ts +1116 -0
  371. package/src/apps/verified-tokens.test.ts +114 -0
  372. package/src/apps/verified-tokens.ts +295 -0
  373. package/src/apps/webhooks.test.ts +1937 -0
  374. package/src/apps/webhooks.ts +2926 -0
  375. package/src/cloudflare.ts +55 -0
  376. package/src/index.ts +15 -0
  377. package/src/internal/Auth.test.ts +980 -0
  378. package/src/internal/Auth.ts +1012 -0
  379. package/src/internal/Cache.test.ts +240 -0
  380. package/src/internal/Cache.ts +262 -0
  381. package/src/internal/Cursor.test.ts +100 -0
  382. package/src/internal/Cursor.ts +149 -0
  383. package/src/internal/EdgeCache.test.ts +234 -0
  384. package/src/internal/EdgeCache.ts +224 -0
  385. package/src/internal/Errors.ts +14 -0
  386. package/src/internal/Log.test.ts +244 -0
  387. package/src/internal/Log.ts +216 -0
  388. package/src/internal/MetricSink.ts +220 -0
  389. package/src/internal/Mpp.test.ts +64 -0
  390. package/src/internal/Mpp.ts +141 -0
  391. package/src/internal/OpenApi.fonts.ts +38 -0
  392. package/src/internal/OpenApi.logo.ts +18 -0
  393. package/src/internal/OpenApi.ts +488 -0
  394. package/src/internal/Path.test.ts +14 -0
  395. package/src/internal/Path.ts +15 -0
  396. package/src/internal/RateLimit.test.ts +93 -0
  397. package/src/internal/RateLimit.ts +93 -0
  398. package/src/internal/Response.ts +131 -0
  399. package/src/internal/Schema.test.ts +34 -0
  400. package/src/internal/Schema.ts +523 -0
  401. package/src/internal/Store.test.ts +545 -0
  402. package/src/internal/Store.ts +526 -0
  403. package/src/internal/Tidx.local.test.ts +33 -0
  404. package/src/internal/Tidx.test.ts +294 -0
  405. package/src/internal/Tidx.ts +238 -0
  406. package/src/internal/Timing.test.ts +22 -0
  407. package/src/internal/Timing.ts +62 -0
  408. package/src/internal/Ttl.test.ts +18 -0
  409. package/src/internal/Ttl.ts +59 -0
  410. package/src/internal/Value.ts +114 -0
  411. package/src/internal/VerifiedTokens.test.ts +333 -0
  412. package/src/internal/VerifiedTokens.ts +645 -0
  413. package/src/internal/Viem.test.ts +115 -0
  414. package/src/internal/Viem.ts +165 -0
  415. package/src/internal/WebhookDestination.test.ts +648 -0
  416. package/src/internal/WebhookDestination.ts +331 -0
  417. package/src/internal/Webhooks.test.ts +668 -0
  418. package/src/internal/Webhooks.ts +940 -0
  419. package/src/internal/webhookDestinations/betterStack.ts +91 -0
  420. package/src/internal/webhookDestinations/slack.ts +496 -0
  421. package/src/internal/webhookDestinations/url.ts +128 -0
  422. package/src/openrpc.json +5302 -0
@@ -0,0 +1,294 @@
1
+ import { Tidx as TidxClient } from 'tidx.ts'
2
+
3
+ import type * as Auth from './Auth.js'
4
+ import * as Tidx from './Tidx.js'
5
+ import type * as Viem from './Viem.js'
6
+
7
+ /** Anonymous (public) request principal. */
8
+ const publicPrincipal: Auth.Principal = { id: 'pub', type: 'public' }
9
+ /** Authenticated API-key request principal. */
10
+ const apiKeyPrincipal: Auth.Principal = {
11
+ apiKey: {} as never,
12
+ environment: 'production',
13
+ id: 'key_test',
14
+ orgId: 'org_test',
15
+ type: 'api_key',
16
+ }
17
+
18
+ /** Builds a `FetchRequestError` as the indexer surfaces it at a given status. */
19
+ function appError(message: string, status = 200) {
20
+ return new TidxClient.FetchRequestError(message, new Response(null, { status }))
21
+ }
22
+
23
+ /** Minimal client whose `fetch` is a controllable mock. */
24
+ function fakeClient(fetch: Tidx.Client['fetch']): Tidx.Client {
25
+ return { fetch } as never
26
+ }
27
+
28
+ describe('escape', () => {
29
+ test('doubles quotes for ANSI string literals, leaving backslashes alone', () => {
30
+ expect(Tidx.escape('USD')).toMatchInlineSnapshot(`"USD"`)
31
+ expect(Tidx.escape("US' OR '1'='1")).toMatchInlineSnapshot(`"US'' OR ''1''=''1"`)
32
+ expect(Tidx.escape('back\\slash')).toMatchInlineSnapshot(`"back\\slash"`)
33
+ expect(Tidx.escape("\\'")).toMatchInlineSnapshot(`"\\''"`)
34
+ })
35
+ })
36
+
37
+ describe('withTransientRetry', () => {
38
+ test('retries a transient `db error` and resolves once it succeeds', async () => {
39
+ vi.useFakeTimers()
40
+ try {
41
+ const result = { hasMore: false, rows: [] } as never
42
+ const fetch = vi
43
+ .fn()
44
+ .mockRejectedValueOnce(appError('db error'))
45
+ .mockRejectedValueOnce(appError('db error'))
46
+ .mockResolvedValueOnce(result)
47
+ const client = Tidx.withTransientRetry(fakeClient(fetch))
48
+
49
+ const promise = client.fetch({ chainId: 42431, query: 'SELECT 1' })
50
+ await vi.runAllTimersAsync()
51
+ await expect(promise).resolves.toBe(result)
52
+ expect(fetch).toHaveBeenCalledTimes(3)
53
+ } finally {
54
+ vi.useRealTimers()
55
+ }
56
+ })
57
+
58
+ test('retries a dropped ClickHouse Cloud connection', async () => {
59
+ vi.useFakeTimers()
60
+ try {
61
+ const result = { hasMore: false, rows: [] } as never
62
+ const fetch = vi
63
+ .fn()
64
+ .mockRejectedValueOnce(
65
+ appError('ClickHouse HTTP request failed: error sending request for url (…)'),
66
+ )
67
+ .mockResolvedValueOnce(result)
68
+ const client = Tidx.withTransientRetry(fakeClient(fetch))
69
+
70
+ const promise = client.fetch({ chainId: 42431, query: 'SELECT 1' })
71
+ await vi.runAllTimersAsync()
72
+ await expect(promise).resolves.toBe(result)
73
+ expect(fetch).toHaveBeenCalledTimes(2)
74
+ } finally {
75
+ vi.useRealTimers()
76
+ }
77
+ })
78
+
79
+ test('retries HTTP-level transient failures under the shared budget', async () => {
80
+ vi.useFakeTimers()
81
+ try {
82
+ const result = { hasMore: false, rows: [] } as never
83
+ const fetch = vi
84
+ .fn()
85
+ .mockRejectedValueOnce(appError('bad gateway', 502))
86
+ .mockRejectedValueOnce(appError('too many requests', 429))
87
+ .mockResolvedValueOnce(result)
88
+ const client = Tidx.withTransientRetry(fakeClient(fetch))
89
+
90
+ const promise = client.fetch({ chainId: 42431, query: 'SELECT 1' })
91
+ await vi.runAllTimersAsync()
92
+ await expect(promise).resolves.toBe(result)
93
+ expect(fetch).toHaveBeenCalledTimes(3)
94
+ } finally {
95
+ vi.useRealTimers()
96
+ }
97
+ })
98
+
99
+ test('disables the inner client retries so the budget is not multiplied', async () => {
100
+ const fetch = vi.fn().mockResolvedValue({ hasMore: false, rows: [] } as never)
101
+ const client = Tidx.withTransientRetry(fakeClient(fetch))
102
+
103
+ await client.fetch({ chainId: 42431, query: 'SELECT 1' })
104
+
105
+ expect(fetch.mock.calls[0]![0]).toMatchInlineSnapshot(`
106
+ {
107
+ "chainId": 42431,
108
+ "query": "SELECT 1",
109
+ "retryCount": 1,
110
+ }
111
+ `)
112
+ })
113
+
114
+ test('does not retry a deterministic application error', async () => {
115
+ const fetch = vi.fn().mockRejectedValue(appError('syntax error near "SELCT"'))
116
+ const client = Tidx.withTransientRetry(fakeClient(fetch))
117
+
118
+ await expect(client.fetch({ chainId: 42431, query: 'SELCT 1' })).rejects.toThrow('syntax error')
119
+ expect(fetch).toHaveBeenCalledTimes(1)
120
+ })
121
+
122
+ test('does not retry an HTTP 422 `db error` (deterministic query-shape failure)', async () => {
123
+ const fetch = vi.fn().mockRejectedValue(appError('db error', 422))
124
+ const client = Tidx.withTransientRetry(fakeClient(fetch))
125
+
126
+ await expect(client.fetch({ chainId: 42431, query: 'SELECT 1' })).rejects.toThrow('db error')
127
+ expect(fetch).toHaveBeenCalledTimes(1)
128
+ })
129
+
130
+ test('gives up after exhausting retries on persistent `db error`', async () => {
131
+ vi.useFakeTimers()
132
+ try {
133
+ const fetch = vi.fn().mockRejectedValue(appError('db error'))
134
+ const client = Tidx.withTransientRetry(fakeClient(fetch))
135
+
136
+ const promise = client.fetch({ chainId: 42431, query: 'SELECT 1' })
137
+ const assertion = expect(promise).rejects.toThrow('db error')
138
+ await vi.runAllTimersAsync()
139
+ await assertion
140
+ // First attempt plus four retries.
141
+ expect(fetch).toHaveBeenCalledTimes(5)
142
+ } finally {
143
+ vi.useRealTimers()
144
+ }
145
+ })
146
+ })
147
+
148
+ describe('resolve', () => {
149
+ test('defaults to the per-chain indexer URL', () => {
150
+ expect(Tidx.resolve(undefined, { chainId: 4217, principal: null })).toMatchInlineSnapshot(`
151
+ {
152
+ "baseUrl": "https://indexer.tempo.xyz",
153
+ "basicAuth": undefined,
154
+ }
155
+ `)
156
+ expect(Tidx.resolve(undefined, { chainId: 42431, principal: null })).toMatchInlineSnapshot(`
157
+ {
158
+ "baseUrl": "https://indexer.testnet.tempo.xyz",
159
+ "basicAuth": undefined,
160
+ }
161
+ `)
162
+ })
163
+
164
+ test('uses a static base URL override', () => {
165
+ expect(Tidx.resolve({ baseUrl: 'https://indexer.test' }, { chainId: 4217, principal: null }))
166
+ .toMatchInlineSnapshot(`
167
+ {
168
+ "baseUrl": "https://indexer.test",
169
+ "basicAuth": undefined,
170
+ }
171
+ `)
172
+ })
173
+
174
+ test('uses a base URL resolver override', () => {
175
+ expect(
176
+ Tidx.resolve(
177
+ { baseUrl: (chainId) => `https://${chainId}.indexer.test` },
178
+ { chainId: 42431, principal: null },
179
+ ),
180
+ ).toMatchInlineSnapshot(`
181
+ {
182
+ "baseUrl": "https://42431.indexer.test",
183
+ "basicAuth": undefined,
184
+ }
185
+ `)
186
+ })
187
+
188
+ test('the static form ignores the principal', () => {
189
+ const tidx = { baseUrl: 'https://internal.indexer', basicAuth: 'user:pass' }
190
+ expect(Tidx.resolve(tidx, { chainId: 4217, principal: publicPrincipal }))
191
+ .toMatchInlineSnapshot(`
192
+ {
193
+ "baseUrl": "https://internal.indexer",
194
+ "basicAuth": "user:pass",
195
+ }
196
+ `)
197
+ expect(Tidx.resolve(tidx, { chainId: 4217, principal: apiKeyPrincipal }))
198
+ .toMatchInlineSnapshot(`
199
+ {
200
+ "baseUrl": "https://internal.indexer",
201
+ "basicAuth": "user:pass",
202
+ }
203
+ `)
204
+ })
205
+
206
+ test('the resolver form routes anonymous callers to a separate upstream', () => {
207
+ const tidx = ({ principal }: Viem.resolve.Context) =>
208
+ principal?.type === 'public'
209
+ ? { baseUrl: 'https://indexer.tempo.xyz' }
210
+ : { baseUrl: 'https://internal.indexer', basicAuth: 'user:pass' }
211
+ expect(Tidx.resolve(tidx, { chainId: 4217, principal: publicPrincipal }))
212
+ .toMatchInlineSnapshot(`
213
+ {
214
+ "baseUrl": "https://indexer.tempo.xyz",
215
+ "basicAuth": undefined,
216
+ }
217
+ `)
218
+ expect(Tidx.resolve(tidx, { chainId: 4217, principal: apiKeyPrincipal }))
219
+ .toMatchInlineSnapshot(`
220
+ {
221
+ "baseUrl": "https://internal.indexer",
222
+ "basicAuth": "user:pass",
223
+ }
224
+ `)
225
+ // A null principal (trusted poller) takes the non-public branch.
226
+ expect(Tidx.resolve(tidx, { chainId: 4217, principal: null })).toMatchInlineSnapshot(`
227
+ {
228
+ "baseUrl": "https://internal.indexer",
229
+ "basicAuth": "user:pass",
230
+ }
231
+ `)
232
+ })
233
+
234
+ test('a resolver that omits baseUrl falls back to the built-in public host', () => {
235
+ const tidx = () => ({})
236
+ expect(Tidx.resolve(tidx, { chainId: 42431, principal: publicPrincipal }))
237
+ .toMatchInlineSnapshot(`
238
+ {
239
+ "baseUrl": "https://indexer.testnet.tempo.xyz",
240
+ "basicAuth": undefined,
241
+ }
242
+ `)
243
+ })
244
+ })
245
+
246
+ describe('createGetClient', () => {
247
+ test('memoizes one client per resolved upstream', () => {
248
+ const getTidx = Tidx.createGetClient({
249
+ tidx: ({ principal }) =>
250
+ principal?.type === 'public'
251
+ ? { baseUrl: 'https://indexer.tempo.xyz' }
252
+ : { baseUrl: 'https://internal.indexer', basicAuth: 'user:pass' },
253
+ })
254
+ // Callers resolving to the same upstream share a client; distinct upstreams
255
+ // (and chain ids) get distinct clients.
256
+ expect(getTidx(4217, apiKeyPrincipal)).toBe(getTidx(4217, apiKeyPrincipal))
257
+ expect(getTidx(4217, publicPrincipal)).toBe(getTidx(4217, publicPrincipal))
258
+ expect(getTidx(4217, publicPrincipal)).not.toBe(getTidx(4217, apiKeyPrincipal))
259
+ expect(getTidx(4217, apiKeyPrincipal)).not.toBe(getTidx(42431, apiKeyPrincipal))
260
+ })
261
+
262
+ test('shares a client across principals when the resolver ignores them', () => {
263
+ const getTidx = Tidx.createGetClient()
264
+ // With no resolver the upstream is identical for every caller, so the key
265
+ // (resolved output) collapses and all principals share one client.
266
+ expect(getTidx(4217, publicPrincipal)).toBe(getTidx(4217, apiKeyPrincipal))
267
+ expect(getTidx(4217, null)).toBe(getTidx(4217, publicPrincipal))
268
+ })
269
+
270
+ test('consults the resolver on every call but builds one client per upstream', () => {
271
+ const seen: (string | undefined)[] = []
272
+ const getTidx = Tidx.createGetClient({
273
+ tidx: ({ principal }) => {
274
+ const config =
275
+ principal?.type === 'public'
276
+ ? { baseUrl: 'https://indexer.tempo.xyz' }
277
+ : { baseUrl: 'https://internal.indexer', basicAuth: 'user:pass' }
278
+ seen.push(principal?.type)
279
+ return config
280
+ },
281
+ })
282
+ const first = getTidx(4217, apiKeyPrincipal)
283
+ const second = getTidx(4217, apiKeyPrincipal)
284
+ getTidx(4217, publicPrincipal)
285
+ // The resolver is consulted on every call (it derives the cache key from its
286
+ // resolved output) — including the repeat that hits the cache — so a
287
+ // per-request principal is always honored.
288
+ expect(seen).toContain('api_key')
289
+ expect(seen).toContain('public')
290
+ expect(seen.length).toBeGreaterThanOrEqual(3)
291
+ // …but the client itself (the costly construction) is built once per upstream.
292
+ expect(first).toBe(second)
293
+ })
294
+ })
@@ -0,0 +1,238 @@
1
+ import { Tidx } from 'tidx.ts'
2
+
3
+ import type * as Auth from './Auth.js'
4
+ import * as Viem from './Viem.js'
5
+
6
+ /** Default Tempo indexer (TIDX) base URLs. */
7
+ export const url: Record<number, string> = {
8
+ 4217: 'https://indexer.tempo.xyz',
9
+ 42431: 'https://indexer.testnet.tempo.xyz',
10
+ }
11
+
12
+ /** TIDX query client. */
13
+ export type Client = Tidx.Tidx
14
+
15
+ /** Gets a TIDX query client for a Tempo chain id and request principal. */
16
+ export type GetClient = (chainId?: Viem.ChainId, principal?: Auth.Principal | null) => Client
17
+
18
+ /**
19
+ * Resolves a TIDX base URL for a Tempo chain id. May return `undefined` to fall
20
+ * back to the built-in {@link url} map (e.g. for a chain the resolver doesn't
21
+ * serve).
22
+ */
23
+ export type UrlResolver = (chainId: Viem.ChainId) => string | undefined
24
+
25
+ /**
26
+ * Resolves the effective TIDX config for a single request.
27
+ *
28
+ * Accepts either a static config object or a per-request resolver function that
29
+ * receives a {@link Viem.resolve.Context} (`chainId` plus the request
30
+ * `principal`), so callers can route traffic per caller — e.g. send anonymous
31
+ * (`principal.type === 'public'`) traffic to a separate, no-auth indexer. A
32
+ * missing `baseUrl` falls back to the built-in public host map ({@link url}); a
33
+ * missing `basicAuth` means no Basic auth is sent.
34
+ */
35
+ export function resolve(
36
+ tidx: getClient.Tidx | undefined,
37
+ context: Viem.resolve.Context,
38
+ ): resolve.Resolved {
39
+ const config = typeof tidx === 'function' ? tidx(context) : tidx
40
+ return {
41
+ baseUrl:
42
+ (typeof config?.baseUrl === 'function' ? config.baseUrl(context.chainId) : config?.baseUrl) ??
43
+ url[context.chainId],
44
+ basicAuth: config?.basicAuth,
45
+ }
46
+ }
47
+
48
+ export declare namespace resolve {
49
+ /** Resolved TIDX config for a single request. */
50
+ type Resolved = {
51
+ /**
52
+ * Resolved TIDX base URL, or `undefined` when neither a configured `tidx`
53
+ * nor the built-in {@link url} map covers the chain (e.g. an unconfigured
54
+ * localnet). A `undefined` base URL falls through to the TIDX client default.
55
+ */
56
+ baseUrl: string | undefined
57
+ /** Basic auth credentials, or undefined to send no Basic auth. */
58
+ basicAuth?: string | undefined
59
+ }
60
+ }
61
+
62
+ export function getClient(options: getClient.Options): Client {
63
+ const { chainId = Viem.defaultChainId, principal = null, tidx } = options
64
+ const { baseUrl, basicAuth } = resolve(tidx, { chainId, principal })
65
+ return withTransientRetry(Tidx.create({ baseUrl, basicAuth, chainId }))
66
+ }
67
+
68
+ /**
69
+ * Builds a memoized {@link GetClient} that constructs one TIDX client per
70
+ * resolved upstream and reuses it. Clients are stateless HTTP transports, so
71
+ * reusing them avoids per-call construction cost. The cache key is the resolved
72
+ * output (`baseUrl` + `basicAuth` + `chainId`), not the principal, so a resolver
73
+ * `tidx` may branch on any principal field while client cardinality stays
74
+ * bounded by the number of distinct upstreams — callers that resolve to the same
75
+ * upstream share a client. Shared by `App.create` (per-request reads) and
76
+ * `Webhooks.createPoller` (the scheduled poller) so the resolver logic lives in
77
+ * one place.
78
+ */
79
+ export function createGetClient(options: createGetClient.Options = {}): GetClient {
80
+ const { defaultChainId = Viem.defaultChainId, tidx } = options
81
+ const clients = new Map<string, Client>()
82
+
83
+ return (chainId = defaultChainId, principal = null) => {
84
+ // Resolve to derive the cache key; `getClient` re-resolves to build the
85
+ // client on a miss. `resolve` is pure and cheap, so the extra call is free.
86
+ const { baseUrl, basicAuth } = resolve(tidx, { chainId, principal })
87
+ const cacheKey = `${baseUrl}|${basicAuth ?? ''}|${chainId}`
88
+ const existing = clients.get(cacheKey)
89
+ if (existing) return existing
90
+
91
+ const client = getClient({ chainId, principal, tidx })
92
+ clients.set(cacheKey, client)
93
+ return client
94
+ }
95
+ }
96
+
97
+ /**
98
+ * Number of additional `fetch` attempts for transient application-level
99
+ * indexer errors. The first attempt plus this many retries.
100
+ *
101
+ * Combined with {@link transientRetryDelay} this gives a ~3.75s backoff window
102
+ * (250 + 500 + 1000 + 2000ms over five attempts). The heaviest reads — e.g.
103
+ * the unfiltered global swap scan — can momentarily trip the indexer's `db
104
+ * error` under concurrent load, and a ~1s window was too short to ride those
105
+ * bursts out, surfacing them to callers as a 502.
106
+ */
107
+ const transientRetryCount = 4
108
+
109
+ /** Base backoff (ms) between transient-error retries; doubles each attempt. */
110
+ const transientRetryDelay = 250
111
+
112
+ /**
113
+ * Application-level indexer failures that are transient and safe to retry on
114
+ * an idempotent read. The indexer surfaces transient ClickHouse failures (a
115
+ * busy/contended planner, a query-execution hiccup) as the literal message
116
+ * `db error`; timeouts and connection resets are likewise non-deterministic.
117
+ * `error sending request` is how TIDX reports a ClickHouse Cloud connection
118
+ * dropped at the server's execution cap — typically a cold object-storage
119
+ * cache read that warms across attempts, so retrying genuinely helps.
120
+ */
121
+ const transientErrorPattern =
122
+ /db error|timeout|timed out|connection reset|connection refused|error sending request/i
123
+
124
+ /**
125
+ * Escapes a free-form string for safe embedding inside a single-quoted SQL
126
+ * string literal sent to the indexer. TIDX parses every query with an ANSI
127
+ * SQL parser before re-rendering it for the engine, and ANSI strings have
128
+ * exactly one special character inside `'…'`: the quote itself, escaped by
129
+ * doubling (`''`). Backslash escaping (`\'`) is **rejected** with a parse
130
+ * error, and a backslash is an ordinary character on both engines (verified
131
+ * against the deployed indexer on `postgres` and `clickhouse`).
132
+ *
133
+ * Most interpolated values (addresses, hashes, ISO timestamps) are already
134
+ * schema-constrained to charsets that cannot contain a quote; reach for this
135
+ * whenever a query must interpolate genuinely free-form user input (e.g. the
136
+ * token `currency` filter).
137
+ */
138
+ export function escape(value: string) {
139
+ return value.replaceAll("'", "''")
140
+ }
141
+
142
+ /**
143
+ * Wraps a TIDX client so `fetch` retries transient errors under **one**
144
+ * shared budget ({@link transientRetryCount} retries with backoff).
145
+ *
146
+ * Two transient failure modes are covered: HTTP-level failures (408/429/5xx)
147
+ * and the indexer's transient ClickHouse errors, which arrive as **HTTP 200**
148
+ * with an `{ ok: false, error: 'db error' }` body (a `FetchRequestError`
149
+ * whose `status` is 200). The raw `tidx.ts` client would retry the HTTP-level
150
+ * mode itself (5 attempts), which stacked multiplicatively under this wrapper
151
+ * — one flapping query shape could cost ~25 upstream executions — so the
152
+ * wrapper disables the client-level retries (`retryCount: 1`) and owns the
153
+ * whole budget: at most {@link transientRetryCount} + 1 upstream executions.
154
+ * Deterministic errors (e.g. a malformed query) fall through immediately.
155
+ */
156
+ export function withTransientRetry(client: Client): Client {
157
+ const fetch = client.fetch.bind(client)
158
+ return {
159
+ ...client,
160
+ fetch: async (options) => {
161
+ let lastError: unknown
162
+ for (let attempt = 0; attempt <= transientRetryCount; attempt++) {
163
+ try {
164
+ // `retryCount: 1` caps the inner client at a single HTTP attempt so
165
+ // the combined budget stays this wrapper's alone.
166
+ return await fetch({ ...options, retryCount: 1 })
167
+ } catch (error) {
168
+ if (!isTransientError(error)) throw error
169
+ lastError = error
170
+ if (attempt < transientRetryCount)
171
+ await new Promise((resolve) => setTimeout(resolve, transientRetryDelay * 2 ** attempt))
172
+ }
173
+ }
174
+ throw lastError
175
+ },
176
+ }
177
+ }
178
+
179
+ /**
180
+ * True for transient indexer errors: an HTTP-level transient failure
181
+ * (408/429/5xx), or a `FetchRequestError` that came back on a **2xx** HTTP
182
+ * response whose message matches a known transient failure.
183
+ *
184
+ * The 2xx gate on the message pattern matters. The indexer reports two
185
+ * different `db error`s: a transient ClickHouse hiccup arrives on an HTTP 200
186
+ * with `{ ok: false, error: 'db error' }` (worth retrying), whereas an HTTP
187
+ * 422 `db error` means the planner cannot execute that query shape at all — a
188
+ * deterministic failure that will never succeed on retry. Retrying the latter
189
+ * would just burn the backoff budget before failing anyway, so 4xx statuses
190
+ * other than 408/429 are excluded.
191
+ */
192
+ function isTransientError(error: unknown): boolean {
193
+ if (!(error instanceof Tidx.FetchRequestError)) return false
194
+ if (error.status === 408 || error.status === 429 || error.status >= 500) return true
195
+ return error.status >= 200 && error.status < 300 && transientErrorPattern.test(error.message)
196
+ }
197
+
198
+ export declare namespace getClient {
199
+ /** Options for getting a TIDX query client. */
200
+ type Options = {
201
+ /** Tempo chain id. */
202
+ chainId?: Viem.ChainId | undefined
203
+ /** Request principal, or `null`/omitted for a trusted non-request caller. */
204
+ principal?: Auth.Principal | null | undefined
205
+ /** TIDX query client options. */
206
+ tidx?: Tidx | undefined
207
+ }
208
+
209
+ /**
210
+ * TIDX query client options: either a static config, or a per-request
211
+ * resolver function that receives a {@link Viem.resolve.Context} and returns
212
+ * the config to use (e.g. routing anonymous callers to a public, no-auth
213
+ * indexer).
214
+ */
215
+ type Tidx =
216
+ | {
217
+ /** Resolves the TIDX base URL for a Tempo chain id, or a static base URL. */
218
+ baseUrl?: UrlResolver | string | undefined
219
+ /** Basic auth credentials for TIDX requests. */
220
+ basicAuth?: string | undefined
221
+ }
222
+ | ((context: Viem.resolve.Context) => {
223
+ /** TIDX base URL to use; falls back to the built-in public host when omitted. */
224
+ baseUrl?: string | undefined
225
+ /** Basic auth credentials for TIDX requests, or undefined for none. */
226
+ basicAuth?: string | undefined
227
+ })
228
+ }
229
+
230
+ export declare namespace createGetClient {
231
+ /** Options for building a memoized TIDX client resolver. */
232
+ type Options = {
233
+ /** Tempo chain id used when a caller omits one. */
234
+ defaultChainId?: Viem.ChainId | undefined
235
+ /** TIDX query client options applied to every constructed client. */
236
+ tidx?: getClient.Tidx | undefined
237
+ }
238
+ }
@@ -0,0 +1,22 @@
1
+ import { Hono } from 'hono'
2
+ import * as Timing from './Timing.js'
3
+
4
+ describe('middleware', () => {
5
+ test('emits request and operation server timings', async () => {
6
+ const app = new Hono<{ Variables: Timing.Variables }>()
7
+
8
+ app.use('*', Timing.middleware())
9
+ app.get('/', async (c) => {
10
+ await Timing.time(c, 'example.work', () => Promise.resolve())
11
+ return c.text('ok')
12
+ })
13
+
14
+ const response = await app.request('/')
15
+ const timing = response.headers.get('server-timing')
16
+
17
+ expect(response.status).toMatchInlineSnapshot(`200`)
18
+ expect(response.headers.get('content-type')).toMatchInlineSnapshot(`"text/plain;charset=UTF-8"`)
19
+ expect(timing?.includes('example.work;dur=')).toMatchInlineSnapshot(`true`)
20
+ expect(timing?.includes('request;dur=')).toMatchInlineSnapshot(`true`)
21
+ })
22
+ })
@@ -0,0 +1,62 @@
1
+ import type { Context, MiddlewareHandler } from 'hono'
2
+
3
+ /** Hono variables used to collect Server-Timing metrics. */
4
+ export type Variables = {
5
+ /** Metrics emitted in the `Server-Timing` response header. */
6
+ serverTiming?: Metric[] | undefined
7
+ }
8
+
9
+ /** Hono environment shape used by timing helpers. */
10
+ export type Environment = {
11
+ Variables: Variables
12
+ }
13
+
14
+ /** One Server-Timing metric. */
15
+ export type Metric = {
16
+ /** Metric duration in milliseconds. */
17
+ duration: number
18
+ /** Metric name. */
19
+ name: string
20
+ }
21
+
22
+ /** Adds a `Server-Timing` header with total request duration. */
23
+ export function middleware<
24
+ environment extends Environment = Environment,
25
+ >(): MiddlewareHandler<environment> {
26
+ return async (c, next) => {
27
+ const metrics: Metric[] = []
28
+ c.set('serverTiming', metrics)
29
+
30
+ const start = performance.now()
31
+ await next()
32
+ metrics.push({ duration: performance.now() - start, name: 'request' })
33
+
34
+ const value = format(metrics)
35
+ const existing = c.res.headers.get('Server-Timing')
36
+ c.res.headers.set('Server-Timing', existing ? `${existing}, ${value}` : value)
37
+ }
38
+ }
39
+
40
+ /** Measures one async operation and adds it to the current request timings. */
41
+ export async function time<environment extends Environment, value>(
42
+ c: Context<environment>,
43
+ name: string,
44
+ fn: () => Promise<value> | value,
45
+ ) {
46
+ const start = performance.now()
47
+ try {
48
+ return await fn()
49
+ } finally {
50
+ c.get('serverTiming')?.push({ duration: performance.now() - start, name })
51
+ }
52
+ }
53
+
54
+ function format(metrics: readonly Metric[]) {
55
+ return metrics
56
+ .map((metric) => `${name(metric.name)};dur=${metric.duration.toFixed(1)}`)
57
+ .join(', ')
58
+ }
59
+
60
+ function name(value: string) {
61
+ return value.replace(/[^A-Za-z0-9_.*-]/g, '_')
62
+ }
@@ -0,0 +1,18 @@
1
+ import * as Ttl from './Ttl.js'
2
+
3
+ describe('Ttl', () => {
4
+ test('converts common durations to milliseconds', () => {
5
+ expect(Ttl.millisecond(1)).toMatchInlineSnapshot(`1`)
6
+ expect(Ttl.milliseconds(2)).toMatchInlineSnapshot(`2`)
7
+ expect(Ttl.second(1)).toMatchInlineSnapshot(`1000`)
8
+ expect(Ttl.seconds(2)).toMatchInlineSnapshot(`2000`)
9
+ expect(Ttl.minute(1)).toMatchInlineSnapshot(`60000`)
10
+ expect(Ttl.minutes(2)).toMatchInlineSnapshot(`120000`)
11
+ expect(Ttl.hour(1)).toMatchInlineSnapshot(`3600000`)
12
+ expect(Ttl.hours(2)).toMatchInlineSnapshot(`7200000`)
13
+ expect(Ttl.day(1)).toMatchInlineSnapshot(`86400000`)
14
+ expect(Ttl.days(2)).toMatchInlineSnapshot(`172800000`)
15
+ expect(Ttl.week(1)).toMatchInlineSnapshot(`604800000`)
16
+ expect(Ttl.weeks(2)).toMatchInlineSnapshot(`1209600000`)
17
+ })
18
+ })
@@ -0,0 +1,59 @@
1
+ /** Returns milliseconds from milliseconds. */
2
+ export function milliseconds(value: number) {
3
+ return value
4
+ }
5
+
6
+ /** Returns milliseconds from seconds. */
7
+ export function seconds(value: number) {
8
+ return value * 1_000
9
+ }
10
+
11
+ /** Returns milliseconds from minutes. */
12
+ export function minutes(value: number) {
13
+ return seconds(value) * 60
14
+ }
15
+
16
+ /** Returns milliseconds from hours. */
17
+ export function hours(value: number) {
18
+ return minutes(value) * 60
19
+ }
20
+
21
+ /** Returns milliseconds from days. */
22
+ export function days(value: number) {
23
+ return hours(value) * 24
24
+ }
25
+
26
+ /** Returns milliseconds from weeks. */
27
+ export function weeks(value: number) {
28
+ return days(value) * 7
29
+ }
30
+
31
+ /** Returns milliseconds from milliseconds. */
32
+ export function millisecond(value: number) {
33
+ return milliseconds(value)
34
+ }
35
+
36
+ /** Returns milliseconds from seconds. */
37
+ export function second(value: number) {
38
+ return seconds(value)
39
+ }
40
+
41
+ /** Returns milliseconds from minutes. */
42
+ export function minute(value: number) {
43
+ return minutes(value)
44
+ }
45
+
46
+ /** Returns milliseconds from hours. */
47
+ export function hour(value: number) {
48
+ return hours(value)
49
+ }
50
+
51
+ /** Returns milliseconds from days. */
52
+ export function day(value: number) {
53
+ return days(value)
54
+ }
55
+
56
+ /** Returns milliseconds from weeks. */
57
+ export function week(value: number) {
58
+ return weeks(value)
59
+ }