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,135 @@
|
|
|
1
|
+
import * as fs from 'node:fs'
|
|
2
|
+
import * as path from 'node:path'
|
|
3
|
+
import * as url from 'node:url'
|
|
4
|
+
import * as zlib from 'node:zlib'
|
|
5
|
+
|
|
6
|
+
import type { PluginOption } from 'vite'
|
|
7
|
+
|
|
8
|
+
// Vite plugin that turns the TanStack Start SPA build output (`dist/client`)
|
|
9
|
+
// into the shipped asset map `src/admin/ui.generated.ts` — the only thing the
|
|
10
|
+
// Hono admin app imports from the UI. The library `tsc`/`zile` build never sees
|
|
11
|
+
// the `.tsx` source (just this generated module), so the published bundle gains
|
|
12
|
+
// no React or TanStack runtime dependency.
|
|
13
|
+
//
|
|
14
|
+
// It hooks `buildApp` with `order: 'post'` because TanStack Start writes the
|
|
15
|
+
// prerendered `_shell.html` from its own `buildApp` post handler — which runs
|
|
16
|
+
// *after* every environment's `closeBundle`. Registering this plugin after
|
|
17
|
+
// `tanstackStart()` makes this handler run last, once the shell exists.
|
|
18
|
+
|
|
19
|
+
const here = path.dirname(url.fileURLToPath(import.meta.url))
|
|
20
|
+
const clientDir = path.join(here, 'dist', 'client')
|
|
21
|
+
const outFile = path.join(here, '..', 'ui.generated.ts')
|
|
22
|
+
|
|
23
|
+
// Total gzipped budget for the whole UI; the React + TanStack + Regen baseline
|
|
24
|
+
// is heavier than a hand-rolled SPA, so fail loudly before it bloats the
|
|
25
|
+
// package. Raise deliberately (and revisit code-splitting) if it trips.
|
|
26
|
+
const maxGzipBytes = 400_000
|
|
27
|
+
|
|
28
|
+
const contentTypes: Record<string, string> = {
|
|
29
|
+
'.css': 'text/css; charset=utf-8',
|
|
30
|
+
'.html': 'text/html; charset=utf-8',
|
|
31
|
+
'.ico': 'image/x-icon',
|
|
32
|
+
'.js': 'text/javascript; charset=utf-8',
|
|
33
|
+
'.json': 'application/json; charset=utf-8',
|
|
34
|
+
'.map': 'application/json; charset=utf-8',
|
|
35
|
+
'.png': 'image/png',
|
|
36
|
+
'.svg': 'image/svg+xml',
|
|
37
|
+
'.webp': 'image/webp',
|
|
38
|
+
'.woff': 'font/woff',
|
|
39
|
+
'.woff2': 'font/woff2',
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Text assets inline as UTF-8; anything else (fonts, images) base64-encodes.
|
|
43
|
+
const textExtensions = new Set(['.css', '.html', '.js', '.json', '.map', '.svg'])
|
|
44
|
+
|
|
45
|
+
type Asset = { content: string; contentType: string; encoding?: 'base64' }
|
|
46
|
+
|
|
47
|
+
function walk(dir: string): readonly string[] {
|
|
48
|
+
return fs.readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
|
|
49
|
+
const full = path.join(dir, entry.name)
|
|
50
|
+
return entry.isDirectory() ? walk(full) : [full]
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function emitAssetMap(): void {
|
|
55
|
+
if (!fs.existsSync(clientDir))
|
|
56
|
+
throw new Error(`Missing ${clientDir}; the SPA client build did not run.`)
|
|
57
|
+
|
|
58
|
+
const shellPath = path.join(clientDir, '_shell.html')
|
|
59
|
+
if (!fs.existsSync(shellPath)) throw new Error(`Missing SPA shell at ${shellPath}.`)
|
|
60
|
+
|
|
61
|
+
// TanStack Start emits absolute `/./assets/...` URLs for the entry script and
|
|
62
|
+
// modulepreloads (CSS is already relative). Normalize to `./assets/...` so they
|
|
63
|
+
// resolve against the `<base href>` the Hono app injects when serving the shell
|
|
64
|
+
// — that's what makes the single artifact work path-mounted (e.g. `/admin`).
|
|
65
|
+
const shell = fs.readFileSync(shellPath, 'utf8').replaceAll('/./assets/', './assets/')
|
|
66
|
+
|
|
67
|
+
const assets: Record<string, Asset> = {}
|
|
68
|
+
let totalGzip = 0
|
|
69
|
+
for (const file of walk(clientDir)) {
|
|
70
|
+
if (file === shellPath) continue
|
|
71
|
+
const key = path.relative(clientDir, file).split(path.sep).join('/')
|
|
72
|
+
const ext = path.extname(file)
|
|
73
|
+
const buffer = fs.readFileSync(file)
|
|
74
|
+
totalGzip += zlib.gzipSync(buffer).length
|
|
75
|
+
assets[key] = {
|
|
76
|
+
content: textExtensions.has(ext) ? buffer.toString('utf8') : buffer.toString('base64'),
|
|
77
|
+
contentType: contentTypes[ext] ?? 'application/octet-stream',
|
|
78
|
+
...(textExtensions.has(ext) ? {} : { encoding: 'base64' as const }),
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
totalGzip += zlib.gzipSync(Buffer.from(shell, 'utf8')).length
|
|
83
|
+
if (totalGzip > maxGzipBytes)
|
|
84
|
+
throw new Error(
|
|
85
|
+
`Admin UI bundle is ${(totalGzip / 1000).toFixed(0)} KB gzip, over the ${maxGzipBytes / 1000} KB budget.`,
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
const sortedAssets = Object.fromEntries(
|
|
89
|
+
Object.entries(assets).sort(([a], [b]) => a.localeCompare(b)),
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
const banner = `// Generated by src/admin/ui/plugin.ts — do not edit by hand.
|
|
93
|
+
// Run \`pnpm build:ui\` to regenerate from the TanStack Start SPA build.
|
|
94
|
+
/* eslint-disable */`
|
|
95
|
+
|
|
96
|
+
const body = `${banner}
|
|
97
|
+
|
|
98
|
+
/** A single built UI file: text inline as UTF-8, binary as base64. */
|
|
99
|
+
export type Asset = {
|
|
100
|
+
content: string
|
|
101
|
+
contentType: string
|
|
102
|
+
encoding?: 'base64'
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** The prerendered SPA shell HTML (assets normalized to relative URLs). */
|
|
106
|
+
export const shell = ${JSON.stringify(shell)}
|
|
107
|
+
|
|
108
|
+
/** Content-hashed UI assets, keyed by their path under the mount (e.g. \`assets/index-*.js\`). */
|
|
109
|
+
export const assets: Record<string, Asset> = ${JSON.stringify(sortedAssets, null, 2)}
|
|
110
|
+
`
|
|
111
|
+
|
|
112
|
+
fs.writeFileSync(outFile, body)
|
|
113
|
+
console.log(
|
|
114
|
+
`Wrote ${path.relative(path.join(here, '..', '..', '..'), outFile)} (${Object.keys(assets).length} assets, ${(totalGzip / 1000).toFixed(0)} KB gzip).`,
|
|
115
|
+
)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Emits `src/admin/ui.generated.ts` from the SPA build output. Register *after*
|
|
120
|
+
* `tanstackStart()` so its `buildApp` post handler (which writes `_shell.html`
|
|
121
|
+
* via prerender) runs first.
|
|
122
|
+
*/
|
|
123
|
+
export function generated(): PluginOption {
|
|
124
|
+
return {
|
|
125
|
+
name: 'tempo-api-admin-ui-generated',
|
|
126
|
+
enforce: 'post',
|
|
127
|
+
buildApp: {
|
|
128
|
+
order: 'post',
|
|
129
|
+
async handler() {
|
|
130
|
+
emitAssetMap()
|
|
131
|
+
process.exit(0)
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
}
|
|
135
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Checkbox as Base } from '@base-ui/react/checkbox'
|
|
2
|
+
import type { ComponentProps } from 'react'
|
|
3
|
+
import { Check } from 'lucide-react'
|
|
4
|
+
import { cx } from 'regen-ui'
|
|
5
|
+
|
|
6
|
+
export function Checkbox({ className, ...props }: ComponentProps<typeof Base.Root>) {
|
|
7
|
+
return (
|
|
8
|
+
<Base.Root
|
|
9
|
+
className={(state) =>
|
|
10
|
+
cx(
|
|
11
|
+
'flex size-[18px] shrink-0 items-center justify-center rounded-button border-[2px] border-border bg-secondary text-on-invert outline-none transition-colors hover:bg-secondary-hover focus-visible:border-focus data-[checked]:border-[var(--background-color-invert)] data-[checked]:bg-invert',
|
|
12
|
+
typeof className === 'function' ? className(state) : className,
|
|
13
|
+
)
|
|
14
|
+
}
|
|
15
|
+
{...props}
|
|
16
|
+
>
|
|
17
|
+
<Base.Indicator className="flex data-[unchecked]:hidden">
|
|
18
|
+
<Check className="size-3" />
|
|
19
|
+
</Base.Indicator>
|
|
20
|
+
</Base.Root>
|
|
21
|
+
)
|
|
22
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Dialog as Base } from '@base-ui/react/dialog'
|
|
2
|
+
import type { ComponentProps, ReactNode } from 'react'
|
|
3
|
+
|
|
4
|
+
/** Backdrop + centered popup shell. Children render inside; pair with Title/Description. */
|
|
5
|
+
function Popup(props: { children: ReactNode }) {
|
|
6
|
+
return (
|
|
7
|
+
<Base.Portal>
|
|
8
|
+
<Base.Backdrop className="fixed inset-0 z-40 bg-black/60 transition-opacity duration-150 data-[ending-style]:opacity-0 data-[starting-style]:opacity-0" />
|
|
9
|
+
<Base.Popup className="fixed left-1/2 top-1/2 z-50 w-[calc(100vw-2rem)] max-w-md -translate-x-1/2 -translate-y-1/2 border border-border bg-surface p-6 shadow-lg outline-none transition-opacity duration-150 data-[ending-style]:opacity-0 data-[starting-style]:opacity-0">
|
|
10
|
+
{props.children}
|
|
11
|
+
</Base.Popup>
|
|
12
|
+
</Base.Portal>
|
|
13
|
+
)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function Title(props: ComponentProps<typeof Base.Title>) {
|
|
17
|
+
return <Base.Title className="text-heading-20 text-foreground" {...props} />
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function Description(props: ComponentProps<typeof Base.Description>) {
|
|
21
|
+
return <Base.Description className="mt-1 text-copy-14 text-foreground-secondary" {...props} />
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Base modal dialog (base-ui). `Root` manages open state via `Trigger`/`Close`. */
|
|
25
|
+
export const Dialog = {
|
|
26
|
+
Close: Base.Close,
|
|
27
|
+
Description,
|
|
28
|
+
Popup,
|
|
29
|
+
Root: Base.Root,
|
|
30
|
+
Title,
|
|
31
|
+
Trigger: Base.Trigger,
|
|
32
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Bytes, Hash, type Address } from 'ox'
|
|
2
|
+
import { Identicon } from 'regen-ui'
|
|
3
|
+
import { LogOut } from 'lucide-react'
|
|
4
|
+
|
|
5
|
+
import * as Config from '../lib/config'
|
|
6
|
+
|
|
7
|
+
/** Mobile-only header profile: identicon + sign-out, shown when the sidebar is hidden. */
|
|
8
|
+
export function MobileProfile() {
|
|
9
|
+
const { data: config } = Config.use()
|
|
10
|
+
const identity = config?.auth.identity
|
|
11
|
+
if (!identity) return null
|
|
12
|
+
return (
|
|
13
|
+
<div className="flex items-center gap-2">
|
|
14
|
+
<Identicon address={identiconAddress(identity.email)} className="size-9 shrink-0" size={36} />
|
|
15
|
+
{config.auth.logoutUrl ? (
|
|
16
|
+
<a
|
|
17
|
+
aria-label="Sign out"
|
|
18
|
+
className="text-foreground-secondary hover:text-foreground"
|
|
19
|
+
href={config.auth.logoutUrl}
|
|
20
|
+
>
|
|
21
|
+
<LogOut className="size-[18px]" />
|
|
22
|
+
</a>
|
|
23
|
+
) : null}
|
|
24
|
+
</div>
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Derives a stable identicon seed address from an email. */
|
|
29
|
+
export function identiconAddress(email: string): Address.Address {
|
|
30
|
+
return Hash.keccak256(Bytes.fromString(email), { as: 'Hex' }).slice(0, 42) as Address.Address
|
|
31
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { ReactNode } from 'react'
|
|
2
|
+
|
|
3
|
+
import { MobileProfile } from './MobileProfile'
|
|
4
|
+
|
|
5
|
+
/** Page header: title, optional description, and an optional right-aligned action. */
|
|
6
|
+
export function Page(props: {
|
|
7
|
+
title: string
|
|
8
|
+
description?: string
|
|
9
|
+
action?: ReactNode
|
|
10
|
+
children: ReactNode
|
|
11
|
+
}) {
|
|
12
|
+
return (
|
|
13
|
+
<section className="flex flex-col gap-6">
|
|
14
|
+
<div className="flex flex-col gap-4 md:flex-row md:items-center md:justify-between md:gap-4">
|
|
15
|
+
<div className="flex min-w-0 flex-1 items-start gap-4">
|
|
16
|
+
<div className="min-w-0 flex-1">
|
|
17
|
+
<h1 className="text-heading-24 text-foreground">{props.title}</h1>
|
|
18
|
+
{props.description ? (
|
|
19
|
+
<p className="mt-1 text-copy-15 text-foreground-secondary">{props.description}</p>
|
|
20
|
+
) : null}
|
|
21
|
+
</div>
|
|
22
|
+
<div className="shrink-0 md:hidden">
|
|
23
|
+
<MobileProfile />
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
{props.action ? (
|
|
27
|
+
<div className="flex items-center gap-2 max-sm:w-full [&>*]:max-sm:flex-1">
|
|
28
|
+
{props.action}
|
|
29
|
+
</div>
|
|
30
|
+
) : null}
|
|
31
|
+
</div>
|
|
32
|
+
{props.children}
|
|
33
|
+
</section>
|
|
34
|
+
)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Bordered surface container used for lists and forms. */
|
|
38
|
+
export function Card(props: { children: ReactNode; className?: string }) {
|
|
39
|
+
return (
|
|
40
|
+
<div className={`border border-border bg-surface ${props.className ?? ''}`}>
|
|
41
|
+
{props.children}
|
|
42
|
+
</div>
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Centered placeholder for loading / empty / error states inside a card. */
|
|
47
|
+
export function Notice(props: { children: ReactNode }) {
|
|
48
|
+
return (
|
|
49
|
+
<div className="flex items-center justify-center px-6 py-12 text-copy-14 text-foreground-secondary">
|
|
50
|
+
{props.children}
|
|
51
|
+
</div>
|
|
52
|
+
)
|
|
53
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/** The Tempo "T" mark as an inline SVG. Inherits `currentColor` for fill. */
|
|
2
|
+
export function TempoMark(props: { className?: string }) {
|
|
3
|
+
return (
|
|
4
|
+
<svg
|
|
5
|
+
aria-label="Tempo"
|
|
6
|
+
className={props.className}
|
|
7
|
+
fill="none"
|
|
8
|
+
height="14"
|
|
9
|
+
role="img"
|
|
10
|
+
viewBox="0 0 13 14"
|
|
11
|
+
width="13"
|
|
12
|
+
>
|
|
13
|
+
<path d="M4.594 14H.944L4.327 3.173H0L.944 0H13l-.944 3.173H7.96z" fill="currentColor" />
|
|
14
|
+
</svg>
|
|
15
|
+
)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** The Tempo wordmark as an inline SVG. Inherits `currentColor` for fill. */
|
|
19
|
+
export function TempoLogo(props: { className?: string }) {
|
|
20
|
+
return (
|
|
21
|
+
<svg
|
|
22
|
+
aria-label="Tempo"
|
|
23
|
+
className={props.className}
|
|
24
|
+
fill="none"
|
|
25
|
+
height="24"
|
|
26
|
+
role="img"
|
|
27
|
+
viewBox="0 0 107 24"
|
|
28
|
+
width="107"
|
|
29
|
+
>
|
|
30
|
+
<title>Tempo</title>
|
|
31
|
+
<path
|
|
32
|
+
d="M7.903 23.516h-6.28L7.445 5.593H0L1.624.34h20.739l-1.624 5.253h-7.046zM31.273 23.516H16.385L23.859.34h14.827l-1.409 4.419h-8.608l-1.562 4.975h8.332l-1.41 4.357h-8.362l-1.562 5.006h8.546zM38.011 23.516h-4.993L40.523.34h8.333l-.276 12.484L56.698.34h9.129l-7.475 23.176h-6.25l5.055-15.852-10.384 15.852h-3.707l.153-15.914zM72.856 4.635l-2.42 7.478h.674q2.297 0 3.829-1.081Q76.47 9.919 76.93 7.88q.398-1.761-.429-2.503-.826-.742-2.757-.742zm-6.066 18.88h-6.28L67.985.34h7.628q2.634 0 4.534.865 1.93.835 2.818 2.41.919 1.546.612 3.616-.398 2.72-2.082 4.79-1.686 2.07-4.381 3.213-2.665 1.113-5.974 1.113h-2.052zM98.546 22.033q-3.124 1.854-6.648 1.854h-.06q-3.126 0-5.27-1.39-2.114-1.422-3.033-3.833-.888-2.41-.428-5.284a16.3 16.3 0 0 1 2.665-6.674q2.082-3.06 5.207-4.883T97.658 0h.06q3.248 0 5.362 1.39 2.144 1.392 2.971 3.801.858 2.38.368 5.346-.582 3.492-2.665 6.582a16.3 16.3 0 0 1-5.208 4.914m-8.67-3.987q.828 1.576 2.88 1.576h.061q1.686 0 3.125-1.267 1.47-1.297 2.481-3.46 1.042-2.164 1.532-4.821.46-2.595-.368-4.172-.826-1.607-2.849-1.607h-.06q-1.563 0-3.034 1.298-1.44 1.298-2.511 3.492a18.8 18.8 0 0 0-1.563 4.759q-.49 2.595.307 4.202"
|
|
33
|
+
fill="currentColor"
|
|
34
|
+
/>
|
|
35
|
+
</svg>
|
|
36
|
+
)
|
|
37
|
+
}
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import * as ReactQuery from '@tanstack/react-query'
|
|
2
|
+
import type { InferRequestType, InferResponseType } from 'hono/client'
|
|
3
|
+
|
|
4
|
+
import { client } from './client'
|
|
5
|
+
|
|
6
|
+
/** A single API key record from the list endpoint. */
|
|
7
|
+
export type ApiKey = InferResponseType<(typeof client)['api-keys']['$get'], 200>['data'][number]
|
|
8
|
+
|
|
9
|
+
/** List API keys. */
|
|
10
|
+
export namespace get {
|
|
11
|
+
export const key = ['api-keys'] as const
|
|
12
|
+
export function fn() {
|
|
13
|
+
return client['api-keys'].$get().then((r) => r.json())
|
|
14
|
+
}
|
|
15
|
+
export function options() {
|
|
16
|
+
return ReactQuery.queryOptions({ queryFn: fn, queryKey: key, staleTime: 30_000 })
|
|
17
|
+
}
|
|
18
|
+
export function useQuery() {
|
|
19
|
+
return ReactQuery.useQuery(options())
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Provision a new API key. */
|
|
24
|
+
export namespace provision {
|
|
25
|
+
export type Input = InferRequestType<(typeof client)['api-keys']['$post']>['json']
|
|
26
|
+
export function fn(json: Input) {
|
|
27
|
+
return client['api-keys'].$post({ json }).then((r) => {
|
|
28
|
+
if (!r.ok) throw new Error('Failed to provision key.')
|
|
29
|
+
return r.json()
|
|
30
|
+
})
|
|
31
|
+
}
|
|
32
|
+
export function useMutation() {
|
|
33
|
+
const queryClient = ReactQuery.useQueryClient()
|
|
34
|
+
return ReactQuery.useMutation({
|
|
35
|
+
mutationFn: fn,
|
|
36
|
+
onSuccess: ({ token: _token, ...record }) =>
|
|
37
|
+
queryClient.setQueryData(
|
|
38
|
+
get.key,
|
|
39
|
+
(previous: { data: ApiKey[] } | undefined) =>
|
|
40
|
+
previous && { data: [record, ...previous.data] },
|
|
41
|
+
),
|
|
42
|
+
})
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Revoke an API key by id. */
|
|
47
|
+
export namespace revoke {
|
|
48
|
+
export function fn(id: string) {
|
|
49
|
+
return client['api-keys'][':id'].$delete({ param: { id } })
|
|
50
|
+
}
|
|
51
|
+
export function useMutation() {
|
|
52
|
+
const queryClient = ReactQuery.useQueryClient()
|
|
53
|
+
return ReactQuery.useMutation({
|
|
54
|
+
mutationFn: fn,
|
|
55
|
+
onSuccess: (_response, id) =>
|
|
56
|
+
queryClient.setQueryData(
|
|
57
|
+
get.key,
|
|
58
|
+
(previous: { data: ApiKey[] } | undefined) =>
|
|
59
|
+
previous && { data: previous.data.filter((key) => key.id !== id) },
|
|
60
|
+
),
|
|
61
|
+
})
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { hc } from 'hono/client'
|
|
2
|
+
|
|
3
|
+
import type { App } from '../../../App'
|
|
4
|
+
|
|
5
|
+
// Mount root for API calls: the shell injects `<base href="<mount>/">`, so use
|
|
6
|
+
// it when present. The Vite dev shell has none, so `document.baseURI` would be
|
|
7
|
+
// the current URL (breaking sub-routes); fall back to the origin root instead.
|
|
8
|
+
function baseUrl() {
|
|
9
|
+
if (typeof document === 'undefined') return '/'
|
|
10
|
+
const base = document.querySelector('base')
|
|
11
|
+
return base ? base.href : `${location.origin}/`
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Typed admin API client (Hono RPC). */
|
|
15
|
+
export const client = hc<App>(baseUrl())
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { queryOptions, useQuery } from '@tanstack/react-query'
|
|
2
|
+
|
|
3
|
+
import { client } from './client'
|
|
4
|
+
|
|
5
|
+
/** The auth contract the SPA reads from `GET config.json` once loaded. */
|
|
6
|
+
export type Config = {
|
|
7
|
+
auth: {
|
|
8
|
+
/** The signed-in admin identity, or `null` when logged out (shell is public). */
|
|
9
|
+
identity: { email: string } | null
|
|
10
|
+
/** Where the deployer's sign-in page lives; shown when logged out. */
|
|
11
|
+
loginUrl?: string
|
|
12
|
+
/** Where to send the user to sign out (e.g. Cloudflare Access logout). */
|
|
13
|
+
logoutUrl?: string
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
declare global {
|
|
18
|
+
interface Window {
|
|
19
|
+
/** Auth config inlined into the shell `<head>` by the server (see App.ts). */
|
|
20
|
+
__TEMPO_API_CONFIG__?: Config
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Query key for the bootstrap config. */
|
|
25
|
+
export const key = ['config'] as const
|
|
26
|
+
|
|
27
|
+
/** The config the server inlined into the shell, if present (synchronous). */
|
|
28
|
+
export function inlineConfig(): Config | undefined {
|
|
29
|
+
return typeof window === 'undefined' ? undefined : window.__TEMPO_API_CONFIG__
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Admin UI bootstrap config: the inlined value, else fetched from `config.json`. */
|
|
33
|
+
export async function getConfig(): Promise<Config> {
|
|
34
|
+
const inlined = inlineConfig()
|
|
35
|
+
if (inlined) return inlined
|
|
36
|
+
const response = await client['config.json'].$get()
|
|
37
|
+
if (!response.ok) return { auth: { identity: null } }
|
|
38
|
+
return response.json() as Promise<Config>
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Bootstrap config query. Seeded from the inlined value and never stale, so an
|
|
43
|
+
* authenticated load reads identity synchronously with no round trip.
|
|
44
|
+
*/
|
|
45
|
+
export function options() {
|
|
46
|
+
return queryOptions({
|
|
47
|
+
queryKey: key,
|
|
48
|
+
queryFn: getConfig,
|
|
49
|
+
initialData: inlineConfig,
|
|
50
|
+
staleTime: Infinity,
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Bootstrap config query hook. */
|
|
55
|
+
export function use() {
|
|
56
|
+
return useQuery(options())
|
|
57
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { defaultShouldDehydrateQuery, QueryClient } from '@tanstack/react-query'
|
|
2
|
+
import { createAsyncStoragePersister } from '@tanstack/query-async-storage-persister'
|
|
3
|
+
import type { PersistQueryClientOptions } from '@tanstack/react-query-persist-client'
|
|
4
|
+
import { Json } from 'ox'
|
|
5
|
+
|
|
6
|
+
export const queryClient = new QueryClient({
|
|
7
|
+
defaultOptions: {
|
|
8
|
+
queries: { gcTime: 1000 * 60 * 60 * 24, retry: false, refetchOnWindowFocus: false },
|
|
9
|
+
},
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
const persister = createAsyncStoragePersister({
|
|
13
|
+
key: 'tempo-admin',
|
|
14
|
+
storage: typeof window === 'undefined' ? undefined : window.localStorage,
|
|
15
|
+
serialize: Json.stringify,
|
|
16
|
+
deserialize: Json.parse,
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
export const persistOptions: Omit<PersistQueryClientOptions, 'queryClient'> = {
|
|
20
|
+
maxAge: 1000 * 60 * 60 * 24,
|
|
21
|
+
persister,
|
|
22
|
+
dehydrateOptions: {
|
|
23
|
+
shouldDehydrateQuery: (query) =>
|
|
24
|
+
query.queryKey[0] !== 'config' && defaultShouldDehydrateQuery(query),
|
|
25
|
+
},
|
|
26
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import * as ReactQuery from '@tanstack/react-query'
|
|
2
|
+
import type { InferResponseType } from 'hono/client'
|
|
3
|
+
|
|
4
|
+
import { client } from './client'
|
|
5
|
+
|
|
6
|
+
/** A single issuable API-key scope from the catalog endpoint. */
|
|
7
|
+
export type Scope = InferResponseType<(typeof client)['scopes']['$get'], 200>['data'][number]
|
|
8
|
+
|
|
9
|
+
/** List the issuable API-key scope catalog. */
|
|
10
|
+
export namespace get {
|
|
11
|
+
export const key = ['scopes'] as const
|
|
12
|
+
export function fn() {
|
|
13
|
+
return client.scopes.$get().then((r) => r.json())
|
|
14
|
+
}
|
|
15
|
+
export function options() {
|
|
16
|
+
return ReactQuery.queryOptions({
|
|
17
|
+
queryFn: fn,
|
|
18
|
+
queryKey: key,
|
|
19
|
+
staleTime: Number.POSITIVE_INFINITY,
|
|
20
|
+
})
|
|
21
|
+
}
|
|
22
|
+
export function useQuery() {
|
|
23
|
+
return ReactQuery.useQuery(options())
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import * as ReactQuery from '@tanstack/react-query'
|
|
2
|
+
import type { InferRequestType, InferResponseType } from 'hono/client'
|
|
3
|
+
|
|
4
|
+
import { client } from './client'
|
|
5
|
+
|
|
6
|
+
/** The verified-token list endpoint's success body. */
|
|
7
|
+
type ListResponse = InferResponseType<(typeof client)['verified-tokens']['$get'], 200>
|
|
8
|
+
|
|
9
|
+
/** A single verified token record from the list endpoint. */
|
|
10
|
+
export type VerifiedToken = ListResponse['data'][number]
|
|
11
|
+
|
|
12
|
+
/** URL of a token's curated logo, served from the admin's asset store. */
|
|
13
|
+
export function logoUrl({ address, chainId }: { address: `0x${string}`; chainId: number }) {
|
|
14
|
+
return client['verified-tokens'][':address'].logo
|
|
15
|
+
.$url({ param: { address }, query: { chainId: String(chainId) } })
|
|
16
|
+
.toString()
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** List verified tokens for a chain. */
|
|
20
|
+
export namespace get {
|
|
21
|
+
export type Input = { chainId: number }
|
|
22
|
+
export function key({ chainId }: Input) {
|
|
23
|
+
return ['verified-tokens', chainId] as const
|
|
24
|
+
}
|
|
25
|
+
export async function fn({ chainId }: Input) {
|
|
26
|
+
const response = await client['verified-tokens'].$get({ query: { chainId: String(chainId) } })
|
|
27
|
+
return response.json() as Promise<ListResponse>
|
|
28
|
+
}
|
|
29
|
+
export function options(input: Input) {
|
|
30
|
+
return ReactQuery.queryOptions({
|
|
31
|
+
queryFn: () => fn(input),
|
|
32
|
+
queryKey: key(input),
|
|
33
|
+
staleTime: 30_000,
|
|
34
|
+
})
|
|
35
|
+
}
|
|
36
|
+
export function useQuery(input: Input) {
|
|
37
|
+
return ReactQuery.useQuery(options(input))
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Create a verified token, optionally with an inline SVG logo. */
|
|
42
|
+
export namespace create {
|
|
43
|
+
type Json = InferRequestType<(typeof client)['verified-tokens']['$post']>['json']
|
|
44
|
+
// `chainId` validates through `Schema.ChainId`, whose `z.coerce.number()` input
|
|
45
|
+
// makes hono/client infer the wire type as `unknown`; narrow it back to the
|
|
46
|
+
// numeric id the UI sends.
|
|
47
|
+
export type Input = Omit<Json, 'chainId' | 'logo'> & {
|
|
48
|
+
chainId: number
|
|
49
|
+
logo?: File | null
|
|
50
|
+
}
|
|
51
|
+
export async function fn({ logo, ...json }: Input) {
|
|
52
|
+
const response = await client['verified-tokens'].$post({
|
|
53
|
+
json: { ...json, ...(logo ? { logo: await logo.text() } : {}) },
|
|
54
|
+
})
|
|
55
|
+
if (!response.ok) throw new Error('Failed to add token.')
|
|
56
|
+
return response.json()
|
|
57
|
+
}
|
|
58
|
+
export function useMutation() {
|
|
59
|
+
const queryClient = ReactQuery.useQueryClient()
|
|
60
|
+
return ReactQuery.useMutation({
|
|
61
|
+
mutationFn: fn,
|
|
62
|
+
onSuccess: (_data, input) =>
|
|
63
|
+
queryClient.invalidateQueries({
|
|
64
|
+
queryKey: get.key({ chainId: input.chainId }),
|
|
65
|
+
}),
|
|
66
|
+
})
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Remove a verified token from a chain. */
|
|
71
|
+
export namespace remove {
|
|
72
|
+
export type Input = { address: `0x${string}`; chainId: number }
|
|
73
|
+
export async function fn({ address, chainId }: Input) {
|
|
74
|
+
const response = await client['verified-tokens'][':address'].$delete({
|
|
75
|
+
param: { address },
|
|
76
|
+
query: { chainId: String(chainId) },
|
|
77
|
+
})
|
|
78
|
+
if (!response.ok) throw new Error('Failed to remove token.')
|
|
79
|
+
return response.json()
|
|
80
|
+
}
|
|
81
|
+
export function useMutation() {
|
|
82
|
+
const queryClient = ReactQuery.useQueryClient()
|
|
83
|
+
return ReactQuery.useMutation({
|
|
84
|
+
mutationFn: fn,
|
|
85
|
+
onSuccess: (_data, input) =>
|
|
86
|
+
queryClient.invalidateQueries({ queryKey: get.key({ chainId: input.chainId }) }),
|
|
87
|
+
})
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { createConfig, http } from 'wagmi'
|
|
2
|
+
import { tempo, tempoTestnet } from 'wagmi/chains'
|
|
3
|
+
|
|
4
|
+
/** Read-only wagmi config for reading on-chain token metadata (no wallet). */
|
|
5
|
+
export const config = createConfig({
|
|
6
|
+
chains: [tempo, tempoTestnet],
|
|
7
|
+
ssr: true,
|
|
8
|
+
transports: {
|
|
9
|
+
[tempo.id]: http(),
|
|
10
|
+
[tempoTestnet.id]: http(),
|
|
11
|
+
},
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
declare module 'wagmi' {
|
|
15
|
+
interface Register {
|
|
16
|
+
config: typeof config
|
|
17
|
+
}
|
|
18
|
+
}
|