tapimo 0.2.0 → 0.3.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 +31 -3
- package/dist/ApiKey.d.ts +1 -0
- package/dist/ApiKey.d.ts.map +1 -1
- package/dist/ApiKey.js +5 -2
- package/dist/ApiKey.js.map +1 -1
- package/dist/ApiKeys.d.ts +28 -1
- package/dist/ApiKeys.d.ts.map +1 -1
- package/dist/ApiKeys.js +34 -1
- package/dist/ApiKeys.js.map +1 -1
- package/dist/App.d.ts +7167 -4507
- package/dist/App.d.ts.map +1 -1
- package/dist/App.js +8 -0
- package/dist/App.js.map +1 -1
- package/dist/Scope.d.ts +11 -2
- package/dist/Scope.d.ts.map +1 -1
- package/dist/Scope.js +14 -3
- package/dist/Scope.js.map +1 -1
- package/dist/admin/App.d.ts +128 -118
- package/dist/admin/App.d.ts.map +1 -1
- package/dist/admin/apps/api-keys.d.ts +7 -0
- package/dist/admin/apps/api-keys.d.ts.map +1 -1
- package/dist/admin/apps/scopes.d.ts +8 -0
- package/dist/admin/apps/scopes.d.ts.map +1 -1
- package/dist/admin/apps/scopes.js +3 -0
- package/dist/admin/apps/scopes.js.map +1 -1
- package/dist/admin/apps/verified-tokens.d.ts +57 -57
- package/dist/admin/ui.generated.d.ts +1 -1
- package/dist/admin/ui.generated.js +13 -13
- package/dist/admin/ui.generated.js.map +1 -1
- package/dist/analytics/Analytics.d.ts +151 -0
- package/dist/analytics/Analytics.d.ts.map +1 -0
- package/dist/analytics/Analytics.js +212 -0
- package/dist/analytics/Analytics.js.map +1 -0
- package/dist/analytics/tables/requestEvents.d.ts +215 -0
- package/dist/analytics/tables/requestEvents.d.ts.map +1 -0
- package/dist/analytics/tables/requestEvents.js +264 -0
- package/dist/analytics/tables/requestEvents.js.map +1 -0
- package/dist/apps/data/App.d.ts +1024 -624
- package/dist/apps/data/App.d.ts.map +1 -1
- package/dist/apps/data/App.js +36 -0
- package/dist/apps/data/App.js.map +1 -1
- package/dist/apps/data/routes/balances.d.ts +45 -45
- package/dist/apps/data/routes/coingecko.d.ts +176 -176
- package/dist/apps/data/routes/exchanges.d.ts +455 -455
- package/dist/apps/data/routes/fee-amm.d.ts +101 -101
- package/dist/apps/data/routes/funding.d.ts +765 -0
- package/dist/apps/data/routes/funding.d.ts.map +1 -0
- package/dist/apps/data/routes/funding.js +383 -0
- package/dist/apps/data/routes/funding.js.map +1 -0
- package/dist/apps/data/routes/receipts.d.ts.map +1 -1
- package/dist/apps/data/routes/receipts.js +2 -8
- package/dist/apps/data/routes/receipts.js.map +1 -1
- package/dist/apps/data/routes/rpc.d.ts +29 -36
- package/dist/apps/data/routes/rpc.d.ts.map +1 -1
- package/dist/apps/data/routes/rpc.js +29 -1
- package/dist/apps/data/routes/rpc.js.map +1 -1
- package/dist/apps/data/routes/tokenlist.d.ts +36 -36
- package/dist/apps/data/routes/transfers.d.ts +56 -56
- package/dist/apps/data/routes/verified-tokens.d.ts +36 -36
- package/dist/apps/data/routes/webhooks.d.ts +45 -45
- package/dist/apps/data/routes/webhooks.d.ts.map +1 -1
- package/dist/apps/data/routes/webhooks.js +8 -8
- package/dist/apps/data/routes/webhooks.js.map +1 -1
- package/dist/apps/docs/App.d.ts +4 -2
- package/dist/apps/docs/App.d.ts.map +1 -1
- package/dist/apps/docs/App.js +27 -95
- package/dist/apps/docs/App.js.map +1 -1
- package/dist/apps/docs/reference.d.ts +21 -0
- package/dist/apps/docs/reference.d.ts.map +1 -0
- package/dist/apps/docs/reference.js +112 -0
- package/dist/apps/docs/reference.js.map +1 -0
- package/dist/apps/index.d.ts +1 -0
- package/dist/apps/index.d.ts.map +1 -1
- package/dist/apps/index.js +1 -0
- package/dist/apps/index.js.map +1 -1
- package/dist/apps/mcp.d.ts +76 -0
- package/dist/apps/mcp.d.ts.map +1 -0
- package/dist/apps/mcp.js +188 -0
- package/dist/apps/mcp.js.map +1 -0
- package/dist/apps/platform/App.d.ts +3005 -728
- package/dist/apps/platform/App.d.ts.map +1 -1
- package/dist/apps/platform/App.js +13 -3
- package/dist/apps/platform/App.js.map +1 -1
- package/dist/apps/platform/Billing.d.ts +279 -0
- package/dist/apps/platform/Billing.d.ts.map +1 -0
- package/dist/apps/platform/Billing.js +477 -0
- package/dist/apps/platform/Billing.js.map +1 -0
- package/dist/apps/platform/index.d.ts +1 -0
- package/dist/apps/platform/index.d.ts.map +1 -1
- package/dist/apps/platform/index.js +1 -0
- package/dist/apps/platform/index.js.map +1 -1
- package/dist/apps/platform/routes/api-keys.d.ts +341 -49
- package/dist/apps/platform/routes/api-keys.d.ts.map +1 -1
- package/dist/apps/platform/routes/api-keys.js +115 -13
- package/dist/apps/platform/routes/api-keys.js.map +1 -1
- package/dist/apps/platform/routes/billing.d.ts +1520 -0
- package/dist/apps/platform/routes/billing.d.ts.map +1 -0
- package/dist/apps/platform/routes/billing.js +795 -0
- package/dist/apps/platform/routes/billing.js.map +1 -0
- package/dist/apps/platform/routes/invitations.d.ts +201 -83
- package/dist/apps/platform/routes/invitations.d.ts.map +1 -1
- package/dist/apps/platform/routes/invitations.js +58 -0
- package/dist/apps/platform/routes/invitations.js.map +1 -1
- package/dist/apps/platform/routes/me.d.ts +17 -17
- package/dist/apps/platform/routes/orgs.d.ts +50 -36
- package/dist/apps/platform/routes/orgs.d.ts.map +1 -1
- package/dist/apps/platform/routes/orgs.js +14 -3
- package/dist/apps/platform/routes/orgs.js.map +1 -1
- package/dist/apps/platform/routes/projects.d.ts +97 -97
- package/dist/apps/platform/routes/scopes.d.ts +107 -0
- package/dist/apps/platform/routes/scopes.d.ts.map +1 -0
- package/dist/apps/platform/routes/scopes.js +66 -0
- package/dist/apps/platform/routes/scopes.js.map +1 -0
- package/dist/apps/platform/routes/usage.d.ts +588 -0
- package/dist/apps/platform/routes/usage.d.ts.map +1 -0
- package/dist/apps/platform/routes/usage.js +391 -0
- package/dist/apps/platform/routes/usage.js.map +1 -0
- package/dist/apps/relay/App.d.ts +4 -1
- package/dist/apps/relay/App.d.ts.map +1 -1
- package/dist/apps/relay/App.js +118 -10
- package/dist/apps/relay/App.js.map +1 -1
- package/dist/apps/relay/Sponsorships.d.ts.map +1 -1
- package/dist/apps/relay/Sponsorships.js +2 -1
- package/dist/apps/relay/Sponsorships.js.map +1 -1
- package/dist/cli/Cli.d.ts +8 -1
- package/dist/cli/Cli.d.ts.map +1 -1
- package/dist/cli/Cli.js +11 -3
- package/dist/cli/Cli.js.map +1 -1
- package/dist/cli/commands/admin.d.ts +4 -0
- package/dist/cli/commands/admin.d.ts.map +1 -1
- package/dist/cli/commands/admin.js +80 -1
- package/dist/cli/commands/admin.js.map +1 -1
- package/dist/db/Db.d.ts +7 -0
- package/dist/db/Db.d.ts.map +1 -1
- package/dist/db/Db.js +126 -0
- package/dist/db/Db.js.map +1 -1
- package/dist/db/Schema.d.ts +79 -0
- package/dist/db/Schema.d.ts.map +1 -1
- package/dist/db/Schema.js +88 -2
- package/dist/db/Schema.js.map +1 -1
- package/dist/db/tables/billingSettings.d.ts +55 -0
- package/dist/db/tables/billingSettings.d.ts.map +1 -0
- package/dist/db/tables/billingSettings.js +70 -0
- package/dist/db/tables/billingSettings.js.map +1 -0
- package/dist/db/tables/invitations.js +2 -2
- package/dist/db/tables/invitations.js.map +1 -1
- package/dist/db/tables/organizations.d.ts +6 -2
- package/dist/db/tables/organizations.d.ts.map +1 -1
- package/dist/db/tables/organizations.js +10 -4
- package/dist/db/tables/organizations.js.map +1 -1
- package/dist/db/tables/projects.js +2 -2
- package/dist/db/tables/projects.js.map +1 -1
- package/dist/db/tables/requestUsage.d.ts +121 -0
- package/dist/db/tables/requestUsage.d.ts.map +1 -0
- package/dist/db/tables/requestUsage.js +150 -0
- package/dist/db/tables/requestUsage.js.map +1 -0
- package/dist/db/tables/sponsoredTransactions.d.ts +119 -0
- package/dist/db/tables/sponsoredTransactions.d.ts.map +1 -1
- package/dist/db/tables/sponsoredTransactions.js +155 -2
- package/dist/db/tables/sponsoredTransactions.js.map +1 -1
- package/dist/db/tables/stripeCustomers.d.ts +81 -0
- package/dist/db/tables/stripeCustomers.d.ts.map +1 -0
- package/dist/db/tables/stripeCustomers.js +120 -0
- package/dist/db/tables/stripeCustomers.js.map +1 -0
- package/dist/db/tables/users.js +2 -2
- package/dist/db/tables/users.js.map +1 -1
- package/dist/handlers/internal/sponsorship.d.ts +5 -2
- package/dist/handlers/internal/sponsorship.d.ts.map +1 -1
- package/dist/handlers/internal/sponsorship.js +19 -5
- package/dist/handlers/internal/sponsorship.js.map +1 -1
- package/dist/handlers/internal/utils.d.ts +10 -3
- package/dist/handlers/internal/utils.d.ts.map +1 -1
- package/dist/handlers/internal/utils.js +14 -6
- package/dist/handlers/internal/utils.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/internal/Auth.d.ts +18 -4
- package/dist/internal/Auth.d.ts.map +1 -1
- package/dist/internal/Auth.js +148 -76
- package/dist/internal/Auth.js.map +1 -1
- package/dist/internal/Cache.d.ts.map +1 -1
- package/dist/internal/Cache.js +4 -1
- package/dist/internal/Cache.js.map +1 -1
- package/dist/internal/EdgeCache.d.ts +10 -0
- package/dist/internal/EdgeCache.d.ts.map +1 -1
- package/dist/internal/EdgeCache.js +39 -8
- package/dist/internal/EdgeCache.js.map +1 -1
- package/dist/internal/Fees.d.ts +27 -0
- package/dist/internal/Fees.d.ts.map +1 -0
- package/dist/internal/Fees.js +48 -0
- package/dist/internal/Fees.js.map +1 -0
- package/dist/internal/FundingRoutes.d.ts +183 -0
- package/dist/internal/FundingRoutes.d.ts.map +1 -0
- package/dist/internal/FundingRoutes.js +490 -0
- package/dist/internal/FundingRoutes.js.map +1 -0
- package/dist/internal/Id.d.ts +15 -0
- package/dist/internal/Id.d.ts.map +1 -0
- package/dist/internal/Id.js +22 -0
- package/dist/internal/Id.js.map +1 -0
- package/dist/internal/Log.d.ts +2 -0
- package/dist/internal/Log.d.ts.map +1 -1
- package/dist/internal/Log.js +3 -0
- package/dist/internal/Log.js.map +1 -1
- package/dist/internal/OpenApi.d.ts +10 -1
- package/dist/internal/OpenApi.d.ts.map +1 -1
- package/dist/internal/OpenApi.js +8 -1
- package/dist/internal/OpenApi.js.map +1 -1
- package/dist/internal/RateLimit.d.ts +25 -0
- package/dist/internal/RateLimit.d.ts.map +1 -1
- package/dist/internal/RateLimit.js +51 -0
- package/dist/internal/RateLimit.js.map +1 -1
- package/dist/internal/Response.d.ts +1 -1
- package/dist/internal/Response.d.ts.map +1 -1
- package/dist/internal/Schema.d.ts +8 -0
- package/dist/internal/Schema.d.ts.map +1 -1
- package/dist/internal/Schema.js +7 -0
- package/dist/internal/Schema.js.map +1 -1
- package/dist/internal/Webhooks.d.ts.map +1 -1
- package/dist/internal/Webhooks.js +3 -3
- package/dist/internal/Webhooks.js.map +1 -1
- package/package.json +4 -3
- package/src/ApiKey.ts +9 -2
- package/src/ApiKeys.test.ts +51 -0
- package/src/ApiKeys.ts +50 -1
- package/src/App.test.ts +33 -86718
- package/src/App.ts +18 -0
- package/src/Client.test-d.ts +351 -0
- package/src/Scope.ts +16 -3
- package/src/admin/apps/scopes.test.ts +6 -0
- package/src/admin/apps/scopes.ts +3 -0
- package/src/admin/ui/src/components/Page.tsx +2 -3
- package/src/admin/ui/src/routes/__root.tsx +2 -2
- package/src/admin/ui/src/routes/api-keys.tsx +2 -2
- package/src/admin/ui.generated.ts +13 -13
- package/src/analytics/Analytics.test.ts +527 -0
- package/src/analytics/Analytics.ts +318 -0
- package/src/analytics/tables/requestEvents.ts +523 -0
- package/src/apps/data/App.test.ts +72 -0
- package/src/apps/data/App.ts +35 -0
- package/src/apps/data/routes/funding.test.ts +214 -0
- package/src/apps/data/routes/funding.ts +584 -0
- package/src/apps/data/routes/receipts.ts +2 -9
- package/src/apps/data/routes/rpc.test.ts +78 -2
- package/src/apps/data/routes/rpc.ts +31 -1
- package/src/apps/data/routes/webhooks.test.ts +1 -1
- package/src/apps/data/routes/webhooks.ts +11 -8
- package/src/apps/docs/App.ts +29 -100
- package/src/apps/docs/reference.ts +132 -0
- package/src/apps/index.ts +1 -0
- package/src/apps/mcp.test.ts +206 -0
- package/src/apps/mcp.ts +250 -0
- package/src/apps/platform/App.ts +43 -4
- package/src/apps/platform/Billing.test.ts +530 -0
- package/src/apps/platform/Billing.ts +682 -0
- package/src/apps/platform/index.ts +1 -0
- package/src/apps/platform/routes/api-keys.test.ts +172 -9
- package/src/apps/platform/routes/api-keys.ts +136 -20
- package/src/apps/platform/routes/billing.test.ts +1100 -0
- package/src/apps/platform/routes/billing.ts +966 -0
- package/src/apps/platform/routes/invitations.test.ts +55 -7
- package/src/apps/platform/routes/invitations.ts +68 -0
- package/src/apps/platform/routes/me.test.ts +230 -0
- package/src/apps/platform/routes/orgs.ts +26 -4
- package/src/apps/platform/routes/scopes.test.ts +29 -0
- package/src/apps/platform/routes/scopes.ts +85 -0
- package/src/apps/platform/routes/usage.test.ts +422 -0
- package/src/apps/platform/routes/usage.ts +527 -0
- package/src/apps/relay/App.test.ts +457 -8
- package/src/apps/relay/App.ts +120 -12
- package/src/apps/relay/Sponsorships.test.ts +1 -1
- package/src/apps/relay/Sponsorships.ts +2 -1
- package/src/cli/Cli.test.ts +25 -2
- package/src/cli/Cli.ts +17 -2
- package/src/cli/commands/admin.test.ts +83 -0
- package/src/cli/commands/admin.ts +89 -1
- package/src/db/Db.ts +137 -0
- package/src/db/Schema.ts +104 -2
- package/src/db/tables/billingSettings.test.ts +87 -0
- package/src/db/tables/billingSettings.ts +108 -0
- package/src/db/tables/invitations.ts +2 -2
- package/src/db/tables/organizations.test.ts +1 -1
- package/src/db/tables/organizations.ts +15 -5
- package/src/db/tables/projects.ts +2 -2
- package/src/db/tables/requestUsage.ts +231 -0
- package/src/db/tables/sponsoredTransactions.test.ts +108 -1
- package/src/db/tables/sponsoredTransactions.ts +230 -2
- package/src/db/tables/stripeCustomers.test.ts +109 -0
- package/src/db/tables/stripeCustomers.ts +162 -0
- package/src/db/tables/users.ts +2 -2
- package/src/handlers/internal/sponsorship.ts +34 -7
- package/src/handlers/internal/utils.ts +19 -7
- package/src/index.ts +1 -0
- package/src/internal/Auth.test.ts +175 -25
- package/src/internal/Auth.ts +175 -86
- package/src/internal/Cache.ts +5 -1
- package/src/internal/EdgeCache.test.ts +27 -4
- package/src/internal/EdgeCache.ts +47 -9
- package/src/internal/Fees.test.ts +32 -0
- package/src/internal/Fees.ts +51 -0
- package/src/internal/FundingRoutes.ts +583 -0
- package/src/internal/Id.test.ts +17 -0
- package/src/internal/Id.ts +24 -0
- package/src/internal/Log.test.ts +1 -0
- package/src/internal/Log.ts +5 -0
- package/src/internal/OpenApi.ts +12 -1
- package/src/internal/RateLimit.test.ts +84 -0
- package/src/internal/RateLimit.ts +70 -0
- package/src/internal/Response.ts +1 -1
- package/src/internal/Schema.ts +8 -0
- package/src/internal/Webhooks.test.ts +3 -3
- package/src/internal/Webhooks.ts +3 -3
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
import * as RequestEvents from './tables/requestEvents.js'
|
|
2
|
+
|
|
3
|
+
export { createQueueSink } from './tables/requestEvents.js'
|
|
4
|
+
|
|
5
|
+
/** One org's deduped billable request count for a UTC hour; the reporter's source row. */
|
|
6
|
+
export type BillableCount = RequestEvents.BillableCount
|
|
7
|
+
|
|
8
|
+
/** Options for {@link readBillableRequestCounts}. */
|
|
9
|
+
export type BillableCountsOptions = RequestEvents.BillableCountsOptions
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Reads deduped billable request counts grouped by `(org, UTC hour)` from
|
|
13
|
+
* `request_events` — the request-metered billing source. Resolves the source
|
|
14
|
+
* at the leaf so a Workers factory builds a fresh connection.
|
|
15
|
+
*
|
|
16
|
+
* @param source - The analytics store or factory.
|
|
17
|
+
* @param options - The read options.
|
|
18
|
+
* @returns One deduped count per `(org, hour)`.
|
|
19
|
+
*/
|
|
20
|
+
export function readBillableRequestCounts(
|
|
21
|
+
source: Source,
|
|
22
|
+
options: BillableCountsOptions,
|
|
23
|
+
): Promise<BillableCount[]> {
|
|
24
|
+
return RequestEvents.readBillableCounts(get(source), options)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* ClickHouse schema; table repository modules (`tables/*`) type their rows
|
|
29
|
+
* through it.
|
|
30
|
+
*/
|
|
31
|
+
export type Database = {
|
|
32
|
+
request_events: RequestEvents.Table
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* A ClickHouse-backed analytics store: row inserts, JSON reads, and schema
|
|
37
|
+
* migrations. Table repository modules (`tables/*`) query through it.
|
|
38
|
+
*/
|
|
39
|
+
export type Analytics = {
|
|
40
|
+
/** Inserts rows into a table. */
|
|
41
|
+
insert<name extends keyof Database>(table: name, rows: readonly Database[name][]): Promise<void>
|
|
42
|
+
/** Applies any pending schema migrations; safe to re-run. */
|
|
43
|
+
migrate(): Promise<void>
|
|
44
|
+
/** Runs a read query, returning JSON rows. */
|
|
45
|
+
query<row>(sql: string): Promise<row[]>
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* An {@link Analytics}, or a factory for per-request construction. Resolve at
|
|
50
|
+
* the leaf with {@link get}.
|
|
51
|
+
*/
|
|
52
|
+
export type Source = Analytics | (() => Analytics)
|
|
53
|
+
|
|
54
|
+
/** One stored request row (`request_events`); hosts use it in their queue-body union. */
|
|
55
|
+
export type Event = RequestEvents.Table
|
|
56
|
+
|
|
57
|
+
/** Queue name {@link handleQueue} consumes; the host's queue producer/consumer bindings must reference it. */
|
|
58
|
+
export const queueName = 'api-request-analytics'
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Resolves a {@link Source} to an {@link Analytics}. Call at the request leaf
|
|
62
|
+
* so factories create per-request instances; singletons resolve to themselves.
|
|
63
|
+
*
|
|
64
|
+
* @param source - The analytics store or factory.
|
|
65
|
+
* @returns The resolved {@link Analytics}.
|
|
66
|
+
*/
|
|
67
|
+
export function get(source: Source): Analytics {
|
|
68
|
+
return typeof source === 'function' ? source() : source
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** One schema migration: a single idempotent DDL statement, safe to re-apply. */
|
|
72
|
+
type Migration = {
|
|
73
|
+
/** Unique, ordered migration name (e.g. `0001_request_events`). */
|
|
74
|
+
name: string
|
|
75
|
+
/** Idempotent DDL applied by {@link Analytics.migrate}. */
|
|
76
|
+
sql: string
|
|
77
|
+
/**
|
|
78
|
+
* Optional `SELECT` run before {@link Migration.sql}; a nonzero first value
|
|
79
|
+
* skips the migration. Lets a scoped user without an `ALTER` grant (preview)
|
|
80
|
+
* skip an `ALTER` whose columns already exist from the baseline `CREATE`.
|
|
81
|
+
*/
|
|
82
|
+
guard?: string
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Ordered migrations for {@link Analytics.migrate}. ClickHouse lacks
|
|
87
|
+
* transactional DDL and advisory locks, so each statement is idempotent
|
|
88
|
+
* (`IF NOT EXISTS`) and re-applied on every run; no applied-migrations ledger.
|
|
89
|
+
*/
|
|
90
|
+
export const migrations: readonly Migration[] = [
|
|
91
|
+
{
|
|
92
|
+
name: '0001_request_events',
|
|
93
|
+
sql: `
|
|
94
|
+
CREATE TABLE IF NOT EXISTS request_events (
|
|
95
|
+
timestamp DateTime64(3, 'UTC'),
|
|
96
|
+
service LowCardinality(String),
|
|
97
|
+
environment LowCardinality(String),
|
|
98
|
+
method LowCardinality(String),
|
|
99
|
+
route LowCardinality(String),
|
|
100
|
+
status UInt16,
|
|
101
|
+
duration_ms Float64,
|
|
102
|
+
principal_type LowCardinality(String),
|
|
103
|
+
key_id Nullable(String),
|
|
104
|
+
key_environment Nullable(String),
|
|
105
|
+
org_id Nullable(String),
|
|
106
|
+
project_id Nullable(String),
|
|
107
|
+
rate_limit_scope Nullable(String),
|
|
108
|
+
error_code Nullable(String),
|
|
109
|
+
request_id Nullable(String),
|
|
110
|
+
billing_active Nullable(UInt8)
|
|
111
|
+
)
|
|
112
|
+
ENGINE = MergeTree
|
|
113
|
+
ORDER BY (timestamp)
|
|
114
|
+
`,
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
name: '0002_request_events_billing',
|
|
118
|
+
// Patches tables created before the billing columns were folded into 0001
|
|
119
|
+
// (production). Fresh databases already carry them from the CREATE, so the
|
|
120
|
+
// guard skips this ALTER there, where the scoped migrate user (preview)
|
|
121
|
+
// lacks the ALTER grant. ADD COLUMN IF NOT EXISTS keeps it idempotent.
|
|
122
|
+
guard: `
|
|
123
|
+
SELECT toUInt8(count() = 2) FROM system.columns
|
|
124
|
+
WHERE database = currentDatabase()
|
|
125
|
+
AND table = 'request_events'
|
|
126
|
+
AND name IN ('request_id', 'billing_active')
|
|
127
|
+
`,
|
|
128
|
+
sql: `
|
|
129
|
+
ALTER TABLE request_events
|
|
130
|
+
ADD COLUMN IF NOT EXISTS request_id Nullable(String),
|
|
131
|
+
ADD COLUMN IF NOT EXISTS billing_active Nullable(UInt8)
|
|
132
|
+
`,
|
|
133
|
+
},
|
|
134
|
+
]
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Creates a ClickHouse-backed {@link Analytics} over the HTTP interface.
|
|
138
|
+
* Requests scope to `database` (the `?database=` parameter); table names are
|
|
139
|
+
* code-owned by the `tables/*` modules, not configuration.
|
|
140
|
+
*
|
|
141
|
+
* @param options - ClickHouse connection options.
|
|
142
|
+
* @returns The analytics store.
|
|
143
|
+
*/
|
|
144
|
+
export function clickhouse(options: clickhouse.Options): Analytics {
|
|
145
|
+
const authorization = `Basic ${btoa(`${options.user}:${options.password}`)}`
|
|
146
|
+
|
|
147
|
+
function endpoint() {
|
|
148
|
+
const url = new URL(options.url)
|
|
149
|
+
url.searchParams.set('database', options.database)
|
|
150
|
+
url.searchParams.set('date_time_input_format', 'best_effort')
|
|
151
|
+
return url
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Runs a migration guard SELECT; true (skip) when its first value is nonzero.
|
|
155
|
+
// A failed guard returns false so the migration still runs and reports.
|
|
156
|
+
async function guarded(sql: string): Promise<boolean> {
|
|
157
|
+
const response = await fetch(endpoint(), {
|
|
158
|
+
body: `${sql} FORMAT JSON`,
|
|
159
|
+
headers: { Authorization: authorization, 'Content-Type': 'text/plain' },
|
|
160
|
+
method: 'POST',
|
|
161
|
+
})
|
|
162
|
+
if (!response.ok) return false
|
|
163
|
+
try {
|
|
164
|
+
const body = (await response.json()) as { data?: Array<Record<string, unknown>> }
|
|
165
|
+
const first = body.data?.[0]
|
|
166
|
+
return first ? Number(Object.values(first)[0]) > 0 : false
|
|
167
|
+
} catch {
|
|
168
|
+
return false
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return {
|
|
173
|
+
async insert(table, rows) {
|
|
174
|
+
if (rows.length === 0) return
|
|
175
|
+
const url = endpoint()
|
|
176
|
+
url.searchParams.set('query', `INSERT INTO ${quote(table)} FORMAT JSONEachRow`)
|
|
177
|
+
const response = await fetch(url, {
|
|
178
|
+
body: `${rows.map((row) => JSON.stringify(row)).join('\n')}\n`,
|
|
179
|
+
headers: { Authorization: authorization, 'Content-Type': 'application/x-ndjson' },
|
|
180
|
+
method: 'POST',
|
|
181
|
+
})
|
|
182
|
+
if (response.ok) return
|
|
183
|
+
throw new InsertError(response.status, await response.text())
|
|
184
|
+
},
|
|
185
|
+
async migrate() {
|
|
186
|
+
for (const migration of migrations) {
|
|
187
|
+
if (migration.guard && (await guarded(migration.guard))) continue
|
|
188
|
+
const response = await fetch(endpoint(), {
|
|
189
|
+
body: migration.sql,
|
|
190
|
+
headers: { Authorization: authorization, 'Content-Type': 'text/plain' },
|
|
191
|
+
method: 'POST',
|
|
192
|
+
})
|
|
193
|
+
if (response.ok) continue
|
|
194
|
+
throw new MigrateError(migration.name, response.status, await response.text())
|
|
195
|
+
}
|
|
196
|
+
},
|
|
197
|
+
async query<row>(sql: string): Promise<row[]> {
|
|
198
|
+
const response = await fetch(endpoint(), {
|
|
199
|
+
body: `${sql} FORMAT JSON`,
|
|
200
|
+
headers: { Authorization: authorization, 'Content-Type': 'text/plain' },
|
|
201
|
+
method: 'POST',
|
|
202
|
+
})
|
|
203
|
+
if (!response.ok) throw new QueryError(response.status, await response.text())
|
|
204
|
+
const body = (await response.json()) as { data?: row[] | undefined }
|
|
205
|
+
return body.data ?? []
|
|
206
|
+
},
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export declare namespace clickhouse {
|
|
211
|
+
/** ClickHouse connection options. */
|
|
212
|
+
type Options = {
|
|
213
|
+
/** Database the connection scopes to (e.g. `tempo_api`). */
|
|
214
|
+
database: string
|
|
215
|
+
/** Password for `user`. */
|
|
216
|
+
password: string
|
|
217
|
+
/** ClickHouse HTTPS endpoint. */
|
|
218
|
+
url: string
|
|
219
|
+
/** ClickHouse user; ingest and read deployments differ only here. */
|
|
220
|
+
user: string
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Consumes one queue batch when it is the analytics queue ({@link queueName}):
|
|
226
|
+
* batch-inserts the queued rows, acking on success and retrying on failure.
|
|
227
|
+
* Returns whether the batch was handled, so a multi-queue worker can chain:
|
|
228
|
+
*
|
|
229
|
+
* ```ts
|
|
230
|
+
* async queue(batch) {
|
|
231
|
+
* if (await Analytics.handleQueue(analytics, batch)) return
|
|
232
|
+
* // … other queues …
|
|
233
|
+
* }
|
|
234
|
+
* ```
|
|
235
|
+
*
|
|
236
|
+
* @param source - The analytics store or factory.
|
|
237
|
+
* @param batch - The queue batch.
|
|
238
|
+
* @param options - Options.
|
|
239
|
+
* @returns Whether the batch belonged to the analytics queue.
|
|
240
|
+
*/
|
|
241
|
+
export async function handleQueue(
|
|
242
|
+
source: Source,
|
|
243
|
+
batch: handleQueue.Batch,
|
|
244
|
+
options: handleQueue.Options = {},
|
|
245
|
+
): Promise<boolean> {
|
|
246
|
+
if (batch.queue !== (options.queue ?? queueName)) return false
|
|
247
|
+
await RequestEvents.insertMessages(
|
|
248
|
+
get(source),
|
|
249
|
+
batch.messages as readonly RequestEvents.insertMessages.Message[],
|
|
250
|
+
)
|
|
251
|
+
return true
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export declare namespace handleQueue {
|
|
255
|
+
/** Options for {@link handleQueue}. */
|
|
256
|
+
type Options = {
|
|
257
|
+
/** Queue name to match instead of {@link queueName} (e.g. a per-preview queue). */
|
|
258
|
+
queue?: string | undefined
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/** Minimal queue batch shape; Cloudflare's `MessageBatch` satisfies it. */
|
|
262
|
+
type Batch = {
|
|
263
|
+
/** Queued messages; bodies are {@link Event} rows when `queue` matches. */
|
|
264
|
+
messages: readonly Message[]
|
|
265
|
+
/** Originating queue name. */
|
|
266
|
+
queue: string
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/** One queued message. */
|
|
270
|
+
type Message = {
|
|
271
|
+
/** Acknowledges the message. */
|
|
272
|
+
ack(): void
|
|
273
|
+
/** Queued body; an {@link Event} when the batch is the analytics queue. */
|
|
274
|
+
body: unknown
|
|
275
|
+
/** Marks the message for redelivery. */
|
|
276
|
+
retry(): void
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/** Valid unquoted ClickHouse identifier. */
|
|
281
|
+
const identifier = /^[A-Za-z_][A-Za-z0-9_]*$/
|
|
282
|
+
|
|
283
|
+
function quote(value: string) {
|
|
284
|
+
if (identifier.test(value)) return `\`${value}\``
|
|
285
|
+
throw new ConfigError(value)
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/** Rejected ClickHouse identifier. */
|
|
289
|
+
export class ConfigError extends Error {
|
|
290
|
+
constructor(value: string) {
|
|
291
|
+
super(`Invalid ClickHouse identifier: ${value}`)
|
|
292
|
+
this.name = 'Analytics.ConfigError'
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/** Failed ClickHouse insert (non-2xx HTTP response). */
|
|
297
|
+
export class InsertError extends Error {
|
|
298
|
+
constructor(status: number, body: string) {
|
|
299
|
+
super(`ClickHouse insert failed with status ${status}: ${body.slice(0, 500)}`)
|
|
300
|
+
this.name = 'Analytics.InsertError'
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/** Failed ClickHouse schema migration (non-2xx HTTP response). */
|
|
305
|
+
export class MigrateError extends Error {
|
|
306
|
+
constructor(name: string, status: number, body: string) {
|
|
307
|
+
super(`ClickHouse migration "${name}" failed with status ${status}: ${body.slice(0, 500)}`)
|
|
308
|
+
this.name = 'Analytics.MigrateError'
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/** Failed ClickHouse read query (non-2xx HTTP response). */
|
|
313
|
+
export class QueryError extends Error {
|
|
314
|
+
constructor(status: number, body: string) {
|
|
315
|
+
super(`ClickHouse query failed with status ${status}: ${body.slice(0, 500)}`)
|
|
316
|
+
this.name = 'Analytics.QueryError'
|
|
317
|
+
}
|
|
318
|
+
}
|