tapimo 0.2.0 → 0.4.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 (362) hide show
  1. package/README.md +36 -8
  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 +7532 -4850
  11. package/dist/App.d.ts.map +1 -1
  12. package/dist/App.js +11 -3
  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 +63 -53
  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 +1049 -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/FundingRouteProviders.d.ts +54 -0
  43. package/dist/apps/data/FundingRouteProviders.d.ts.map +1 -0
  44. package/dist/apps/data/FundingRouteProviders.js +355 -0
  45. package/dist/apps/data/FundingRouteProviders.js.map +1 -0
  46. package/dist/apps/data/index.d.ts +1 -0
  47. package/dist/apps/data/index.d.ts.map +1 -1
  48. package/dist/apps/data/index.js +1 -0
  49. package/dist/apps/data/index.js.map +1 -1
  50. package/dist/apps/data/routes/balances.d.ts +45 -45
  51. package/dist/apps/data/routes/coingecko.d.ts +176 -176
  52. package/dist/apps/data/routes/exchanges.d.ts +455 -455
  53. package/dist/apps/data/routes/fee-amm.d.ts +101 -101
  54. package/dist/apps/data/routes/funding.d.ts +843 -0
  55. package/dist/apps/data/routes/funding.d.ts.map +1 -0
  56. package/dist/apps/data/routes/funding.js +443 -0
  57. package/dist/apps/data/routes/funding.js.map +1 -0
  58. package/dist/apps/data/routes/indexer.d.ts +5 -5
  59. package/dist/apps/data/routes/receipts.d.ts.map +1 -1
  60. package/dist/apps/data/routes/receipts.js +2 -8
  61. package/dist/apps/data/routes/receipts.js.map +1 -1
  62. package/dist/apps/data/routes/rpc.d.ts +29 -36
  63. package/dist/apps/data/routes/rpc.d.ts.map +1 -1
  64. package/dist/apps/data/routes/rpc.js +29 -1
  65. package/dist/apps/data/routes/rpc.js.map +1 -1
  66. package/dist/apps/data/routes/tokenlist.d.ts +36 -36
  67. package/dist/apps/data/routes/transfers.d.ts +56 -56
  68. package/dist/apps/data/routes/verified-tokens.d.ts +36 -36
  69. package/dist/apps/data/routes/webhooks.d.ts +45 -45
  70. package/dist/apps/data/routes/webhooks.d.ts.map +1 -1
  71. package/dist/apps/data/routes/webhooks.js +8 -8
  72. package/dist/apps/data/routes/webhooks.js.map +1 -1
  73. package/dist/apps/docs/App.d.ts +5 -3
  74. package/dist/apps/docs/App.d.ts.map +1 -1
  75. package/dist/apps/docs/App.js +28 -96
  76. package/dist/apps/docs/App.js.map +1 -1
  77. package/dist/apps/docs/reference.d.ts +21 -0
  78. package/dist/apps/docs/reference.d.ts.map +1 -0
  79. package/dist/apps/docs/reference.js +112 -0
  80. package/dist/apps/docs/reference.js.map +1 -0
  81. package/dist/apps/index.d.ts +3 -2
  82. package/dist/apps/index.d.ts.map +1 -1
  83. package/dist/apps/index.js +3 -2
  84. package/dist/apps/index.js.map +1 -1
  85. package/dist/apps/{platform → management}/App.d.ts +3015 -738
  86. package/dist/apps/management/App.d.ts.map +1 -0
  87. package/dist/apps/{platform → management}/App.js +20 -10
  88. package/dist/apps/management/App.js.map +1 -0
  89. package/dist/apps/management/Billing.d.ts +279 -0
  90. package/dist/apps/management/Billing.d.ts.map +1 -0
  91. package/dist/apps/management/Billing.js +477 -0
  92. package/dist/apps/management/Billing.js.map +1 -0
  93. package/dist/apps/management/index.d.ts +3 -0
  94. package/dist/apps/management/index.d.ts.map +1 -0
  95. package/dist/apps/management/index.js +3 -0
  96. package/dist/apps/management/index.js.map +1 -0
  97. package/dist/apps/{platform → management}/routes/api-keys.d.ts +341 -49
  98. package/dist/apps/management/routes/api-keys.d.ts.map +1 -0
  99. package/dist/apps/{platform → management}/routes/api-keys.js +116 -14
  100. package/dist/apps/management/routes/api-keys.js.map +1 -0
  101. package/dist/apps/management/routes/billing.d.ts +1520 -0
  102. package/dist/apps/management/routes/billing.d.ts.map +1 -0
  103. package/dist/apps/management/routes/billing.js +795 -0
  104. package/dist/apps/management/routes/billing.js.map +1 -0
  105. package/dist/apps/{platform → management}/routes/invitations.d.ts +202 -84
  106. package/dist/apps/management/routes/invitations.d.ts.map +1 -0
  107. package/dist/apps/{platform → management}/routes/invitations.js +59 -1
  108. package/dist/apps/management/routes/invitations.js.map +1 -0
  109. package/dist/apps/{platform → management}/routes/me.d.ts +17 -17
  110. package/dist/apps/management/routes/me.d.ts.map +1 -0
  111. package/dist/apps/management/routes/me.js.map +1 -0
  112. package/dist/apps/management/routes/members.d.ts.map +1 -0
  113. package/dist/apps/management/routes/members.js.map +1 -0
  114. package/dist/apps/{platform → management}/routes/orgs.d.ts +50 -36
  115. package/dist/apps/management/routes/orgs.d.ts.map +1 -0
  116. package/dist/apps/{platform → management}/routes/orgs.js +14 -3
  117. package/dist/apps/management/routes/orgs.js.map +1 -0
  118. package/dist/apps/{platform → management}/routes/projects.d.ts +97 -97
  119. package/dist/apps/{platform → management}/routes/projects.d.ts.map +1 -1
  120. package/dist/apps/management/routes/projects.js.map +1 -0
  121. package/dist/apps/management/routes/scopes.d.ts +107 -0
  122. package/dist/apps/management/routes/scopes.d.ts.map +1 -0
  123. package/dist/apps/management/routes/scopes.js +66 -0
  124. package/dist/apps/management/routes/scopes.js.map +1 -0
  125. package/dist/apps/management/routes/usage.d.ts +588 -0
  126. package/dist/apps/management/routes/usage.d.ts.map +1 -0
  127. package/dist/apps/management/routes/usage.js +391 -0
  128. package/dist/apps/management/routes/usage.js.map +1 -0
  129. package/dist/apps/mcp.d.ts +76 -0
  130. package/dist/apps/mcp.d.ts.map +1 -0
  131. package/dist/apps/mcp.js +188 -0
  132. package/dist/apps/mcp.js.map +1 -0
  133. package/dist/apps/relay/App.d.ts +4 -1
  134. package/dist/apps/relay/App.d.ts.map +1 -1
  135. package/dist/apps/relay/App.js +118 -10
  136. package/dist/apps/relay/App.js.map +1 -1
  137. package/dist/apps/relay/Sponsorships.d.ts.map +1 -1
  138. package/dist/apps/relay/Sponsorships.js +2 -1
  139. package/dist/apps/relay/Sponsorships.js.map +1 -1
  140. package/dist/cli/Cli.d.ts +8 -1
  141. package/dist/cli/Cli.d.ts.map +1 -1
  142. package/dist/cli/Cli.js +11 -3
  143. package/dist/cli/Cli.js.map +1 -1
  144. package/dist/cli/commands/admin.d.ts +4 -0
  145. package/dist/cli/commands/admin.d.ts.map +1 -1
  146. package/dist/cli/commands/admin.js +80 -1
  147. package/dist/cli/commands/admin.js.map +1 -1
  148. package/dist/db/Db.d.ts +7 -0
  149. package/dist/db/Db.d.ts.map +1 -1
  150. package/dist/db/Db.js +126 -0
  151. package/dist/db/Db.js.map +1 -1
  152. package/dist/db/Schema.d.ts +79 -0
  153. package/dist/db/Schema.d.ts.map +1 -1
  154. package/dist/db/Schema.js +88 -2
  155. package/dist/db/Schema.js.map +1 -1
  156. package/dist/db/tables/billingSettings.d.ts +55 -0
  157. package/dist/db/tables/billingSettings.d.ts.map +1 -0
  158. package/dist/db/tables/billingSettings.js +70 -0
  159. package/dist/db/tables/billingSettings.js.map +1 -0
  160. package/dist/db/tables/invitations.js +2 -2
  161. package/dist/db/tables/invitations.js.map +1 -1
  162. package/dist/db/tables/organizations.d.ts +6 -2
  163. package/dist/db/tables/organizations.d.ts.map +1 -1
  164. package/dist/db/tables/organizations.js +10 -4
  165. package/dist/db/tables/organizations.js.map +1 -1
  166. package/dist/db/tables/projects.js +2 -2
  167. package/dist/db/tables/projects.js.map +1 -1
  168. package/dist/db/tables/requestUsage.d.ts +121 -0
  169. package/dist/db/tables/requestUsage.d.ts.map +1 -0
  170. package/dist/db/tables/requestUsage.js +150 -0
  171. package/dist/db/tables/requestUsage.js.map +1 -0
  172. package/dist/db/tables/sponsoredTransactions.d.ts +119 -0
  173. package/dist/db/tables/sponsoredTransactions.d.ts.map +1 -1
  174. package/dist/db/tables/sponsoredTransactions.js +155 -2
  175. package/dist/db/tables/sponsoredTransactions.js.map +1 -1
  176. package/dist/db/tables/stripeCustomers.d.ts +81 -0
  177. package/dist/db/tables/stripeCustomers.d.ts.map +1 -0
  178. package/dist/db/tables/stripeCustomers.js +120 -0
  179. package/dist/db/tables/stripeCustomers.js.map +1 -0
  180. package/dist/db/tables/users.js +2 -2
  181. package/dist/db/tables/users.js.map +1 -1
  182. package/dist/handlers/internal/sponsorship.d.ts +5 -2
  183. package/dist/handlers/internal/sponsorship.d.ts.map +1 -1
  184. package/dist/handlers/internal/sponsorship.js +19 -5
  185. package/dist/handlers/internal/sponsorship.js.map +1 -1
  186. package/dist/handlers/internal/utils.d.ts +10 -3
  187. package/dist/handlers/internal/utils.d.ts.map +1 -1
  188. package/dist/handlers/internal/utils.js +14 -6
  189. package/dist/handlers/internal/utils.js.map +1 -1
  190. package/dist/index.d.ts +1 -0
  191. package/dist/index.d.ts.map +1 -1
  192. package/dist/index.js +1 -0
  193. package/dist/index.js.map +1 -1
  194. package/dist/internal/Auth.d.ts +18 -4
  195. package/dist/internal/Auth.d.ts.map +1 -1
  196. package/dist/internal/Auth.js +149 -77
  197. package/dist/internal/Auth.js.map +1 -1
  198. package/dist/internal/Cache.d.ts.map +1 -1
  199. package/dist/internal/Cache.js +4 -1
  200. package/dist/internal/Cache.js.map +1 -1
  201. package/dist/internal/EdgeCache.d.ts +10 -0
  202. package/dist/internal/EdgeCache.d.ts.map +1 -1
  203. package/dist/internal/EdgeCache.js +39 -8
  204. package/dist/internal/EdgeCache.js.map +1 -1
  205. package/dist/internal/Fees.d.ts +27 -0
  206. package/dist/internal/Fees.d.ts.map +1 -0
  207. package/dist/internal/Fees.js +48 -0
  208. package/dist/internal/Fees.js.map +1 -0
  209. package/dist/internal/FundingRoutes.d.ts +915 -0
  210. package/dist/internal/FundingRoutes.d.ts.map +1 -0
  211. package/dist/internal/FundingRoutes.js +625 -0
  212. package/dist/internal/FundingRoutes.js.map +1 -0
  213. package/dist/internal/Id.d.ts +15 -0
  214. package/dist/internal/Id.d.ts.map +1 -0
  215. package/dist/internal/Id.js +22 -0
  216. package/dist/internal/Id.js.map +1 -0
  217. package/dist/internal/Log.d.ts +2 -0
  218. package/dist/internal/Log.d.ts.map +1 -1
  219. package/dist/internal/Log.js +3 -0
  220. package/dist/internal/Log.js.map +1 -1
  221. package/dist/internal/OpenApi.d.ts +10 -1
  222. package/dist/internal/OpenApi.d.ts.map +1 -1
  223. package/dist/internal/OpenApi.js +8 -1
  224. package/dist/internal/OpenApi.js.map +1 -1
  225. package/dist/internal/RateLimit.d.ts +25 -0
  226. package/dist/internal/RateLimit.d.ts.map +1 -1
  227. package/dist/internal/RateLimit.js +51 -0
  228. package/dist/internal/RateLimit.js.map +1 -1
  229. package/dist/internal/Response.d.ts +1 -1
  230. package/dist/internal/Response.d.ts.map +1 -1
  231. package/dist/internal/Schema.d.ts +8 -0
  232. package/dist/internal/Schema.d.ts.map +1 -1
  233. package/dist/internal/Schema.js +7 -0
  234. package/dist/internal/Schema.js.map +1 -1
  235. package/dist/internal/Webhooks.d.ts.map +1 -1
  236. package/dist/internal/Webhooks.js +3 -3
  237. package/dist/internal/Webhooks.js.map +1 -1
  238. package/package.json +8 -7
  239. package/src/ApiKey.ts +9 -2
  240. package/src/ApiKeys.test.ts +51 -0
  241. package/src/ApiKeys.ts +50 -1
  242. package/src/App.test.ts +33 -86718
  243. package/src/App.ts +26 -8
  244. package/src/Client.test-d.ts +353 -2
  245. package/src/Scope.ts +16 -3
  246. package/src/admin/apps/scopes.test.ts +6 -0
  247. package/src/admin/apps/scopes.ts +3 -0
  248. package/src/admin/ui/src/components/Page.tsx +2 -3
  249. package/src/admin/ui/src/routes/__root.tsx +2 -2
  250. package/src/admin/ui/src/routes/api-keys.tsx +2 -2
  251. package/src/admin/ui.generated.ts +13 -13
  252. package/src/analytics/Analytics.test.ts +527 -0
  253. package/src/analytics/Analytics.ts +318 -0
  254. package/src/analytics/tables/requestEvents.ts +523 -0
  255. package/src/apps/composition.test.ts +26 -13
  256. package/src/apps/data/App.test.ts +72 -0
  257. package/src/apps/data/App.ts +37 -0
  258. package/src/apps/data/FundingRouteProviders.test.ts +281 -0
  259. package/src/apps/data/FundingRouteProviders.ts +459 -0
  260. package/src/apps/data/index.ts +1 -0
  261. package/src/apps/data/routes/funding.test.ts +314 -0
  262. package/src/apps/data/routes/funding.ts +698 -0
  263. package/src/apps/data/routes/receipts.ts +2 -9
  264. package/src/apps/data/routes/rpc.test.ts +78 -2
  265. package/src/apps/data/routes/rpc.ts +31 -1
  266. package/src/apps/data/routes/webhooks.test.ts +1 -1
  267. package/src/apps/data/routes/webhooks.ts +11 -8
  268. package/src/apps/docs/App.ts +30 -101
  269. package/src/apps/docs/reference.ts +132 -0
  270. package/src/apps/index.ts +3 -2
  271. package/src/apps/{platform → management}/App.ts +56 -17
  272. package/src/apps/management/Billing.test.ts +530 -0
  273. package/src/apps/management/Billing.ts +682 -0
  274. package/src/apps/management/index.ts +2 -0
  275. package/src/apps/{platform → management}/routes/api-keys.test.ts +172 -9
  276. package/src/apps/{platform → management}/routes/api-keys.ts +137 -21
  277. package/src/apps/management/routes/billing.test.ts +1100 -0
  278. package/src/apps/management/routes/billing.ts +966 -0
  279. package/src/apps/{platform → management}/routes/invitations.test.ts +55 -7
  280. package/src/apps/{platform → management}/routes/invitations.ts +69 -1
  281. package/src/apps/management/routes/me.test.ts +351 -0
  282. package/src/apps/{platform → management}/routes/orgs.ts +26 -4
  283. package/src/apps/management/routes/scopes.test.ts +29 -0
  284. package/src/apps/management/routes/scopes.ts +85 -0
  285. package/src/apps/management/routes/usage.test.ts +422 -0
  286. package/src/apps/management/routes/usage.ts +527 -0
  287. package/src/apps/mcp.test.ts +206 -0
  288. package/src/apps/mcp.ts +250 -0
  289. package/src/apps/relay/App.test.ts +457 -8
  290. package/src/apps/relay/App.ts +120 -12
  291. package/src/apps/relay/Sponsorships.test.ts +1 -1
  292. package/src/apps/relay/Sponsorships.ts +2 -1
  293. package/src/cli/Cli.test.ts +25 -2
  294. package/src/cli/Cli.ts +17 -2
  295. package/src/cli/commands/admin.test.ts +83 -0
  296. package/src/cli/commands/admin.ts +89 -1
  297. package/src/db/Db.ts +137 -0
  298. package/src/db/Schema.ts +104 -2
  299. package/src/db/tables/billingSettings.test.ts +87 -0
  300. package/src/db/tables/billingSettings.ts +108 -0
  301. package/src/db/tables/invitations.ts +2 -2
  302. package/src/db/tables/organizations.test.ts +1 -1
  303. package/src/db/tables/organizations.ts +15 -5
  304. package/src/db/tables/projects.ts +2 -2
  305. package/src/db/tables/requestUsage.ts +231 -0
  306. package/src/db/tables/sponsoredTransactions.test.ts +108 -1
  307. package/src/db/tables/sponsoredTransactions.ts +230 -2
  308. package/src/db/tables/stripeCustomers.test.ts +109 -0
  309. package/src/db/tables/stripeCustomers.ts +162 -0
  310. package/src/db/tables/users.ts +2 -2
  311. package/src/handlers/internal/sponsorship.ts +34 -7
  312. package/src/handlers/internal/utils.ts +19 -7
  313. package/src/index.ts +1 -0
  314. package/src/internal/Auth.test.ts +175 -25
  315. package/src/internal/Auth.ts +176 -87
  316. package/src/internal/Cache.ts +5 -1
  317. package/src/internal/EdgeCache.test.ts +27 -4
  318. package/src/internal/EdgeCache.ts +47 -9
  319. package/src/internal/Fees.test.ts +32 -0
  320. package/src/internal/Fees.ts +51 -0
  321. package/src/internal/FundingRoutes.test.ts +213 -0
  322. package/src/internal/FundingRoutes.ts +898 -0
  323. package/src/internal/Id.test.ts +17 -0
  324. package/src/internal/Id.ts +24 -0
  325. package/src/internal/Log.test.ts +1 -0
  326. package/src/internal/Log.ts +5 -0
  327. package/src/internal/OpenApi.ts +12 -1
  328. package/src/internal/RateLimit.test.ts +84 -0
  329. package/src/internal/RateLimit.ts +70 -0
  330. package/src/internal/Response.ts +1 -1
  331. package/src/internal/Schema.ts +8 -0
  332. package/src/internal/Webhooks.test.ts +3 -3
  333. package/src/internal/Webhooks.ts +3 -3
  334. package/dist/apps/platform/App.d.ts.map +0 -1
  335. package/dist/apps/platform/App.js.map +0 -1
  336. package/dist/apps/platform/index.d.ts +0 -2
  337. package/dist/apps/platform/index.d.ts.map +0 -1
  338. package/dist/apps/platform/index.js +0 -2
  339. package/dist/apps/platform/index.js.map +0 -1
  340. package/dist/apps/platform/routes/api-keys.d.ts.map +0 -1
  341. package/dist/apps/platform/routes/api-keys.js.map +0 -1
  342. package/dist/apps/platform/routes/invitations.d.ts.map +0 -1
  343. package/dist/apps/platform/routes/invitations.js.map +0 -1
  344. package/dist/apps/platform/routes/me.d.ts.map +0 -1
  345. package/dist/apps/platform/routes/me.js.map +0 -1
  346. package/dist/apps/platform/routes/members.d.ts.map +0 -1
  347. package/dist/apps/platform/routes/members.js.map +0 -1
  348. package/dist/apps/platform/routes/orgs.d.ts.map +0 -1
  349. package/dist/apps/platform/routes/orgs.js.map +0 -1
  350. package/dist/apps/platform/routes/projects.js.map +0 -1
  351. package/src/apps/platform/index.ts +0 -1
  352. package/src/apps/platform/routes/me.test.ts +0 -121
  353. /package/dist/apps/{platform → management}/routes/me.js +0 -0
  354. /package/dist/apps/{platform → management}/routes/members.d.ts +0 -0
  355. /package/dist/apps/{platform → management}/routes/members.js +0 -0
  356. /package/dist/apps/{platform → management}/routes/projects.js +0 -0
  357. /package/src/apps/{platform → management}/routes/me.ts +0 -0
  358. /package/src/apps/{platform → management}/routes/members.test.ts +0 -0
  359. /package/src/apps/{platform → management}/routes/members.ts +0 -0
  360. /package/src/apps/{platform → management}/routes/orgs.test.ts +0 -0
  361. /package/src/apps/{platform → management}/routes/projects.test.ts +0 -0
  362. /package/src/apps/{platform → management}/routes/projects.ts +0 -0
package/src/db/Db.ts CHANGED
@@ -10,11 +10,14 @@ import {
10
10
  } from 'kysely'
11
11
  import * as pg from 'pg'
12
12
 
13
+ import type * as BillingSettings from './tables/billingSettings.js'
13
14
  import type * as Invitations from './tables/invitations.js'
14
15
  import type * as Memberships from './tables/memberships.js'
15
16
  import type * as Organizations from './tables/organizations.js'
16
17
  import type * as Projects from './tables/projects.js'
18
+ import type * as RequestUsage from './tables/requestUsage.js'
17
19
  import type * as SponsoredTransactions from './tables/sponsoredTransactions.js'
20
+ import type * as StripeCustomers from './tables/stripeCustomers.js'
18
21
  import type * as Users from './tables/users.js'
19
22
  import type * as VerifiedTokens from './tables/verifiedTokens.js'
20
23
  import type * as WebhookDeliveries from './tables/webhookDeliveries.js'
@@ -32,11 +35,15 @@ export type Database = {
32
35
  /** SQL as applied; null on rows written before the column existed. */
33
36
  sql: string | null
34
37
  }
38
+ billing_settings: BillingSettings.Table
35
39
  invitations: Invitations.Table
36
40
  memberships: Memberships.Table
37
41
  organizations: Organizations.Table
38
42
  projects: Projects.Table
43
+ request_usage_buckets: RequestUsage.BucketTable
44
+ request_usage_meter_events: RequestUsage.MeterEventTable
39
45
  sponsored_transactions: SponsoredTransactions.Table
46
+ stripe_customers: StripeCustomers.Table
40
47
  users: Users.Table
41
48
  verified_token_lists: VerifiedTokens.ListTable
42
49
  verified_tokens: VerifiedTokens.Table
@@ -383,6 +390,136 @@ export const migrations: readonly Migration[] = [
383
390
  up: (db) =>
384
391
  db.schema.createIndex('sponsored_transactions_org_idx').ifNotExists().on('sponsoredTransactions').columns(['orgId', 'createdAt']), // prettier-ignore
385
392
  },
393
+ {
394
+ name: '0027_stripe_customers',
395
+ up: (db) =>
396
+ db.schema
397
+ .createTable('stripeCustomers')
398
+ .ifNotExists()
399
+ .addColumn('orgId', 'text', (c) => c.primaryKey())
400
+ .addColumn('stripeCustomerId', 'text', (c) => c.notNull())
401
+ .addColumn('status', 'text', (c) => c.notNull())
402
+ .addColumn('createdAt', 'text', (c) => c.notNull())
403
+ .addColumn('updatedAt', 'text', (c) => c.notNull())
404
+ .addUniqueConstraint('stripe_customers_stripe_customer_id_key', ['stripeCustomerId']),
405
+ },
406
+ {
407
+ name: '0028_billing_settings',
408
+ up: (db) =>
409
+ db.schema
410
+ .createTable('billingSettings')
411
+ .ifNotExists()
412
+ .addColumn('orgId', 'text', (c) => c.primaryKey())
413
+ .addColumn('spendLimit', 'text')
414
+ .addColumn('txFeeLimit', 'text')
415
+ .addColumn('currency', 'text', (c) => c.notNull().defaultTo('usd'))
416
+ .addColumn('period', 'text', (c) => c.notNull().defaultTo('month'))
417
+ .addColumn('createdAt', 'text', (c) => c.notNull())
418
+ .addColumn('updatedAt', 'text', (c) => c.notNull()),
419
+ },
420
+ {
421
+ name: '0029_sponsored_transactions_fee_max',
422
+ up: (db) => raw(db, sql`ALTER TABLE sponsored_transactions ADD COLUMN IF NOT EXISTS fee_max text`), // prettier-ignore
423
+ },
424
+ {
425
+ name: '0030_sponsored_transactions_meter_reported_at',
426
+ up: (db) => raw(db, sql`ALTER TABLE sponsored_transactions ADD COLUMN IF NOT EXISTS meter_reported_at text`), // prettier-ignore
427
+ },
428
+ {
429
+ name: '0031_sponsored_transactions_billing_index',
430
+ // Partial: the spend-limit aggregate only ever scans billable rows.
431
+ up: (db) => raw(db, sql`CREATE INDEX IF NOT EXISTS sponsored_transactions_billing_idx ON sponsored_transactions (org_id, status, created_at) WHERE billable`), // prettier-ignore
432
+ },
433
+ {
434
+ name: '0032_sponsored_transactions_fee_amount_rescale',
435
+ // Rows finalized before fee scaling shipped stored attodollars (1e12× the
436
+ // token unit); `div` is exact ceiling division (`Fees.fromGas`). The
437
+ // threshold keeps re-runs idempotent: real scaled fees sit far below
438
+ // 1e9 ($1,000); attodollar values far above.
439
+ up: (db) => raw(db, sql`UPDATE sponsored_transactions SET fee_amount = div(fee_amount::numeric + 999999999999, 1000000000000)::text WHERE status = 'finalized' AND fee_amount IS NOT NULL AND fee_amount::numeric >= 1000000000`), // prettier-ignore
440
+ },
441
+ {
442
+ name: '0033_sponsored_transactions_unreported_index',
443
+ // Partial queue index: the per-minute reporter scans only unreported
444
+ // finalized billable rows, which marking immediately evicts.
445
+ up: (db) => raw(db, sql`CREATE INDEX IF NOT EXISTS sponsored_transactions_unreported_idx ON sponsored_transactions (finalized_at, id) WHERE billable AND status = 'finalized' AND meter_reported_at IS NULL`), // prettier-ignore
446
+ },
447
+ {
448
+ name: '0034_sponsored_transactions_currency',
449
+ up: (db) => raw(db, sql`ALTER TABLE sponsored_transactions ADD COLUMN IF NOT EXISTS currency text`), // prettier-ignore
450
+ },
451
+ {
452
+ name: '0035_sponsored_transactions_currency_backfill',
453
+ // Every fee payer to date sponsors in pathUSD, so rows predating the
454
+ // currency snapshot are USD. The predicate keeps re-runs idempotent.
455
+ up: (db) => raw(db, sql`UPDATE sponsored_transactions SET currency = 'usd' WHERE currency IS NULL`), // prettier-ignore
456
+ },
457
+ {
458
+ name: '0036_stripe_customers_environment',
459
+ up: (db) => raw(db, sql`ALTER TABLE stripe_customers ADD COLUMN IF NOT EXISTS environment text NOT NULL DEFAULT 'production'`), // prettier-ignore
460
+ },
461
+ {
462
+ name: '0037_stripe_customers_org_environment_key',
463
+ // Enables a second (sandbox) row per org; targeted by the upsert conflict
464
+ // arbiter so it survives the org pkey drop in the follow-up release.
465
+ up: (db) => raw(db, sql`CREATE UNIQUE INDEX IF NOT EXISTS stripe_customers_org_environment_key ON stripe_customers (org_id, environment)`), // prettier-ignore
466
+ },
467
+ {
468
+ name: '0038_billing_settings_environment',
469
+ up: (db) => raw(db, sql`ALTER TABLE billing_settings ADD COLUMN IF NOT EXISTS environment text NOT NULL DEFAULT 'production'`), // prettier-ignore
470
+ },
471
+ {
472
+ name: '0039_billing_settings_org_environment_key',
473
+ up: (db) => raw(db, sql`CREATE UNIQUE INDEX IF NOT EXISTS billing_settings_org_environment_key ON billing_settings (org_id, environment)`), // prettier-ignore
474
+ },
475
+ {
476
+ name: '0040_stripe_customers_drop_org_pkey',
477
+ // Release B: the `(org_id, environment)` unique index now guards uniqueness,
478
+ // so dropping the org pkey unblocks a second (sandbox) row per org.
479
+ up: (db) => raw(db, sql`ALTER TABLE stripe_customers DROP CONSTRAINT IF EXISTS stripe_customers_pkey`), // prettier-ignore
480
+ },
481
+ {
482
+ name: '0041_billing_settings_drop_org_pkey',
483
+ up: (db) => raw(db, sql`ALTER TABLE billing_settings DROP CONSTRAINT IF EXISTS billing_settings_pkey`), // prettier-ignore
484
+ },
485
+ {
486
+ name: '0042_request_usage_buckets',
487
+ up: (db) =>
488
+ db.schema
489
+ .createTable('requestUsageBuckets')
490
+ .ifNotExists()
491
+ .addColumn('orgId', 'text', (c) => c.notNull())
492
+ .addColumn('environment', 'text', (c) => c.notNull())
493
+ .addColumn('bucketStart', 'text', (c) => c.notNull())
494
+ .addColumn('reportedCount', 'bigint', (c) => c.notNull().defaultTo(0))
495
+ .addColumn('updatedAt', 'text', (c) => c.notNull())
496
+ .addPrimaryKeyConstraint('request_usage_buckets_pkey', ['orgId', 'environment', 'bucketStart']), // prettier-ignore
497
+ },
498
+ {
499
+ name: '0043_request_usage_meter_events',
500
+ up: (db) =>
501
+ db.schema
502
+ .createTable('requestUsageMeterEvents')
503
+ .ifNotExists()
504
+ .addColumn('identifier', 'text', (c) => c.primaryKey())
505
+ .addColumn('orgId', 'text', (c) => c.notNull())
506
+ .addColumn('environment', 'text', (c) => c.notNull())
507
+ .addColumn('bucketStart', 'text', (c) => c.notNull())
508
+ .addColumn('sequence', 'integer', (c) => c.notNull())
509
+ .addColumn('deltaCount', 'bigint', (c) => c.notNull())
510
+ .addColumn('stripeCustomerId', 'text', (c) => c.notNull())
511
+ .addColumn('state', 'text', (c) => c.notNull())
512
+ .addColumn('reason', 'text')
513
+ .addColumn('error', 'text')
514
+ .addColumn('firstAttemptedAt', 'text', (c) => c.notNull())
515
+ .addColumn('reportedAt', 'text')
516
+ .addUniqueConstraint('request_usage_meter_events_bucket_sequence_key', ['orgId', 'environment', 'bucketStart', 'sequence']), // prettier-ignore
517
+ },
518
+ {
519
+ name: '0044_request_usage_meter_events_pending_index',
520
+ // Partial: the reporter scans only unreported (pending) rows.
521
+ up: (db) => raw(db, sql`CREATE INDEX IF NOT EXISTS request_usage_meter_events_pending_idx ON request_usage_meter_events (first_attempted_at, identifier) WHERE state = 'pending'`), // prettier-ignore
522
+ },
386
523
  ]
387
524
 
388
525
  /**
package/src/db/Schema.ts CHANGED
@@ -8,6 +8,31 @@ import type * as Webhooks from '../internal/Webhooks.js'
8
8
  // Domain-owned unions and jsonb values stay typed by their owning module via
9
9
  // `z.custom` (type-only imports) so the two cannot drift.
10
10
 
11
+ /** An organization's billing configuration; source-agnostic (limits apply to any billing source). */
12
+ export const BillingSettings = z.object({
13
+ createdAt: z.string().check(z.describe('When the settings row was created (ISO 8601).')),
14
+ currency: z
15
+ .enum(['usd'])
16
+ .check(z.describe('ISO 4217 currency (lowercase) both limits are denominated in.')),
17
+ environment: z
18
+ .enum(['production', 'sandbox'])
19
+ .check(z.describe('Environment these settings apply to; `production` or `sandbox`.')),
20
+ orgId: z.string().check(z.describe('Organization id (`org_…`) these settings belong to.')),
21
+ period: z
22
+ .enum(['month'])
23
+ .check(z.describe('Window the spend limit applies over; `month` is a UTC calendar month.')),
24
+ spendLimit: z
25
+ .nullable(z.string())
26
+ .check(z.describe('Spend limit per period as a decimal string in `currency` units, or null for no limit.')), // prettier-ignore
27
+ txFeeLimit: z
28
+ .nullable(z.string())
29
+ .check(z.describe('Per-transaction fee cap as a decimal string in `currency` units, or null for the platform default.')), // prettier-ignore
30
+ updatedAt: z.string().check(z.describe('When the settings last changed (ISO 8601).')),
31
+ })
32
+
33
+ /** Columns of the `billing_settings` table. */
34
+ export type BillingSettings = z.output<typeof BillingSettings>
35
+
11
36
  /** A pending, expiring offer of organization membership. */
12
37
  export const Invitation = z.object({
13
38
  acceptedAt: z
@@ -104,12 +129,18 @@ export const SponsoredTransaction = z.object({
104
129
  .check(z.describe('Whether the sponsorship accrues billable spend; false for sandbox.')),
105
130
  chainId: z.number().check(z.describe('Chain the sponsored transaction targets.')),
106
131
  createdAt: z.string().check(z.describe('When the sponsorship was recorded (ISO 8601).')),
132
+ currency: z
133
+ .nullable(z.string())
134
+ .check(z.describe('Lowercase fee currency the sponsorship settles in (`usd`), or null when the fee token is unverified.')), // prettier-ignore
107
135
  environment: z
108
136
  .enum(['production', 'sandbox'])
109
137
  .check(z.describe('Key environment the sponsorship was requested under.')),
110
138
  feeAmount: z
111
139
  .nullable(z.string())
112
140
  .check(z.describe('Actual fee paid in fee-token base units, or null until finalized.')),
141
+ feeMax: z
142
+ .nullable(z.string())
143
+ .check(z.describe('Signed fee cap (`gas × maxFeePerGas`) in base units; pending rows count it against spend limits.')), // prettier-ignore
113
144
  feeToken: z
114
145
  .nullable(z.string())
115
146
  .check(z.describe('Fee token address, or null when the chain default applied.')),
@@ -122,6 +153,9 @@ export const SponsoredTransaction = z.object({
122
153
  z.describe('Opaque sponsored-transaction id (`stx_…`).'),
123
154
  z.meta({ examples: ['stx_1a2b3c4d5e6f7g8h9j0k1m2n'] }),
124
155
  ),
156
+ meterReportedAt: z
157
+ .nullable(z.string())
158
+ .check(z.describe('When the row was reported to the billing meter (ISO 8601), or null; exactly-once marker.')), // prettier-ignore
125
159
  orgId: z.string().check(z.describe('Organization id (`org_…`) the spend attributes to.')),
126
160
  projectId: z.string().check(z.describe('Project id (`prj_…`) the spend attributes to.')),
127
161
  signPayload: z
@@ -141,6 +175,74 @@ export const SponsoredTransaction = z.object({
141
175
  /** Columns of the `sponsored_transactions` table. */
142
176
  export type SponsoredTransaction = z.output<typeof SponsoredTransaction>
143
177
 
178
+ /** A per-hour request-count watermark: the running total already reported to the meter. */
179
+ export const RequestUsageBucket = z.object({
180
+ bucketStart: z.string().check(z.describe('UTC hour start this bucket covers (ISO 8601).')),
181
+ environment: z
182
+ .enum(['production', 'sandbox'])
183
+ .check(z.describe('Environment the requests were served under; `production` or `sandbox`.')),
184
+ orgId: z.string().check(z.describe('Organization id (`org_…`) the requests attribute to.')),
185
+ reportedCount: z
186
+ .number()
187
+ .check(z.describe('Cumulative request count already reported to the meter for this bucket.')),
188
+ updatedAt: z.string().check(z.describe('When the watermark last advanced (ISO 8601).')),
189
+ })
190
+
191
+ /** Columns of the `request_usage_buckets` table. */
192
+ export type RequestUsageBucket = z.output<typeof RequestUsageBucket>
193
+
194
+ /** One request-count meter event: a frozen additive delta with its exactly-once state. */
195
+ export const RequestUsageMeterEvent = z.object({
196
+ bucketStart: z.string().check(z.describe('UTC hour start this event bills for (ISO 8601).')),
197
+ deltaCount: z
198
+ .number()
199
+ .check(z.describe('Frozen request count this event bills; a positive additive delta.')),
200
+ environment: z
201
+ .enum(['production', 'sandbox'])
202
+ .check(z.describe('Environment the requests were served under; `production` or `sandbox`.')),
203
+ error: z.nullable(z.string()).check(z.describe('Last failure detail while `pending`, or null.')),
204
+ firstAttemptedAt: z.string().check(z.describe('When this event was first claimed (ISO 8601).')),
205
+ identifier: z
206
+ .string()
207
+ .check(z.describe('Deterministic id; also the Stripe meter-event identifier and idempotency key.')), // prettier-ignore
208
+ orgId: z.string().check(z.describe('Organization id (`org_…`) the requests attribute to.')),
209
+ reason: z
210
+ .nullable(z.string())
211
+ .check(z.describe('Skip reason when `state` is `skipped` (`no_customer` | `customer_canceled`), or null.')), // prettier-ignore
212
+ reportedAt: z
213
+ .nullable(z.string())
214
+ .check(z.describe('When Stripe acknowledged the event (ISO 8601), or null.')),
215
+ sequence: z
216
+ .number()
217
+ .check(z.describe('Delta sequence within the bucket; `0` is the initial seal, `1..n` later deltas.')), // prettier-ignore
218
+ state: z
219
+ .enum(['pending', 'reported', 'skipped'])
220
+ .check(z.describe('Exactly-once state: `pending` retries, `reported` is done, `skipped` is audited.')), // prettier-ignore
221
+ stripeCustomerId: z.string().check(z.describe('Stripe customer id the event bills against.')),
222
+ })
223
+
224
+ /** Columns of the `request_usage_meter_events` table. */
225
+ export type RequestUsageMeterEvent = z.output<typeof RequestUsageMeterEvent>
226
+
227
+ /** An organization's Stripe billing source: the customer link and derived status. */
228
+ export const StripeCustomer = z.object({
229
+ createdAt: z.string().check(z.describe('When the Stripe source was connected (ISO 8601).')),
230
+ environment: z
231
+ .enum(['production', 'sandbox'])
232
+ .check(z.describe('Environment this Stripe source backs; `production` (live) or `sandbox` (test).')), // prettier-ignore
233
+ orgId: z.string().check(z.describe('Organization id (`org_…`) this Stripe source belongs to.')),
234
+ status: z
235
+ .enum(['active', 'canceled', 'none', 'past_due'])
236
+ .check(z.describe('Billing status derived from Stripe state; gates production sponsorship.')),
237
+ stripeCustomerId: z
238
+ .string()
239
+ .check(z.describe('Stripe customer id backing this source; never deleted by us.')),
240
+ updatedAt: z.string().check(z.describe('When the billing status last changed (ISO 8601).')),
241
+ })
242
+
243
+ /** Columns of the `stripe_customers` table. */
244
+ export type StripeCustomer = z.output<typeof StripeCustomer>
245
+
144
246
  /** A developer identity. */
145
247
  export const User = z.object({
146
248
  address: z
@@ -233,7 +335,7 @@ export const WebhookSubscription = z.object({
233
335
  .string()
234
336
  .check(
235
337
  z.describe('Subscription id (`wh_…`, lexically time-ordered).'),
236
- z.meta({ examples: ['wh_0000019c1a2b3c4d5e6f7g8h'] }),
338
+ z.meta({ examples: ['wh_001718668800000_2ZPE2gvateYEQ0dQslgvkhjx'] }),
237
339
  ),
238
340
  lastDeliveryAt: z
239
341
  .nullable(z.string())
@@ -282,7 +384,7 @@ export const WebhookDelivery = z.object({
282
384
  .string()
283
385
  .check(
284
386
  z.describe('Delivery id (`whd_…`, lexically time-ordered).'),
285
- z.meta({ examples: ['whd_0000019c1a2b3c4d5e6f7g8h'] }),
387
+ z.meta({ examples: ['whd_001718668800000_C1yJkk7CuML98HFthEDk9PsY'] }),
286
388
  ),
287
389
  requestUrl: z.string().check(z.describe('Subscriber callback URL the attempt targeted.')),
288
390
  responseMs: z
@@ -0,0 +1,87 @@
1
+ import { nanoid } from 'nanoid'
2
+
3
+ import { postgresUrl } from '../../../test/containers.js'
4
+ import * as Db from '../Db.js'
5
+ import * as BillingSettings from './billingSettings.js'
6
+
7
+ const create = () => Db.postgres({ connectionString: postgresUrl, schema: `t_${nanoid()}` })
8
+
9
+ /** Redacts nondeterministic row fields (timestamps). */
10
+ function redact(record: BillingSettings.Record) {
11
+ return { ...record, createdAt: '<timestamp>', updatedAt: '<timestamp>' }
12
+ }
13
+
14
+ describe('upsert', () => {
15
+ test('behavior: inserts defaults for fields not provided', async () => {
16
+ const db = create()
17
+ await db.migrate()
18
+ const record = await BillingSettings.upsert(db, { orgId: 'org_1', spendLimit: '250' })
19
+ expect(redact(record)).toMatchInlineSnapshot(`
20
+ {
21
+ "createdAt": "<timestamp>",
22
+ "currency": "usd",
23
+ "environment": "production",
24
+ "orgId": "org_1",
25
+ "period": "month",
26
+ "spendLimit": "250",
27
+ "txFeeLimit": null,
28
+ "updatedAt": "<timestamp>",
29
+ }
30
+ `)
31
+ await db.close()
32
+ })
33
+
34
+ test('behavior: absent fields keep their stored value', async () => {
35
+ const db = create()
36
+ await db.migrate()
37
+ await BillingSettings.upsert(db, { orgId: 'org_1', spendLimit: '250' })
38
+ const record = await BillingSettings.upsert(db, { orgId: 'org_1', txFeeLimit: '0.50' })
39
+ expect(record.spendLimit).toBe('250')
40
+ expect(record.txFeeLimit).toBe('0.50')
41
+ await db.close()
42
+ })
43
+
44
+ test('behavior: null clears a limit without touching the other', async () => {
45
+ const db = create()
46
+ await db.migrate()
47
+ await BillingSettings.upsert(db, { orgId: 'org_1', spendLimit: '250', txFeeLimit: '0.50' }) // prettier-ignore
48
+ const record = await BillingSettings.upsert(db, { orgId: 'org_1', spendLimit: null })
49
+ expect(record.spendLimit).toBeNull()
50
+ expect(record.txFeeLimit).toBe('0.50')
51
+ await db.close()
52
+ })
53
+ })
54
+
55
+ describe('get', () => {
56
+ test('behavior: returns the stored row, undefined for orgs without settings', async () => {
57
+ const db = create()
58
+ await db.migrate()
59
+ expect(await BillingSettings.get(db, 'org_1')).toBeUndefined()
60
+ const record = await BillingSettings.upsert(db, { orgId: 'org_1', txFeeLimit: '1' })
61
+ expect(await BillingSettings.get(db, 'org_1')).toStrictEqual(record)
62
+ await db.close()
63
+ })
64
+
65
+ test('behavior: production and sandbox settings coexist per org and read disjointly', async () => {
66
+ const db = create()
67
+ await db.migrate()
68
+ const production = await BillingSettings.upsert(db, { orgId: 'org_1', spendLimit: '250' })
69
+ const sandbox = await BillingSettings.upsert(db, { environment: 'sandbox', orgId: 'org_1', spendLimit: '10' }) // prettier-ignore
70
+ expect(sandbox.environment).toBe('sandbox')
71
+ expect(await BillingSettings.get(db, 'org_1')).toStrictEqual(production)
72
+ expect(await BillingSettings.get(db, 'org_1', 'sandbox')).toStrictEqual(sandbox)
73
+ await db.close()
74
+ })
75
+ })
76
+
77
+ describe('deleteByOrg', () => {
78
+ test('behavior: deletes the row and reports absence', async () => {
79
+ const db = create()
80
+ await db.migrate()
81
+ await BillingSettings.upsert(db, { orgId: 'org_1', spendLimit: '250' })
82
+ expect(await BillingSettings.deleteByOrg(db, 'org_1')).toBe(true)
83
+ expect(await BillingSettings.deleteByOrg(db, 'org_1')).toBe(false)
84
+ expect(await BillingSettings.get(db, 'org_1')).toBeUndefined()
85
+ await db.close()
86
+ })
87
+ })
@@ -0,0 +1,108 @@
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 `billing_settings` table, derived from `Schema.BillingSettings`. */
7
+ export type Table = db_Schema.BillingSettings
8
+
9
+ /** A stored billing-settings row. */
10
+ export type Record = Selectable<Table>
11
+
12
+ /** Currencies limits may be denominated in; single-member today, widens without schema changes. */
13
+ export const currencies = ['usd'] as const satisfies readonly Record['currency'][]
14
+
15
+ /** Spend-limit windows; single-member today, widens without schema changes. */
16
+ export const periods = ['month'] as const satisfies readonly Record['period'][]
17
+
18
+ /**
19
+ * Reads an organization's billing settings.
20
+ *
21
+ * @param db - The database.
22
+ * @param orgId - The organization id (`org_…`).
23
+ * @param environment - The environment to read; defaults to `production`.
24
+ * @returns The stored record, or `undefined` when the org has none.
25
+ */
26
+ export function get(
27
+ db: Db.Db,
28
+ orgId: string,
29
+ environment: Record['environment'] = 'production',
30
+ ): Promise<Record | undefined> {
31
+ return db.kysely
32
+ .selectFrom('billing_settings')
33
+ .selectAll()
34
+ .where('orgId', '=', orgId)
35
+ .where('environment', '=', environment)
36
+ .executeTakeFirst()
37
+ }
38
+
39
+ /**
40
+ * Upserts an organization's billing settings with PATCH semantics: absent
41
+ * fields keep their stored value, `null` clears the limit.
42
+ *
43
+ * @param db - The database.
44
+ * @param input - The settings to apply.
45
+ * @returns The stored record.
46
+ */
47
+ export async function upsert(db: Db.Db, input: upsert.Input): Promise<Record> {
48
+ const now = new Date().toISOString()
49
+ const environment = input.environment ?? 'production'
50
+ return await db.kysely
51
+ .insertInto('billing_settings')
52
+ .values({
53
+ createdAt: now,
54
+ currency: input.currency ?? 'usd',
55
+ environment,
56
+ orgId: input.orgId,
57
+ period: input.period ?? 'month',
58
+ spendLimit: input.spendLimit ?? null,
59
+ txFeeLimit: input.txFeeLimit ?? null,
60
+ updatedAt: now,
61
+ })
62
+ // Targets the `(org_id, environment)` unique index, which exists both
63
+ // before and after the org pkey drop release.
64
+ .onConflict((oc) =>
65
+ oc.columns(['orgId', 'environment']).doUpdateSet({
66
+ ...(input.currency !== undefined ? { currency: input.currency } : {}),
67
+ ...(input.period !== undefined ? { period: input.period } : {}),
68
+ ...(input.spendLimit !== undefined ? { spendLimit: input.spendLimit } : {}),
69
+ ...(input.txFeeLimit !== undefined ? { txFeeLimit: input.txFeeLimit } : {}),
70
+ updatedAt: now,
71
+ }),
72
+ )
73
+ .returningAll()
74
+ .executeTakeFirstOrThrow()
75
+ }
76
+
77
+ export declare namespace upsert {
78
+ /** Fields accepted when upserting billing settings; absent = unchanged, null = cleared. */
79
+ type Input = {
80
+ /** Currency both limits are denominated in. */
81
+ currency?: Record['currency'] | undefined
82
+ /** Environment these settings apply to; defaults to `production`. */
83
+ environment?: Record['environment'] | undefined
84
+ /** Organization id (`org_…`) the settings belong to. */
85
+ orgId: string
86
+ /** Spend-limit window. */
87
+ period?: Record['period'] | undefined
88
+ /** Spend limit per period (decimal string in `currency` units), or null to clear. */
89
+ spendLimit?: string | null | undefined
90
+ /** Per-transaction fee cap (decimal string in `currency` units), or null to clear. */
91
+ txFeeLimit?: string | null | undefined
92
+ }
93
+ }
94
+
95
+ /**
96
+ * Deletes an organization's billing settings.
97
+ *
98
+ * @param db - The database.
99
+ * @param orgId - The organization id (`org_…`).
100
+ * @returns Whether a settings row existed and was deleted.
101
+ */
102
+ export async function deleteByOrg(db: Db.Db, orgId: string): Promise<boolean> {
103
+ const result = await db.kysely
104
+ .deleteFrom('billing_settings')
105
+ .where('orgId', '=', orgId)
106
+ .executeTakeFirst()
107
+ return result.numDeletedRows > 0n
108
+ }
@@ -1,6 +1,6 @@
1
1
  import type { Selectable } from 'kysely'
2
- import { nanoid } from 'nanoid'
3
2
 
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
  import * as Memberships from './memberships.js'
@@ -31,7 +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
- id: `inv_${nanoid()}`,
34
+ id: Id.generate('inv'),
35
35
  invitedBy: input.invitedBy,
36
36
  orgId: input.orgId,
37
37
  revokedAt: null,
@@ -12,7 +12,7 @@ function redact(record: Organizations.Record) {
12
12
  return {
13
13
  ...record,
14
14
  createdAt: '<timestamp>',
15
- id: /^org_[\w-]{21}$/.test(record.id) ? 'org_<id>' : record.id,
15
+ id: /^org_[0-9A-Za-z]{24}$/.test(record.id) ? 'org_<id>' : record.id,
16
16
  updatedAt: '<timestamp>',
17
17
  }
18
18
  }
@@ -1,8 +1,9 @@
1
1
  import type { Selectable } from 'kysely'
2
- import { nanoid } from 'nanoid'
3
2
 
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
+ import type * as Memberships from './memberships.js'
6
7
 
7
8
  /** Columns of the `organizations` table, derived from `Schema.Organization`. */
8
9
  export type Table = db_Schema.Organization
@@ -25,7 +26,7 @@ export function create(db: Db.Db, input: create.Input): Promise<Record> {
25
26
  .values({
26
27
  createdAt: now,
27
28
  createdBy: input.createdBy ?? null,
28
- id: input.id ?? `org_${nanoid()}`,
29
+ id: input.id ?? Id.generate('org'),
29
30
  name: input.name,
30
31
  updatedAt: now,
31
32
  userId: input.userId ?? null,
@@ -64,7 +65,7 @@ export function createOwned(db: Db.Db, input: createOwned.Input): Promise<Record
64
65
  .values({
65
66
  createdAt: now,
66
67
  createdBy: input.userId,
67
- id: `org_${nanoid()}`,
68
+ id: Id.generate('org'),
68
69
  name: input.name,
69
70
  updatedAt: now,
70
71
  userId: input.userId,
@@ -109,9 +110,13 @@ export function deleteOrganization(db: Db.Db, id: string): Promise<boolean> {
109
110
  .where('ownerType', '=', 'api_key')
110
111
  .where('ownerId', '=', id)
111
112
  .execute()
113
+ await trx.deleteFrom('billing_settings').where('orgId', '=', id).execute()
112
114
  await trx.deleteFrom('invitations').where('orgId', '=', id).execute()
113
115
  await trx.deleteFrom('memberships').where('orgId', '=', id).execute()
114
116
  await trx.deleteFrom('projects').where('orgId', '=', id).execute()
117
+ await trx.deleteFrom('request_usage_buckets').where('orgId', '=', id).execute()
118
+ await trx.deleteFrom('request_usage_meter_events').where('orgId', '=', id).execute()
119
+ await trx.deleteFrom('stripe_customers').where('orgId', '=', id).execute()
115
120
  const result = await trx.deleteFrom('organizations').where('id', '=', id).executeTakeFirst()
116
121
  return result.numDeletedRows > 0n
117
122
  })
@@ -155,18 +160,23 @@ export function listByUser(db: Db.Db, userId: string): Promise<Record[]> {
155
160
  }
156
161
 
157
162
  /**
158
- * Lists organizations `userId` is a member of (any role), newest first.
163
+ * Lists organizations `userId` is a member of (any role), newest first. Each
164
+ * row carries the caller's membership `role`.
159
165
  *
160
166
  * @param db - The database.
161
167
  * @param userId - The member user id (`usr_…`).
162
168
  * @returns The records.
163
169
  */
164
- export function listByMember(db: Db.Db, userId: string): Promise<Record[]> {
170
+ export function listByMember(
171
+ db: Db.Db,
172
+ userId: string,
173
+ ): Promise<(Record & { role: Memberships.Role })[]> {
165
174
  return db.kysely
166
175
  .selectFrom('organizations')
167
176
  .innerJoin('memberships', 'memberships.orgId', 'organizations.id')
168
177
  .where('memberships.userId', '=', userId)
169
178
  .selectAll('organizations')
179
+ .select('memberships.role')
170
180
  .orderBy('organizations.createdAt', 'desc')
171
181
  .execute()
172
182
  }
@@ -1,6 +1,6 @@
1
1
  import type { Selectable } from 'kysely'
2
- import { nanoid } from 'nanoid'
3
2
 
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
 
@@ -23,7 +23,7 @@ export function create(db: Db.Db, input: create.Input): Promise<Record> {
23
23
  .insertInto('projects')
24
24
  .values({
25
25
  createdAt: now,
26
- id: `prj_${nanoid()}`,
26
+ id: Id.generate('prj'),
27
27
  name: input.name,
28
28
  orgId: input.orgId,
29
29
  updatedAt: now,