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,240 @@
1
+ import { Hono } from 'hono'
2
+ import * as Cache from './Cache.js'
3
+
4
+ describe('response', () => {
5
+ test('preserves rate-limit headers without caching them', async () => {
6
+ await withCaches(async () => {
7
+ let calls = 0
8
+ const app = new Hono()
9
+
10
+ app.use('*', async (c, next) => {
11
+ c.header('RateLimit-Remaining', calls === 0 ? '9' : '8')
12
+ await next()
13
+ })
14
+ app.get('/', Cache.response({ key: (c) => c.req.url, name: 'test' }), (c) =>
15
+ c.json({ calls: ++calls }),
16
+ )
17
+
18
+ const first = await app.request('/')
19
+ const second = await app.request('/')
20
+ const third = await app.request('/', {
21
+ headers: { 'If-None-Match': first.headers.get('etag') ?? '' },
22
+ })
23
+
24
+ expect(calls).toMatchInlineSnapshot(`1`)
25
+ expect(await first.json()).toMatchInlineSnapshot(`
26
+ {
27
+ "calls": 1,
28
+ }
29
+ `)
30
+ expect(first.headers.get('cache-control')).toMatchInlineSnapshot(
31
+ `"private, max-age=30, stale-while-revalidate=300"`,
32
+ )
33
+ expect(etag(first)).toMatchInlineSnapshot(`"<etag>"`)
34
+ expect(first.headers.get('ratelimit-remaining')).toMatchInlineSnapshot(`"9"`)
35
+ expect(first.headers.get('vary')).toMatchInlineSnapshot(
36
+ `"Accept-Encoding, Authorization, Tempo-API-Key"`,
37
+ )
38
+
39
+ expect(await second.json()).toMatchInlineSnapshot(`
40
+ {
41
+ "calls": 1,
42
+ }
43
+ `)
44
+ expect(second.headers.get('cache-control')).toMatchInlineSnapshot(
45
+ `"private, max-age=30, stale-while-revalidate=300"`,
46
+ )
47
+ expect(second.headers.get('etag') === first.headers.get('etag')).toMatchInlineSnapshot(`true`)
48
+ expect(second.headers.get('ratelimit-remaining')).toMatchInlineSnapshot(`"8"`)
49
+ expect(second.headers.get('vary')).toMatchInlineSnapshot(
50
+ `"Accept-Encoding, Authorization, Tempo-API-Key"`,
51
+ )
52
+
53
+ expect(third.status).toMatchInlineSnapshot(`304`)
54
+ expect(await third.text()).toMatchInlineSnapshot(`""`)
55
+ expect(third.headers.get('cache-control')).toMatchInlineSnapshot(
56
+ `"private, max-age=30, stale-while-revalidate=300"`,
57
+ )
58
+ expect(third.headers.get('etag') === first.headers.get('etag')).toMatchInlineSnapshot(`true`)
59
+ expect(third.headers.get('ratelimit-remaining')).toMatchInlineSnapshot(`"8"`)
60
+ expect(third.headers.get('vary')).toMatchInlineSnapshot(
61
+ `"Accept-Encoding, Authorization, Tempo-API-Key"`,
62
+ )
63
+ })
64
+ })
65
+
66
+ test('stores entries with the shared policy form, not the client form', async () => {
67
+ await withCaches(async (stores) => {
68
+ const app = new Hono()
69
+ app.get('/', Cache.response({ key: (c) => c.req.url, name: 'test' }), (c) =>
70
+ c.json({ ok: true }),
71
+ )
72
+
73
+ await app.request('/')
74
+
75
+ const [stored] = [...(stores.get('test')?.values() ?? [])]
76
+ // The stored copy carries `public, s-maxage` so platform caches bound
77
+ // the entry's lifetime to the policy; the client copy stays private.
78
+ expect(stored?.headers.get('cache-control')).toMatchInlineSnapshot(
79
+ `"public, max-age=30, s-maxage=30, stale-while-revalidate=300"`,
80
+ )
81
+ })
82
+ })
83
+
84
+ test('refreshes the stored entry when the request sends `Cache-Control: no-cache`', async () => {
85
+ await withCaches(async () => {
86
+ let calls = 0
87
+ const app = new Hono()
88
+ app.get('/', Cache.response({ key: (c) => c.req.url, name: 'test' }), (c) =>
89
+ c.json({ calls: ++calls }),
90
+ )
91
+
92
+ await app.request('/')
93
+ // `no-cache` purges the stored entry, re-runs the handler, and stores
94
+ // the fresh response, so the following plain request is served the
95
+ // refreshed copy.
96
+ const refreshed = await app.request('/', { headers: { 'cache-control': 'no-cache' } })
97
+ const after = await app.request('/')
98
+
99
+ expect(calls).toMatchInlineSnapshot(`2`)
100
+ expect(await refreshed.json()).toMatchInlineSnapshot(`
101
+ {
102
+ "calls": 2,
103
+ }
104
+ `)
105
+ expect(await after.json()).toMatchInlineSnapshot(`
106
+ {
107
+ "calls": 2,
108
+ }
109
+ `)
110
+ })
111
+ })
112
+
113
+ test('bypasses without purging when the request sends `Cache-Control: no-store`', async () => {
114
+ await withCaches(async () => {
115
+ let calls = 0
116
+ const app = new Hono()
117
+ app.get('/', Cache.response({ key: (c) => c.req.url, name: 'test' }), (c) =>
118
+ c.json({ calls: ++calls }),
119
+ )
120
+
121
+ await app.request('/')
122
+ // `no-store` skips reads and writes but leaves the stored entry alone:
123
+ // the bypassing caller gets a fresh response while the next plain
124
+ // request is still served the original copy.
125
+ const bypassed = await app.request('/', { headers: { 'cache-control': 'no-store' } })
126
+ const after = await app.request('/')
127
+
128
+ expect(calls).toMatchInlineSnapshot(`2`)
129
+ expect(await bypassed.json()).toMatchInlineSnapshot(`
130
+ {
131
+ "calls": 2,
132
+ }
133
+ `)
134
+ expect(await after.json()).toMatchInlineSnapshot(`
135
+ {
136
+ "calls": 1,
137
+ }
138
+ `)
139
+ })
140
+ })
141
+
142
+ test('honors `Pragma: no-cache` when `Cache-Control` is absent', async () => {
143
+ await withCaches(async () => {
144
+ let calls = 0
145
+ const app = new Hono()
146
+ app.get('/', Cache.response({ key: (c) => c.req.url, name: 'test' }), (c) =>
147
+ c.json({ calls: ++calls }),
148
+ )
149
+
150
+ await app.request('/')
151
+ const refreshed = await app.request('/', { headers: { pragma: 'no-cache' } })
152
+
153
+ expect(calls).toMatchInlineSnapshot(`2`)
154
+ expect(await refreshed.json()).toMatchInlineSnapshot(`
155
+ {
156
+ "calls": 2,
157
+ }
158
+ `)
159
+ })
160
+ })
161
+
162
+ test('does not store responses a handler overrides to `noStore`', async () => {
163
+ await withCaches(async (stores) => {
164
+ let calls = 0
165
+ const app = new Hono()
166
+ app.get('/', Cache.response({ key: (c) => c.req.url, name: 'test' }), (c) => {
167
+ Cache.setPolicy(c, Cache.policies.noStore)
168
+ return c.json({ calls: ++calls })
169
+ })
170
+
171
+ const first = await app.request('/')
172
+ const second = await app.request('/')
173
+
174
+ // The `noStore` override must keep the response out of the inner cache
175
+ // (e.g. a pending transaction), so every request re-runs the handler.
176
+ expect(calls).toMatchInlineSnapshot(`2`)
177
+ expect(stores.get('test')?.size ?? 0).toMatchInlineSnapshot(`0`)
178
+ expect(first.headers.get('cache-control')).toMatchInlineSnapshot(`"no-store"`)
179
+ expect(second.headers.get('cache-control')).toMatchInlineSnapshot(`"no-store"`)
180
+ })
181
+ })
182
+
183
+ test('leaves error response `Cache-Control` alone', async () => {
184
+ await withCaches(async (stores) => {
185
+ const app = new Hono()
186
+ app.get('/', Cache.response({ key: (c) => c.req.url, name: 'test' }), (c) => {
187
+ // Mirrors `Response.error`, which marks envelopes `no-store`.
188
+ c.header('Cache-Control', 'no-store')
189
+ return c.json({ error: true }, 404)
190
+ })
191
+
192
+ const first = await app.request('/')
193
+ const second = await app.request('/')
194
+
195
+ expect(first.status).toMatchInlineSnapshot(`404`)
196
+ expect(first.headers.get('cache-control')).toMatchInlineSnapshot(`"no-store"`)
197
+ expect(second.headers.get('cache-control')).toMatchInlineSnapshot(`"no-store"`)
198
+ expect(stores.get('test')?.size ?? 0).toMatchInlineSnapshot(`0`)
199
+ })
200
+ })
201
+ })
202
+
203
+ function etag(response: Response) {
204
+ return response.headers.has('etag') ? '<etag>' : null
205
+ }
206
+
207
+ async function withCaches(fn: (stores: Map<string, Map<string, Response>>) => Promise<void>) {
208
+ const descriptor = Object.getOwnPropertyDescriptor(globalThis, 'caches')
209
+ const stores = new Map<string, Map<string, Response>>()
210
+ Object.defineProperty(globalThis, 'caches', {
211
+ configurable: true,
212
+ value: {
213
+ async open(name: string) {
214
+ let store = stores.get(name)
215
+ if (!store) {
216
+ store = new Map()
217
+ stores.set(name, store)
218
+ }
219
+ return {
220
+ async delete(key: string) {
221
+ return store.delete(key)
222
+ },
223
+ async match(key: string) {
224
+ return store.get(key)?.clone()
225
+ },
226
+ async put(key: string, response: Response) {
227
+ store.set(key, response.clone())
228
+ },
229
+ }
230
+ },
231
+ },
232
+ })
233
+
234
+ try {
235
+ await fn(stores)
236
+ } finally {
237
+ if (descriptor) Object.defineProperty(globalThis, 'caches', descriptor)
238
+ else delete (globalThis as { caches?: unknown }).caches
239
+ }
240
+ }
@@ -0,0 +1,262 @@
1
+ import type { Context, Env, MiddlewareHandler } from 'hono'
2
+ import { cache as hono_cache } from 'hono/cache'
3
+ import { etag as hono_etag, RETAINED_304_HEADERS } from 'hono/etag'
4
+ import type * as z from 'zod/mini'
5
+
6
+ import * as EdgeCache from './EdgeCache.js'
7
+
8
+ /**
9
+ * Named `Cache-Control` policies, tiered by how volatile each resource is. The
10
+ * `max-age` bounds how stale a shared (edge) hit can be; `stale-while-revalidate`
11
+ * lets clients/CDNs serve a slightly older copy while refreshing in the
12
+ * background. Handlers may override per response via `Cache.setPolicy` (e.g. a
13
+ * mined transaction is `immutable`, a pending one is `noStore`).
14
+ */
15
+ export const policies = {
16
+ /**
17
+ * Publicly shareable binary assets (token logos). `public` because the bytes
18
+ * carry no per-principal data and the edge layer strips per-request headers.
19
+ * Deliberately NOT `immutable`: these live at a stable URL
20
+ * (`/tokens/:token/logo`) whose underlying R2 icon can be replaced in place
21
+ * (admin upload), so a long `max-age` is paired with `stale-while-revalidate`
22
+ * to revalidate rather than pin a replaced logo for a year. Use a versioned/
23
+ * content-addressed URL if true `immutable` forever-caching is needed.
24
+ */
25
+ asset: 'public, max-age=86400, stale-while-revalidate=604800',
26
+ /** Fallback for routes that do not specify a policy. */
27
+ default: 'private, max-age=30, stale-while-revalidate=300',
28
+ /** Newest-first feeds (transactions, receipts, transfers): refresh quickly. */
29
+ feed: 'private, max-age=10, stale-while-revalidate=30',
30
+ /** Immutable resources (mined transactions and receipts): cache aggressively. */
31
+ immutable: 'private, max-age=86400, stale-while-revalidate=604800',
32
+ /** Token metadata and indexed listings: change slowly. */
33
+ metadata: 'private, max-age=60, stale-while-revalidate=300',
34
+ /** Transient or uncacheable responses (pending transactions). */
35
+ noStore: 'no-store',
36
+ /** Slow-changing curated data (verified tokens). */
37
+ stable: 'private, max-age=300, stale-while-revalidate=3600',
38
+ /** Current-state pages (holders, balances): moderately volatile. */
39
+ state: 'private, max-age=30, stale-while-revalidate=120',
40
+ } as const
41
+
42
+ /**
43
+ * Overrides the cache policy for the current response, taking precedence over
44
+ * the route's default `Cache.response` policy. Lets a handler vary caching by
45
+ * outcome — e.g. cache a mined transaction as `immutable` but a pending one as
46
+ * `noStore`. Must be called before the handler returns.
47
+ */
48
+ export function setPolicy<environment extends Env>(
49
+ c: Context<environment>,
50
+ cacheControl: string,
51
+ ): void {
52
+ c.set('cacheControl' as never, cacheControl as never)
53
+ }
54
+
55
+ /** Hono response-cache helpers. */
56
+ export function response<environment extends Env>(
57
+ options: response.Options<environment>,
58
+ ): MiddlewareHandler<environment> {
59
+ const defaultCacheControl = options.cacheControl ?? policies.default
60
+ const vary = options.vary ?? ['Accept-Encoding', 'Authorization', 'Tempo-API-Key']
61
+ // hono/cache suffixes its key with `::<directive>=<value>` for every
62
+ // configured vary directive (trimmed, lowercased, deduped). Mirror that
63
+ // normalization so the `no-cache` purge below deletes exactly the entry the
64
+ // wrapped middleware reads — drift here would make refreshes silently miss.
65
+ const varyKeyDirectives = [
66
+ ...new Set(vary.map((name) => name.trim().toLowerCase()).filter(Boolean)),
67
+ ]
68
+ const cache = hono_cache({
69
+ cacheName: options.name,
70
+ keyGenerator: options.key,
71
+ onCacheNotAvailable: false,
72
+ vary: [...vary],
73
+ wait: true,
74
+ })
75
+ const etag = hono_etag()
76
+
77
+ return async (c, next) => {
78
+ const values = response.preservedHeaders.map((name) => [name, c.res.headers.get(name)] as const)
79
+
80
+ // Honor the standard request `Cache-Control` directives (plus the HTTP/1.0
81
+ // `Pragma: no-cache` fallback, used only when `Cache-Control` is absent),
82
+ // mirroring `EdgeCache.middleware`: a refresh that bypasses the edge layer
83
+ // must also bypass this inner response cache, or the caller is served the
84
+ // inner stored copy and the "refresh" never reaches the handler.
85
+ // `no-cache` purges the stored entry so the wrapped hono/cache misses and
86
+ // re-stores the fresh response; `no-store` skips reads and writes entirely
87
+ // while leaving the stored entry untouched.
88
+ const directives = (c.req.header('Cache-Control') ?? c.req.header('Pragma') ?? '').toLowerCase()
89
+ const noStore = directives.includes('no-store')
90
+ const noCache = noStore || directives.includes('no-cache')
91
+ // The Cache API global is platform-provided (Cloudflare Workers, browsers)
92
+ // and absent from Node's type surface, so probe it structurally — on
93
+ // runtimes without it the wrapped hono/cache is a no-op and there is
94
+ // nothing to purge.
95
+ type Caches = { open(name: string): Promise<{ delete(key: string): Promise<unknown> }> }
96
+ const caches = (globalThis as { caches?: Caches }).caches
97
+ if (noCache && !noStore && caches) {
98
+ let key = await options.key(c)
99
+ for (const name of varyKeyDirectives)
100
+ key += `::${name}=${encodeURIComponent(c.req.raw.headers.get(name) ?? '')}`
101
+ await (await caches.open(options.name)).delete(key)
102
+ }
103
+
104
+ // Run ETag inside Hono's cache miss path so fresh responses are hashed
105
+ // once before storage. Cached hits bypass Hono's ETag middleware, so this
106
+ // wrapper handles conditional requests from the stored ETag below.
107
+ const handler = async () => {
108
+ await etag(c, async () => {
109
+ await next()
110
+ // Rate-limit headers are per request and would be stale if stored with
111
+ // the shared cached body.
112
+ for (const name of response.preservedHeaders) c.res.headers.delete(name)
113
+ })
114
+ // Stamp the resolved policy's shared form onto 2xx responses BEFORE
115
+ // hono/cache stores them: the explicit `s-maxage` bounds the stored
116
+ // entry's lifetime to the policy (instead of the platform cache's
117
+ // default TTL), and a policy resolved to `noStore` (e.g. a pending
118
+ // transaction via `setPolicy`) becomes `no-store`, which hono/cache
119
+ // refuses to store. Client-facing headers are applied after storage
120
+ // below; non-2xx responses keep their own headers (`Response.error`
121
+ // marks envelopes `no-store`).
122
+ if (c.res.status >= 200 && c.res.status < 300) {
123
+ const policy = edgeCachePolicy(
124
+ (c.get('cacheControl' as never) as string | undefined) ?? defaultCacheControl,
125
+ )
126
+ c.res.headers.set('Cache-Control', policy ? EdgeCache.cacheControl(policy) : 'no-store')
127
+ }
128
+ }
129
+ const result = noStore ? await handler() : await cache(c, handler)
130
+
131
+ // A handler may override the policy per response (see `setPolicy`); fall back
132
+ // to the route default. Publishing the derived policy is what marks the
133
+ // response as eligible for the shared edge cache (see `EdgeCache.middleware`),
134
+ // so `noStore` responses are never persisted there. The edge layer reads this
135
+ // after the whole chain runs, so setting it here (post-handler) is in time.
136
+ const cacheControl =
137
+ (c.get('cacheControl' as never) as string | undefined) ?? defaultCacheControl
138
+ c.set('edgeCache' as never, edgeCachePolicy(cacheControl) as never)
139
+
140
+ let res = result instanceof Response ? new Response(result.body, result) : c.res
141
+ if (
142
+ result instanceof Response &&
143
+ etagMatches(res.headers.get('ETag'), c.req.header('If-None-Match'))
144
+ )
145
+ res = notModified(res)
146
+
147
+ // Apply private client cache headers after Hono cache storage; the stored
148
+ // copy carries the shared `public, s-maxage` form stamped above, while the
149
+ // client receives the policy's private form. Error responses keep their
150
+ // own `Cache-Control` (`no-store` from `Response.error` or the app-level
151
+ // safety net) instead of inheriting the route policy.
152
+ if ((res.status >= 200 && res.status < 300) || res.status === 304)
153
+ setCacheHeaders(res.headers, { cacheControl, vary })
154
+ for (const [name, value] of values) if (value) res.headers.set(name, value)
155
+ if (result instanceof Response) return res
156
+ return undefined
157
+ }
158
+ }
159
+
160
+ export declare namespace response {
161
+ /** Options for response caching. */
162
+ type Options<environment extends Env> = {
163
+ /** Cache-control value returned to clients. */
164
+ cacheControl?: string | undefined
165
+ /** Cache name passed to Hono's cache middleware. */
166
+ name: string
167
+ /** Cache key generator. */
168
+ key: (c: Context<environment>) => Promise<string> | string
169
+ /** Vary headers used for the response and cache key. */
170
+ vary?: readonly string[] | undefined
171
+ }
172
+ }
173
+
174
+ export namespace response {
175
+ /** Per-request headers that must not be stored in shared response caches. */
176
+ export const preservedHeaders = ['RateLimit-Limit', 'RateLimit-Remaining', 'RateLimit-Reset']
177
+ }
178
+
179
+ /**
180
+ * Builds a cache key from the request URL after parsing query params through
181
+ * the given Zod schema. Defaults from the schema are baked into the key so
182
+ * cached entries collide regardless of param ordering, casing, or omitted
183
+ * defaults. Resolves an optional `chainId` query parameter against the app's
184
+ * default chain id when the caller did not provide one.
185
+ */
186
+ export function urlKey<
187
+ environment extends { Variables: { chainId: number } },
188
+ schema extends z.ZodMiniType,
189
+ >(c: Context<environment>, schema: schema): string {
190
+ const url = new URL(c.req.url)
191
+ const query = schema.parse(Object.fromEntries(url.searchParams)) as Record<string, unknown>
192
+ url.search = ''
193
+ if (query['chainId'] === undefined) url.searchParams.set('chainId', String(c.get('chainId')))
194
+ for (const [key, value] of Object.entries(query)) {
195
+ if (value === undefined) continue
196
+ url.searchParams.set(key, String(value as number | string | boolean))
197
+ }
198
+ return url.toString()
199
+ }
200
+
201
+ function etagMatches(etag: string | null, ifNoneMatch: string | undefined) {
202
+ if (!etag || !ifNoneMatch) return false
203
+ return ifNoneMatch.split(/,\s*/).some((tag) => stripWeak(tag) === stripWeak(etag))
204
+ }
205
+
206
+ function notModified(response: Response) {
207
+ const headers = new Headers()
208
+ for (const name of RETAINED_304_HEADERS) {
209
+ const value = response.headers.get(name)
210
+ if (value) headers.set(name, value)
211
+ }
212
+ return new Response(null, {
213
+ headers,
214
+ status: 304,
215
+ statusText: 'Not Modified',
216
+ })
217
+ }
218
+
219
+ function setCacheHeaders(
220
+ headers: Headers,
221
+ options: { cacheControl: string; vary: readonly string[] },
222
+ ) {
223
+ headers.set('Cache-Control', options.cacheControl)
224
+ const vary = new Map(
225
+ (headers.get('Vary') ?? '')
226
+ .split(',')
227
+ .map((value) => value.trim())
228
+ .filter(Boolean)
229
+ .map((value) => [value.toLowerCase(), value] as const),
230
+ )
231
+ for (const value of options.vary) vary.set(value.toLowerCase(), value)
232
+ headers.set('Vary', Array.from(vary.values()).join(', '))
233
+ }
234
+
235
+ function stripWeak(etag: string) {
236
+ return etag.replace(/^W\//, '')
237
+ }
238
+
239
+ // Derive the shared-cache policy from a route's client `Cache-Control`. A
240
+ // positive `s-maxage`/`max-age` (and no `no-store`/`no-cache`) is what makes a
241
+ // route eligible for the anonymous edge cache; the `private` directive only
242
+ // scopes the client copy and is intentionally ignored here.
243
+ function edgeCachePolicy(cacheControl: string): EdgeCache.Policy | undefined {
244
+ const directives = new Map(
245
+ cacheControl
246
+ .split(',')
247
+ .map((part) => part.trim().toLowerCase())
248
+ .filter(Boolean)
249
+ .map((part) => part.split('=') as [string, string | undefined]),
250
+ )
251
+ if (directives.has('no-store') || directives.has('no-cache')) return undefined
252
+ const maxAge = Number(directives.get('s-maxage') ?? directives.get('max-age'))
253
+ if (!Number.isFinite(maxAge) || maxAge <= 0) return undefined
254
+ const staleWhileRevalidate = Number(directives.get('stale-while-revalidate'))
255
+ return {
256
+ maxAge,
257
+ staleWhileRevalidate:
258
+ Number.isFinite(staleWhileRevalidate) && staleWhileRevalidate > 0
259
+ ? staleWhileRevalidate
260
+ : undefined,
261
+ }
262
+ }
@@ -0,0 +1,100 @@
1
+ import * as Cursor from './Cursor.js'
2
+
3
+ describe('Cursor', () => {
4
+ test('round-trips an int tuple through an opaque, URL-safe token', () => {
5
+ const token = Cursor.encode([123456, 7])
6
+ expect(token).toMatch(/^[A-Za-z0-9_-]+$/)
7
+ expect(Cursor.decode(token, ['int', 'int'])).toEqual([123456, 7])
8
+ })
9
+
10
+ test('round-trips a balance/address tuple', () => {
11
+ const token = Cursor.encode([
12
+ '1000000000000000000',
13
+ '0xBE058E1C4DF8A4366A387BF595B284246A93039E',
14
+ ])
15
+ expect(Cursor.decode(token, ['uint', 'address'])).toEqual([
16
+ '1000000000000000000',
17
+ '0xBE058E1C4DF8A4366A387BF595B284246A93039E',
18
+ ])
19
+ })
20
+
21
+ test('returns undefined for malformed, mis-typed, or wrong-arity tokens', () => {
22
+ expect(Cursor.decode('not-base64!!', ['int', 'int'])).toBeUndefined()
23
+ expect(Cursor.decode(btoa('{"oops":true}'), ['int', 'int'])).toBeUndefined()
24
+ expect(Cursor.decode(btoa('[1]'), ['int', 'int'])).toBeUndefined()
25
+ expect(Cursor.decode(btoa('[1,2,3]'), ['int', 'int'])).toBeUndefined()
26
+ expect(Cursor.decode(btoa('[1.5,2]'), ['int', 'int'])).toBeUndefined()
27
+ expect(Cursor.decode(btoa('[-1,2]'), ['int', 'int'])).toBeUndefined()
28
+ expect(Cursor.decode(btoa('["x",2]'), ['uint', 'int'])).toBeUndefined()
29
+ expect(Cursor.decode(btoa('["0xnotanaddress",2]'), ['address', 'int'])).toBeUndefined()
30
+ })
31
+
32
+ test('renders injection-safe SQL literals per field kind', () => {
33
+ expect(Cursor.literal(42, 'int')).toBe('42')
34
+ expect(Cursor.literal('1000', 'uint')).toBe('1000')
35
+ expect(Cursor.literal('0xAbC0000000000000000000000000000000000000', 'address')).toBe(
36
+ "'0xabc0000000000000000000000000000000000000'",
37
+ )
38
+ expect(Cursor.literal('2024-01-01T00:00:00Z', 'timestamp')).toBe("'2024-01-01T00:00:00Z'")
39
+ })
40
+
41
+ test('paginates a keyset result, deriving hasMore and nextCursor from the extra row', () => {
42
+ const rows = [
43
+ { block: 10, idx: 2 },
44
+ { block: 10, idx: 1 },
45
+ { block: 9, idx: 0 },
46
+ ]
47
+ // Fetched `limit + 1` rows, so the trailing row signals another page.
48
+ const page = Cursor.paginate({ rows, limit: 2, key: (row) => [row.block, row.idx] })
49
+ expect(page.rows).toEqual([
50
+ { block: 10, idx: 2 },
51
+ { block: 10, idx: 1 },
52
+ ])
53
+ expect(page.hasMore).toBe(true)
54
+ expect(Cursor.decode(page.nextCursor!, ['int', 'int'])).toEqual([10, 1])
55
+ })
56
+
57
+ test('paginate reports the last page with a null nextCursor', () => {
58
+ const rows = [{ block: 9, idx: 0 }]
59
+ const page = Cursor.paginate({ rows, limit: 2, key: (row) => [row.block, row.idx] })
60
+ expect(page.rows).toEqual(rows)
61
+ expect(page.hasMore).toBe(false)
62
+ expect(page.nextCursor).toBeNull()
63
+ })
64
+
65
+ test('paginate yields a null nextCursor when the last row has no sort-key', () => {
66
+ const rows = [{ ok: true }, { ok: true }, { ok: false }]
67
+ const page = Cursor.paginate({ rows, limit: 2, key: (row) => (row.ok ? [1, 2] : undefined) })
68
+ expect(page.hasMore).toBe(true)
69
+ // The boundary row (last of the page) is `{ ok: true }`, so a cursor exists.
70
+ expect(page.nextCursor).not.toBeNull()
71
+ })
72
+
73
+ test('builds a row-value tuple predicate with a prunable first-column bound', () => {
74
+ expect(
75
+ Cursor.keyset([
76
+ { literal: '100', name: 'block_num', order: 'desc' },
77
+ { literal: '3', name: 'idx', order: 'desc' },
78
+ ]),
79
+ ).toBe('(block_num <= 100 AND (block_num, idx) < (100, 3))')
80
+ expect(
81
+ Cursor.keyset([
82
+ { literal: '100', name: 'block_num', order: 'asc' },
83
+ { literal: '3', name: 'idx', order: 'asc' },
84
+ ]),
85
+ ).toBe('(block_num >= 100 AND (block_num, idx) > (100, 3))')
86
+ })
87
+
88
+ test('omits the redundant bound for a single-column keyset', () => {
89
+ expect(Cursor.keyset([{ literal: '100', name: 'num', order: 'desc' }])).toBe('((num) < (100))')
90
+ })
91
+
92
+ test('falls back to an OR expansion for a mixed-direction keyset', () => {
93
+ expect(
94
+ Cursor.keyset([
95
+ { literal: '1000', name: 'balance', order: 'desc' },
96
+ { literal: "'0xabc'", name: 'holder', order: 'asc' },
97
+ ]),
98
+ ).toBe("(balance <= 1000 AND ((balance < 1000) OR (balance = 1000 AND holder > '0xabc')))")
99
+ })
100
+ })