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.
Files changed (313) hide show
  1. package/README.md +31 -3
  2. package/dist/ApiKey.d.ts +1 -0
  3. package/dist/ApiKey.d.ts.map +1 -1
  4. package/dist/ApiKey.js +5 -2
  5. package/dist/ApiKey.js.map +1 -1
  6. package/dist/ApiKeys.d.ts +28 -1
  7. package/dist/ApiKeys.d.ts.map +1 -1
  8. package/dist/ApiKeys.js +34 -1
  9. package/dist/ApiKeys.js.map +1 -1
  10. package/dist/App.d.ts +7167 -4507
  11. package/dist/App.d.ts.map +1 -1
  12. package/dist/App.js +8 -0
  13. package/dist/App.js.map +1 -1
  14. package/dist/Scope.d.ts +11 -2
  15. package/dist/Scope.d.ts.map +1 -1
  16. package/dist/Scope.js +14 -3
  17. package/dist/Scope.js.map +1 -1
  18. package/dist/admin/App.d.ts +128 -118
  19. package/dist/admin/App.d.ts.map +1 -1
  20. package/dist/admin/apps/api-keys.d.ts +7 -0
  21. package/dist/admin/apps/api-keys.d.ts.map +1 -1
  22. package/dist/admin/apps/scopes.d.ts +8 -0
  23. package/dist/admin/apps/scopes.d.ts.map +1 -1
  24. package/dist/admin/apps/scopes.js +3 -0
  25. package/dist/admin/apps/scopes.js.map +1 -1
  26. package/dist/admin/apps/verified-tokens.d.ts +57 -57
  27. package/dist/admin/ui.generated.d.ts +1 -1
  28. package/dist/admin/ui.generated.js +13 -13
  29. package/dist/admin/ui.generated.js.map +1 -1
  30. package/dist/analytics/Analytics.d.ts +151 -0
  31. package/dist/analytics/Analytics.d.ts.map +1 -0
  32. package/dist/analytics/Analytics.js +212 -0
  33. package/dist/analytics/Analytics.js.map +1 -0
  34. package/dist/analytics/tables/requestEvents.d.ts +215 -0
  35. package/dist/analytics/tables/requestEvents.d.ts.map +1 -0
  36. package/dist/analytics/tables/requestEvents.js +264 -0
  37. package/dist/analytics/tables/requestEvents.js.map +1 -0
  38. package/dist/apps/data/App.d.ts +1024 -624
  39. package/dist/apps/data/App.d.ts.map +1 -1
  40. package/dist/apps/data/App.js +36 -0
  41. package/dist/apps/data/App.js.map +1 -1
  42. package/dist/apps/data/routes/balances.d.ts +45 -45
  43. package/dist/apps/data/routes/coingecko.d.ts +176 -176
  44. package/dist/apps/data/routes/exchanges.d.ts +455 -455
  45. package/dist/apps/data/routes/fee-amm.d.ts +101 -101
  46. package/dist/apps/data/routes/funding.d.ts +765 -0
  47. package/dist/apps/data/routes/funding.d.ts.map +1 -0
  48. package/dist/apps/data/routes/funding.js +383 -0
  49. package/dist/apps/data/routes/funding.js.map +1 -0
  50. package/dist/apps/data/routes/receipts.d.ts.map +1 -1
  51. package/dist/apps/data/routes/receipts.js +2 -8
  52. package/dist/apps/data/routes/receipts.js.map +1 -1
  53. package/dist/apps/data/routes/rpc.d.ts +29 -36
  54. package/dist/apps/data/routes/rpc.d.ts.map +1 -1
  55. package/dist/apps/data/routes/rpc.js +29 -1
  56. package/dist/apps/data/routes/rpc.js.map +1 -1
  57. package/dist/apps/data/routes/tokenlist.d.ts +36 -36
  58. package/dist/apps/data/routes/transfers.d.ts +56 -56
  59. package/dist/apps/data/routes/verified-tokens.d.ts +36 -36
  60. package/dist/apps/data/routes/webhooks.d.ts +45 -45
  61. package/dist/apps/data/routes/webhooks.d.ts.map +1 -1
  62. package/dist/apps/data/routes/webhooks.js +8 -8
  63. package/dist/apps/data/routes/webhooks.js.map +1 -1
  64. package/dist/apps/docs/App.d.ts +4 -2
  65. package/dist/apps/docs/App.d.ts.map +1 -1
  66. package/dist/apps/docs/App.js +27 -95
  67. package/dist/apps/docs/App.js.map +1 -1
  68. package/dist/apps/docs/reference.d.ts +21 -0
  69. package/dist/apps/docs/reference.d.ts.map +1 -0
  70. package/dist/apps/docs/reference.js +112 -0
  71. package/dist/apps/docs/reference.js.map +1 -0
  72. package/dist/apps/index.d.ts +1 -0
  73. package/dist/apps/index.d.ts.map +1 -1
  74. package/dist/apps/index.js +1 -0
  75. package/dist/apps/index.js.map +1 -1
  76. package/dist/apps/mcp.d.ts +76 -0
  77. package/dist/apps/mcp.d.ts.map +1 -0
  78. package/dist/apps/mcp.js +188 -0
  79. package/dist/apps/mcp.js.map +1 -0
  80. package/dist/apps/platform/App.d.ts +3005 -728
  81. package/dist/apps/platform/App.d.ts.map +1 -1
  82. package/dist/apps/platform/App.js +13 -3
  83. package/dist/apps/platform/App.js.map +1 -1
  84. package/dist/apps/platform/Billing.d.ts +279 -0
  85. package/dist/apps/platform/Billing.d.ts.map +1 -0
  86. package/dist/apps/platform/Billing.js +477 -0
  87. package/dist/apps/platform/Billing.js.map +1 -0
  88. package/dist/apps/platform/index.d.ts +1 -0
  89. package/dist/apps/platform/index.d.ts.map +1 -1
  90. package/dist/apps/platform/index.js +1 -0
  91. package/dist/apps/platform/index.js.map +1 -1
  92. package/dist/apps/platform/routes/api-keys.d.ts +341 -49
  93. package/dist/apps/platform/routes/api-keys.d.ts.map +1 -1
  94. package/dist/apps/platform/routes/api-keys.js +115 -13
  95. package/dist/apps/platform/routes/api-keys.js.map +1 -1
  96. package/dist/apps/platform/routes/billing.d.ts +1520 -0
  97. package/dist/apps/platform/routes/billing.d.ts.map +1 -0
  98. package/dist/apps/platform/routes/billing.js +795 -0
  99. package/dist/apps/platform/routes/billing.js.map +1 -0
  100. package/dist/apps/platform/routes/invitations.d.ts +201 -83
  101. package/dist/apps/platform/routes/invitations.d.ts.map +1 -1
  102. package/dist/apps/platform/routes/invitations.js +58 -0
  103. package/dist/apps/platform/routes/invitations.js.map +1 -1
  104. package/dist/apps/platform/routes/me.d.ts +17 -17
  105. package/dist/apps/platform/routes/orgs.d.ts +50 -36
  106. package/dist/apps/platform/routes/orgs.d.ts.map +1 -1
  107. package/dist/apps/platform/routes/orgs.js +14 -3
  108. package/dist/apps/platform/routes/orgs.js.map +1 -1
  109. package/dist/apps/platform/routes/projects.d.ts +97 -97
  110. package/dist/apps/platform/routes/scopes.d.ts +107 -0
  111. package/dist/apps/platform/routes/scopes.d.ts.map +1 -0
  112. package/dist/apps/platform/routes/scopes.js +66 -0
  113. package/dist/apps/platform/routes/scopes.js.map +1 -0
  114. package/dist/apps/platform/routes/usage.d.ts +588 -0
  115. package/dist/apps/platform/routes/usage.d.ts.map +1 -0
  116. package/dist/apps/platform/routes/usage.js +391 -0
  117. package/dist/apps/platform/routes/usage.js.map +1 -0
  118. package/dist/apps/relay/App.d.ts +4 -1
  119. package/dist/apps/relay/App.d.ts.map +1 -1
  120. package/dist/apps/relay/App.js +118 -10
  121. package/dist/apps/relay/App.js.map +1 -1
  122. package/dist/apps/relay/Sponsorships.d.ts.map +1 -1
  123. package/dist/apps/relay/Sponsorships.js +2 -1
  124. package/dist/apps/relay/Sponsorships.js.map +1 -1
  125. package/dist/cli/Cli.d.ts +8 -1
  126. package/dist/cli/Cli.d.ts.map +1 -1
  127. package/dist/cli/Cli.js +11 -3
  128. package/dist/cli/Cli.js.map +1 -1
  129. package/dist/cli/commands/admin.d.ts +4 -0
  130. package/dist/cli/commands/admin.d.ts.map +1 -1
  131. package/dist/cli/commands/admin.js +80 -1
  132. package/dist/cli/commands/admin.js.map +1 -1
  133. package/dist/db/Db.d.ts +7 -0
  134. package/dist/db/Db.d.ts.map +1 -1
  135. package/dist/db/Db.js +126 -0
  136. package/dist/db/Db.js.map +1 -1
  137. package/dist/db/Schema.d.ts +79 -0
  138. package/dist/db/Schema.d.ts.map +1 -1
  139. package/dist/db/Schema.js +88 -2
  140. package/dist/db/Schema.js.map +1 -1
  141. package/dist/db/tables/billingSettings.d.ts +55 -0
  142. package/dist/db/tables/billingSettings.d.ts.map +1 -0
  143. package/dist/db/tables/billingSettings.js +70 -0
  144. package/dist/db/tables/billingSettings.js.map +1 -0
  145. package/dist/db/tables/invitations.js +2 -2
  146. package/dist/db/tables/invitations.js.map +1 -1
  147. package/dist/db/tables/organizations.d.ts +6 -2
  148. package/dist/db/tables/organizations.d.ts.map +1 -1
  149. package/dist/db/tables/organizations.js +10 -4
  150. package/dist/db/tables/organizations.js.map +1 -1
  151. package/dist/db/tables/projects.js +2 -2
  152. package/dist/db/tables/projects.js.map +1 -1
  153. package/dist/db/tables/requestUsage.d.ts +121 -0
  154. package/dist/db/tables/requestUsage.d.ts.map +1 -0
  155. package/dist/db/tables/requestUsage.js +150 -0
  156. package/dist/db/tables/requestUsage.js.map +1 -0
  157. package/dist/db/tables/sponsoredTransactions.d.ts +119 -0
  158. package/dist/db/tables/sponsoredTransactions.d.ts.map +1 -1
  159. package/dist/db/tables/sponsoredTransactions.js +155 -2
  160. package/dist/db/tables/sponsoredTransactions.js.map +1 -1
  161. package/dist/db/tables/stripeCustomers.d.ts +81 -0
  162. package/dist/db/tables/stripeCustomers.d.ts.map +1 -0
  163. package/dist/db/tables/stripeCustomers.js +120 -0
  164. package/dist/db/tables/stripeCustomers.js.map +1 -0
  165. package/dist/db/tables/users.js +2 -2
  166. package/dist/db/tables/users.js.map +1 -1
  167. package/dist/handlers/internal/sponsorship.d.ts +5 -2
  168. package/dist/handlers/internal/sponsorship.d.ts.map +1 -1
  169. package/dist/handlers/internal/sponsorship.js +19 -5
  170. package/dist/handlers/internal/sponsorship.js.map +1 -1
  171. package/dist/handlers/internal/utils.d.ts +10 -3
  172. package/dist/handlers/internal/utils.d.ts.map +1 -1
  173. package/dist/handlers/internal/utils.js +14 -6
  174. package/dist/handlers/internal/utils.js.map +1 -1
  175. package/dist/index.d.ts +1 -0
  176. package/dist/index.d.ts.map +1 -1
  177. package/dist/index.js +1 -0
  178. package/dist/index.js.map +1 -1
  179. package/dist/internal/Auth.d.ts +18 -4
  180. package/dist/internal/Auth.d.ts.map +1 -1
  181. package/dist/internal/Auth.js +148 -76
  182. package/dist/internal/Auth.js.map +1 -1
  183. package/dist/internal/Cache.d.ts.map +1 -1
  184. package/dist/internal/Cache.js +4 -1
  185. package/dist/internal/Cache.js.map +1 -1
  186. package/dist/internal/EdgeCache.d.ts +10 -0
  187. package/dist/internal/EdgeCache.d.ts.map +1 -1
  188. package/dist/internal/EdgeCache.js +39 -8
  189. package/dist/internal/EdgeCache.js.map +1 -1
  190. package/dist/internal/Fees.d.ts +27 -0
  191. package/dist/internal/Fees.d.ts.map +1 -0
  192. package/dist/internal/Fees.js +48 -0
  193. package/dist/internal/Fees.js.map +1 -0
  194. package/dist/internal/FundingRoutes.d.ts +183 -0
  195. package/dist/internal/FundingRoutes.d.ts.map +1 -0
  196. package/dist/internal/FundingRoutes.js +490 -0
  197. package/dist/internal/FundingRoutes.js.map +1 -0
  198. package/dist/internal/Id.d.ts +15 -0
  199. package/dist/internal/Id.d.ts.map +1 -0
  200. package/dist/internal/Id.js +22 -0
  201. package/dist/internal/Id.js.map +1 -0
  202. package/dist/internal/Log.d.ts +2 -0
  203. package/dist/internal/Log.d.ts.map +1 -1
  204. package/dist/internal/Log.js +3 -0
  205. package/dist/internal/Log.js.map +1 -1
  206. package/dist/internal/OpenApi.d.ts +10 -1
  207. package/dist/internal/OpenApi.d.ts.map +1 -1
  208. package/dist/internal/OpenApi.js +8 -1
  209. package/dist/internal/OpenApi.js.map +1 -1
  210. package/dist/internal/RateLimit.d.ts +25 -0
  211. package/dist/internal/RateLimit.d.ts.map +1 -1
  212. package/dist/internal/RateLimit.js +51 -0
  213. package/dist/internal/RateLimit.js.map +1 -1
  214. package/dist/internal/Response.d.ts +1 -1
  215. package/dist/internal/Response.d.ts.map +1 -1
  216. package/dist/internal/Schema.d.ts +8 -0
  217. package/dist/internal/Schema.d.ts.map +1 -1
  218. package/dist/internal/Schema.js +7 -0
  219. package/dist/internal/Schema.js.map +1 -1
  220. package/dist/internal/Webhooks.d.ts.map +1 -1
  221. package/dist/internal/Webhooks.js +3 -3
  222. package/dist/internal/Webhooks.js.map +1 -1
  223. package/package.json +4 -3
  224. package/src/ApiKey.ts +9 -2
  225. package/src/ApiKeys.test.ts +51 -0
  226. package/src/ApiKeys.ts +50 -1
  227. package/src/App.test.ts +33 -86718
  228. package/src/App.ts +18 -0
  229. package/src/Client.test-d.ts +351 -0
  230. package/src/Scope.ts +16 -3
  231. package/src/admin/apps/scopes.test.ts +6 -0
  232. package/src/admin/apps/scopes.ts +3 -0
  233. package/src/admin/ui/src/components/Page.tsx +2 -3
  234. package/src/admin/ui/src/routes/__root.tsx +2 -2
  235. package/src/admin/ui/src/routes/api-keys.tsx +2 -2
  236. package/src/admin/ui.generated.ts +13 -13
  237. package/src/analytics/Analytics.test.ts +527 -0
  238. package/src/analytics/Analytics.ts +318 -0
  239. package/src/analytics/tables/requestEvents.ts +523 -0
  240. package/src/apps/data/App.test.ts +72 -0
  241. package/src/apps/data/App.ts +35 -0
  242. package/src/apps/data/routes/funding.test.ts +214 -0
  243. package/src/apps/data/routes/funding.ts +584 -0
  244. package/src/apps/data/routes/receipts.ts +2 -9
  245. package/src/apps/data/routes/rpc.test.ts +78 -2
  246. package/src/apps/data/routes/rpc.ts +31 -1
  247. package/src/apps/data/routes/webhooks.test.ts +1 -1
  248. package/src/apps/data/routes/webhooks.ts +11 -8
  249. package/src/apps/docs/App.ts +29 -100
  250. package/src/apps/docs/reference.ts +132 -0
  251. package/src/apps/index.ts +1 -0
  252. package/src/apps/mcp.test.ts +206 -0
  253. package/src/apps/mcp.ts +250 -0
  254. package/src/apps/platform/App.ts +43 -4
  255. package/src/apps/platform/Billing.test.ts +530 -0
  256. package/src/apps/platform/Billing.ts +682 -0
  257. package/src/apps/platform/index.ts +1 -0
  258. package/src/apps/platform/routes/api-keys.test.ts +172 -9
  259. package/src/apps/platform/routes/api-keys.ts +136 -20
  260. package/src/apps/platform/routes/billing.test.ts +1100 -0
  261. package/src/apps/platform/routes/billing.ts +966 -0
  262. package/src/apps/platform/routes/invitations.test.ts +55 -7
  263. package/src/apps/platform/routes/invitations.ts +68 -0
  264. package/src/apps/platform/routes/me.test.ts +230 -0
  265. package/src/apps/platform/routes/orgs.ts +26 -4
  266. package/src/apps/platform/routes/scopes.test.ts +29 -0
  267. package/src/apps/platform/routes/scopes.ts +85 -0
  268. package/src/apps/platform/routes/usage.test.ts +422 -0
  269. package/src/apps/platform/routes/usage.ts +527 -0
  270. package/src/apps/relay/App.test.ts +457 -8
  271. package/src/apps/relay/App.ts +120 -12
  272. package/src/apps/relay/Sponsorships.test.ts +1 -1
  273. package/src/apps/relay/Sponsorships.ts +2 -1
  274. package/src/cli/Cli.test.ts +25 -2
  275. package/src/cli/Cli.ts +17 -2
  276. package/src/cli/commands/admin.test.ts +83 -0
  277. package/src/cli/commands/admin.ts +89 -1
  278. package/src/db/Db.ts +137 -0
  279. package/src/db/Schema.ts +104 -2
  280. package/src/db/tables/billingSettings.test.ts +87 -0
  281. package/src/db/tables/billingSettings.ts +108 -0
  282. package/src/db/tables/invitations.ts +2 -2
  283. package/src/db/tables/organizations.test.ts +1 -1
  284. package/src/db/tables/organizations.ts +15 -5
  285. package/src/db/tables/projects.ts +2 -2
  286. package/src/db/tables/requestUsage.ts +231 -0
  287. package/src/db/tables/sponsoredTransactions.test.ts +108 -1
  288. package/src/db/tables/sponsoredTransactions.ts +230 -2
  289. package/src/db/tables/stripeCustomers.test.ts +109 -0
  290. package/src/db/tables/stripeCustomers.ts +162 -0
  291. package/src/db/tables/users.ts +2 -2
  292. package/src/handlers/internal/sponsorship.ts +34 -7
  293. package/src/handlers/internal/utils.ts +19 -7
  294. package/src/index.ts +1 -0
  295. package/src/internal/Auth.test.ts +175 -25
  296. package/src/internal/Auth.ts +175 -86
  297. package/src/internal/Cache.ts +5 -1
  298. package/src/internal/EdgeCache.test.ts +27 -4
  299. package/src/internal/EdgeCache.ts +47 -9
  300. package/src/internal/Fees.test.ts +32 -0
  301. package/src/internal/Fees.ts +51 -0
  302. package/src/internal/FundingRoutes.ts +583 -0
  303. package/src/internal/Id.test.ts +17 -0
  304. package/src/internal/Id.ts +24 -0
  305. package/src/internal/Log.test.ts +1 -0
  306. package/src/internal/Log.ts +5 -0
  307. package/src/internal/OpenApi.ts +12 -1
  308. package/src/internal/RateLimit.test.ts +84 -0
  309. package/src/internal/RateLimit.ts +70 -0
  310. package/src/internal/Response.ts +1 -1
  311. package/src/internal/Schema.ts +8 -0
  312. package/src/internal/Webhooks.test.ts +3 -3
  313. 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
+ }