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.
- package/LICENSE +21 -0
- package/README.md +119 -0
- package/dist/ApiKey.d.ts +65 -0
- package/dist/ApiKey.d.ts.map +1 -0
- package/dist/ApiKey.js +105 -0
- package/dist/ApiKey.js.map +1 -0
- package/dist/ApiKeySource.d.ts +166 -0
- package/dist/ApiKeySource.d.ts.map +1 -0
- package/dist/ApiKeySource.js +234 -0
- package/dist/ApiKeySource.js.map +1 -0
- package/dist/App.d.ts +19317 -0
- package/dist/App.d.ts.map +1 -0
- package/dist/App.js +612 -0
- package/dist/App.js.map +1 -0
- package/dist/Assets.d.ts +44 -0
- package/dist/Assets.d.ts.map +1 -0
- package/dist/Assets.js +23 -0
- package/dist/Assets.js.map +1 -0
- package/dist/Auth.d.ts +2 -0
- package/dist/Auth.d.ts.map +1 -0
- package/dist/Auth.js +2 -0
- package/dist/Auth.js.map +1 -0
- package/dist/Cli.d.ts +21 -0
- package/dist/Cli.d.ts.map +1 -0
- package/dist/Cli.js +13 -0
- package/dist/Cli.js.map +1 -0
- package/dist/Client.d.ts +20 -0
- package/dist/Client.d.ts.map +1 -0
- package/dist/Client.js +24 -0
- package/dist/Client.js.map +1 -0
- package/dist/Metrics.d.ts +70 -0
- package/dist/Metrics.d.ts.map +1 -0
- package/dist/Metrics.js +91 -0
- package/dist/Metrics.js.map +1 -0
- package/dist/Scope.d.ts +35 -0
- package/dist/Scope.d.ts.map +1 -0
- package/dist/Scope.js +38 -0
- package/dist/Scope.js.map +1 -0
- package/dist/Webhooks.d.ts +79 -0
- package/dist/Webhooks.d.ts.map +1 -0
- package/dist/Webhooks.js +107 -0
- package/dist/Webhooks.js.map +1 -0
- package/dist/admin/Access.d.ts +41 -0
- package/dist/admin/Access.d.ts.map +1 -0
- package/dist/admin/Access.js +75 -0
- package/dist/admin/Access.js.map +1 -0
- package/dist/admin/App.d.ts +1221 -0
- package/dist/admin/App.d.ts.map +1 -0
- package/dist/admin/App.js +113 -0
- package/dist/admin/App.js.map +1 -0
- package/dist/admin/apps/api-keys.d.ts +209 -0
- package/dist/admin/apps/api-keys.d.ts.map +1 -0
- package/dist/admin/apps/api-keys.js +129 -0
- package/dist/admin/apps/api-keys.js.map +1 -0
- package/dist/admin/apps/scopes.d.ts +46 -0
- package/dist/admin/apps/scopes.d.ts.map +1 -0
- package/dist/admin/apps/scopes.js +41 -0
- package/dist/admin/apps/scopes.js.map +1 -0
- package/dist/admin/apps/verified-tokens.d.ts +1206 -0
- package/dist/admin/apps/verified-tokens.d.ts.map +1 -0
- package/dist/admin/apps/verified-tokens.js +485 -0
- package/dist/admin/apps/verified-tokens.js.map +1 -0
- package/dist/admin/index.d.ts +3 -0
- package/dist/admin/index.d.ts.map +1 -0
- package/dist/admin/index.js +3 -0
- package/dist/admin/index.js.map +1 -0
- package/dist/admin/ui.generated.d.ts +11 -0
- package/dist/admin/ui.generated.d.ts.map +1 -0
- package/dist/admin/ui.generated.js +43 -0
- package/dist/admin/ui.generated.js.map +1 -0
- package/dist/apps/activities.d.ts +18725 -0
- package/dist/apps/activities.d.ts.map +1 -0
- package/dist/apps/activities.js +3138 -0
- package/dist/apps/activities.js.map +1 -0
- package/dist/apps/addresses.d.ts +3662 -0
- package/dist/apps/addresses.d.ts.map +1 -0
- package/dist/apps/addresses.js +22 -0
- package/dist/apps/addresses.js.map +1 -0
- package/dist/apps/balances.d.ts +342 -0
- package/dist/apps/balances.d.ts.map +1 -0
- package/dist/apps/balances.js +393 -0
- package/dist/apps/balances.js.map +1 -0
- package/dist/apps/blocks.d.ts +931 -0
- package/dist/apps/blocks.d.ts.map +1 -0
- package/dist/apps/blocks.js +689 -0
- package/dist/apps/blocks.js.map +1 -0
- package/dist/apps/coingecko.d.ts +1053 -0
- package/dist/apps/coingecko.d.ts.map +1 -0
- package/dist/apps/coingecko.js +1015 -0
- package/dist/apps/coingecko.js.map +1 -0
- package/dist/apps/exchanges.d.ts +3000 -0
- package/dist/apps/exchanges.d.ts.map +1 -0
- package/dist/apps/exchanges.js +3570 -0
- package/dist/apps/exchanges.js.map +1 -0
- package/dist/apps/fee-amm.d.ts +640 -0
- package/dist/apps/fee-amm.d.ts.map +1 -0
- package/dist/apps/fee-amm.js +815 -0
- package/dist/apps/fee-amm.js.map +1 -0
- package/dist/apps/indexer.d.ts +116 -0
- package/dist/apps/indexer.d.ts.map +1 -0
- package/dist/apps/indexer.js +270 -0
- package/dist/apps/indexer.js.map +1 -0
- package/dist/apps/receipts.d.ts +1438 -0
- package/dist/apps/receipts.d.ts.map +1 -0
- package/dist/apps/receipts.js +874 -0
- package/dist/apps/receipts.js.map +1 -0
- package/dist/apps/rpc.d.ts +173 -0
- package/dist/apps/rpc.d.ts.map +1 -0
- package/dist/apps/rpc.js +191 -0
- package/dist/apps/rpc.js.map +1 -0
- package/dist/apps/tokenlist.d.ts +192 -0
- package/dist/apps/tokenlist.d.ts.map +1 -0
- package/dist/apps/tokenlist.js +153 -0
- package/dist/apps/tokenlist.js.map +1 -0
- package/dist/apps/tokens.d.ts +2246 -0
- package/dist/apps/tokens.d.ts.map +1 -0
- package/dist/apps/tokens.js +1789 -0
- package/dist/apps/tokens.js.map +1 -0
- package/dist/apps/transactions.d.ts +2327 -0
- package/dist/apps/transactions.d.ts.map +1 -0
- package/dist/apps/transactions.js +975 -0
- package/dist/apps/transactions.js.map +1 -0
- package/dist/apps/transfers.d.ts +589 -0
- package/dist/apps/transfers.d.ts.map +1 -0
- package/dist/apps/transfers.js +769 -0
- package/dist/apps/transfers.js.map +1 -0
- package/dist/apps/verified-tokens.d.ts +544 -0
- package/dist/apps/verified-tokens.d.ts.map +1 -0
- package/dist/apps/verified-tokens.js +234 -0
- package/dist/apps/verified-tokens.js.map +1 -0
- package/dist/apps/webhooks.d.ts +7684 -0
- package/dist/apps/webhooks.d.ts.map +1 -0
- package/dist/apps/webhooks.js +2112 -0
- package/dist/apps/webhooks.js.map +1 -0
- package/dist/cloudflare.d.ts +32 -0
- package/dist/cloudflare.d.ts.map +1 -0
- package/dist/cloudflare.js +45 -0
- package/dist/cloudflare.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/Auth.d.ts +298 -0
- package/dist/internal/Auth.d.ts.map +1 -0
- package/dist/internal/Auth.js +618 -0
- package/dist/internal/Auth.js.map +1 -0
- package/dist/internal/Cache.d.ts +74 -0
- package/dist/internal/Cache.d.ts.map +1 -0
- package/dist/internal/Cache.js +215 -0
- package/dist/internal/Cache.js.map +1 -0
- package/dist/internal/Cursor.d.ts +79 -0
- package/dist/internal/Cursor.d.ts.map +1 -0
- package/dist/internal/Cursor.js +116 -0
- package/dist/internal/Cursor.js.map +1 -0
- package/dist/internal/EdgeCache.d.ts +59 -0
- package/dist/internal/EdgeCache.d.ts.map +1 -0
- package/dist/internal/EdgeCache.js +174 -0
- package/dist/internal/EdgeCache.js.map +1 -0
- package/dist/internal/Log.d.ts +104 -0
- package/dist/internal/Log.d.ts.map +1 -0
- package/dist/internal/Log.js +118 -0
- package/dist/internal/Log.js.map +1 -0
- package/dist/internal/MetricSink.d.ts +65 -0
- package/dist/internal/MetricSink.d.ts.map +1 -0
- package/dist/internal/MetricSink.js +146 -0
- package/dist/internal/MetricSink.js.map +1 -0
- package/dist/internal/Mpp.d.ts +71 -0
- package/dist/internal/Mpp.d.ts.map +1 -0
- package/dist/internal/Mpp.js +105 -0
- package/dist/internal/Mpp.js.map +1 -0
- package/dist/internal/OpenApi.d.ts +313 -0
- package/dist/internal/OpenApi.d.ts.map +1 -0
- package/dist/internal/OpenApi.fonts.d.ts +2 -0
- package/dist/internal/OpenApi.fonts.d.ts.map +1 -0
- package/dist/internal/OpenApi.fonts.js +39 -0
- package/dist/internal/OpenApi.fonts.js.map +1 -0
- package/dist/internal/OpenApi.js +335 -0
- package/dist/internal/OpenApi.js.map +1 -0
- package/dist/internal/OpenApi.logo.d.ts +6 -0
- package/dist/internal/OpenApi.logo.d.ts.map +1 -0
- package/dist/internal/OpenApi.logo.js +19 -0
- package/dist/internal/OpenApi.logo.js.map +1 -0
- package/dist/internal/Path.d.ts +5 -0
- package/dist/internal/Path.d.ts.map +1 -0
- package/dist/internal/Path.js +16 -0
- package/dist/internal/Path.js.map +1 -0
- package/dist/internal/RateLimit.d.ts +60 -0
- package/dist/internal/RateLimit.d.ts.map +1 -0
- package/dist/internal/RateLimit.js +48 -0
- package/dist/internal/RateLimit.js.map +1 -0
- package/dist/internal/Response.d.ts +132 -0
- package/dist/internal/Response.d.ts.map +1 -0
- package/dist/internal/Response.js +72 -0
- package/dist/internal/Response.js.map +1 -0
- package/dist/internal/Schema.d.ts +257 -0
- package/dist/internal/Schema.d.ts.map +1 -0
- package/dist/internal/Schema.js +360 -0
- package/dist/internal/Schema.js.map +1 -0
- package/dist/internal/Store.d.ts +204 -0
- package/dist/internal/Store.d.ts.map +1 -0
- package/dist/internal/Store.js +291 -0
- package/dist/internal/Store.js.map +1 -0
- package/dist/internal/Tidx.d.ts +120 -0
- package/dist/internal/Tidx.d.ts.map +1 -0
- package/dist/internal/Tidx.js +156 -0
- package/dist/internal/Tidx.js.map +1 -0
- package/dist/internal/Timing.d.ts +22 -0
- package/dist/internal/Timing.d.ts.map +1 -0
- package/dist/internal/Timing.js +32 -0
- package/dist/internal/Timing.js.map +1 -0
- package/dist/internal/Ttl.d.ts +25 -0
- package/dist/internal/Ttl.d.ts.map +1 -0
- package/dist/internal/Ttl.js +49 -0
- package/dist/internal/Ttl.js.map +1 -0
- package/dist/internal/Value.d.ts +47 -0
- package/dist/internal/Value.d.ts.map +1 -0
- package/dist/internal/Value.js +122 -0
- package/dist/internal/Value.js.map +1 -0
- package/dist/internal/VerifiedTokens.d.ts +228 -0
- package/dist/internal/VerifiedTokens.d.ts.map +1 -0
- package/dist/internal/VerifiedTokens.js +450 -0
- package/dist/internal/VerifiedTokens.js.map +1 -0
- package/dist/internal/Viem.d.ts +2082 -0
- package/dist/internal/Viem.d.ts.map +1 -0
- package/dist/internal/Viem.js +63 -0
- package/dist/internal/Viem.js.map +1 -0
- package/dist/internal/WebhookDestination.d.ts +198 -0
- package/dist/internal/WebhookDestination.d.ts.map +1 -0
- package/dist/internal/WebhookDestination.js +170 -0
- package/dist/internal/WebhookDestination.js.map +1 -0
- package/dist/internal/Webhooks.d.ts +433 -0
- package/dist/internal/Webhooks.d.ts.map +1 -0
- package/dist/internal/Webhooks.js +489 -0
- package/dist/internal/Webhooks.js.map +1 -0
- package/dist/internal/webhookDestinations/betterStack.d.ts +26 -0
- package/dist/internal/webhookDestinations/betterStack.d.ts.map +1 -0
- package/dist/internal/webhookDestinations/betterStack.js +76 -0
- package/dist/internal/webhookDestinations/betterStack.js.map +1 -0
- package/dist/internal/webhookDestinations/slack.d.ts +59 -0
- package/dist/internal/webhookDestinations/slack.d.ts.map +1 -0
- package/dist/internal/webhookDestinations/slack.js +422 -0
- package/dist/internal/webhookDestinations/slack.js.map +1 -0
- package/dist/internal/webhookDestinations/url.d.ts +17 -0
- package/dist/internal/webhookDestinations/url.d.ts.map +1 -0
- package/dist/internal/webhookDestinations/url.js +130 -0
- package/dist/internal/webhookDestinations/url.js.map +1 -0
- package/dist/openrpc.json +5302 -0
- package/package.json +46 -4
- package/seeds/31318/icons/0x20c0000000000000000000000000000000000000.svg +1 -0
- package/seeds/31318/icons/0x20c0000000000000000000000000000000000001.svg +1 -0
- package/seeds/31318/icons/0x20c0000000000000000000000000000000000002.svg +1 -0
- package/seeds/31318/icons/0x20c0000000000000000000000000000000000003.svg +1 -0
- package/seeds/31318/verified-tokens.json +30 -0
- package/seeds/4217/icons/0x20c0000000000000000000000000000000000000.svg +25 -0
- package/seeds/4217/icons/0x20c000000000000000000000048c8f36df1c9a4a.svg +1 -0
- package/seeds/4217/icons/0x20c0000000000000000000000520792dcccccccc.svg +1 -0
- package/seeds/4217/icons/0x20c0000000000000000000000a6da882d075a4c3.svg +4 -0
- package/seeds/4217/icons/0x20c000000000000000000000111111111e910f0f.svg +30 -0
- package/seeds/4217/icons/0x20c00000000000000000000014f22ca97301eb73.svg +1 -0
- package/seeds/4217/icons/0x20c0000000000000000000001621e21f71cf12fb.svg +1 -0
- package/seeds/4217/icons/0x20c0000000000000000000002f52d5cc21a3207b.svg +1 -0
- package/seeds/4217/icons/0x20c0000000000000000000003158081efd85bfc2.svg +11 -0
- package/seeds/4217/icons/0x20c0000000000000000000003554d28269e0f3c2.svg +1 -0
- package/seeds/4217/icons/0x20c000000000000000000000383a23bacb546ab9.svg +1 -0
- package/seeds/4217/icons/0x20c00000000000000000000042109aef2f8b28e1.png +0 -0
- package/seeds/4217/icons/0x20c0000000000000000000005c0bac7cef389a11.svg +1 -0
- package/seeds/4217/icons/0x20c0000000000000000000006fd9a167923ba194.png +0 -0
- package/seeds/4217/icons/0x20c0000000000000000000007f7ba549dd0251b9.svg +1 -0
- package/seeds/4217/icons/0x20c0000000000000000000008191667423f70e67.svg +5 -0
- package/seeds/4217/icons/0x20c0000000000000000000008ee4fcff88888888.svg +1 -0
- package/seeds/4217/icons/0x20c0000000000000000000009a4a4b17e0dc6651.svg +1 -0
- package/seeds/4217/icons/0x20c000000000000000000000ab02d39df30bd17e.svg +1 -0
- package/seeds/4217/icons/0x20c000000000000000000000ae247a1130450f09.svg +1 -0
- package/seeds/4217/icons/0x20c000000000000000000000aeed2ec36a54d0e5.svg +1 -0
- package/seeds/4217/icons/0x20c000000000000000000000b9537d11c60e8b50.svg +1 -0
- package/seeds/4217/icons/0x20c000000000000000000000bd95bfb69fbe6ce3.svg +1 -0
- package/seeds/4217/icons/0x20c000000000000000000000c412ec89d0c08be5.svg +1 -0
- package/seeds/4217/icons/0x20c000000000000000000000f047dd7018e50367.png +0 -0
- package/seeds/4217/verified-tokens.json +177 -0
- package/seeds/42431/icons/0x20c0000000000000000000000000000000000000.svg +25 -0
- package/seeds/42431/icons/0x20c0000000000000000000000000000000000001.svg +1 -0
- package/seeds/42431/icons/0x20c0000000000000000000000000000000000002.svg +1 -0
- package/seeds/42431/icons/0x20c0000000000000000000000000000000000003.svg +1 -0
- package/seeds/42431/icons/0x20c0000000000000000000009e8d7eb59b783726.svg +1 -0
- package/seeds/42431/icons/0x20c000000000000000000000d72572838bbee59c.svg +1 -0
- package/seeds/42431/verified-tokens.json +44 -0
- package/seeds/openrpc/README.md +27 -0
- package/seeds/openrpc/execution-apis.json +8989 -0
- package/src/ApiKey.test.ts +91 -0
- package/src/ApiKey.ts +122 -0
- package/src/ApiKeySource.test-d.ts +11 -0
- package/src/ApiKeySource.test.ts +295 -0
- package/src/ApiKeySource.ts +303 -0
- package/src/App.test-d.ts +38 -0
- package/src/App.test.ts +65927 -0
- package/src/App.ts +855 -0
- package/src/Assets.ts +58 -0
- package/src/Auth.ts +1 -0
- package/src/Cli.test.ts +81 -0
- package/src/Cli.ts +38 -0
- package/src/Client.test-d.ts +1438 -0
- package/src/Client.test.ts +134 -0
- package/src/Client.ts +45 -0
- package/src/Metrics.ts +154 -0
- package/src/Scope.ts +50 -0
- package/src/Webhooks.test.ts +49 -0
- package/src/Webhooks.ts +172 -0
- package/src/admin/Access.test.ts +130 -0
- package/src/admin/Access.ts +87 -0
- package/src/admin/App.test.ts +225 -0
- package/src/admin/App.ts +216 -0
- package/src/admin/apps/api-keys.test.ts +156 -0
- package/src/admin/apps/api-keys.ts +157 -0
- package/src/admin/apps/scopes.test.ts +52 -0
- package/src/admin/apps/scopes.ts +47 -0
- package/src/admin/apps/verified-tokens.test.ts +415 -0
- package/src/admin/apps/verified-tokens.ts +566 -0
- package/src/admin/index.ts +2 -0
- package/src/admin/ui/plugin.ts +135 -0
- package/src/admin/ui/src/components/Checkbox.tsx +22 -0
- package/src/admin/ui/src/components/Dialog.tsx +32 -0
- package/src/admin/ui/src/components/MobileProfile.tsx +31 -0
- package/src/admin/ui/src/components/Page.tsx +53 -0
- package/src/admin/ui/src/components/TempoLogo.tsx +37 -0
- package/src/admin/ui/src/fonts/Pilat-Demi.woff2 +0 -0
- package/src/admin/ui/src/fonts/Pilat-Regular.woff2 +0 -0
- package/src/admin/ui/src/lib/api-keys.ts +63 -0
- package/src/admin/ui/src/lib/client.ts +15 -0
- package/src/admin/ui/src/lib/config.ts +57 -0
- package/src/admin/ui/src/lib/query-client.ts +26 -0
- package/src/admin/ui/src/lib/scopes.ts +25 -0
- package/src/admin/ui/src/lib/verified-tokens.ts +89 -0
- package/src/admin/ui/src/lib/wagmi.ts +18 -0
- package/src/admin/ui/src/routeTree.gen.ts +104 -0
- package/src/admin/ui/src/router.tsx +25 -0
- package/src/admin/ui/src/routes/__root.tsx +219 -0
- package/src/admin/ui/src/routes/api-keys.tsx +329 -0
- package/src/admin/ui/src/routes/index.tsx +7 -0
- package/src/admin/ui/src/routes/verified-tokens.tsx +331 -0
- package/src/admin/ui/src/styles.css +20 -0
- package/src/admin/ui/tsconfig.json +16 -0
- package/src/admin/ui/vite.config.ts +29 -0
- package/src/admin/ui.generated.ts +60 -0
- package/src/apps/activities.test.ts +1020 -0
- package/src/apps/activities.ts +3934 -0
- package/src/apps/addresses.ts +24 -0
- package/src/apps/balances.test.ts +274 -0
- package/src/apps/balances.ts +533 -0
- package/src/apps/blocks.test.ts +385 -0
- package/src/apps/blocks.ts +896 -0
- package/src/apps/coingecko.test.ts +218 -0
- package/src/apps/coingecko.ts +1383 -0
- package/src/apps/exchanges.test.ts +958 -0
- package/src/apps/exchanges.ts +4773 -0
- package/src/apps/fee-amm.test.ts +321 -0
- package/src/apps/fee-amm.ts +1121 -0
- package/src/apps/indexer.test.ts +153 -0
- package/src/apps/indexer.ts +330 -0
- package/src/apps/receipts.test.ts +285 -0
- package/src/apps/receipts.ts +1193 -0
- package/src/apps/rpc.test.ts +302 -0
- package/src/apps/rpc.ts +222 -0
- package/src/apps/tokenlist.test.ts +80 -0
- package/src/apps/tokenlist.ts +207 -0
- package/src/apps/tokens.test.ts +1062 -0
- package/src/apps/tokens.ts +2292 -0
- package/src/apps/transactions.test.ts +408 -0
- package/src/apps/transactions.ts +1363 -0
- package/src/apps/transfers.test.ts +371 -0
- package/src/apps/transfers.ts +1116 -0
- package/src/apps/verified-tokens.test.ts +114 -0
- package/src/apps/verified-tokens.ts +295 -0
- package/src/apps/webhooks.test.ts +1937 -0
- package/src/apps/webhooks.ts +2926 -0
- package/src/cloudflare.ts +55 -0
- package/src/index.ts +15 -0
- package/src/internal/Auth.test.ts +980 -0
- package/src/internal/Auth.ts +1012 -0
- package/src/internal/Cache.test.ts +240 -0
- package/src/internal/Cache.ts +262 -0
- package/src/internal/Cursor.test.ts +100 -0
- package/src/internal/Cursor.ts +149 -0
- package/src/internal/EdgeCache.test.ts +234 -0
- package/src/internal/EdgeCache.ts +224 -0
- package/src/internal/Errors.ts +14 -0
- package/src/internal/Log.test.ts +244 -0
- package/src/internal/Log.ts +216 -0
- package/src/internal/MetricSink.ts +220 -0
- package/src/internal/Mpp.test.ts +64 -0
- package/src/internal/Mpp.ts +141 -0
- package/src/internal/OpenApi.fonts.ts +38 -0
- package/src/internal/OpenApi.logo.ts +18 -0
- package/src/internal/OpenApi.ts +488 -0
- package/src/internal/Path.test.ts +14 -0
- package/src/internal/Path.ts +15 -0
- package/src/internal/RateLimit.test.ts +93 -0
- package/src/internal/RateLimit.ts +93 -0
- package/src/internal/Response.ts +131 -0
- package/src/internal/Schema.test.ts +34 -0
- package/src/internal/Schema.ts +523 -0
- package/src/internal/Store.test.ts +545 -0
- package/src/internal/Store.ts +526 -0
- package/src/internal/Tidx.local.test.ts +33 -0
- package/src/internal/Tidx.test.ts +294 -0
- package/src/internal/Tidx.ts +238 -0
- package/src/internal/Timing.test.ts +22 -0
- package/src/internal/Timing.ts +62 -0
- package/src/internal/Ttl.test.ts +18 -0
- package/src/internal/Ttl.ts +59 -0
- package/src/internal/Value.ts +114 -0
- package/src/internal/VerifiedTokens.test.ts +333 -0
- package/src/internal/VerifiedTokens.ts +645 -0
- package/src/internal/Viem.test.ts +115 -0
- package/src/internal/Viem.ts +165 -0
- package/src/internal/WebhookDestination.test.ts +648 -0
- package/src/internal/WebhookDestination.ts +331 -0
- package/src/internal/Webhooks.test.ts +668 -0
- package/src/internal/Webhooks.ts +940 -0
- package/src/internal/webhookDestinations/betterStack.ts +91 -0
- package/src/internal/webhookDestinations/slack.ts +496 -0
- package/src/internal/webhookDestinations/url.ts +128 -0
- package/src/openrpc.json +5302 -0
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import { Hono } from 'hono'
|
|
2
|
+
import { requestId } from 'hono/request-id'
|
|
3
|
+
|
|
4
|
+
import * as Log from './Log.js'
|
|
5
|
+
import * as Response from './Response.js'
|
|
6
|
+
import * as Timing from './Timing.js'
|
|
7
|
+
|
|
8
|
+
function app(options: { emit: Log.Emit }) {
|
|
9
|
+
const app = new Hono<Log.Environment>()
|
|
10
|
+
app.use('*', requestId())
|
|
11
|
+
app.use('*', Log.middleware(options))
|
|
12
|
+
app.use('*', Timing.middleware())
|
|
13
|
+
return app
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function stable(entry: Log.Entry) {
|
|
17
|
+
const { duration: _duration, requestId: _requestId, timings: _timings, ...rest } = entry
|
|
18
|
+
return rest
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
describe('middleware', () => {
|
|
22
|
+
test('emits one canonical entry per request', async () => {
|
|
23
|
+
const entries: Log.Entry[] = []
|
|
24
|
+
const api = app({ emit: (entry) => void entries.push(entry) })
|
|
25
|
+
api.get('/things/:id', async (c) => {
|
|
26
|
+
await Timing.time(c, 'rpc.example', () => Promise.resolve())
|
|
27
|
+
return c.json({ ok: true }, 200)
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
await api.request('/things/42?chainId=4217')
|
|
31
|
+
|
|
32
|
+
expect(entries.length).toMatchInlineSnapshot(`1`)
|
|
33
|
+
const entry = entries[0]!
|
|
34
|
+
expect(typeof entry.duration).toMatchInlineSnapshot(`"number"`)
|
|
35
|
+
expect(typeof entry.requestId).toMatchInlineSnapshot(`"string"`)
|
|
36
|
+
expect(Object.keys(entry.timings ?? {})).toMatchInlineSnapshot(`
|
|
37
|
+
[
|
|
38
|
+
"rpc.example",
|
|
39
|
+
]
|
|
40
|
+
`)
|
|
41
|
+
expect(stable(entry)).toMatchInlineSnapshot(`
|
|
42
|
+
{
|
|
43
|
+
"level": "info",
|
|
44
|
+
"method": "GET",
|
|
45
|
+
"path": "/things/42",
|
|
46
|
+
"query": "chainId=4217",
|
|
47
|
+
"route": "/things/:id",
|
|
48
|
+
"status": 200,
|
|
49
|
+
}
|
|
50
|
+
`)
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
test('includes the caller principal, never the raw token', async () => {
|
|
54
|
+
const entries: Log.Entry[] = []
|
|
55
|
+
const api = app({ emit: (entry) => void entries.push(entry) })
|
|
56
|
+
api.use('*', async (c, next) => {
|
|
57
|
+
c.set('principal', {
|
|
58
|
+
apiKey: { environment: 'production', id: 'key_1', orgId: 'org_1', scopes: ['data:read'] },
|
|
59
|
+
environment: 'production',
|
|
60
|
+
id: 'key_1',
|
|
61
|
+
orgId: 'org_1',
|
|
62
|
+
type: 'api_key',
|
|
63
|
+
})
|
|
64
|
+
await next()
|
|
65
|
+
})
|
|
66
|
+
api.get('/secure', (c) => c.json({ ok: true }, 200))
|
|
67
|
+
|
|
68
|
+
const token = `tempo:sk:${'a'.repeat(48)}`
|
|
69
|
+
await api.request(`/secure?apiKey=${token}`)
|
|
70
|
+
|
|
71
|
+
expect(stable(entries[0]!)).toMatchInlineSnapshot(`
|
|
72
|
+
{
|
|
73
|
+
"level": "info",
|
|
74
|
+
"method": "GET",
|
|
75
|
+
"path": "/secure",
|
|
76
|
+
"principal": {
|
|
77
|
+
"environment": "production",
|
|
78
|
+
"id": "key_1",
|
|
79
|
+
"orgId": "org_1",
|
|
80
|
+
"type": "api_key",
|
|
81
|
+
},
|
|
82
|
+
"query": "apiKey=tempo:sk:…",
|
|
83
|
+
"route": "/secure",
|
|
84
|
+
"status": 200,
|
|
85
|
+
}
|
|
86
|
+
`)
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
test('includes the MPP payment reason for paid calls', async () => {
|
|
90
|
+
const entries: Log.Entry[] = []
|
|
91
|
+
const api = app({ emit: (entry) => void entries.push(entry) })
|
|
92
|
+
api.use('*', async (c, next) => {
|
|
93
|
+
c.set('principal', {
|
|
94
|
+
id: 'ip:203.0.113.7',
|
|
95
|
+
payment: { payer: '0xpayer', reason: 'public_over_quota', type: 'mpp' },
|
|
96
|
+
type: 'public',
|
|
97
|
+
})
|
|
98
|
+
await next()
|
|
99
|
+
})
|
|
100
|
+
api.get('/paid', (c) => c.json({ ok: true }, 200))
|
|
101
|
+
|
|
102
|
+
await api.request('/paid')
|
|
103
|
+
|
|
104
|
+
expect(stable(entries[0]!)).toMatchInlineSnapshot(`
|
|
105
|
+
{
|
|
106
|
+
"level": "info",
|
|
107
|
+
"method": "GET",
|
|
108
|
+
"path": "/paid",
|
|
109
|
+
"payment": "public_over_quota",
|
|
110
|
+
"principal": {
|
|
111
|
+
"id": "ip:203.0.113.7",
|
|
112
|
+
"type": "public",
|
|
113
|
+
},
|
|
114
|
+
"route": "/paid",
|
|
115
|
+
"status": 200,
|
|
116
|
+
}
|
|
117
|
+
`)
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
test('records the error code and warn level for error envelopes', async () => {
|
|
121
|
+
const entries: Log.Entry[] = []
|
|
122
|
+
const api = app({ emit: (entry) => void entries.push(entry) })
|
|
123
|
+
api.get('/missing', (c) =>
|
|
124
|
+
Response.error(c, { code: 'not_found', message: 'Route not found', status: 404 }),
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
await api.request('/missing')
|
|
128
|
+
|
|
129
|
+
expect(stable(entries[0]!)).toMatchInlineSnapshot(`
|
|
130
|
+
{
|
|
131
|
+
"errorCode": "not_found",
|
|
132
|
+
"level": "warn",
|
|
133
|
+
"method": "GET",
|
|
134
|
+
"path": "/missing",
|
|
135
|
+
"route": "/missing",
|
|
136
|
+
"status": 404,
|
|
137
|
+
}
|
|
138
|
+
`)
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
test('logs thrown errors at error level', async () => {
|
|
142
|
+
const entries: Log.Entry[] = []
|
|
143
|
+
const api = app({ emit: (entry) => void entries.push(entry) })
|
|
144
|
+
api.onError((_, c) => c.json({ error: true }, 500))
|
|
145
|
+
api.get('/boom', () => {
|
|
146
|
+
throw new Error('boom')
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
await api.request('/boom')
|
|
150
|
+
|
|
151
|
+
expect(stable(entries[0]!)).toMatchInlineSnapshot(`
|
|
152
|
+
{
|
|
153
|
+
"level": "error",
|
|
154
|
+
"method": "GET",
|
|
155
|
+
"path": "/boom",
|
|
156
|
+
"route": "/boom",
|
|
157
|
+
"status": 500,
|
|
158
|
+
}
|
|
159
|
+
`)
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
test('parses rate limit state from response headers', async () => {
|
|
163
|
+
const entries: Log.Entry[] = []
|
|
164
|
+
const api = app({ emit: (entry) => void entries.push(entry) })
|
|
165
|
+
api.get('/limited', (c) => {
|
|
166
|
+
c.header('RateLimit-Limit', '100')
|
|
167
|
+
c.header('RateLimit-Remaining', '37')
|
|
168
|
+
c.header('RateLimit-Reset', '1710000000')
|
|
169
|
+
c.header('RateLimit-Scope', 'data:read')
|
|
170
|
+
return c.json({ ok: true }, 200)
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
await api.request('/limited')
|
|
174
|
+
|
|
175
|
+
expect(stable(entries[0]!)).toMatchInlineSnapshot(`
|
|
176
|
+
{
|
|
177
|
+
"level": "info",
|
|
178
|
+
"method": "GET",
|
|
179
|
+
"path": "/limited",
|
|
180
|
+
"rateLimit": {
|
|
181
|
+
"limit": 100,
|
|
182
|
+
"remaining": 37,
|
|
183
|
+
"reset": 1710000000,
|
|
184
|
+
"scope": "data:read",
|
|
185
|
+
},
|
|
186
|
+
"route": "/limited",
|
|
187
|
+
"status": 200,
|
|
188
|
+
}
|
|
189
|
+
`)
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
test('marks edge-cache hits', async () => {
|
|
193
|
+
const entries: Log.Entry[] = []
|
|
194
|
+
const api = app({ emit: (entry) => void entries.push(entry) })
|
|
195
|
+
// Simulate the edge-cache layer answering before routing runs.
|
|
196
|
+
api.use('*', async (c) => {
|
|
197
|
+
c.set('edgeCacheStatus', 'hit')
|
|
198
|
+
return c.json({ cached: true }, 200)
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
await api.request('/anything')
|
|
202
|
+
|
|
203
|
+
expect(stable(entries[0]!)).toMatchInlineSnapshot(`
|
|
204
|
+
{
|
|
205
|
+
"cache": "hit",
|
|
206
|
+
"level": "info",
|
|
207
|
+
"method": "GET",
|
|
208
|
+
"path": "/anything",
|
|
209
|
+
"route": "/*",
|
|
210
|
+
"status": 200,
|
|
211
|
+
}
|
|
212
|
+
`)
|
|
213
|
+
})
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
describe('emit', () => {
|
|
217
|
+
test('routes entries to the level-matched console method', () => {
|
|
218
|
+
const error = vi.spyOn(console, 'error').mockImplementation(() => {})
|
|
219
|
+
const log = vi.spyOn(console, 'log').mockImplementation(() => {})
|
|
220
|
+
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {})
|
|
221
|
+
try {
|
|
222
|
+
const base = {
|
|
223
|
+
duration: 1,
|
|
224
|
+
method: 'GET',
|
|
225
|
+
path: '/',
|
|
226
|
+
requestId: 'req',
|
|
227
|
+
route: '/',
|
|
228
|
+
} as const
|
|
229
|
+
|
|
230
|
+
Log.emit({ ...base, level: 'info', status: 200 })
|
|
231
|
+
Log.emit({ ...base, level: 'warn', status: 404 })
|
|
232
|
+
Log.emit({ ...base, level: 'error', status: 500 })
|
|
233
|
+
|
|
234
|
+
expect(log.mock.calls.length).toMatchInlineSnapshot(`1`)
|
|
235
|
+
expect(warn.mock.calls.length).toMatchInlineSnapshot(`1`)
|
|
236
|
+
expect(error.mock.calls.length).toMatchInlineSnapshot(`1`)
|
|
237
|
+
expect(log.mock.calls[0]?.[0]).toMatchObject({ level: 'info', status: 200 })
|
|
238
|
+
} finally {
|
|
239
|
+
error.mockRestore()
|
|
240
|
+
log.mockRestore()
|
|
241
|
+
warn.mockRestore()
|
|
242
|
+
}
|
|
243
|
+
})
|
|
244
|
+
})
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import type { MiddlewareHandler } from 'hono'
|
|
2
|
+
import type { RequestIdVariables } from 'hono/request-id'
|
|
3
|
+
|
|
4
|
+
import * as ApiKey from '../ApiKey.js'
|
|
5
|
+
import type * as Auth from './Auth.js'
|
|
6
|
+
import type * as EdgeCache from './EdgeCache.js'
|
|
7
|
+
import type * as Timing from './Timing.js'
|
|
8
|
+
|
|
9
|
+
/** Hono variables read by the request logger and written by other layers. */
|
|
10
|
+
export type Variables = {
|
|
11
|
+
/** Stable error code recorded by `Response.error` for the request log entry. */
|
|
12
|
+
errorCode?: string | undefined
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Hono environment shape used by the request logger. */
|
|
16
|
+
export type Environment = {
|
|
17
|
+
Variables: Variables &
|
|
18
|
+
EdgeCache.Variables &
|
|
19
|
+
RequestIdVariables &
|
|
20
|
+
Timing.Variables & {
|
|
21
|
+
/** Authenticated or anonymous caller, set by auth middleware. */
|
|
22
|
+
principal?: Auth.Principal | undefined
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Log level, derived from the response status. */
|
|
27
|
+
export type Level = 'error' | 'info' | 'warn'
|
|
28
|
+
|
|
29
|
+
/** One canonical request log entry. */
|
|
30
|
+
export type Entry = {
|
|
31
|
+
/** `'hit'` when the edge response cache served the request without reaching the origin handlers. */
|
|
32
|
+
cache?: 'hit' | undefined
|
|
33
|
+
/** Total request duration in milliseconds. */
|
|
34
|
+
duration: number
|
|
35
|
+
/** Stable error code when the response is a JSON error envelope (e.g. `not_found`, `api_key_invalid`). */
|
|
36
|
+
errorCode?: string | undefined
|
|
37
|
+
/** Log level derived from the response status: 5xx `error`, 4xx `warn`, else `info`. */
|
|
38
|
+
level: Level
|
|
39
|
+
/** Request method. */
|
|
40
|
+
method: string
|
|
41
|
+
/** Request pathname. */
|
|
42
|
+
path: string
|
|
43
|
+
/** MPP payment reason when the request was paid per call. */
|
|
44
|
+
payment?: Auth.Payment['reason'] | undefined
|
|
45
|
+
/** Caller identity. Raw API-key tokens are never logged. */
|
|
46
|
+
principal?:
|
|
47
|
+
| {
|
|
48
|
+
/** Key environment for API-key callers. */
|
|
49
|
+
environment?: ApiKey.ApiKey['environment'] | undefined
|
|
50
|
+
/** Principal id: the API key id, or the public quota identity. */
|
|
51
|
+
id: string
|
|
52
|
+
/** Owning organization id for API-key callers. */
|
|
53
|
+
orgId?: string | undefined
|
|
54
|
+
/** Principal kind. */
|
|
55
|
+
type: 'api_key' | 'public'
|
|
56
|
+
}
|
|
57
|
+
| undefined
|
|
58
|
+
/** Raw query string (no leading `?`) with API-key tokens redacted, when present. */
|
|
59
|
+
query?: string | undefined
|
|
60
|
+
/** Rate limit state from the `RateLimit-*` response headers, when enforced. */
|
|
61
|
+
rateLimit?:
|
|
62
|
+
| {
|
|
63
|
+
/** Quota for the current window. */
|
|
64
|
+
limit: number
|
|
65
|
+
/** Requests remaining in the current window. */
|
|
66
|
+
remaining: number
|
|
67
|
+
/** Reset timestamp in Unix seconds. */
|
|
68
|
+
reset?: number | undefined
|
|
69
|
+
/** Quota bucket scope, when available. */
|
|
70
|
+
scope?: string | undefined
|
|
71
|
+
}
|
|
72
|
+
| undefined
|
|
73
|
+
/** Request id, also echoed in the `tempo-request-id` response header. */
|
|
74
|
+
requestId: string
|
|
75
|
+
/** Matched handler path (bounded cardinality), e.g. `/v1/tokens/:address`; `/*` when a middleware (e.g. an edge-cache hit) short-circuited routing. */
|
|
76
|
+
route: string
|
|
77
|
+
/** Response status code. */
|
|
78
|
+
status: number
|
|
79
|
+
/** Upstream operation durations in milliseconds, from the request's Server-Timing metrics (e.g. RPC and TIDX calls). */
|
|
80
|
+
timings?: Record<string, number> | undefined
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Sink receiving one {@link Entry} per request. */
|
|
84
|
+
export type Emit = (entry: Entry) => Promise<void> | void
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Emits one canonical structured log entry per request, after the response is
|
|
88
|
+
* written. Register after `requestId()` and before the timing and edge-cache
|
|
89
|
+
* layers so the entry covers edge-cache hits and includes the Server-Timing
|
|
90
|
+
* metrics collected inside the request.
|
|
91
|
+
*
|
|
92
|
+
* The entry derives everything from state other layers already produce —
|
|
93
|
+
* `requestId`, the auth principal, `RateLimit-*` headers, Server-Timing
|
|
94
|
+
* metrics, the `errorCode` recorded by `Response.error`, and the edge-cache
|
|
95
|
+
* hit marker — so handlers never log per-request lines themselves.
|
|
96
|
+
*/
|
|
97
|
+
export function middleware<environment extends Environment = Environment>(
|
|
98
|
+
options: middleware.Options = {},
|
|
99
|
+
): MiddlewareHandler<environment> {
|
|
100
|
+
const fn = options.emit ?? emit
|
|
101
|
+
return async (c, next) => {
|
|
102
|
+
const start = performance.now()
|
|
103
|
+
await next()
|
|
104
|
+
const duration = performance.now() - start
|
|
105
|
+
|
|
106
|
+
const url = new URL(c.req.url)
|
|
107
|
+
const status = c.res.status
|
|
108
|
+
// Hono cannot resolve variable types through the generic `environment`,
|
|
109
|
+
// so shared-variable reads are cast from the environment contract above
|
|
110
|
+
// (the same pattern EdgeCache uses for its cross-layer reads).
|
|
111
|
+
const principal = c.get('principal' as never) as Auth.Principal | undefined
|
|
112
|
+
const errorCode = c.get('errorCode' as never) as string | undefined
|
|
113
|
+
|
|
114
|
+
// The `request` total metric duplicates `duration` (measured here, one
|
|
115
|
+
// layer further out), so only operation timings are kept.
|
|
116
|
+
const metrics = (c.get('serverTiming' as never) as Timing.Metric[] | undefined)?.filter(
|
|
117
|
+
(metric) => metric.name !== 'request',
|
|
118
|
+
)
|
|
119
|
+
const timings = metrics?.length
|
|
120
|
+
? Object.fromEntries(metrics.map((metric) => [metric.name, round(metric.duration)]))
|
|
121
|
+
: undefined
|
|
122
|
+
|
|
123
|
+
const rateLimit = (() => {
|
|
124
|
+
const limit = c.res.headers.get('RateLimit-Limit')
|
|
125
|
+
const remaining = c.res.headers.get('RateLimit-Remaining')
|
|
126
|
+
const reset = c.res.headers.get('RateLimit-Reset')
|
|
127
|
+
const scope = c.res.headers.get('RateLimit-Scope')
|
|
128
|
+
if (limit === null || remaining === null) return undefined
|
|
129
|
+
return {
|
|
130
|
+
limit: Number(limit),
|
|
131
|
+
remaining: Number(remaining),
|
|
132
|
+
...(reset === null ? {} : { reset: Number(reset) }),
|
|
133
|
+
...(scope === null ? {} : { scope }),
|
|
134
|
+
}
|
|
135
|
+
})()
|
|
136
|
+
|
|
137
|
+
const result = fn({
|
|
138
|
+
...(c.get('edgeCacheStatus' as never) === 'hit' ? { cache: 'hit' as const } : {}),
|
|
139
|
+
duration: round(duration),
|
|
140
|
+
...(errorCode === undefined ? {} : { errorCode }),
|
|
141
|
+
level: status >= 500 ? 'error' : status >= 400 ? 'warn' : 'info',
|
|
142
|
+
method: c.req.method,
|
|
143
|
+
path: url.pathname,
|
|
144
|
+
...(principal?.payment === undefined ? {} : { payment: principal.payment.reason }),
|
|
145
|
+
...(principal === undefined
|
|
146
|
+
? {}
|
|
147
|
+
: {
|
|
148
|
+
principal: {
|
|
149
|
+
...(principal.type === 'api_key' ? { environment: principal.environment } : {}),
|
|
150
|
+
id: principal.id,
|
|
151
|
+
...(principal.type === 'api_key' ? { orgId: principal.orgId } : {}),
|
|
152
|
+
type: principal.type,
|
|
153
|
+
},
|
|
154
|
+
}),
|
|
155
|
+
// Keys belong in headers, but a confused client may pass one in the
|
|
156
|
+
// query string; redact defensively before it reaches a log store.
|
|
157
|
+
...(url.search.length > 1 ? { query: ApiKey.redact(url.search.slice(1)) } : {}),
|
|
158
|
+
...(rateLimit === undefined ? {} : { rateLimit }),
|
|
159
|
+
requestId: c.get('requestId' as never) as string,
|
|
160
|
+
route: c.req.routePath,
|
|
161
|
+
status,
|
|
162
|
+
...(timings === undefined ? {} : { timings }),
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
if (result instanceof Promise) {
|
|
166
|
+
const promise = result.catch((error) => console.error('request log sink failed', error))
|
|
167
|
+
// Unit tests and non-Worker runtimes do not always provide an execution
|
|
168
|
+
// context. In Workers, waitUntil keeps async sinks alive without holding
|
|
169
|
+
// the response open; elsewhere the promise is deliberately fire-and-forget.
|
|
170
|
+
try {
|
|
171
|
+
c.executionCtx.waitUntil(promise)
|
|
172
|
+
} catch {
|
|
173
|
+
void promise
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export declare namespace middleware {
|
|
180
|
+
/** Options for the request logger middleware. */
|
|
181
|
+
type Options = {
|
|
182
|
+
/** Log sink. Defaults to {@link emit}: the level-matched console method, passed the entry as a structured object. */
|
|
183
|
+
emit?: Emit | undefined
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Default log sink: writes the entry as a structured object through the
|
|
189
|
+
* console method matching its level. Passing the object (not a pre-serialized
|
|
190
|
+
* JSON string) lets Cloudflare Workers Logs index the fields for filtering
|
|
191
|
+
* and querying; Node prints the same object inline on stdout/stderr.
|
|
192
|
+
*/
|
|
193
|
+
export function emit(entry: Entry) {
|
|
194
|
+
if (entry.level === 'error') console.error(entry)
|
|
195
|
+
else if (entry.level === 'warn') console.warn(entry)
|
|
196
|
+
else console.log(entry)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/** Severity ranking used to filter entries below a minimum level. */
|
|
200
|
+
const severity = { error: 2, info: 0, warn: 1 } as const
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* An {@link Emit} that drops entries below `min` and otherwise delegates to
|
|
204
|
+
* {@link emit}. `'warn'` keeps warn/error (e.g. production, where the request
|
|
205
|
+
* metrics sink carries successful-request volume instead of one info log each).
|
|
206
|
+
*/
|
|
207
|
+
export function withMinLevel(min: Level): Emit {
|
|
208
|
+
return (entry) => {
|
|
209
|
+
if (severity[entry.level] < severity[min]) return
|
|
210
|
+
emit(entry)
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function round(value: number) {
|
|
215
|
+
return Math.round(value * 10) / 10
|
|
216
|
+
}
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import type { Metrics } from '../Metrics.js'
|
|
2
|
+
import type * as Webhooks from '../Webhooks.js'
|
|
3
|
+
import type * as Log from './Log.js'
|
|
4
|
+
|
|
5
|
+
/** Webhook event type, including the synthetic `ping` carried on test envelopes. */
|
|
6
|
+
export type EventType = Webhooks.Envelope['type']
|
|
7
|
+
|
|
8
|
+
/** Where a delivery attempt originated. */
|
|
9
|
+
export type Trigger = 'manual_retry' | 'ping' | 'queue'
|
|
10
|
+
|
|
11
|
+
/** Context for one poller tick that the {@link Webhooks.createPoller.Result} omits. */
|
|
12
|
+
export type TickMeta = {
|
|
13
|
+
/** Chain polled this tick; used when the tick errored before a result existed. */
|
|
14
|
+
chainId?: number | undefined
|
|
15
|
+
/** Wall-clock duration of the tick in ms. */
|
|
16
|
+
durationMs: number
|
|
17
|
+
/** Whether the tick completed. */
|
|
18
|
+
outcome: 'error' | 'ok'
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* A webhook metric event, recorded by the {@link webhooks} sink. The poller
|
|
23
|
+
* emits `webhook:tick`/`webhook:enqueue` and the delivery path emits
|
|
24
|
+
* `webhook:delivery`.
|
|
25
|
+
*/
|
|
26
|
+
export type WebhookEvent =
|
|
27
|
+
| {
|
|
28
|
+
meta: TickMeta
|
|
29
|
+
result: Webhooks.createPoller.Result | undefined
|
|
30
|
+
type: 'webhook:tick'
|
|
31
|
+
}
|
|
32
|
+
| {
|
|
33
|
+
chainId: number
|
|
34
|
+
durationMs: number
|
|
35
|
+
eventType: EventType
|
|
36
|
+
ok: boolean
|
|
37
|
+
type: 'webhook:enqueue'
|
|
38
|
+
}
|
|
39
|
+
| {
|
|
40
|
+
envelope: Webhooks.Envelope
|
|
41
|
+
result: Webhooks.Result
|
|
42
|
+
subscription: Webhooks.Subscription
|
|
43
|
+
trigger: Trigger
|
|
44
|
+
type: 'webhook:delivery'
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Records request (HTTP) metrics for one request-log entry. Built by {@link requests}. */
|
|
48
|
+
export type RequestSink = (entry: Log.Entry) => void
|
|
49
|
+
|
|
50
|
+
/** Records webhook reliability metrics. Built by {@link webhooks}. */
|
|
51
|
+
export type WebhookSink = {
|
|
52
|
+
/** Ships buffered metrics (call once per poller tick / delivery). */
|
|
53
|
+
flush(): void
|
|
54
|
+
/** Records one webhook event. */
|
|
55
|
+
record(event: WebhookEvent): void
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Low-cardinality tags shared by every HTTP metric, derived from a request log entry. */
|
|
59
|
+
type RouteTags = {
|
|
60
|
+
cache: 'hit' | 'miss'
|
|
61
|
+
method: string
|
|
62
|
+
principal_environment: 'none' | 'production' | 'sandbox'
|
|
63
|
+
principal_type: 'api_key' | 'public' | 'unknown'
|
|
64
|
+
route: string
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Builds a request (HTTP) metrics sink over `backend`: emits the bounded Worker
|
|
69
|
+
* metrics derived from one request-log entry, then flushes (one entry == one
|
|
70
|
+
* request).
|
|
71
|
+
*
|
|
72
|
+
* Metric tags deliberately exclude caller ids, organization ids, request ids,
|
|
73
|
+
* raw paths, and IP-derived identities — those belong in logs or analytics, not
|
|
74
|
+
* StatsD-style series.
|
|
75
|
+
*/
|
|
76
|
+
export function requests(backend: Metrics): RequestSink {
|
|
77
|
+
return (entry) => {
|
|
78
|
+
const tags = routeTags(entry)
|
|
79
|
+
|
|
80
|
+
backend.count('http_response_count', 1, {
|
|
81
|
+
...tags,
|
|
82
|
+
...(entry.errorCode === undefined ? {} : { error_code: entry.errorCode }),
|
|
83
|
+
...(entry.payment === undefined ? {} : { payment: entry.payment }),
|
|
84
|
+
status: entry.status,
|
|
85
|
+
})
|
|
86
|
+
backend.histogram('http_response_duration_ms', entry.duration, tags)
|
|
87
|
+
|
|
88
|
+
if (entry.status === 429 || entry.errorCode === 'rate_limit_exceeded')
|
|
89
|
+
backend.count('http_rate_limit_count', 1, {
|
|
90
|
+
...tags,
|
|
91
|
+
rate_limit_scope: entry.rateLimit?.scope ?? 'unknown',
|
|
92
|
+
status: entry.status,
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
for (const [operation, duration] of Object.entries(entry.timings ?? {}))
|
|
96
|
+
backend.histogram('upstream_duration_ms', duration, { ...tags, operation })
|
|
97
|
+
|
|
98
|
+
backend.flush()
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function routeTags(entry: Log.Entry): RouteTags {
|
|
103
|
+
return {
|
|
104
|
+
cache: entry.cache ?? 'miss',
|
|
105
|
+
method: entry.method,
|
|
106
|
+
principal_environment: entry.principal?.environment ?? 'none',
|
|
107
|
+
principal_type: entry.principal?.type ?? 'unknown',
|
|
108
|
+
route: entry.route,
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Bounded attempt-count buckets so the attempt tag never explodes cardinality. */
|
|
113
|
+
type AttemptBucket = '1' | '2' | '3-5' | '6-10' | 'gt10'
|
|
114
|
+
|
|
115
|
+
/** Low-cardinality delivery destination kind, tagged on `webhook:delivery`. */
|
|
116
|
+
type DestinationType = Webhooks.Subscription['destination']['type']
|
|
117
|
+
|
|
118
|
+
/** Low-cardinality delivery outcome class derived from an HTTP status / error. */
|
|
119
|
+
type StatusClass = '2xx' | '4xx' | '5xx' | 'network' | 'timeout' | 'unknown' | 'url_rejected'
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Builds a webhook delivery-reliability sink over `backend`. Tags stay
|
|
123
|
+
* low-cardinality (`chain_id`, `event_type`, `destination_type`, `status_class`,
|
|
124
|
+
* `attempt_bucket`, `outcome`) — never subscription/org/event ids, which belong
|
|
125
|
+
* in logs or analytics, not StatsD-style series.
|
|
126
|
+
*/
|
|
127
|
+
export function webhooks(backend: Metrics): WebhookSink {
|
|
128
|
+
return {
|
|
129
|
+
flush() {
|
|
130
|
+
backend.flush()
|
|
131
|
+
},
|
|
132
|
+
record(event) {
|
|
133
|
+
switch (event.type) {
|
|
134
|
+
case 'webhook:tick': {
|
|
135
|
+
const { meta, result } = event
|
|
136
|
+
const chain_id = String(result?.chainId ?? meta.chainId ?? 'unknown')
|
|
137
|
+
backend.count('webhook_poller_tick_count', 1, { chain_id, outcome: meta.outcome })
|
|
138
|
+
backend.histogram('webhook_poller_tick_duration_ms', meta.durationMs, {
|
|
139
|
+
chain_id,
|
|
140
|
+
outcome: meta.outcome,
|
|
141
|
+
})
|
|
142
|
+
if (!result) return
|
|
143
|
+
backend.histogram('webhook_poller_subscription_count', result.subscriptions, { chain_id })
|
|
144
|
+
backend.count('webhook_poller_scanned_count', result.scanned, { chain_id })
|
|
145
|
+
backend.gauge('webhook_poller_safe_block', result.safeBlock, { chain_id })
|
|
146
|
+
backend.count('webhook_poller_dispatch_count', result.dispatched, {
|
|
147
|
+
chain_id,
|
|
148
|
+
outcome: 'dispatched',
|
|
149
|
+
})
|
|
150
|
+
backend.count('webhook_poller_dispatch_count', result.failed, {
|
|
151
|
+
chain_id,
|
|
152
|
+
outcome: 'failed',
|
|
153
|
+
})
|
|
154
|
+
return
|
|
155
|
+
}
|
|
156
|
+
case 'webhook:enqueue': {
|
|
157
|
+
const tags = { chain_id: String(event.chainId), event_type: event.eventType }
|
|
158
|
+
const outcome = event.ok ? 'ok' : 'error'
|
|
159
|
+
backend.count('webhook_dispatch_enqueue_count', 1, { ...tags, outcome })
|
|
160
|
+
backend.histogram('webhook_dispatch_enqueue_duration_ms', event.durationMs, {
|
|
161
|
+
...tags,
|
|
162
|
+
outcome,
|
|
163
|
+
})
|
|
164
|
+
return
|
|
165
|
+
}
|
|
166
|
+
case 'webhook:delivery': {
|
|
167
|
+
const { envelope, result, subscription } = event
|
|
168
|
+
const tags = {
|
|
169
|
+
chain_id: String(envelope.chainId),
|
|
170
|
+
destination_type: subscription.destination.type satisfies DestinationType,
|
|
171
|
+
event_type: envelope.type,
|
|
172
|
+
}
|
|
173
|
+
const outcome = result.ok ? 'succeeded' : 'failed'
|
|
174
|
+
backend.count('webhook_delivery_attempt_count', 1, {
|
|
175
|
+
...tags,
|
|
176
|
+
attempt_bucket: attemptBucket(subscription.failureCount + 1),
|
|
177
|
+
outcome,
|
|
178
|
+
status_class: statusClass(result),
|
|
179
|
+
trigger: event.trigger,
|
|
180
|
+
})
|
|
181
|
+
if (result.durationMs !== undefined)
|
|
182
|
+
backend.histogram('webhook_delivery_duration_ms', result.durationMs, {
|
|
183
|
+
...tags,
|
|
184
|
+
outcome,
|
|
185
|
+
})
|
|
186
|
+
backend.histogram(
|
|
187
|
+
'webhook_delivery_queue_latency_ms',
|
|
188
|
+
Math.max(0, Date.now() - Date.parse(envelope.createdAt)),
|
|
189
|
+
tags,
|
|
190
|
+
)
|
|
191
|
+
return
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
},
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** Buckets a 1-based attempt number into a bounded set of attempt labels. */
|
|
199
|
+
function attemptBucket(attempt: number): AttemptBucket {
|
|
200
|
+
if (attempt <= 1) return '1'
|
|
201
|
+
if (attempt === 2) return '2'
|
|
202
|
+
if (attempt <= 5) return '3-5'
|
|
203
|
+
if (attempt <= 10) return '6-10'
|
|
204
|
+
return 'gt10'
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/** Maps a delivery result to a low-cardinality status class. */
|
|
208
|
+
function statusClass(result: Webhooks.Result): StatusClass {
|
|
209
|
+
if (result.ok) return '2xx'
|
|
210
|
+
const { error, status } = result
|
|
211
|
+
if (status !== undefined) {
|
|
212
|
+
if (status >= 300 && status < 400) return 'url_rejected'
|
|
213
|
+
if (status >= 400 && status < 500) return '4xx'
|
|
214
|
+
if (status >= 500) return '5xx'
|
|
215
|
+
}
|
|
216
|
+
if (error === undefined) return 'unknown'
|
|
217
|
+
if (/redirect/i.test(error)) return 'url_rejected'
|
|
218
|
+
if (/timeout|abort/i.test(error)) return 'timeout'
|
|
219
|
+
return 'network'
|
|
220
|
+
}
|