tapimo 0.5.2 → 0.6.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/dist/App.d.ts +13987 -5755
- package/dist/App.d.ts.map +1 -1
- package/dist/App.js.map +1 -1
- package/dist/admin/App.d.ts +910 -412
- package/dist/admin/App.d.ts.map +1 -1
- package/dist/admin/App.js +4 -0
- package/dist/admin/App.js.map +1 -1
- package/dist/admin/apps/early-access.d.ts +148 -0
- package/dist/admin/apps/early-access.d.ts.map +1 -0
- package/dist/admin/apps/early-access.js +133 -0
- package/dist/admin/apps/early-access.js.map +1 -0
- package/dist/admin/apps/verified-tokens.d.ts +643 -118
- package/dist/admin/apps/verified-tokens.d.ts.map +1 -1
- package/dist/admin/apps/verified-tokens.js +214 -1
- package/dist/admin/apps/verified-tokens.js.map +1 -1
- package/dist/admin/ui.generated.d.ts +1 -1
- package/dist/admin/ui.generated.d.ts.map +1 -1
- package/dist/admin/ui.generated.js +45 -33
- package/dist/admin/ui.generated.js.map +1 -1
- package/dist/apps/data/App.d.ts +3733 -2428
- package/dist/apps/data/App.d.ts.map +1 -1
- package/dist/apps/data/App.js +11 -6
- package/dist/apps/data/App.js.map +1 -1
- package/dist/apps/data/FundingQuoteProviders.d.ts +34 -1
- package/dist/apps/data/FundingQuoteProviders.d.ts.map +1 -1
- package/dist/apps/data/FundingQuoteProviders.js +193 -2
- package/dist/apps/data/FundingQuoteProviders.js.map +1 -1
- package/dist/apps/data/FxOracle.d.ts +71 -0
- package/dist/apps/data/FxOracle.d.ts.map +1 -0
- package/dist/apps/data/FxOracle.js +97 -0
- package/dist/apps/data/FxOracle.js.map +1 -0
- package/dist/apps/data/index.d.ts +1 -0
- package/dist/apps/data/index.d.ts.map +1 -1
- package/dist/apps/data/index.js +1 -0
- package/dist/apps/data/index.js.map +1 -1
- package/dist/apps/data/routes/activities.d.ts +3575 -1358
- package/dist/apps/data/routes/activities.d.ts.map +1 -1
- package/dist/apps/data/routes/activities.js +230 -88
- package/dist/apps/data/routes/activities.js.map +1 -1
- package/dist/apps/data/routes/addresses.d.ts +963 -342
- package/dist/apps/data/routes/addresses.d.ts.map +1 -1
- package/dist/apps/data/routes/addresses.js +11 -9
- package/dist/apps/data/routes/addresses.js.map +1 -1
- package/dist/apps/data/routes/balances.d.ts +108 -57
- package/dist/apps/data/routes/balances.d.ts.map +1 -1
- package/dist/apps/data/routes/balances.js +88 -45
- package/dist/apps/data/routes/balances.js.map +1 -1
- package/dist/apps/data/routes/coingecko.d.ts +169 -169
- package/dist/apps/data/routes/exchanges.d.ts +952 -201
- package/dist/apps/data/routes/exchanges.d.ts.map +1 -1
- package/dist/apps/data/routes/exchanges.js +518 -76
- package/dist/apps/data/routes/exchanges.js.map +1 -1
- package/dist/apps/data/routes/fee-amm.d.ts +24 -24
- package/dist/apps/data/routes/funding.d.ts +8 -2
- package/dist/apps/data/routes/funding.d.ts.map +1 -1
- package/dist/apps/data/routes/funding.js +32 -6
- package/dist/apps/data/routes/funding.js.map +1 -1
- package/dist/apps/data/routes/indexer.d.ts +5 -5
- package/dist/apps/data/routes/receipts.d.ts +392 -315
- package/dist/apps/data/routes/receipts.d.ts.map +1 -1
- package/dist/apps/data/routes/receipts.js +152 -77
- package/dist/apps/data/routes/receipts.js.map +1 -1
- package/dist/apps/data/routes/tokens.d.ts +166 -173
- package/dist/apps/data/routes/tokens.d.ts.map +1 -1
- package/dist/apps/data/routes/tokens.js +14 -12
- package/dist/apps/data/routes/tokens.js.map +1 -1
- package/dist/apps/data/routes/transactions.d.ts +277 -536
- package/dist/apps/data/routes/transactions.d.ts.map +1 -1
- package/dist/apps/data/routes/transactions.js +96 -77
- package/dist/apps/data/routes/transactions.js.map +1 -1
- package/dist/apps/data/routes/transfers.d.ts +82 -17
- package/dist/apps/data/routes/transfers.d.ts.map +1 -1
- package/dist/apps/data/routes/transfers.js +70 -13
- package/dist/apps/data/routes/transfers.js.map +1 -1
- package/dist/apps/data/routes/valuation.d.ts +307 -0
- package/dist/apps/data/routes/valuation.d.ts.map +1 -0
- package/dist/apps/data/routes/valuation.js +292 -0
- package/dist/apps/data/routes/valuation.js.map +1 -0
- package/dist/apps/data/routes/verified-tokens.d.ts.map +1 -1
- package/dist/apps/data/routes/verified-tokens.js +7 -5
- package/dist/apps/data/routes/verified-tokens.js.map +1 -1
- package/dist/apps/data/routes/webhooks.d.ts +459 -479
- package/dist/apps/data/routes/webhooks.d.ts.map +1 -1
- package/dist/apps/data/routes/webhooks.js +124 -25
- package/dist/apps/data/routes/webhooks.js.map +1 -1
- package/dist/apps/index.d.ts +1 -1
- package/dist/apps/index.d.ts.map +1 -1
- package/dist/apps/index.js +1 -1
- package/dist/apps/index.js.map +1 -1
- package/dist/apps/management/App.d.ts +8606 -1681
- package/dist/apps/management/App.d.ts.map +1 -1
- package/dist/apps/management/App.js +12 -1
- package/dist/apps/management/App.js.map +1 -1
- package/dist/apps/management/routes/api-keys.d.ts +23 -0
- package/dist/apps/management/routes/api-keys.d.ts.map +1 -1
- package/dist/apps/management/routes/api-keys.js +5 -3
- package/dist/apps/management/routes/api-keys.js.map +1 -1
- package/dist/apps/management/routes/billing.d.ts +88 -48
- package/dist/apps/management/routes/billing.d.ts.map +1 -1
- package/dist/apps/management/routes/billing.js +10 -2
- package/dist/apps/management/routes/billing.js.map +1 -1
- package/dist/apps/management/routes/faucet.d.ts +218 -0
- package/dist/apps/management/routes/faucet.d.ts.map +1 -0
- package/dist/apps/management/routes/faucet.js +139 -0
- package/dist/apps/management/routes/faucet.js.map +1 -0
- package/dist/apps/management/routes/invitations.d.ts +121 -121
- package/dist/apps/management/routes/invitations.d.ts.map +1 -1
- package/dist/apps/management/routes/invitations.js +3 -0
- package/dist/apps/management/routes/invitations.js.map +1 -1
- package/dist/apps/management/routes/invite-links.d.ts +197 -168
- package/dist/apps/management/routes/invite-links.d.ts.map +1 -1
- package/dist/apps/management/routes/invite-links.js +26 -1
- package/dist/apps/management/routes/invite-links.js.map +1 -1
- package/dist/apps/management/routes/me.d.ts +2 -0
- package/dist/apps/management/routes/me.d.ts.map +1 -1
- package/dist/apps/management/routes/me.js +4 -0
- package/dist/apps/management/routes/me.js.map +1 -1
- package/dist/apps/management/routes/members.d.ts +49 -49
- package/dist/apps/management/routes/orgs.d.ts +91 -91
- package/dist/apps/management/routes/projects.d.ts +115 -115
- package/dist/apps/management/routes/usage.d.ts +35 -32
- package/dist/apps/management/routes/usage.d.ts.map +1 -1
- package/dist/apps/management/routes/usage.js +4 -0
- package/dist/apps/management/routes/usage.js.map +1 -1
- package/dist/apps/management/routes/verified-token-requests.d.ts +697 -0
- package/dist/apps/management/routes/verified-token-requests.d.ts.map +1 -0
- package/dist/apps/management/routes/verified-token-requests.js +271 -0
- package/dist/apps/management/routes/verified-token-requests.js.map +1 -0
- package/dist/apps/management/routes/webhooks.d.ts +6977 -0
- package/dist/apps/management/routes/webhooks.d.ts.map +1 -0
- package/dist/apps/management/routes/webhooks.js +573 -0
- package/dist/apps/management/routes/webhooks.js.map +1 -0
- package/dist/apps/mcp.d.ts +9 -2
- package/dist/apps/mcp.d.ts.map +1 -1
- package/dist/apps/mcp.js +9 -4
- package/dist/apps/mcp.js.map +1 -1
- package/dist/cli/Cli.d.ts.map +1 -1
- package/dist/cli/Cli.js +7 -2
- package/dist/cli/Cli.js.map +1 -1
- package/dist/db/Db.d.ts +4 -0
- package/dist/db/Db.d.ts.map +1 -1
- package/dist/db/Db.js +83 -0
- package/dist/db/Db.js.map +1 -1
- package/dist/db/Schema.d.ts +41 -0
- package/dist/db/Schema.d.ts.map +1 -1
- package/dist/db/Schema.js +45 -0
- package/dist/db/Schema.js.map +1 -1
- package/dist/db/tables/earlyAccess.d.ts +50 -0
- package/dist/db/tables/earlyAccess.d.ts.map +1 -0
- package/dist/db/tables/earlyAccess.js +63 -0
- package/dist/db/tables/earlyAccess.js.map +1 -0
- package/dist/db/tables/invitations.d.ts +12 -1
- package/dist/db/tables/invitations.d.ts.map +1 -1
- package/dist/db/tables/invitations.js +23 -0
- package/dist/db/tables/invitations.js.map +1 -1
- package/dist/db/tables/inviteLinks.d.ts +10 -2
- package/dist/db/tables/inviteLinks.d.ts.map +1 -1
- package/dist/db/tables/inviteLinks.js +10 -1
- package/dist/db/tables/inviteLinks.js.map +1 -1
- package/dist/db/tables/sponsoredTransactions.d.ts +2 -0
- package/dist/db/tables/sponsoredTransactions.d.ts.map +1 -1
- package/dist/db/tables/sponsoredTransactions.js +2 -0
- package/dist/db/tables/sponsoredTransactions.js.map +1 -1
- package/dist/db/tables/verifiedTokenRequests.d.ts +94 -0
- package/dist/db/tables/verifiedTokenRequests.d.ts.map +1 -0
- package/dist/db/tables/verifiedTokenRequests.js +120 -0
- package/dist/db/tables/verifiedTokenRequests.js.map +1 -0
- package/dist/db/tables/webhookSubscriptions.d.ts +14 -0
- package/dist/db/tables/webhookSubscriptions.d.ts.map +1 -1
- package/dist/db/tables/webhookSubscriptions.js +16 -0
- package/dist/db/tables/webhookSubscriptions.js.map +1 -1
- package/dist/internal/Auth.d.ts +43 -2
- package/dist/internal/Auth.d.ts.map +1 -1
- package/dist/internal/Auth.js +60 -45
- package/dist/internal/Auth.js.map +1 -1
- package/dist/internal/FundingQuotes.d.ts +69 -15
- package/dist/internal/FundingQuotes.d.ts.map +1 -1
- package/dist/internal/FundingQuotes.js +74 -25
- package/dist/internal/FundingQuotes.js.map +1 -1
- package/dist/internal/Log.d.ts +19 -1
- package/dist/internal/Log.d.ts.map +1 -1
- package/dist/internal/Log.js +2 -0
- package/dist/internal/Log.js.map +1 -1
- package/dist/internal/MetricSink.d.ts.map +1 -1
- package/dist/internal/MetricSink.js +9 -0
- package/dist/internal/MetricSink.js.map +1 -1
- package/dist/internal/OpenApi.d.ts +2 -2
- package/dist/internal/OpenApi.d.ts.map +1 -1
- package/dist/internal/OpenApi.js +3 -0
- package/dist/internal/OpenApi.js.map +1 -1
- package/dist/internal/Schema.d.ts +14 -5
- package/dist/internal/Schema.d.ts.map +1 -1
- package/dist/internal/Schema.js +23 -5
- package/dist/internal/Schema.js.map +1 -1
- package/dist/internal/Value.d.ts +18 -0
- package/dist/internal/Value.d.ts.map +1 -1
- package/dist/internal/Value.js +11 -1
- package/dist/internal/Value.js.map +1 -1
- package/dist/internal/VerifiedTokens.d.ts.map +1 -1
- package/dist/internal/VerifiedTokens.js +10 -1
- package/dist/internal/VerifiedTokens.js.map +1 -1
- package/dist/internal/Viem.d.ts +1 -1
- package/dist/internal/Webhooks.d.ts +24 -3
- package/dist/internal/Webhooks.d.ts.map +1 -1
- package/dist/internal/Webhooks.js +34 -8
- package/dist/internal/Webhooks.js.map +1 -1
- package/package.json +2 -2
- package/src/App.ts +9 -1
- package/src/Client.test-d.ts +638 -6
- package/src/admin/App.ts +4 -0
- package/src/admin/apps/early-access.test.ts +125 -0
- package/src/admin/apps/early-access.ts +175 -0
- package/src/admin/apps/verified-tokens.test.ts +201 -0
- package/src/admin/apps/verified-tokens.ts +532 -286
- package/src/admin/ui/plugin.ts +2 -1
- package/src/admin/ui/src/lib/early-access.ts +63 -0
- package/src/admin/ui/src/lib/verified-tokens.ts +79 -0
- package/src/admin/ui/src/routeTree.gen.ts +33 -2
- package/src/admin/ui/src/routes/__root.tsx +2 -1
- package/src/admin/ui/src/routes/early-access.tsx +157 -0
- package/src/admin/ui/src/routes/verified-tokens.tsx +230 -19
- package/src/admin/ui.generated.ts +48 -33
- package/src/apps/data/App.ts +22 -14
- package/src/apps/data/FundingQuoteProviders.test.ts +298 -0
- package/src/apps/data/FundingQuoteProviders.ts +257 -4
- package/src/apps/data/FxOracle.test.ts +118 -0
- package/src/apps/data/FxOracle.ts +153 -0
- package/src/apps/data/index.ts +1 -0
- package/src/apps/data/routes/activities.test.ts +173 -3
- package/src/apps/data/routes/activities.ts +403 -132
- package/src/apps/data/routes/addresses.ts +19 -9
- package/src/apps/data/routes/balances.test.ts +144 -11
- package/src/apps/data/routes/balances.ts +140 -51
- package/src/apps/data/routes/exchanges.test.ts +540 -9
- package/src/apps/data/routes/exchanges.ts +690 -103
- package/src/apps/data/routes/funding.test.ts +84 -0
- package/src/apps/data/routes/funding.ts +38 -6
- package/src/apps/data/routes/receipts.test.ts +125 -23
- package/src/apps/data/routes/receipts.ts +205 -100
- package/src/apps/data/routes/tokens.test.ts +43 -0
- package/src/apps/data/routes/tokens.ts +27 -12
- package/src/apps/data/routes/transactions.test.ts +72 -19
- package/src/apps/data/routes/transactions.ts +162 -92
- package/src/apps/data/routes/transfers.test.ts +146 -4
- package/src/apps/data/routes/transfers.ts +91 -13
- package/src/apps/data/routes/valuation.test.ts +422 -0
- package/src/apps/data/routes/valuation.ts +423 -0
- package/src/apps/data/routes/verified-tokens.ts +14 -2
- package/src/apps/data/routes/webhooks.test.ts +56 -8
- package/src/apps/data/routes/webhooks.ts +144 -27
- package/src/apps/index.ts +1 -1
- package/src/apps/management/App.ts +13 -0
- package/src/apps/management/routes/api-keys.test.ts +87 -0
- package/src/apps/management/routes/api-keys.ts +4 -3
- package/src/apps/management/routes/billing.test.ts +112 -1
- package/src/apps/management/routes/billing.ts +7 -2
- package/src/apps/management/routes/faucet.test.ts +79 -0
- package/src/apps/management/routes/faucet.ts +185 -0
- package/src/apps/management/routes/invitations.test.ts +172 -0
- package/src/apps/management/routes/invitations.ts +3 -0
- package/src/apps/management/routes/invite-links.test.ts +117 -0
- package/src/apps/management/routes/invite-links.ts +43 -1
- package/src/apps/management/routes/me.test.ts +140 -88
- package/src/apps/management/routes/me.ts +6 -0
- package/src/apps/management/routes/usage.test.ts +6 -0
- package/src/apps/management/routes/usage.ts +7 -0
- package/src/apps/management/routes/verified-token-requests.test.ts +377 -0
- package/src/apps/management/routes/verified-token-requests.ts +397 -0
- package/src/apps/management/routes/webhooks.test.ts +426 -0
- package/src/apps/management/routes/webhooks.ts +672 -0
- package/src/apps/mcp.test.ts +13 -0
- package/src/apps/mcp.ts +18 -5
- package/src/cli/Cli.test.ts +36 -0
- package/src/cli/Cli.ts +7 -2
- package/src/db/Db.test.ts +19 -0
- package/src/db/Db.ts +95 -0
- package/src/db/Schema.ts +53 -0
- package/src/db/tables/earlyAccess.ts +86 -0
- package/src/db/tables/invitations.ts +26 -1
- package/src/db/tables/inviteLinks.ts +22 -3
- package/src/db/tables/sponsoredTransactions.ts +4 -0
- package/src/db/tables/verifiedTokenRequests.test.ts +200 -0
- package/src/db/tables/verifiedTokenRequests.ts +182 -0
- package/src/db/tables/webhookSubscriptions.ts +31 -0
- package/src/internal/Auth.ts +73 -45
- package/src/internal/FundingQuotes.test.ts +143 -1
- package/src/internal/FundingQuotes.ts +95 -28
- package/src/internal/Log.test-d.ts +21 -0
- package/src/internal/Log.test.ts +50 -0
- package/src/internal/Log.ts +24 -1
- package/src/internal/MetricSink.test.ts +160 -0
- package/src/internal/MetricSink.ts +10 -0
- package/src/internal/OpenApi.ts +3 -0
- package/src/internal/Schema.ts +50 -5
- package/src/internal/Value.test.ts +20 -0
- package/src/internal/Value.ts +24 -1
- package/src/internal/VerifiedTokens.ts +9 -1
- package/src/internal/Webhooks.test.ts +140 -0
- package/src/internal/Webhooks.ts +53 -9
package/src/apps/mcp.ts
CHANGED
|
@@ -15,7 +15,7 @@ const excludedTags = ['CoinGecko', 'MCP']
|
|
|
15
15
|
|
|
16
16
|
/** Usage guidance surfaced to MCP clients on initialize. */
|
|
17
17
|
const instructions =
|
|
18
|
-
"
|
|
18
|
+
"Tempo MCP provides access to the Tempo API's data domain and documentation. Authenticated tools accept a Tempo API key sent as an `Authorization: Bearer` header on the HTTP request. List endpoints paginate via `cursor`/`nextCursor`. `docs_*` tools search the Tempo documentation."
|
|
19
19
|
|
|
20
20
|
/** Zod schemas owned by the MCP endpoint's OpenAPI description. */
|
|
21
21
|
export namespace schema {
|
|
@@ -61,11 +61,22 @@ export namespace schema {
|
|
|
61
61
|
* the other route groups:
|
|
62
62
|
*
|
|
63
63
|
* ```ts
|
|
64
|
-
* App.create(options)
|
|
64
|
+
* const app = App.create(options)
|
|
65
|
+
* const fetch = app.fetch.bind(app)
|
|
66
|
+
* app.route('/', mcp({ fetch }))
|
|
65
67
|
* ```
|
|
68
|
+
*
|
|
69
|
+
* Capture `fetch` before adding request-level wrappers, so nested calls stay
|
|
70
|
+
* within the outer request's instrumentation lifecycle.
|
|
66
71
|
*/
|
|
67
72
|
export function mcp(options: mcp.Options) {
|
|
68
|
-
const {
|
|
73
|
+
const {
|
|
74
|
+
docs = 'https://mcp.tempo.xyz/mcp',
|
|
75
|
+
fetch,
|
|
76
|
+
name = 'tempo',
|
|
77
|
+
title = 'Tempo MCP',
|
|
78
|
+
version = '0.0.0',
|
|
79
|
+
} = options
|
|
69
80
|
|
|
70
81
|
// One incur instance per caller origin: the OpenAPI document is parsed once,
|
|
71
82
|
// and generated commands rebase onto the origin so tool results embed real
|
|
@@ -95,7 +106,7 @@ export function mcp(options: mcp.Options) {
|
|
|
95
106
|
const url = new URL(request.url)
|
|
96
107
|
return fetch(new Request(new URL(url.pathname + url.search, origin), request))
|
|
97
108
|
},
|
|
98
|
-
mcp: { instructions },
|
|
109
|
+
mcp: { instructions, title },
|
|
99
110
|
openapi: scope(document),
|
|
100
111
|
// `compact` + `security: false` keep tool schemas lean: no examples,
|
|
101
112
|
// oversized regexes, or per-tool credential inputs (auth rides the
|
|
@@ -271,8 +282,10 @@ export declare namespace mcp {
|
|
|
271
282
|
docs?: Source | false | undefined
|
|
272
283
|
/** In-process fetch handler for the composed app (e.g. `(request) => app.fetch(request)`). */
|
|
273
284
|
fetch: (request: Request) => Response | Promise<Response>
|
|
274
|
-
/** MCP server name. @default '
|
|
285
|
+
/** MCP server name. @default 'tempo' */
|
|
275
286
|
name?: string | undefined
|
|
287
|
+
/** Human-readable MCP server title. @default 'Tempo MCP' */
|
|
288
|
+
title?: string | undefined
|
|
276
289
|
/** MCP server version. @default '0.0.0' */
|
|
277
290
|
version?: string | undefined
|
|
278
291
|
}
|
package/src/cli/Cli.test.ts
CHANGED
|
@@ -16,6 +16,14 @@ describe('from', () => {
|
|
|
16
16
|
|
|
17
17
|
expect(cli.name).toMatchInlineSnapshot(`"tapimo"`)
|
|
18
18
|
expect(cli.description).toMatchInlineSnapshot(`"Tempo API CLI"`)
|
|
19
|
+
expect((await initialize(cli)).result).toMatchObject({
|
|
20
|
+
instructions: expect.stringContaining('Tempo MCP provides access'),
|
|
21
|
+
serverInfo: {
|
|
22
|
+
name: 'tempo',
|
|
23
|
+
title: 'Tempo MCP',
|
|
24
|
+
version: '1.2.3',
|
|
25
|
+
},
|
|
26
|
+
})
|
|
19
27
|
} finally {
|
|
20
28
|
fetch.mockRestore()
|
|
21
29
|
}
|
|
@@ -91,6 +99,34 @@ function mockHostedApi(app = TestApp.create()) {
|
|
|
91
99
|
})
|
|
92
100
|
}
|
|
93
101
|
|
|
102
|
+
async function initialize(cli: ReturnType<typeof Cli.from>) {
|
|
103
|
+
const response = await cli.fetch(
|
|
104
|
+
new Request('http://localhost/mcp', {
|
|
105
|
+
body: JSON.stringify({
|
|
106
|
+
id: 1,
|
|
107
|
+
jsonrpc: '2.0',
|
|
108
|
+
method: 'initialize',
|
|
109
|
+
params: {
|
|
110
|
+
capabilities: {},
|
|
111
|
+
clientInfo: { name: 'test-client', version: '1.0.0' },
|
|
112
|
+
protocolVersion: '2025-03-26',
|
|
113
|
+
},
|
|
114
|
+
}),
|
|
115
|
+
headers: {
|
|
116
|
+
accept: 'application/json, text/event-stream',
|
|
117
|
+
'content-type': 'application/json',
|
|
118
|
+
},
|
|
119
|
+
method: 'POST',
|
|
120
|
+
}),
|
|
121
|
+
)
|
|
122
|
+
return (await response.json()) as {
|
|
123
|
+
result: {
|
|
124
|
+
instructions: string
|
|
125
|
+
serverInfo: { name: string; title: string; version: string }
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
94
130
|
async function serve(cli: ReturnType<typeof Cli.from>, argv: string[]) {
|
|
95
131
|
const output: string[] = []
|
|
96
132
|
await cli.serve(argv, {
|
package/src/cli/Cli.ts
CHANGED
|
@@ -8,7 +8,7 @@ const excludedTools = ['admin_*', 'gecko_*', 'v1_auth_*']
|
|
|
8
8
|
|
|
9
9
|
/** Usage guidance surfaced to MCP clients on initialize (`<cli> --mcp`). */
|
|
10
10
|
const instructions =
|
|
11
|
-
'
|
|
11
|
+
'Tempo MCP provides access to the hosted Tempo API and documentation. Authenticated tools accept an `authorization` input (`Bearer <api key>`). List endpoints paginate via `cursor`/`nextCursor`. `docs_*` tools search the Tempo documentation.'
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Creates an incur CLI backed by a hosted Tempo API, plus the super admin
|
|
@@ -28,7 +28,12 @@ export function from(url: string | URL, options: from.Options = {}) {
|
|
|
28
28
|
.create(name, {
|
|
29
29
|
description,
|
|
30
30
|
fetch: Fetch.fromRequest(url, request),
|
|
31
|
-
mcp: {
|
|
31
|
+
mcp: {
|
|
32
|
+
instructions,
|
|
33
|
+
name: 'tempo',
|
|
34
|
+
title: 'Tempo MCP',
|
|
35
|
+
tools: { exclude: excludedTools },
|
|
36
|
+
},
|
|
32
37
|
openapi,
|
|
33
38
|
openapiConfig: options.openapiConfig ?? { mode: 'namespace' },
|
|
34
39
|
version,
|
package/src/db/Db.test.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { sql } from 'kysely'
|
|
1
2
|
import { nanoid } from 'nanoid'
|
|
2
3
|
|
|
3
4
|
import * as Runtime from '../../test/runtime.js'
|
|
4
5
|
import * as Db from './Db.js'
|
|
6
|
+
import * as Invitations from './tables/invitations.js'
|
|
5
7
|
import * as Organizations from './tables/organizations.js'
|
|
6
8
|
|
|
7
9
|
const create = () => Db.postgres({ connectionString: Runtime.postgresUrl, schema: `t_${nanoid()}` })
|
|
@@ -15,6 +17,23 @@ describe('postgres', () => {
|
|
|
15
17
|
await db.close()
|
|
16
18
|
})
|
|
17
19
|
|
|
20
|
+
test('behavior: legacy invitation inserts retain early access during cutover', async () => {
|
|
21
|
+
const db = create()
|
|
22
|
+
await db.migrate()
|
|
23
|
+
await Organizations.create(db, { id: 'org_legacy', name: 'Legacy' })
|
|
24
|
+
await sql`
|
|
25
|
+
INSERT INTO invitations (
|
|
26
|
+
id, org_id, email, role, invited_by, expires_at, accepted_at, revoked_at, created_at
|
|
27
|
+
) VALUES (
|
|
28
|
+
'inv_legacy', 'org_legacy', 'legacy@example.org', 'member', 'usr_legacy',
|
|
29
|
+
'2027-01-01T00:00:00.000Z', NULL, NULL, '2026-07-20T00:00:00.000Z'
|
|
30
|
+
)
|
|
31
|
+
`.execute(db.kysely)
|
|
32
|
+
|
|
33
|
+
expect((await Invitations.get(db, 'inv_legacy'))?.grantsEarlyAccess).toBe(true)
|
|
34
|
+
await db.close()
|
|
35
|
+
})
|
|
36
|
+
|
|
18
37
|
test('behavior: creates a Postgres db without schema scoping', async () => {
|
|
19
38
|
const db = Db.postgres({ connectionString: Runtime.postgresUrl })
|
|
20
39
|
await db.close()
|
package/src/db/Db.ts
CHANGED
|
@@ -12,6 +12,7 @@ import * as pg from 'pg'
|
|
|
12
12
|
|
|
13
13
|
import type * as AdminAuditLogs from './tables/adminAuditLogs.js'
|
|
14
14
|
import type * as BillingSettings from './tables/billingSettings.js'
|
|
15
|
+
import type * as EarlyAccess from './tables/earlyAccess.js'
|
|
15
16
|
import type * as Invitations from './tables/invitations.js'
|
|
16
17
|
import type * as InviteLinks from './tables/inviteLinks.js'
|
|
17
18
|
import type * as Memberships from './tables/memberships.js'
|
|
@@ -21,6 +22,7 @@ import type * as RequestUsage from './tables/requestUsage.js'
|
|
|
21
22
|
import type * as SponsoredTransactions from './tables/sponsoredTransactions.js'
|
|
22
23
|
import type * as StripeCustomers from './tables/stripeCustomers.js'
|
|
23
24
|
import type * as Users from './tables/users.js'
|
|
25
|
+
import type * as VerifiedTokenRequests from './tables/verifiedTokenRequests.js'
|
|
24
26
|
import type * as VerifiedTokens from './tables/verifiedTokens.js'
|
|
25
27
|
import type * as WebhookDeliveries from './tables/webhookDeliveries.js'
|
|
26
28
|
import type * as WebhookSubscriptions from './tables/webhookSubscriptions.js'
|
|
@@ -32,6 +34,7 @@ import type * as WebhookSubscriptions from './tables/webhookSubscriptions.js'
|
|
|
32
34
|
export type Database = {
|
|
33
35
|
admin_audit_logs: AdminAuditLogs.Table
|
|
34
36
|
billing_settings: BillingSettings.Table
|
|
37
|
+
early_access: EarlyAccess.Table
|
|
35
38
|
invitations: Invitations.Table
|
|
36
39
|
invite_link_redemptions: InviteLinks.RedemptionTable
|
|
37
40
|
invite_links: InviteLinks.Table
|
|
@@ -51,6 +54,7 @@ export type Database = {
|
|
|
51
54
|
stripe_customers: StripeCustomers.Table
|
|
52
55
|
users: Users.Table
|
|
53
56
|
verified_token_lists: VerifiedTokens.ListTable
|
|
57
|
+
verified_token_requests: VerifiedTokenRequests.Table
|
|
54
58
|
verified_tokens: VerifiedTokens.Table
|
|
55
59
|
webhook_deliveries: WebhookDeliveries.Table
|
|
56
60
|
webhook_subscriptions: WebhookSubscriptions.Table
|
|
@@ -645,6 +649,97 @@ export const migrations: readonly Migration[] = [
|
|
|
645
649
|
name: '0058_request_usage_sequence_bigint',
|
|
646
650
|
up: (db) => raw(db, sql`ALTER TABLE request_usage_meter_events ALTER COLUMN sequence TYPE bigint`), // prettier-ignore
|
|
647
651
|
},
|
|
652
|
+
{
|
|
653
|
+
name: '0059_verified_token_requests',
|
|
654
|
+
up: (db) =>
|
|
655
|
+
db.schema
|
|
656
|
+
.createTable('verifiedTokenRequests')
|
|
657
|
+
.ifNotExists()
|
|
658
|
+
.addColumn('id', 'text', (c) => c.primaryKey())
|
|
659
|
+
.addColumn('orgId', 'text', (c) => c.notNull())
|
|
660
|
+
.addColumn('requestedBy', 'text', (c) => c.notNull())
|
|
661
|
+
.addColumn('chainId', 'integer', (c) => c.notNull())
|
|
662
|
+
.addColumn('address', 'text', (c) => c.notNull())
|
|
663
|
+
.addColumn('symbol', 'text', (c) => c.notNull())
|
|
664
|
+
.addColumn('name', 'text', (c) => c.notNull())
|
|
665
|
+
.addColumn('currency', 'text', (c) => c.notNull())
|
|
666
|
+
.addColumn('decimals', 'integer', (c) => c.notNull())
|
|
667
|
+
.addColumn('logoUri', 'text')
|
|
668
|
+
.addColumn('note', 'text')
|
|
669
|
+
.addColumn('status', 'text', (c) => c.notNull().defaultTo('pending'))
|
|
670
|
+
.addColumn('reviewNote', 'text')
|
|
671
|
+
.addColumn('reviewedBy', 'text')
|
|
672
|
+
.addColumn('reviewedAt', 'text')
|
|
673
|
+
.addColumn('createdAt', 'text', (c) => c.notNull())
|
|
674
|
+
.addColumn('updatedAt', 'text', (c) => c.notNull()),
|
|
675
|
+
},
|
|
676
|
+
{
|
|
677
|
+
name: '0060_verified_token_requests_org_index',
|
|
678
|
+
up: (db) =>
|
|
679
|
+
db.schema
|
|
680
|
+
.createIndex('verified_token_requests_org_idx')
|
|
681
|
+
.ifNotExists()
|
|
682
|
+
.on('verifiedTokenRequests')
|
|
683
|
+
.columns(['orgId', 'createdAt']),
|
|
684
|
+
},
|
|
685
|
+
{
|
|
686
|
+
name: '0061_verified_token_requests_pending_unique',
|
|
687
|
+
// Raw SQL: a partial unique index (snake_case; the CamelCase plugin does
|
|
688
|
+
// not map raw statements). At most one live pending request per
|
|
689
|
+
// (chain, address) globally; addresses are stored lowercase.
|
|
690
|
+
up: (db) =>
|
|
691
|
+
raw(
|
|
692
|
+
db,
|
|
693
|
+
sql`CREATE UNIQUE INDEX IF NOT EXISTS verified_token_requests_pending_unique ON verified_token_requests (chain_id, address) WHERE status = 'pending'`,
|
|
694
|
+
),
|
|
695
|
+
},
|
|
696
|
+
{
|
|
697
|
+
name: '0062_verified_token_requests_status_index',
|
|
698
|
+
up: (db) =>
|
|
699
|
+
db.schema
|
|
700
|
+
.createIndex('verified_token_requests_status_idx')
|
|
701
|
+
.ifNotExists()
|
|
702
|
+
.on('verifiedTokenRequests')
|
|
703
|
+
.columns(['status', 'createdAt']),
|
|
704
|
+
},
|
|
705
|
+
{
|
|
706
|
+
name: '0063_verified_token_requests_logo',
|
|
707
|
+
up: (db) => raw(db, sql`ALTER TABLE verified_token_requests ADD COLUMN IF NOT EXISTS logo text`), // prettier-ignore
|
|
708
|
+
},
|
|
709
|
+
{
|
|
710
|
+
name: '0064_invitations_grants_early_access',
|
|
711
|
+
up: (db) => raw(db, sql`ALTER TABLE invitations ADD COLUMN IF NOT EXISTS grants_early_access boolean NOT NULL DEFAULT false`), // prettier-ignore
|
|
712
|
+
},
|
|
713
|
+
{
|
|
714
|
+
name: '0065_invitations_grants_early_access_backfill',
|
|
715
|
+
// Pre-gate invitations came from trusted-era users; the fixed cutoff keeps
|
|
716
|
+
// re-runs (test DBs replay every migration) from flipping newer rows.
|
|
717
|
+
up: (db) => raw(db, sql`UPDATE invitations SET grants_early_access = true WHERE created_at < '2026-07-20T00:00:00.000Z'`), // prettier-ignore
|
|
718
|
+
},
|
|
719
|
+
{
|
|
720
|
+
name: '0066_early_access',
|
|
721
|
+
up: (db) =>
|
|
722
|
+
db.schema
|
|
723
|
+
.createTable('earlyAccess')
|
|
724
|
+
.ifNotExists()
|
|
725
|
+
.addColumn('entry', 'text', (c) => c.primaryKey())
|
|
726
|
+
.addColumn('createdAt', 'text', (c) => c.notNull())
|
|
727
|
+
.addColumn('createdBy', 'text', (c) => c.notNull()),
|
|
728
|
+
},
|
|
729
|
+
{
|
|
730
|
+
name: '0067_invitations_grants_early_access_cutover',
|
|
731
|
+
// Every invitation present at deployment came from a trusted-era user.
|
|
732
|
+
up: (db) => raw(db, sql`UPDATE invitations SET grants_early_access = true`),
|
|
733
|
+
},
|
|
734
|
+
{
|
|
735
|
+
name: '0068_invitations_grants_early_access_legacy_default',
|
|
736
|
+
// Old workers omit this column during deploy; new writes set it explicitly.
|
|
737
|
+
up: (db) => raw(db, sql`ALTER TABLE invitations ALTER COLUMN grants_early_access SET DEFAULT true`), // prettier-ignore
|
|
738
|
+
},
|
|
739
|
+
{
|
|
740
|
+
name: '0069_invite_links_allowed_email_domains',
|
|
741
|
+
up: (db) => raw(db, sql`ALTER TABLE invite_links ADD COLUMN IF NOT EXISTS allowed_email_domains jsonb`), // prettier-ignore
|
|
742
|
+
},
|
|
648
743
|
]
|
|
649
744
|
|
|
650
745
|
/**
|
package/src/db/Schema.ts
CHANGED
|
@@ -52,6 +52,20 @@ export const BillingSettings = z.object({
|
|
|
52
52
|
/** Columns of the `billing_settings` table. */
|
|
53
53
|
export type BillingSettings = z.output<typeof BillingSettings>
|
|
54
54
|
|
|
55
|
+
/** An early-access allowlist entry: a lowercase domain or exact email. */
|
|
56
|
+
export const EarlyAccessEntry = z.object({
|
|
57
|
+
createdAt: z.string().check(z.describe('When the entry was added (ISO 8601).')),
|
|
58
|
+
createdBy: z
|
|
59
|
+
.string()
|
|
60
|
+
.check(z.describe('Admin email that added the entry, or `migration` for seeded rows.')),
|
|
61
|
+
entry: z
|
|
62
|
+
.string()
|
|
63
|
+
.check(z.describe('Lowercase domain or exact email (contains `@`) granted early access.')),
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
/** Columns of the `early_access` table. */
|
|
67
|
+
export type EarlyAccessEntry = z.output<typeof EarlyAccessEntry>
|
|
68
|
+
|
|
55
69
|
/** A pending, expiring offer of organization membership. */
|
|
56
70
|
export const Invitation = z.object({
|
|
57
71
|
acceptedAt: z
|
|
@@ -62,6 +76,9 @@ export const Invitation = z.object({
|
|
|
62
76
|
.string()
|
|
63
77
|
.check(z.describe('Invitee email (stored lowercase); matched against verified emails.')),
|
|
64
78
|
expiresAt: z.string().check(z.describe('When the invitation expires (ISO 8601).')),
|
|
79
|
+
grantsEarlyAccess: z
|
|
80
|
+
.boolean()
|
|
81
|
+
.check(z.describe("Whether the invitation grants early access; stamped from the inviter's own access at creation.")), // prettier-ignore
|
|
65
82
|
id: z
|
|
66
83
|
.string()
|
|
67
84
|
.check(
|
|
@@ -83,6 +100,9 @@ export type Invitation = z.output<typeof Invitation>
|
|
|
83
100
|
|
|
84
101
|
/** A reusable bearer link granting organization membership. */
|
|
85
102
|
export const InviteLink = z.object({
|
|
103
|
+
allowedEmailDomains: z
|
|
104
|
+
.nullable(z.readonly(z.array(z.string())))
|
|
105
|
+
.check(z.describe('Lowercase email domains allowed to redeem, or null for unrestricted.')),
|
|
86
106
|
createdAt: z.string().check(z.describe('When the link was created (ISO 8601).')),
|
|
87
107
|
createdBy: z.string().check(z.describe('User id that created the link, or `super_admin`.')),
|
|
88
108
|
deletedAt: z.nullable(z.string()).check(z.describe('When the link was soft deleted, or null.')),
|
|
@@ -363,6 +383,39 @@ export const VerifiedTokenList = z.object({
|
|
|
363
383
|
/** Columns of the `verified_token_lists` table. */
|
|
364
384
|
export type VerifiedTokenList = z.output<typeof VerifiedTokenList>
|
|
365
385
|
|
|
386
|
+
/**
|
|
387
|
+
* An organization's request to add a token to the curated verified list.
|
|
388
|
+
* Addresses are stored lowercase; a partial unique index allows at most one
|
|
389
|
+
* pending request per (chain, address) globally.
|
|
390
|
+
*/
|
|
391
|
+
export const VerifiedTokenRequest = z.object({
|
|
392
|
+
address: z.string().check(z.describe('Requested TIP-20 token contract address (lowercase).')),
|
|
393
|
+
chainId: z.number().check(z.int(), z.describe('Chain the token belongs to.')),
|
|
394
|
+
createdAt: z.string().check(z.describe('When the request was created (ISO 8601).')),
|
|
395
|
+
currency: z.string().check(z.describe('Display currency, e.g. `USD`.')),
|
|
396
|
+
decimals: z.number().check(z.int(), z.describe('Decimal precision.')),
|
|
397
|
+
id: z.string().check(z.describe('Opaque request id (`vtr_…`).')),
|
|
398
|
+
logo: z
|
|
399
|
+
.nullable(z.string())
|
|
400
|
+
.check(z.describe('Inline SVG logo markup uploaded by the requester, or null.')),
|
|
401
|
+
logoUri: z.nullable(z.string()).check(z.describe('HTTPS logo URL from chain metadata, or null.')),
|
|
402
|
+
name: z.string().check(z.describe('Display name.')),
|
|
403
|
+
note: z.nullable(z.string()).check(z.describe('Requester note to reviewers, or null.')),
|
|
404
|
+
orgId: z.string().check(z.describe('Requesting organization id (`org_…`).')),
|
|
405
|
+
requestedBy: z
|
|
406
|
+
.string()
|
|
407
|
+
.check(z.describe('User id that submitted the request, or `super_admin`.')),
|
|
408
|
+
reviewNote: z.nullable(z.string()).check(z.describe('Reviewer note shown on denial, or null.')),
|
|
409
|
+
reviewedAt: z.nullable(z.string()).check(z.describe('When the request was reviewed, or null.')),
|
|
410
|
+
reviewedBy: z.nullable(z.string()).check(z.describe('Reviewing admin email, or null.')),
|
|
411
|
+
status: z.enum(['approved', 'denied', 'pending']).check(z.describe('Review status.')),
|
|
412
|
+
symbol: z.string().check(z.describe('Ticker symbol.'), z.meta({ examples: ['USDC'] })),
|
|
413
|
+
updatedAt: z.string().check(z.describe('When the request was last changed (ISO 8601).')),
|
|
414
|
+
})
|
|
415
|
+
|
|
416
|
+
/** Columns of the `verified_token_requests` table. */
|
|
417
|
+
export type VerifiedTokenRequest = z.output<typeof VerifiedTokenRequest>
|
|
418
|
+
|
|
366
419
|
/**
|
|
367
420
|
* A webhook subscription. The `Webhooks.Owner` union maps to (`ownerType`,
|
|
368
421
|
* `ownerId`); jsonb object columns round-trip as parsed values. `pollerCursor`
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { Selectable } from 'kysely'
|
|
2
|
+
|
|
3
|
+
import type * as Db from '../Db.js'
|
|
4
|
+
import type * as db_Schema from '../Schema.js'
|
|
5
|
+
|
|
6
|
+
/** Columns of the `early_access` table, derived from `Schema.EarlyAccessEntry`. */
|
|
7
|
+
export type Table = db_Schema.EarlyAccessEntry
|
|
8
|
+
|
|
9
|
+
/** A stored early-access allowlist row. */
|
|
10
|
+
export type Record = Selectable<Table>
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Inserts an allowlist entry (a domain or exact email, normalized lowercase).
|
|
14
|
+
* Returns the stored record, or `undefined` when the entry already exists.
|
|
15
|
+
*
|
|
16
|
+
* @param db - The database.
|
|
17
|
+
* @param input - The entry to insert.
|
|
18
|
+
* @returns The stored record, or `undefined` when already present.
|
|
19
|
+
*/
|
|
20
|
+
export function add(db: Db.Db, input: add.Input): Promise<Record | undefined> {
|
|
21
|
+
return db.kysely
|
|
22
|
+
.insertInto('early_access')
|
|
23
|
+
.values({
|
|
24
|
+
createdAt: new Date().toISOString(),
|
|
25
|
+
createdBy: input.createdBy,
|
|
26
|
+
entry: input.entry.trim().toLowerCase(),
|
|
27
|
+
})
|
|
28
|
+
.onConflict((oc) => oc.column('entry').doNothing())
|
|
29
|
+
.returningAll()
|
|
30
|
+
.executeTakeFirst()
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export declare namespace add {
|
|
34
|
+
/** Fields accepted when inserting an allowlist entry. */
|
|
35
|
+
type Input = {
|
|
36
|
+
/** Admin email adding the entry, or `migration` for seeded rows. */
|
|
37
|
+
createdBy: string
|
|
38
|
+
/** Domain or exact email to grant early access; normalized lowercase. */
|
|
39
|
+
entry: string
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Lists all allowlist entries, ordered by entry.
|
|
45
|
+
*
|
|
46
|
+
* @param db - The database.
|
|
47
|
+
* @returns The records.
|
|
48
|
+
*/
|
|
49
|
+
export function list(db: Db.Db): Promise<Record[]> {
|
|
50
|
+
return db.kysely.selectFrom('early_access').selectAll().orderBy('entry', 'asc').execute()
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Removes an allowlist entry.
|
|
55
|
+
*
|
|
56
|
+
* @param db - The database.
|
|
57
|
+
* @param entry - The entry to remove (matched lowercase).
|
|
58
|
+
* @returns The removed record, or `undefined` when absent.
|
|
59
|
+
*/
|
|
60
|
+
export function remove(db: Db.Db, entry: string): Promise<Record | undefined> {
|
|
61
|
+
return db.kysely
|
|
62
|
+
.deleteFrom('early_access')
|
|
63
|
+
.where('entry', '=', entry.trim().toLowerCase())
|
|
64
|
+
.returningAll()
|
|
65
|
+
.executeTakeFirst()
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Whether an allowlist entry matches `email`: the exact email or its domain.
|
|
70
|
+
* Two primary-key lookups via `IN`.
|
|
71
|
+
*
|
|
72
|
+
* @param db - The database.
|
|
73
|
+
* @param email - The email to match (case-insensitive).
|
|
74
|
+
* @returns Whether an entry matches.
|
|
75
|
+
*/
|
|
76
|
+
export async function matches(db: Db.Db, email: string): Promise<boolean> {
|
|
77
|
+
const normalized = email.trim().toLowerCase()
|
|
78
|
+
const domain = normalized.slice(normalized.lastIndexOf('@') + 1)
|
|
79
|
+
const row = await db.kysely
|
|
80
|
+
.selectFrom('early_access')
|
|
81
|
+
.select('entry')
|
|
82
|
+
.where('entry', 'in', [normalized, domain])
|
|
83
|
+
.limit(1)
|
|
84
|
+
.executeTakeFirst()
|
|
85
|
+
return row !== undefined
|
|
86
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type Selectable, sql } from 'kysely'
|
|
2
2
|
|
|
3
3
|
import * as Id from '../../internal/Id.js'
|
|
4
4
|
import type * as Db from '../Db.js'
|
|
@@ -31,6 +31,7 @@ export function create(db: Db.Db, input: create.Input): Promise<Record> {
|
|
|
31
31
|
createdAt: now,
|
|
32
32
|
email: input.email.toLowerCase(),
|
|
33
33
|
expiresAt: input.expiresAt,
|
|
34
|
+
grantsEarlyAccess: input.grantsEarlyAccess,
|
|
34
35
|
id: Id.generate('inv'),
|
|
35
36
|
invitedBy: input.invitedBy,
|
|
36
37
|
orgId: input.orgId,
|
|
@@ -45,6 +46,8 @@ export function create(db: Db.Db, input: create.Input): Promise<Record> {
|
|
|
45
46
|
.doUpdateSet({
|
|
46
47
|
createdAt: now,
|
|
47
48
|
expiresAt: input.expiresAt,
|
|
49
|
+
// Re-invites upgrade the grant, never withdraw it (raw SQL stays snake_case).
|
|
50
|
+
grantsEarlyAccess: sql<boolean>`invitations.grants_early_access OR excluded.grants_early_access`,
|
|
48
51
|
invitedBy: input.invitedBy,
|
|
49
52
|
role: input.role,
|
|
50
53
|
}),
|
|
@@ -60,6 +63,8 @@ export declare namespace create {
|
|
|
60
63
|
email: string
|
|
61
64
|
/** When the invitation expires (ISO 8601). */
|
|
62
65
|
expiresAt: string
|
|
66
|
+
/** Whether accepting grants early access; stamped from the inviter's own access. */
|
|
67
|
+
grantsEarlyAccess: boolean
|
|
63
68
|
/** User or API key id creating the invitation. */
|
|
64
69
|
invitedBy: string
|
|
65
70
|
/** Organization id (`org_…`) the invitation joins. */
|
|
@@ -114,6 +119,26 @@ export function listByEmail(db: Db.Db, email: string): Promise<(Record & { orgNa
|
|
|
114
119
|
.execute()
|
|
115
120
|
}
|
|
116
121
|
|
|
122
|
+
/**
|
|
123
|
+
* Whether any unrevoked invitation grants `email` early access. Acceptance and
|
|
124
|
+
* expiry are deliberately ignored: a trusted invite grants access until revoked.
|
|
125
|
+
*
|
|
126
|
+
* @param db - The database.
|
|
127
|
+
* @param email - The invitee email (matched lowercase).
|
|
128
|
+
* @returns Whether a grant exists.
|
|
129
|
+
*/
|
|
130
|
+
export async function hasEarlyAccessGrant(db: Db.Db, email: string): Promise<boolean> {
|
|
131
|
+
const row = await db.kysely
|
|
132
|
+
.selectFrom('invitations')
|
|
133
|
+
.select('id')
|
|
134
|
+
.where('email', '=', email.toLowerCase())
|
|
135
|
+
.where('grantsEarlyAccess', '=', true)
|
|
136
|
+
.where('revokedAt', 'is', null)
|
|
137
|
+
.limit(1)
|
|
138
|
+
.executeTakeFirst()
|
|
139
|
+
return row !== undefined
|
|
140
|
+
}
|
|
141
|
+
|
|
117
142
|
/**
|
|
118
143
|
* Revokes a pending invitation.
|
|
119
144
|
*
|
|
@@ -1,11 +1,18 @@
|
|
|
1
|
-
import type { Selectable } from 'kysely'
|
|
1
|
+
import type { JSONColumnType, Selectable } from 'kysely'
|
|
2
2
|
|
|
3
3
|
import * as Id from '../../internal/Id.js'
|
|
4
4
|
import type * as Db from '../Db.js'
|
|
5
5
|
import type * as db_Schema from '../Schema.js'
|
|
6
6
|
|
|
7
7
|
/** Columns of the `invite_links` table. */
|
|
8
|
-
export type Table = db_Schema.InviteLink
|
|
8
|
+
export type Table = Omit<db_Schema.InviteLink, 'allowedEmailDomains'> & {
|
|
9
|
+
/** Lowercase email domains allowed to redeem, or null for unrestricted. */
|
|
10
|
+
allowedEmailDomains: JSONColumnType<
|
|
11
|
+
db_Schema.InviteLink['allowedEmailDomains'],
|
|
12
|
+
string | null,
|
|
13
|
+
string | null
|
|
14
|
+
>
|
|
15
|
+
}
|
|
9
16
|
/** Columns of the `invite_link_redemptions` table. */
|
|
10
17
|
export type RedemptionTable = db_Schema.InviteLinkRedemption
|
|
11
18
|
/** A stored invite link. */
|
|
@@ -28,6 +35,7 @@ export function status(record: Record, now = new Date().toISOString()): Status {
|
|
|
28
35
|
export function create(
|
|
29
36
|
db: Db.Db,
|
|
30
37
|
input: {
|
|
38
|
+
allowedEmailDomains: readonly string[] | null
|
|
31
39
|
createdBy: string
|
|
32
40
|
expiresAt: string | null
|
|
33
41
|
maxUses: number | null
|
|
@@ -40,6 +48,8 @@ export function create(
|
|
|
40
48
|
return db.kysely
|
|
41
49
|
.insertInto('invite_links')
|
|
42
50
|
.values({
|
|
51
|
+
allowedEmailDomains:
|
|
52
|
+
input.allowedEmailDomains === null ? null : JSON.stringify(input.allowedEmailDomains),
|
|
43
53
|
createdAt: now,
|
|
44
54
|
createdBy: input.createdBy,
|
|
45
55
|
deletedAt: null,
|
|
@@ -155,6 +165,10 @@ export function accept(
|
|
|
155
165
|
.executeTakeFirst()
|
|
156
166
|
if (existing) return { consumed: false, link }
|
|
157
167
|
if (status(link, now) !== 'active') return undefined
|
|
168
|
+
const normalizedEmail = email.trim().toLowerCase()
|
|
169
|
+
const domain = normalizedEmail.slice(normalizedEmail.lastIndexOf('@') + 1)
|
|
170
|
+
if (link.allowedEmailDomains !== null && !link.allowedEmailDomains.includes(domain))
|
|
171
|
+
throw new EmailDomainForbiddenError()
|
|
158
172
|
const inserted = await trx
|
|
159
173
|
.insertInto('memberships')
|
|
160
174
|
.values({ createdAt: now, orgId: link.orgId, role: 'member', updatedAt: now, userId })
|
|
@@ -171,7 +185,7 @@ export function accept(
|
|
|
171
185
|
.insertInto('invite_link_redemptions')
|
|
172
186
|
.values({
|
|
173
187
|
createdAt: now,
|
|
174
|
-
email:
|
|
188
|
+
email: normalizedEmail,
|
|
175
189
|
id: Id.generate('ilr'),
|
|
176
190
|
inviteLinkId: link.id,
|
|
177
191
|
inviteLinkName: link.name,
|
|
@@ -185,3 +199,8 @@ export function accept(
|
|
|
185
199
|
}
|
|
186
200
|
})
|
|
187
201
|
}
|
|
202
|
+
|
|
203
|
+
/** The verified email domain is not allowed by the invite link. */
|
|
204
|
+
export class EmailDomainForbiddenError extends Error {
|
|
205
|
+
override name = 'InviteLinks.EmailDomainForbiddenError'
|
|
206
|
+
}
|
|
@@ -397,6 +397,7 @@ export async function usage(db: Db.Db, options: usage.Options): Promise<usage.Bu
|
|
|
397
397
|
.select([
|
|
398
398
|
sql<string>`to_char(${bucket}, 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"')`.as('timestamp'),
|
|
399
399
|
sql<string>`count(*)`.as('count'),
|
|
400
|
+
sql<string>`count(*) filter (where status = 'failed')`.as('failed'),
|
|
400
401
|
sql<string>`coalesce(sum(case when status = 'finalized' then fee_amount::numeric when status = 'pending' then fee_max::numeric else 0 end), 0)`.as('feeTotal'), // prettier-ignore
|
|
401
402
|
])
|
|
402
403
|
.where('orgId', '=', orgId)
|
|
@@ -410,6 +411,7 @@ export async function usage(db: Db.Db, options: usage.Options): Promise<usage.Bu
|
|
|
410
411
|
const rows = await query.execute()
|
|
411
412
|
return rows.map((row) => ({
|
|
412
413
|
count: Number(row.count),
|
|
414
|
+
failed: Number(row.failed),
|
|
413
415
|
feeTotal: BigInt(row.feeTotal),
|
|
414
416
|
timestamp: row.timestamp,
|
|
415
417
|
}))
|
|
@@ -420,6 +422,8 @@ export declare namespace usage {
|
|
|
420
422
|
type Bucket = {
|
|
421
423
|
/** Sponsored transactions recorded in the bucket, any status. */
|
|
422
424
|
count: number
|
|
425
|
+
/** Sponsored transactions in the bucket whose status is `failed`. */
|
|
426
|
+
failed: number
|
|
423
427
|
/** Committed fees in fee-token base units: finalized fees plus pending caps. */
|
|
424
428
|
feeTotal: bigint
|
|
425
429
|
/** Bucket start (ISO 8601), aligned to UTC calendar boundaries. */
|