tapimo 0.0.2 → 0.1.0
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/README.md +11 -2
- package/dist/ApiKey.d.ts +12 -11
- package/dist/ApiKey.d.ts.map +1 -1
- package/dist/ApiKey.js +10 -12
- package/dist/ApiKey.js.map +1 -1
- package/dist/ApiKeys.d.ts +139 -0
- package/dist/ApiKeys.d.ts.map +1 -0
- package/dist/ApiKeys.js +166 -0
- package/dist/ApiKeys.js.map +1 -0
- package/dist/App.d.ts +920 -910
- package/dist/App.d.ts.map +1 -1
- package/dist/App.js +36 -12
- package/dist/App.js.map +1 -1
- package/dist/Handler.d.ts +86 -0
- package/dist/Handler.d.ts.map +1 -0
- package/dist/Handler.js +83 -0
- package/dist/Handler.js.map +1 -0
- package/dist/Scope.d.ts +4 -1
- package/dist/Scope.d.ts.map +1 -1
- package/dist/Scope.js +5 -1
- package/dist/Scope.js.map +1 -1
- package/dist/Webhooks.d.ts +3 -5
- package/dist/Webhooks.d.ts.map +1 -1
- package/dist/Webhooks.js +9 -2
- package/dist/Webhooks.js.map +1 -1
- package/dist/admin/Access.d.ts +1 -3
- package/dist/admin/Access.d.ts.map +1 -1
- package/dist/admin/Access.js +1 -3
- package/dist/admin/Access.js.map +1 -1
- package/dist/admin/App.d.ts +98 -209
- package/dist/admin/App.d.ts.map +1 -1
- package/dist/admin/App.js +2 -4
- package/dist/admin/App.js.map +1 -1
- package/dist/admin/apps/api-keys.d.ts +7 -5
- package/dist/admin/apps/api-keys.d.ts.map +1 -1
- package/dist/admin/apps/api-keys.js +8 -9
- package/dist/admin/apps/api-keys.js.map +1 -1
- package/dist/admin/apps/scopes.d.ts +3 -0
- package/dist/admin/apps/scopes.d.ts.map +1 -1
- package/dist/admin/apps/verified-tokens.d.ts +101 -215
- package/dist/admin/apps/verified-tokens.d.ts.map +1 -1
- package/dist/admin/apps/verified-tokens.js +17 -38
- package/dist/admin/apps/verified-tokens.js.map +1 -1
- package/dist/admin/ui.generated.d.ts +1 -1
- package/dist/admin/ui.generated.js +1 -1
- package/dist/apps/balances.d.ts +2 -2
- package/dist/apps/receipts.d.ts +43 -43
- package/dist/apps/tokens.d.ts +454 -454
- package/dist/apps/transactions.d.ts +43 -43
- package/dist/apps/transfers.d.ts +29 -29
- package/dist/apps/webhooks.d.ts +5 -2
- package/dist/apps/webhooks.d.ts.map +1 -1
- package/dist/apps/webhooks.js +38 -34
- package/dist/apps/webhooks.js.map +1 -1
- package/dist/{Cli.d.ts → cli/Cli.d.ts} +13 -2
- package/dist/cli/Cli.d.ts.map +1 -0
- package/dist/cli/Cli.js +19 -0
- package/dist/cli/Cli.js.map +1 -0
- package/dist/cli/bin.d.ts +3 -0
- package/dist/cli/bin.d.ts.map +1 -0
- package/dist/cli/bin.js +4 -0
- package/dist/cli/bin.js.map +1 -0
- package/dist/cli/commands/admin.d.ts +15 -0
- package/dist/cli/commands/admin.d.ts.map +1 -0
- package/dist/cli/commands/admin.js +60 -0
- package/dist/cli/commands/admin.js.map +1 -0
- package/dist/cloudflare.d.ts +7 -0
- package/dist/cloudflare.d.ts.map +1 -1
- package/dist/cloudflare.js +9 -0
- package/dist/cloudflare.js.map +1 -1
- package/dist/db/Db.d.ts +103 -0
- package/dist/db/Db.d.ts.map +1 -0
- package/dist/db/Db.js +274 -0
- package/dist/db/Db.js.map +1 -0
- package/dist/db/Schema.d.ts +88 -0
- package/dist/db/Schema.d.ts.map +1 -0
- package/dist/db/Schema.js +130 -0
- package/dist/db/Schema.js.map +1 -0
- package/dist/db/tables/organizations.d.ts +79 -0
- package/dist/db/tables/organizations.d.ts.map +1 -0
- package/dist/db/tables/organizations.js +81 -0
- package/dist/db/tables/organizations.js.map +1 -0
- package/dist/db/tables/verifiedTokens.d.ts +53 -0
- package/dist/db/tables/verifiedTokens.d.ts.map +1 -0
- package/dist/db/tables/verifiedTokens.js +74 -0
- package/dist/db/tables/verifiedTokens.js.map +1 -0
- package/dist/db/tables/webhookDeliveries.d.ts +71 -0
- package/dist/db/tables/webhookDeliveries.d.ts.map +1 -0
- package/dist/db/tables/webhookDeliveries.js +119 -0
- package/dist/db/tables/webhookDeliveries.js.map +1 -0
- package/dist/db/tables/webhookSubscriptions.d.ts +158 -0
- package/dist/db/tables/webhookSubscriptions.d.ts.map +1 -0
- package/dist/db/tables/webhookSubscriptions.js +272 -0
- package/dist/db/tables/webhookSubscriptions.js.map +1 -0
- package/dist/handlers/internal/executionError.d.ts +25 -0
- package/dist/handlers/internal/executionError.d.ts.map +1 -0
- package/dist/handlers/internal/executionError.js +184 -0
- package/dist/handlers/internal/executionError.js.map +1 -0
- package/dist/handlers/internal/multisig.d.ts +237 -0
- package/dist/handlers/internal/multisig.d.ts.map +1 -0
- package/dist/handlers/internal/multisig.js +567 -0
- package/dist/handlers/internal/multisig.js.map +1 -0
- package/dist/handlers/internal/requestListener.d.ts +124 -0
- package/dist/handlers/internal/requestListener.d.ts.map +1 -0
- package/dist/handlers/internal/requestListener.js +173 -0
- package/dist/handlers/internal/requestListener.js.map +1 -0
- package/dist/handlers/internal/sponsorship.d.ts +83 -0
- package/dist/handlers/internal/sponsorship.d.ts.map +1 -0
- package/dist/handlers/internal/sponsorship.js +103 -0
- package/dist/handlers/internal/sponsorship.js.map +1 -0
- package/dist/handlers/internal/types.d.ts +284 -0
- package/dist/handlers/internal/types.d.ts.map +1 -0
- package/dist/handlers/internal/types.js +2 -0
- package/dist/handlers/internal/types.js.map +1 -0
- package/dist/handlers/internal/utils.d.ts +34 -0
- package/dist/handlers/internal/utils.d.ts.map +1 -0
- package/dist/handlers/internal/utils.js +131 -0
- package/dist/handlers/internal/utils.js.map +1 -0
- package/dist/handlers/relay.d.ts +119 -0
- package/dist/handlers/relay.d.ts.map +1 -0
- package/dist/handlers/relay.js +1264 -0
- package/dist/handlers/relay.js.map +1 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/internal/Auth.d.ts +16 -10
- package/dist/internal/Auth.d.ts.map +1 -1
- package/dist/internal/Auth.js +72 -12
- package/dist/internal/Auth.js.map +1 -1
- package/dist/internal/RpcSchema.d.ts +10 -0
- package/dist/internal/RpcSchema.d.ts.map +1 -0
- package/dist/internal/RpcSchema.js +2 -0
- package/dist/internal/RpcSchema.js.map +1 -0
- package/dist/internal/Store.d.ts +42 -3
- package/dist/internal/Store.d.ts.map +1 -1
- package/dist/internal/Store.js +65 -3
- package/dist/internal/Store.js.map +1 -1
- package/dist/internal/VerifiedTokens.d.ts +15 -34
- package/dist/internal/VerifiedTokens.d.ts.map +1 -1
- package/dist/internal/VerifiedTokens.js +115 -159
- package/dist/internal/VerifiedTokens.js.map +1 -1
- package/dist/internal/Webhooks.d.ts +49 -35
- package/dist/internal/Webhooks.d.ts.map +1 -1
- package/dist/internal/Webhooks.js +92 -205
- package/dist/internal/Webhooks.js.map +1 -1
- package/package.json +7 -2
- package/src/ApiKey.test.ts +3 -3
- package/src/ApiKey.ts +10 -12
- package/src/ApiKeys.test.ts +175 -0
- package/src/ApiKeys.ts +205 -0
- package/src/App.test-d.ts +1 -1
- package/src/App.test.ts +10768 -351
- package/src/App.ts +79 -36
- package/src/Client.test-d.ts +1 -1
- package/src/Handler.ts +154 -0
- package/src/Scope.ts +5 -1
- package/src/Webhooks.test.ts +8 -7
- package/src/Webhooks.ts +11 -5
- package/src/admin/Access.ts +1 -3
- package/src/admin/App.ts +19 -24
- package/src/admin/apps/api-keys.test.ts +19 -18
- package/src/admin/apps/api-keys.ts +8 -9
- package/src/admin/apps/scopes.test.ts +4 -0
- package/src/admin/apps/verified-tokens.test.ts +3 -30
- package/src/admin/apps/verified-tokens.ts +17 -36
- package/src/admin/ui.generated.ts +1 -1
- package/src/apps/balances.test.ts +4 -3
- package/src/apps/blocks.test.ts +1 -1
- package/src/apps/exchanges.test.ts +1 -1
- package/src/apps/indexer.test.ts +1 -1
- package/src/apps/receipts.test.ts +1 -1
- package/src/apps/rpc.test.ts +100 -10
- package/src/apps/tokenlist.test.ts +5 -7
- package/src/apps/tokens.test.ts +4 -3
- package/src/apps/transactions.test.ts +1 -1
- package/src/apps/transfers.test.ts +1 -1
- package/src/apps/verified-tokens.test.ts +24 -12
- package/src/apps/webhooks.test.ts +130 -143
- package/src/apps/webhooks.ts +45 -47
- package/src/{Cli.test.ts → cli/Cli.test.ts} +10 -9
- package/src/{Cli.ts → cli/Cli.ts} +16 -9
- package/src/cli/bin.ts +4 -0
- package/src/cli/commands/admin.test.ts +71 -0
- package/src/cli/commands/admin.ts +60 -0
- package/src/cloudflare.ts +10 -0
- package/src/db/Db.test.ts +84 -0
- package/src/db/Db.ts +383 -0
- package/src/db/Schema.ts +164 -0
- package/src/db/tables/organizations.test.ts +124 -0
- package/src/db/tables/organizations.ts +127 -0
- package/src/db/tables/verifiedTokens.ts +111 -0
- package/src/db/tables/webhookDeliveries.ts +159 -0
- package/src/db/tables/webhookSubscriptions.ts +358 -0
- package/src/handlers/internal/executionError.ts +205 -0
- package/src/handlers/internal/multisig.localnet.test.ts +146 -0
- package/src/handlers/internal/multisig.test.ts +744 -0
- package/src/handlers/internal/multisig.ts +858 -0
- package/src/handlers/internal/relay.localnet.test.ts +2035 -0
- package/src/handlers/internal/requestListener.ts +273 -0
- package/src/handlers/internal/sponsorship.ts +186 -0
- package/src/handlers/internal/types.ts +378 -0
- package/src/handlers/internal/utils.ts +153 -0
- package/src/handlers/relay.ts +1605 -0
- package/src/index.ts +4 -2
- package/src/internal/Auth.test.ts +21 -21
- package/src/internal/Auth.ts +92 -19
- package/src/internal/RateLimit.test.ts +1 -1
- package/src/internal/RpcSchema.ts +10 -0
- package/src/internal/Store.test.ts +98 -10
- package/src/internal/Store.ts +112 -6
- package/src/internal/VerifiedTokens.test.ts +99 -115
- package/src/internal/VerifiedTokens.ts +123 -175
- package/src/internal/Webhooks.test.ts +107 -107
- package/src/internal/Webhooks.ts +102 -220
- package/dist/ApiKeySource.d.ts +0 -166
- package/dist/ApiKeySource.d.ts.map +0 -1
- package/dist/ApiKeySource.js +0 -234
- package/dist/ApiKeySource.js.map +0 -1
- package/dist/Cli.d.ts.map +0 -1
- package/dist/Cli.js +0 -13
- package/dist/Cli.js.map +0 -1
- package/seeds/31318/icons/0x20c0000000000000000000000000000000000000.svg +0 -1
- package/seeds/31318/icons/0x20c0000000000000000000000000000000000001.svg +0 -1
- package/seeds/31318/icons/0x20c0000000000000000000000000000000000002.svg +0 -1
- package/seeds/31318/icons/0x20c0000000000000000000000000000000000003.svg +0 -1
- package/seeds/31318/verified-tokens.json +0 -30
- package/seeds/4217/icons/0x20c0000000000000000000000000000000000000.svg +0 -25
- package/seeds/4217/icons/0x20c000000000000000000000048c8f36df1c9a4a.svg +0 -1
- package/seeds/4217/icons/0x20c0000000000000000000000520792dcccccccc.svg +0 -1
- package/seeds/4217/icons/0x20c0000000000000000000000a6da882d075a4c3.svg +0 -4
- package/seeds/4217/icons/0x20c000000000000000000000111111111e910f0f.svg +0 -30
- package/seeds/4217/icons/0x20c00000000000000000000014f22ca97301eb73.svg +0 -1
- package/seeds/4217/icons/0x20c0000000000000000000001621e21f71cf12fb.svg +0 -1
- package/seeds/4217/icons/0x20c0000000000000000000002f52d5cc21a3207b.svg +0 -1
- package/seeds/4217/icons/0x20c0000000000000000000003158081efd85bfc2.svg +0 -11
- package/seeds/4217/icons/0x20c0000000000000000000003554d28269e0f3c2.svg +0 -1
- package/seeds/4217/icons/0x20c000000000000000000000383a23bacb546ab9.svg +0 -1
- package/seeds/4217/icons/0x20c00000000000000000000042109aef2f8b28e1.png +0 -0
- package/seeds/4217/icons/0x20c0000000000000000000005c0bac7cef389a11.svg +0 -1
- package/seeds/4217/icons/0x20c0000000000000000000006fd9a167923ba194.png +0 -0
- package/seeds/4217/icons/0x20c0000000000000000000007f7ba549dd0251b9.svg +0 -1
- package/seeds/4217/icons/0x20c0000000000000000000008191667423f70e67.svg +0 -5
- package/seeds/4217/icons/0x20c0000000000000000000008ee4fcff88888888.svg +0 -1
- package/seeds/4217/icons/0x20c0000000000000000000009a4a4b17e0dc6651.svg +0 -1
- package/seeds/4217/icons/0x20c000000000000000000000ab02d39df30bd17e.svg +0 -1
- package/seeds/4217/icons/0x20c000000000000000000000ae247a1130450f09.svg +0 -1
- package/seeds/4217/icons/0x20c000000000000000000000aeed2ec36a54d0e5.svg +0 -1
- package/seeds/4217/icons/0x20c000000000000000000000b9537d11c60e8b50.svg +0 -1
- package/seeds/4217/icons/0x20c000000000000000000000bd95bfb69fbe6ce3.svg +0 -1
- package/seeds/4217/icons/0x20c000000000000000000000c412ec89d0c08be5.svg +0 -1
- package/seeds/4217/icons/0x20c000000000000000000000f047dd7018e50367.png +0 -0
- package/seeds/4217/verified-tokens.json +0 -177
- package/seeds/42431/icons/0x20c0000000000000000000000000000000000000.svg +0 -25
- package/seeds/42431/icons/0x20c0000000000000000000000000000000000001.svg +0 -1
- package/seeds/42431/icons/0x20c0000000000000000000000000000000000002.svg +0 -1
- package/seeds/42431/icons/0x20c0000000000000000000000000000000000003.svg +0 -1
- package/seeds/42431/icons/0x20c0000000000000000000009e8d7eb59b783726.svg +0 -1
- package/seeds/42431/icons/0x20c000000000000000000000d72572838bbee59c.svg +0 -1
- package/seeds/42431/verified-tokens.json +0 -44
- package/seeds/openrpc/README.md +0 -27
- package/seeds/openrpc/execution-apis.json +0 -8989
- package/src/ApiKeySource.test-d.ts +0 -11
- package/src/ApiKeySource.test.ts +0 -295
- package/src/ApiKeySource.ts +0 -303
package/src/App.ts
CHANGED
|
@@ -5,13 +5,15 @@ import { cors as hono_cors } from 'hono/cors'
|
|
|
5
5
|
import { HTTPException } from 'hono/http-exception'
|
|
6
6
|
import { requestId, type RequestIdVariables } from 'hono/request-id'
|
|
7
7
|
import { generateSpecs } from 'hono-openapi'
|
|
8
|
-
import { Handler } from 'vocs/server'
|
|
8
|
+
import { Handler as vocs_Handler } from 'vocs/server'
|
|
9
9
|
import * as z from 'zod/mini'
|
|
10
10
|
|
|
11
11
|
import * as ApiKey from './ApiKey.js'
|
|
12
12
|
import * as Assets from './Assets.js'
|
|
13
13
|
import * as Auth from './internal/Auth.js'
|
|
14
|
+
import * as Db from './db/Db.js'
|
|
14
15
|
import * as EdgeCache from './internal/EdgeCache.js'
|
|
16
|
+
import * as Handler from './Handler.js'
|
|
15
17
|
import * as Log from './internal/Log.js'
|
|
16
18
|
import * as MetricSink from './internal/MetricSink.js'
|
|
17
19
|
import type * as Metrics from './Metrics.js'
|
|
@@ -22,6 +24,7 @@ import * as Response from './internal/Response.js'
|
|
|
22
24
|
import * as Schema from './internal/Schema.js'
|
|
23
25
|
import * as Tidx from './internal/Tidx.js'
|
|
24
26
|
import * as Timing from './internal/Timing.js'
|
|
27
|
+
import * as VerifiedTokens from './internal/VerifiedTokens.js'
|
|
25
28
|
import * as Viem from './internal/Viem.js'
|
|
26
29
|
|
|
27
30
|
import openrpcSpec from './openrpc.json' with { type: 'json' }
|
|
@@ -50,6 +53,12 @@ export type Environment = {
|
|
|
50
53
|
basePath: string
|
|
51
54
|
/** Default Tempo chain id for this API app. */
|
|
52
55
|
chainId: Viem.ChainId
|
|
56
|
+
/** Authoritative Postgres store, or a per-request factory. Resolve at the leaf with `Db.get`. */
|
|
57
|
+
db: Db.Source
|
|
58
|
+
/** Store for staleness-tolerant reads (a caching Hyperdrive config when configured); falls back to `db`. */
|
|
59
|
+
dbCached: Db.Source
|
|
60
|
+
/** KV state store backing API-key records, or undefined when key auth is closed. */
|
|
61
|
+
kv: { store: Store.State } | undefined
|
|
53
62
|
/** Gets one static API asset. */
|
|
54
63
|
getAsset: GetAsset
|
|
55
64
|
/** Gets a Tempo RPC client for a chain id. */
|
|
@@ -64,10 +73,10 @@ export type Environment = {
|
|
|
64
73
|
supportedChainIds: ReadonlySet<number>
|
|
65
74
|
/** Configured TIDX client options used by the raw indexer passthrough. */
|
|
66
75
|
tidx: Tidx.getClient.Tidx | undefined
|
|
67
|
-
/** Verified-token feature configuration (
|
|
68
|
-
verifiedTokens: { refreshMs?: number | undefined
|
|
69
|
-
/** Webhook feature configuration
|
|
70
|
-
webhook: { maxPerOwner?: number | undefined
|
|
76
|
+
/** Verified-token feature configuration (refresh window), or undefined when the feature is disabled. Token rows live in `db`. */
|
|
77
|
+
verifiedTokens: { refreshMs?: number | undefined } | undefined
|
|
78
|
+
/** Webhook feature configuration, or undefined when webhooks are disabled. Rows live in `db`. */
|
|
79
|
+
webhook: { maxPerOwner?: number | undefined } | undefined
|
|
71
80
|
} & EdgeCache.Variables &
|
|
72
81
|
Log.Variables &
|
|
73
82
|
Timing.Variables
|
|
@@ -108,7 +117,7 @@ export type GetAsset = (
|
|
|
108
117
|
const infoDescription = 'REST API for reading and interacting with Tempo'
|
|
109
118
|
|
|
110
119
|
/** Creates the Tempo API Hono app. */
|
|
111
|
-
export function create<const path extends string = '/'>(options: create.Options<path>
|
|
120
|
+
export function create<const path extends string = '/'>(options: create.Options<path>) {
|
|
112
121
|
const { assets, defaultChainId = Viem.defaultChainId, path = '/' as path, rpc } = options
|
|
113
122
|
|
|
114
123
|
// Capture as the literal generic so `Hono.basePath(...)` preserves the
|
|
@@ -123,8 +132,8 @@ export function create<const path extends string = '/'>(options: create.Options<
|
|
|
123
132
|
// miss simply re-fetches — so it defaults to in-memory. Point it at a
|
|
124
133
|
// colo-local store (`Store.cache(caches.default)`) in production; never the
|
|
125
134
|
// Durable Object, or edge-cache hits re-incur the round-trip they skip.
|
|
126
|
-
// Authoritative state
|
|
127
|
-
//
|
|
135
|
+
// Authoritative state lives elsewhere: webhooks + verified tokens in
|
|
136
|
+
// `options.db`, API-key records in `options.kv`.
|
|
128
137
|
const store = options.cache?.store ?? Store.memory()
|
|
129
138
|
|
|
130
139
|
// Memoize one viem client per resolved upstream. Clients are stateless HTTP
|
|
@@ -197,8 +206,8 @@ export function create<const path extends string = '/'>(options: create.Options<
|
|
|
197
206
|
base.use(
|
|
198
207
|
'*',
|
|
199
208
|
Log.middleware<Environment>({
|
|
200
|
-
emit: (entry) => {
|
|
201
|
-
log?.(entry)
|
|
209
|
+
emit: async (entry) => {
|
|
210
|
+
await log?.(entry)
|
|
202
211
|
recordRequest?.(entry)
|
|
203
212
|
// Return the analytics promise so `Log.middleware` extends the
|
|
204
213
|
// response with `waitUntil`; otherwise the async sink's `queue.send`
|
|
@@ -219,21 +228,29 @@ export function create<const path extends string = '/'>(options: create.Options<
|
|
|
219
228
|
...(options.supportedChainIds ?? []),
|
|
220
229
|
])
|
|
221
230
|
|
|
231
|
+
// Normalize the `db` option: the plain form is `{ source }` with no cached
|
|
232
|
+
// side; `cached` falls back to `source` so consumers never branch.
|
|
233
|
+
const db = 'source' in options.db ? options.db : { source: options.db }
|
|
234
|
+
|
|
222
235
|
base.use('*', Timing.middleware())
|
|
223
236
|
base.use('*', async (c, next) => {
|
|
224
237
|
// Put app-level dependencies on Hono context so mounted handlers only
|
|
225
238
|
// receive request-local state from `c`.
|
|
226
239
|
c.set('basePath', basePath)
|
|
227
240
|
c.set('chainId', defaultChainId)
|
|
241
|
+
c.set('db', db.source)
|
|
242
|
+
c.set('dbCached', db.cached ?? db.source)
|
|
228
243
|
c.set('getAsset', createGetAsset({ assets, basePath, origin: new URL(c.req.url).origin }))
|
|
229
244
|
c.set('getClient', (chainId) => getClient(chainId, Auth.getPrincipal(c)))
|
|
230
245
|
c.set('getTidx', (chainId) => getTidx(chainId, Auth.getPrincipal(c)))
|
|
246
|
+
c.set('kv', options.kv)
|
|
231
247
|
c.set('rpc', rpc)
|
|
232
248
|
c.set('store', store)
|
|
233
249
|
c.set('supportedChainIds', supportedChainIds)
|
|
234
250
|
c.set('tidx', options.tidx)
|
|
235
|
-
|
|
236
|
-
c.set('
|
|
251
|
+
// Feature configs default to enabled empty configs; `false` disables.
|
|
252
|
+
c.set('verifiedTokens', options.verifiedTokens === false ? undefined : (options.verifiedTokens ?? {})) // prettier-ignore
|
|
253
|
+
c.set('webhook', options.webhook === false ? undefined : (options.webhook ?? {}))
|
|
237
254
|
await next()
|
|
238
255
|
})
|
|
239
256
|
|
|
@@ -297,8 +314,33 @@ export function create<const path extends string = '/'>(options: create.Options<
|
|
|
297
314
|
.route('/v1/transactions', transactionActivities())
|
|
298
315
|
.route('/v1/transfers', transfers())
|
|
299
316
|
.route('/v1/verified-tokens', verifiedTokens())
|
|
300
|
-
.route('/v1/webhooks', webhooks({ enabled: options.webhook !==
|
|
317
|
+
.route('/v1/webhooks', webhooks({ enabled: options.webhook !== false }))
|
|
301
318
|
.route('/rpc', rpcApp())
|
|
319
|
+
.use(
|
|
320
|
+
'/rpc/relay',
|
|
321
|
+
Auth.policy({ apiKey: { scopes: ['rpc-relay'] }, mpp: false, public: false }),
|
|
322
|
+
OpenApi.describeRoute({ hide: true, summary: 'Relay RPC' }),
|
|
323
|
+
)
|
|
324
|
+
.use(
|
|
325
|
+
'/rpc/relay/*',
|
|
326
|
+
Auth.policy({ apiKey: { scopes: ['rpc-relay'] }, mpp: false, public: false }),
|
|
327
|
+
OpenApi.describeRoute({ hide: true, summary: 'Relay RPC' }),
|
|
328
|
+
)
|
|
329
|
+
.route(
|
|
330
|
+
'/rpc/relay',
|
|
331
|
+
Handler.relay({
|
|
332
|
+
cache: store,
|
|
333
|
+
getClient: (chainId) => getClient(chainId as Viem.ChainId | undefined),
|
|
334
|
+
...(options.verifiedTokens === false
|
|
335
|
+
? {}
|
|
336
|
+
: {
|
|
337
|
+
resolveTokens: async (chainId: number) => {
|
|
338
|
+
const snapshot = await VerifiedTokens.read(Db.get(db.cached ?? db.source), chainId)
|
|
339
|
+
return snapshot?.list.map((token) => token.address) ?? []
|
|
340
|
+
},
|
|
341
|
+
}),
|
|
342
|
+
}),
|
|
343
|
+
)
|
|
302
344
|
.route('/gecko', coingecko())
|
|
303
345
|
// Hidden compatibility route for the prior Tempo API's
|
|
304
346
|
// `GET /chains/:chainId/blocks/by-timestamp` (chain id in the path).
|
|
@@ -308,7 +350,7 @@ export function create<const path extends string = '/'>(options: create.Options<
|
|
|
308
350
|
// OAS 3.1 `webhooks`: document the signed envelope Tempo API POSTs to subscriber
|
|
309
351
|
// URLs (an outbound payload, not an endpoint of this API). Only when enabled.
|
|
310
352
|
const webhooksDoc = (() => {
|
|
311
|
-
if (
|
|
353
|
+
if (options.webhook === false) return {}
|
|
312
354
|
// `io: 'output'` documents the delivered (serialized) shape; `unrepresentable: 'any'`
|
|
313
355
|
// tolerates the transaction payload's transforms (e.g. lazy fee-token metadata),
|
|
314
356
|
// which have no JSON-Schema form, by emitting `{}` for those nodes rather than throwing.
|
|
@@ -422,7 +464,7 @@ export function create<const path extends string = '/'>(options: create.Options<
|
|
|
422
464
|
name: 'Verified Tokens',
|
|
423
465
|
description: 'A curated, trusted list of TIP-20 tokens.',
|
|
424
466
|
},
|
|
425
|
-
...(options.webhook
|
|
467
|
+
...(options.webhook !== false
|
|
426
468
|
? [
|
|
427
469
|
{
|
|
428
470
|
name: 'Webhooks',
|
|
@@ -470,7 +512,7 @@ export function create<const path extends string = '/'>(options: create.Options<
|
|
|
470
512
|
if (options.docs !== false)
|
|
471
513
|
app.route(
|
|
472
514
|
'/',
|
|
473
|
-
|
|
515
|
+
vocs_Handler.openApi(
|
|
474
516
|
{
|
|
475
517
|
// Inline the OpenRPC doc so vocs expands it without a self-subrequest
|
|
476
518
|
// (see `inlineOpenrpc`); the public `/openapi.json` keeps the URL ref.
|
|
@@ -529,8 +571,12 @@ export declare namespace create {
|
|
|
529
571
|
compress?: boolean | Compress | undefined
|
|
530
572
|
/** CORS for API handlers. Enabled by default (wildcard origin, no credentials); pass a config object to customize, true for defaults, or false to disable (e.g. when CORS is handled by a fronting gateway/CDN). */
|
|
531
573
|
cors?: boolean | Cors | undefined
|
|
574
|
+
/** Authoritative Postgres store backing stateful features (webhooks, verified tokens). Pass a long-lived `Db.postgres(...)` singleton on Node, or a factory (`() => Db.postgres(...)`) on Workers so each request builds a fresh Hyperdrive-pooled connection. Pass `{ source, cached }` to route staleness-tolerant reads (verified tokens) through a second, query-caching Hyperdrive config; `source` stays uncached for read-after-write correctness. Apply migrations out of band via `tapimo admin migrate`. */
|
|
575
|
+
db: Db.Source | { cached?: Db.Source | undefined; source: Db.Source }
|
|
532
576
|
/** Default Tempo chain id for requests that omit `chainId`. */
|
|
533
577
|
defaultChainId?: Viem.ChainId | undefined
|
|
578
|
+
/** KV state store backing API-key records (e.g. `Store.cloudflareKv(...)`). Omit to leave API-key auth closed — every token resolves to `null`. */
|
|
579
|
+
kv?: Kv | undefined
|
|
534
580
|
/** Vocs API reference options. Set to false to disable the `/` docs page. */
|
|
535
581
|
docs?: Docs | false | undefined
|
|
536
582
|
/** Request logging. Disabled by default. Pass `true` to emit one canonical structured log entry per request through the level-matched console method (indexed by Workers Logs); a level (e.g. `'warn'`) to emit only that level and above (dropping successful-request info lines); or an emit function for a custom sink. */
|
|
@@ -549,10 +595,21 @@ export declare namespace create {
|
|
|
549
595
|
rpc?: Rpc | undefined
|
|
550
596
|
/** TIDX query client options. */
|
|
551
597
|
tidx?: Tidx.getClient.Tidx | undefined
|
|
552
|
-
/** Verified-token feature configuration.
|
|
553
|
-
verifiedTokens?: VerifiedTokens | undefined
|
|
554
|
-
/** Webhook feature configuration.
|
|
555
|
-
webhook?: Webhook | undefined
|
|
598
|
+
/** Verified-token feature configuration. Enabled by default (token rows live in `db`; empty until published); pass `false` to always serve an empty verified list. */
|
|
599
|
+
verifiedTokens?: VerifiedTokens | false | undefined
|
|
600
|
+
/** Webhook feature configuration. Enabled by default (subscription/delivery rows live in `db`). Pass `false` to disable: the `/webhooks` routes stay mounted (for a stable schema and typed test client) but return `404` and are hidden from the OpenAPI document. */
|
|
601
|
+
webhook?: Webhook | false | undefined
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
/** KV state-store configuration. */
|
|
605
|
+
type Kv = {
|
|
606
|
+
/**
|
|
607
|
+
* State store backing API-key records. Must be a {@link Store.State}
|
|
608
|
+
* (e.g. `Store.cloudflareKv(...)` or `Store.memory()`); the Web Cache
|
|
609
|
+
* adapter (`Store.cache`) is rejected at the type level since a miss there
|
|
610
|
+
* would silently drop authoritative state.
|
|
611
|
+
*/
|
|
612
|
+
store: Store.State
|
|
556
613
|
}
|
|
557
614
|
|
|
558
615
|
/** Caching configuration. */
|
|
@@ -568,17 +625,10 @@ export declare namespace create {
|
|
|
568
625
|
/**
|
|
569
626
|
* Per-isolate soft-refresh window in milliseconds. Within this window
|
|
570
627
|
* verified-token lookups are served from the in-memory snapshot with no
|
|
571
|
-
*
|
|
628
|
+
* database I/O; after it, the next lookup re-checks the head version
|
|
572
629
|
* (single-flight) and recompiles only when it changed. Defaults to 10_000.
|
|
573
630
|
*/
|
|
574
631
|
refreshMs?: number | undefined
|
|
575
|
-
/**
|
|
576
|
-
* State store backing the curated verified-token snapshots. Must be a
|
|
577
|
-
* {@link Store.State} (e.g. `Store.cloudflareKv(...)`, `Store.durableObject(...)`,
|
|
578
|
-
* or `Store.memory()`); the Web Cache adapter (`Store.cache`) is rejected at
|
|
579
|
-
* the type level since a miss there would silently drop authoritative state.
|
|
580
|
-
*/
|
|
581
|
-
store: Store.State
|
|
582
632
|
}
|
|
583
633
|
|
|
584
634
|
/** Webhook feature configuration. */
|
|
@@ -589,13 +639,6 @@ export declare namespace create {
|
|
|
589
639
|
* spam/SSRF surface and unbounded downstream delivery cost per owner.
|
|
590
640
|
*/
|
|
591
641
|
maxPerOwner?: number | undefined
|
|
592
|
-
/**
|
|
593
|
-
* State store backing webhook subscriptions, cursors, and the delivery
|
|
594
|
-
* log. Must be a {@link Store.State} (e.g. `Store.durableObject(...)` or
|
|
595
|
-
* `Store.memory()`); the Web Cache adapter (`Store.cache`) is rejected at
|
|
596
|
-
* the type level since a miss there would silently drop authoritative state.
|
|
597
|
-
*/
|
|
598
|
-
store: Store.State
|
|
599
642
|
}
|
|
600
643
|
|
|
601
644
|
/** Binary asset store for static API assets (e.g. `Assets.cloudflareR2(...)`). */
|
|
@@ -608,7 +651,7 @@ export declare namespace create {
|
|
|
608
651
|
type Cors = NonNullable<Parameters<typeof hono_cors>[0]>
|
|
609
652
|
|
|
610
653
|
/** Vocs API reference options (theme, top navigation, logo, socials, …). */
|
|
611
|
-
type Docs = NonNullable<Parameters<typeof
|
|
654
|
+
type Docs = NonNullable<Parameters<typeof vocs_Handler.openApi>[0]['vocs']>
|
|
612
655
|
|
|
613
656
|
/** Custom request-log sink, receiving one canonical entry per request. */
|
|
614
657
|
type Logger = Log.Emit
|
package/src/Client.test-d.ts
CHANGED
package/src/Handler.ts
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { Hono } from 'hono'
|
|
2
|
+
import type { ExtractSchema, MergeSchemaPath, Schema } from 'hono/types'
|
|
3
|
+
|
|
4
|
+
import type { UnionToIntersection } from './handlers/internal/types.js'
|
|
5
|
+
import * as RequestListener from './handlers/internal/requestListener.js'
|
|
6
|
+
|
|
7
|
+
export { relay } from './handlers/relay.js'
|
|
8
|
+
export * as Multisig from './handlers/internal/multisig.js'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* A request handler. Generic over the underlying Hono app shape so chained
|
|
12
|
+
* route definitions (`.post`, `.get`, …) can flow their type information
|
|
13
|
+
* through to consumers — most notably for Hono's typed RPC client
|
|
14
|
+
* (`hc<typeof handler>`).
|
|
15
|
+
*/
|
|
16
|
+
export type Handler<app extends Hono<any, any, any> = Hono> = app & {
|
|
17
|
+
listener: (req: any, res: any) => void
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Merges the route schemas of every sub-handler into a single Hono schema,
|
|
22
|
+
* prefixing each route key with `path`. Used by `compose()` to preserve
|
|
23
|
+
* typed routes for Hono's RPC client (`hc`).
|
|
24
|
+
*/
|
|
25
|
+
export type ComposedSchema<subs extends readonly unknown[], path extends string> =
|
|
26
|
+
UnionToIntersection<
|
|
27
|
+
subs[number] extends infer sub
|
|
28
|
+
? sub extends unknown
|
|
29
|
+
? MergeSchemaPath<Extract<ExtractSchema<sub>, Schema>, path>
|
|
30
|
+
: never
|
|
31
|
+
: never
|
|
32
|
+
> extends infer schema extends Schema
|
|
33
|
+
? schema
|
|
34
|
+
: never
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Mounts each sub-handler onto a fresh Hono app at `path` (default `/`) and
|
|
38
|
+
* returns a single composed handler. Routes are dispatched by Hono's trie
|
|
39
|
+
* — a deterministic match per path — instead of the older "try each handler
|
|
40
|
+
* in order until one returns non-404" loop.
|
|
41
|
+
*
|
|
42
|
+
* The returned handler preserves each sub-handler's route schema so the
|
|
43
|
+
* composed app stays usable from Hono's typed RPC client:
|
|
44
|
+
*
|
|
45
|
+
* ```ts
|
|
46
|
+
* import { hc } from 'hono/client'
|
|
47
|
+
*
|
|
48
|
+
* const app = Handler.compose(
|
|
49
|
+
* [Handler.exchange(), Handler.codeAuth({ store })],
|
|
50
|
+
* { path: '/api' },
|
|
51
|
+
* )
|
|
52
|
+
* type App = typeof app
|
|
53
|
+
*
|
|
54
|
+
* const client = hc<App>('https://wallet.example.com')
|
|
55
|
+
* await client.api.exchange.quote.$post({ json: { ... } }) // typed
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
export function compose<const subs extends readonly Handler[], const path extends string = '/'>(
|
|
59
|
+
handlers: subs,
|
|
60
|
+
options: compose.Options & { path?: path } = {},
|
|
61
|
+
): Handler<Hono<{}, ComposedSchema<subs, path>, '/'>> {
|
|
62
|
+
const mountPath = (options.path ?? '/') as path
|
|
63
|
+
|
|
64
|
+
const app = from(options) as unknown as Hono
|
|
65
|
+
for (const sub of handlers) app.route(mountPath, sub)
|
|
66
|
+
app.notFound(() => new Response('Not Found', { status: 404 }))
|
|
67
|
+
|
|
68
|
+
return app as never
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export declare namespace compose {
|
|
72
|
+
export type Options = from.Options & {
|
|
73
|
+
/** The path to use for the handler. */
|
|
74
|
+
path?: string | undefined
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Instantiates a new request handler.
|
|
80
|
+
*
|
|
81
|
+
* @param options - constructor options
|
|
82
|
+
* @returns Handler instance
|
|
83
|
+
*/
|
|
84
|
+
export function from(options: from.Options = {}): Handler {
|
|
85
|
+
const corsHeaders = corsToHeaders(options.cors)
|
|
86
|
+
const mergedHeaders = new Headers(corsHeaders)
|
|
87
|
+
for (const [key, value] of normalizeHeaders(options.headers).entries())
|
|
88
|
+
mergedHeaders.set(key, value)
|
|
89
|
+
|
|
90
|
+
const app = new Hono()
|
|
91
|
+
|
|
92
|
+
app.use(async (c, next) => {
|
|
93
|
+
if (c.req.method === 'OPTIONS') return new Response(null, { headers: mergedHeaders })
|
|
94
|
+
await next()
|
|
95
|
+
for (const [key, value] of mergedHeaders.entries()) c.res.headers.set(key, value)
|
|
96
|
+
return undefined
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
return Object.assign(app, {
|
|
100
|
+
listener: RequestListener.fromFetchHandler((request) => app.fetch(request)),
|
|
101
|
+
}) as never
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export declare namespace from {
|
|
105
|
+
export type Options = {
|
|
106
|
+
/**
|
|
107
|
+
* CORS configuration.
|
|
108
|
+
* - `true` (default): Allow all origins with default methods/headers
|
|
109
|
+
* - `false`: Disable CORS headers
|
|
110
|
+
* - Object: Custom CORS configuration
|
|
111
|
+
*/
|
|
112
|
+
cors?: boolean | Cors | undefined
|
|
113
|
+
/** Headers to add to the response. */
|
|
114
|
+
headers?: Headers | Record<string, string> | undefined
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export type Cors = {
|
|
118
|
+
/** Allowed origins. Defaults to `'*'`. */
|
|
119
|
+
origin?: string | string[] | undefined
|
|
120
|
+
/** Allowed methods. Defaults to `'GET, POST, PUT, DELETE, OPTIONS'`. */
|
|
121
|
+
methods?: string | undefined
|
|
122
|
+
/** Allowed headers. Defaults to `'Content-Type'`. */
|
|
123
|
+
headers?: string | undefined
|
|
124
|
+
/** Whether to allow credentials. */
|
|
125
|
+
credentials?: boolean | undefined
|
|
126
|
+
/** Headers to expose to the browser. */
|
|
127
|
+
exposeHeaders?: string | undefined
|
|
128
|
+
/** Max age for preflight cache in seconds. */
|
|
129
|
+
maxAge?: number | undefined
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function normalizeHeaders(headers?: Headers | Record<string, string>): Headers {
|
|
134
|
+
if (!headers) return new Headers()
|
|
135
|
+
if (headers instanceof Headers) return headers
|
|
136
|
+
return new Headers(headers)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function corsToHeaders(cors?: boolean | from.Cors): Headers {
|
|
140
|
+
if (cors === false) return new Headers()
|
|
141
|
+
|
|
142
|
+
const config = cors === true || cors === undefined ? {} : cors
|
|
143
|
+
|
|
144
|
+
const headers = new Headers()
|
|
145
|
+
const origin = Array.isArray(config.origin) ? config.origin.join(', ') : (config.origin ?? '*')
|
|
146
|
+
headers.set('Access-Control-Allow-Origin', origin)
|
|
147
|
+
headers.set('Access-Control-Allow-Methods', config.methods ?? 'GET, POST, PUT, DELETE, OPTIONS')
|
|
148
|
+
headers.set('Access-Control-Allow-Headers', config.headers ?? 'Content-Type')
|
|
149
|
+
if (config.credentials) headers.set('Access-Control-Allow-Credentials', 'true')
|
|
150
|
+
if (config.exposeHeaders) headers.set('Access-Control-Expose-Headers', config.exposeHeaders)
|
|
151
|
+
if (config.maxAge !== undefined) headers.set('Access-Control-Max-Age', String(config.maxAge))
|
|
152
|
+
|
|
153
|
+
return headers
|
|
154
|
+
}
|
package/src/Scope.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The issuable API-key scope vocabulary: the single source of truth for the
|
|
3
|
-
* `/scopes` catalog, for key minting (`
|
|
3
|
+
* `/scopes` catalog, for key minting (`ApiKeys.mint`), and for the scopes
|
|
4
4
|
* required by route policies (`Auth.policy`). Exposing it publicly lets operator
|
|
5
5
|
* tooling (e.g. the provisioning CLI) validate scopes against the same list the
|
|
6
6
|
* server enforces.
|
|
@@ -36,6 +36,10 @@ export const catalog = [
|
|
|
36
36
|
description: 'Run ad-hoc queries against the raw indexer passthrough.',
|
|
37
37
|
scope: 'indexer:query',
|
|
38
38
|
},
|
|
39
|
+
{
|
|
40
|
+
description: 'Fill and submit transactions through the relay.',
|
|
41
|
+
scope: 'rpc-relay',
|
|
42
|
+
},
|
|
39
43
|
{
|
|
40
44
|
description: 'Read webhook subscriptions.',
|
|
41
45
|
scope: 'webhooks:read',
|
package/src/Webhooks.test.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Webhooks } from 'tapimo'
|
|
2
|
+
import * as TestApp from '../test/App.js'
|
|
2
3
|
|
|
3
4
|
describe('createPoller', () => {
|
|
4
5
|
/** Decodes the opaque `(block, index)` keyset cursor the poller stores. */
|
|
@@ -8,9 +9,9 @@ describe('createPoller', () => {
|
|
|
8
9
|
return JSON.parse(json) as [number, number]
|
|
9
10
|
}
|
|
10
11
|
|
|
11
|
-
test('binds a
|
|
12
|
-
const
|
|
13
|
-
const subscription = await Webhooks.createSubscription(
|
|
12
|
+
test('binds a database and runs a tick at an injected head', async () => {
|
|
13
|
+
const db = TestApp.database()
|
|
14
|
+
const subscription = await Webhooks.createSubscription(db, {
|
|
14
15
|
chainId: 42431,
|
|
15
16
|
destination: { type: 'url', url: 'https://hooks.example.com/x' },
|
|
16
17
|
eventType: 'token:transfer',
|
|
@@ -18,12 +19,12 @@ describe('createPoller', () => {
|
|
|
18
19
|
})
|
|
19
20
|
|
|
20
21
|
const poller = Webhooks.createPoller({
|
|
22
|
+
db,
|
|
21
23
|
defaultChainId: 42431,
|
|
22
|
-
webhook: { store },
|
|
23
24
|
})
|
|
24
25
|
|
|
25
26
|
// A brand-new subscription anchors at the confirmed head on its first tick
|
|
26
|
-
// (no backfill), so this exercises the createPoller → poll wiring (
|
|
27
|
+
// (no backfill), so this exercises the createPoller → poll wiring (db
|
|
27
28
|
// binding, default chain id, injected head, result shape) without touching
|
|
28
29
|
// TIDX. Detailed poll/getIndexedHead behavior lives in
|
|
29
30
|
// src/apps/webhooks.test.ts.
|
|
@@ -39,7 +40,7 @@ describe('createPoller', () => {
|
|
|
39
40
|
"subscriptions": 1,
|
|
40
41
|
}
|
|
41
42
|
`)
|
|
42
|
-
expect(decodeCursor(await Webhooks.getCursor(
|
|
43
|
+
expect(decodeCursor(await Webhooks.getCursor(db, subscription.id))).toMatchInlineSnapshot(`
|
|
43
44
|
[
|
|
44
45
|
198,
|
|
45
46
|
2147483647,
|
package/src/Webhooks.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as AppWebhooks from './apps/webhooks.js'
|
|
2
|
+
import * as Db from './db/Db.js'
|
|
2
3
|
import * as MetricSink from './internal/MetricSink.js'
|
|
3
|
-
import
|
|
4
|
+
import * as Store from './internal/Store.js'
|
|
4
5
|
import type * as Metrics from './Metrics.js'
|
|
5
6
|
import * as Tidx from './internal/Tidx.js'
|
|
6
7
|
import * as Viem from './internal/Viem.js'
|
|
@@ -35,7 +36,9 @@ export { eventTypes, schema } from './apps/webhooks.js'
|
|
|
35
36
|
* request state.
|
|
36
37
|
*/
|
|
37
38
|
export function createPoller(options: createPoller.Options): createPoller.Poller {
|
|
38
|
-
|
|
39
|
+
// Persistent memoize cache for token metadata, shared across ticks (the
|
|
40
|
+
// webhook state store used to double-duty as this cache).
|
|
41
|
+
const cache = Store.memory()
|
|
39
42
|
const getTidx = options.tidx
|
|
40
43
|
? Tidx.createGetClient({ defaultChainId: options.defaultChainId, tidx: options.tidx })
|
|
41
44
|
: undefined
|
|
@@ -80,9 +83,13 @@ export function createPoller(options: createPoller.Options): createPoller.Poller
|
|
|
80
83
|
try {
|
|
81
84
|
const result = await AppWebhooks.poll({
|
|
82
85
|
batchSize: input.batchSize ?? options.batchSize,
|
|
86
|
+
cache,
|
|
83
87
|
chainId,
|
|
84
88
|
client: getClient(chainId),
|
|
85
89
|
confirmations: input.confirmations ?? options.confirmations,
|
|
90
|
+
// Resolve per tick so Workers factories build a fresh
|
|
91
|
+
// Hyperdrive-pooled connection each run.
|
|
92
|
+
db: Db.get(options.db),
|
|
86
93
|
dispatch,
|
|
87
94
|
eventTypes: input.eventTypes ?? options.eventTypes,
|
|
88
95
|
...(input.headBlockNumber === undefined
|
|
@@ -90,7 +97,6 @@ export function createPoller(options: createPoller.Options): createPoller.Poller
|
|
|
90
97
|
: { headBlockNumber: input.headBlockNumber }),
|
|
91
98
|
maxBlocks: input.maxBlocks ?? options.maxBlocks,
|
|
92
99
|
now: input.now ?? options.now,
|
|
93
|
-
store,
|
|
94
100
|
...(getTidx ? { tidx: getTidx(chainId) } : {}),
|
|
95
101
|
})
|
|
96
102
|
metrics?.record({
|
|
@@ -144,6 +150,8 @@ export declare namespace createPoller {
|
|
|
144
150
|
|
|
145
151
|
/** Options for {@link createPoller}. */
|
|
146
152
|
type Options = Defaults & {
|
|
153
|
+
/** Database holding subscriptions + cursors, or a factory resolved per tick (Workers/Hyperdrive). */
|
|
154
|
+
db: Db.Source
|
|
147
155
|
/** Tempo chain id polled when a tick omits one. */
|
|
148
156
|
defaultChainId?: Viem.ChainId | undefined
|
|
149
157
|
/** Metrics backend; emits `webhook:tick` per tick and `webhook:enqueue` per dispatch. */
|
|
@@ -152,8 +160,6 @@ export declare namespace createPoller {
|
|
|
152
160
|
rpc?: Viem.getClient.Rpc | undefined
|
|
153
161
|
/** TIDX query client options for the legacy `token:transfer` type. */
|
|
154
162
|
tidx?: Tidx.getClient.Tidx | undefined
|
|
155
|
-
/** Webhook state store (subscriptions + cursors). */
|
|
156
|
-
webhook: { store: Store.State }
|
|
157
163
|
}
|
|
158
164
|
|
|
159
165
|
/** Per-tick overrides; falls back to the {@link Options} defaults. */
|
package/src/admin/Access.ts
CHANGED
|
@@ -29,10 +29,8 @@ const cookieName = 'CF_Authorization'
|
|
|
29
29
|
* ```ts
|
|
30
30
|
* import { App, Access } from 'tapimo/admin'
|
|
31
31
|
*
|
|
32
|
-
* const source = {} as App.create.Options['apiKey']['source']
|
|
33
|
-
*
|
|
34
32
|
* App.create({
|
|
35
|
-
*
|
|
33
|
+
* // ...
|
|
36
34
|
* identify: Access.cloudflareAccess({
|
|
37
35
|
* teamDomain: 'https://tempo.cloudflareaccess.com',
|
|
38
36
|
* }),
|
package/src/admin/App.ts
CHANGED
|
@@ -3,10 +3,10 @@ import { HTTPException } from 'hono/http-exception'
|
|
|
3
3
|
import { requestId, type RequestIdVariables } from 'hono/request-id'
|
|
4
4
|
|
|
5
5
|
import * as ApiKey from '../ApiKey.js'
|
|
6
|
-
import * as ApiKeySource from '../ApiKeySource.js'
|
|
7
6
|
import type * as Assets from '../Assets.js'
|
|
8
|
-
import * as
|
|
7
|
+
import type * as Db from '../db/Db.js'
|
|
9
8
|
import type * as Store from '../internal/Store.js'
|
|
9
|
+
import * as Response from '../internal/Response.js'
|
|
10
10
|
|
|
11
11
|
import { apiKeys } from './apps/api-keys.js'
|
|
12
12
|
import { scopes } from './apps/scopes.js'
|
|
@@ -24,12 +24,12 @@ export type Environment = {
|
|
|
24
24
|
Variables: RequestIdVariables & {
|
|
25
25
|
/** Static-asset writer (e.g. the R2 icon bucket), when configured. */
|
|
26
26
|
assets: create.Assets | undefined
|
|
27
|
+
/** Authoritative Postgres store, or a per-request factory. Resolve at the leaf with `Db.get`. */
|
|
28
|
+
db: Db.Source
|
|
29
|
+
/** KV state store backing API-key records (mint/list/revoke). */
|
|
30
|
+
kv: { store: Store.State }
|
|
27
31
|
/** The admin identity, or `null` when unauthenticated (gated routes reject). */
|
|
28
32
|
identity: Identity | null
|
|
29
|
-
/** The API-key source provisioned against (mint/list/revoke). */
|
|
30
|
-
source: ApiKeySource.Source
|
|
31
|
-
/** Verified-token state store, when management is enabled. */
|
|
32
|
-
verifiedTokens: { store: Store.State } | undefined
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
35
|
|
|
@@ -39,7 +39,6 @@ export type App = ReturnType<typeof create>
|
|
|
39
39
|
/** Creates the admin portal Hono app. */
|
|
40
40
|
export function create(options: create.Options) {
|
|
41
41
|
const { identify } = options
|
|
42
|
-
const { source } = options.apiKey
|
|
43
42
|
// `false` (disabled) collapses to `undefined` so only a real config surfaces
|
|
44
43
|
// `loginUrl`/`logoutUrl` in `/config.json`.
|
|
45
44
|
const ui = options.ui || undefined
|
|
@@ -55,9 +54,9 @@ export function create(options: create.Options) {
|
|
|
55
54
|
app.use('*', async (c, next) => {
|
|
56
55
|
// Admin data is per-principal and mutating: never cache.
|
|
57
56
|
c.header('Cache-Control', 'no-store')
|
|
58
|
-
c.set('source', source)
|
|
59
57
|
c.set('assets', options.assets)
|
|
60
|
-
c.set('
|
|
58
|
+
c.set('db', options.db)
|
|
59
|
+
c.set('kv', options.kv)
|
|
61
60
|
c.set('identity', await identify(c.req.raw))
|
|
62
61
|
return next()
|
|
63
62
|
})
|
|
@@ -173,16 +172,22 @@ export declare namespace create {
|
|
|
173
172
|
|
|
174
173
|
/** Options for {@link create}. */
|
|
175
174
|
type Options = {
|
|
176
|
-
/** API-key provisioning config (mirrors the API app's `apiKey` option). */
|
|
177
|
-
apiKey: {
|
|
178
|
-
/** Source to provision against (mint/list/revoke). */
|
|
179
|
-
source: ApiKeySource.Source
|
|
180
|
-
}
|
|
181
175
|
/**
|
|
182
176
|
* Static-asset writer for logo uploads (shared with the main API's `assets`
|
|
183
177
|
* loader) to enable `PUT /verified-tokens/:address/logo`; omit for `404`.
|
|
184
178
|
*/
|
|
185
179
|
assets?: Assets | undefined
|
|
180
|
+
/**
|
|
181
|
+
* Authoritative Postgres store shared with the main API (verified
|
|
182
|
+
* tokens). Pass a long-lived `Db.postgres(...)` singleton on Node, or a
|
|
183
|
+
* factory (`() => Db.postgres(...)`) on Workers.
|
|
184
|
+
*/
|
|
185
|
+
db: Db.Source
|
|
186
|
+
/**
|
|
187
|
+
* KV state store backing API-key records (shared with the main API's
|
|
188
|
+
* `kv` option), provisioned against by the `/api-keys` routes.
|
|
189
|
+
*/
|
|
190
|
+
kv: { store: Store.State }
|
|
186
191
|
/**
|
|
187
192
|
* Extracts the verified admin identity from a request, or `null` to deny.
|
|
188
193
|
* Must only return an identity for an already-authenticated request (a
|
|
@@ -194,16 +199,6 @@ export declare namespace create {
|
|
|
194
199
|
* `loginUrl`/`logoutUrl` are surfaced in `GET /config.json`.
|
|
195
200
|
*/
|
|
196
201
|
ui?: Ui | false | undefined
|
|
197
|
-
/**
|
|
198
|
-
* Verified-token management config (store shared with the main API's reads)
|
|
199
|
-
* to enable the `/verified-tokens` routes; omit to serve them as `404`.
|
|
200
|
-
*/
|
|
201
|
-
verifiedTokens?:
|
|
202
|
-
| {
|
|
203
|
-
/** Verified-token state store (shared with the main API's reads). */
|
|
204
|
-
store: Store.State
|
|
205
|
-
}
|
|
206
|
-
| undefined
|
|
207
202
|
}
|
|
208
203
|
|
|
209
204
|
/** Admin UI options surfaced to the SPA via `GET /config.json`. */
|