tapimo 0.1.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 (520) hide show
  1. package/README.md +57 -16
  2. package/dist/ApiKey.d.ts +4 -1
  3. package/dist/ApiKey.d.ts.map +1 -1
  4. package/dist/ApiKey.js +6 -2
  5. package/dist/ApiKey.js.map +1 -1
  6. package/dist/ApiKeys.d.ts +98 -3
  7. package/dist/ApiKeys.d.ts.map +1 -1
  8. package/dist/ApiKeys.js +150 -1
  9. package/dist/ApiKeys.js.map +1 -1
  10. package/dist/App.d.ts +7917 -2315
  11. package/dist/App.d.ts.map +1 -1
  12. package/dist/App.js +141 -329
  13. package/dist/App.js.map +1 -1
  14. package/dist/Scope.d.ts +20 -4
  15. package/dist/Scope.d.ts.map +1 -1
  16. package/dist/Scope.js +23 -4
  17. package/dist/Scope.js.map +1 -1
  18. package/dist/Webhooks.d.ts +2 -2
  19. package/dist/Webhooks.d.ts.map +1 -1
  20. package/dist/Webhooks.js +2 -2
  21. package/dist/Webhooks.js.map +1 -1
  22. package/dist/admin/App.d.ts +203 -120
  23. package/dist/admin/App.d.ts.map +1 -1
  24. package/dist/admin/apps/api-keys.d.ts +111 -6
  25. package/dist/admin/apps/api-keys.d.ts.map +1 -1
  26. package/dist/admin/apps/api-keys.js +67 -0
  27. package/dist/admin/apps/api-keys.js.map +1 -1
  28. package/dist/admin/apps/scopes.d.ts +12 -1
  29. package/dist/admin/apps/scopes.d.ts.map +1 -1
  30. package/dist/admin/apps/scopes.js +3 -0
  31. package/dist/admin/apps/scopes.js.map +1 -1
  32. package/dist/admin/apps/verified-tokens.d.ts +116 -116
  33. package/dist/admin/apps/verified-tokens.js +1 -1
  34. package/dist/admin/apps/verified-tokens.js.map +1 -1
  35. package/dist/admin/ui.generated.d.ts +1 -1
  36. package/dist/admin/ui.generated.js +13 -13
  37. package/dist/admin/ui.generated.js.map +1 -1
  38. package/dist/analytics/Analytics.d.ts +151 -0
  39. package/dist/analytics/Analytics.d.ts.map +1 -0
  40. package/dist/analytics/Analytics.js +212 -0
  41. package/dist/analytics/Analytics.js.map +1 -0
  42. package/dist/analytics/tables/requestEvents.d.ts +215 -0
  43. package/dist/analytics/tables/requestEvents.d.ts.map +1 -0
  44. package/dist/analytics/tables/requestEvents.js +264 -0
  45. package/dist/analytics/tables/requestEvents.js.map +1 -0
  46. package/dist/apps/data/App.d.ts +19576 -0
  47. package/dist/apps/data/App.d.ts.map +1 -0
  48. package/dist/apps/data/App.js +272 -0
  49. package/dist/apps/data/App.js.map +1 -0
  50. package/dist/apps/data/index.d.ts +3 -0
  51. package/dist/apps/data/index.d.ts.map +1 -0
  52. package/dist/apps/data/index.js +3 -0
  53. package/dist/apps/data/index.js.map +1 -0
  54. package/dist/apps/{activities.d.ts → data/routes/activities.d.ts} +32 -32
  55. package/dist/apps/{activities.d.ts.map → data/routes/activities.d.ts.map} +1 -1
  56. package/dist/apps/{activities.js → data/routes/activities.js} +13 -13
  57. package/dist/apps/data/routes/activities.js.map +1 -0
  58. package/dist/apps/{addresses.d.ts → data/routes/addresses.d.ts} +22 -22
  59. package/dist/apps/{addresses.d.ts.map → data/routes/addresses.d.ts.map} +1 -1
  60. package/dist/apps/{addresses.js → data/routes/addresses.js} +5 -5
  61. package/dist/apps/data/routes/addresses.js.map +1 -0
  62. package/dist/apps/{balances.d.ts → data/routes/balances.d.ts} +4 -4
  63. package/dist/apps/data/routes/balances.d.ts.map +1 -0
  64. package/dist/apps/{balances.js → data/routes/balances.js} +42 -11
  65. package/dist/apps/data/routes/balances.js.map +1 -0
  66. package/dist/apps/{blocks.d.ts → data/routes/blocks.d.ts} +47 -47
  67. package/dist/apps/data/routes/blocks.d.ts.map +1 -0
  68. package/dist/apps/{blocks.js → data/routes/blocks.js} +13 -13
  69. package/dist/apps/data/routes/blocks.js.map +1 -0
  70. package/dist/apps/{coingecko.d.ts → data/routes/coingecko.d.ts} +8 -8
  71. package/dist/apps/data/routes/coingecko.d.ts.map +1 -0
  72. package/dist/apps/{coingecko.js → data/routes/coingecko.js} +13 -13
  73. package/dist/apps/data/routes/coingecko.js.map +1 -0
  74. package/dist/apps/{exchanges.d.ts → data/routes/exchanges.d.ts} +108 -108
  75. package/dist/apps/data/routes/exchanges.d.ts.map +1 -0
  76. package/dist/apps/{exchanges.js → data/routes/exchanges.js} +19 -19
  77. package/dist/apps/data/routes/exchanges.js.map +1 -0
  78. package/dist/apps/{fee-amm.d.ts → data/routes/fee-amm.d.ts} +39 -39
  79. package/dist/apps/data/routes/fee-amm.d.ts.map +1 -0
  80. package/dist/apps/{fee-amm.js → data/routes/fee-amm.js} +17 -17
  81. package/dist/apps/data/routes/fee-amm.js.map +1 -0
  82. package/dist/apps/data/routes/funding.d.ts +765 -0
  83. package/dist/apps/data/routes/funding.d.ts.map +1 -0
  84. package/dist/apps/data/routes/funding.js +383 -0
  85. package/dist/apps/data/routes/funding.js.map +1 -0
  86. package/dist/apps/{indexer.d.ts → data/routes/indexer.d.ts} +4 -4
  87. package/dist/apps/data/routes/indexer.d.ts.map +1 -0
  88. package/dist/apps/{indexer.js → data/routes/indexer.js} +13 -9
  89. package/dist/apps/data/routes/indexer.js.map +1 -0
  90. package/dist/apps/{receipts.d.ts → data/routes/receipts.d.ts} +49 -49
  91. package/dist/apps/data/routes/receipts.d.ts.map +1 -0
  92. package/dist/apps/{receipts.js → data/routes/receipts.js} +14 -20
  93. package/dist/apps/data/routes/receipts.js.map +1 -0
  94. package/dist/apps/{rpc.d.ts → data/routes/rpc.d.ts} +33 -40
  95. package/dist/apps/data/routes/rpc.d.ts.map +1 -0
  96. package/dist/apps/{rpc.js → data/routes/rpc.js} +35 -7
  97. package/dist/apps/data/routes/rpc.js.map +1 -0
  98. package/dist/apps/{tokenlist.d.ts → data/routes/tokenlist.d.ts} +40 -40
  99. package/dist/apps/data/routes/tokenlist.d.ts.map +1 -0
  100. package/dist/apps/{tokenlist.js → data/routes/tokenlist.js} +8 -8
  101. package/dist/apps/data/routes/tokenlist.js.map +1 -0
  102. package/dist/apps/{tokens.d.ts → data/routes/tokens.d.ts} +468 -468
  103. package/dist/apps/data/routes/tokens.d.ts.map +1 -0
  104. package/dist/apps/{tokens.js → data/routes/tokens.js} +19 -19
  105. package/dist/apps/data/routes/tokens.js.map +1 -0
  106. package/dist/apps/{transactions.d.ts → data/routes/transactions.d.ts} +304 -304
  107. package/dist/apps/data/routes/transactions.d.ts.map +1 -0
  108. package/dist/apps/{transactions.js → data/routes/transactions.js} +13 -13
  109. package/dist/apps/data/routes/transactions.js.map +1 -0
  110. package/dist/apps/{transfers.d.ts → data/routes/transfers.d.ts} +62 -62
  111. package/dist/apps/data/routes/transfers.d.ts.map +1 -0
  112. package/dist/apps/{transfers.js → data/routes/transfers.js} +13 -13
  113. package/dist/apps/data/routes/transfers.js.map +1 -0
  114. package/dist/apps/{verified-tokens.d.ts → data/routes/verified-tokens.d.ts} +6 -6
  115. package/dist/apps/data/routes/verified-tokens.d.ts.map +1 -0
  116. package/dist/apps/{verified-tokens.js → data/routes/verified-tokens.js} +9 -9
  117. package/dist/apps/data/routes/verified-tokens.js.map +1 -0
  118. package/dist/apps/{webhooks.d.ts → data/routes/webhooks.d.ts} +17 -17
  119. package/dist/apps/{webhooks.d.ts.map → data/routes/webhooks.d.ts.map} +1 -1
  120. package/dist/apps/{webhooks.js → data/routes/webhooks.js} +38 -34
  121. package/dist/apps/data/routes/webhooks.js.map +1 -0
  122. package/dist/apps/docs/App.d.ts +29 -0
  123. package/dist/apps/docs/App.d.ts.map +1 -0
  124. package/dist/apps/docs/App.js +54 -0
  125. package/dist/apps/docs/App.js.map +1 -0
  126. package/dist/{internal/OpenApi.fonts.d.ts → apps/docs/fonts.d.ts} +1 -1
  127. package/dist/apps/docs/fonts.d.ts.map +1 -0
  128. package/dist/{internal/OpenApi.fonts.js → apps/docs/fonts.js} +1 -1
  129. package/dist/apps/docs/fonts.js.map +1 -0
  130. package/dist/apps/docs/index.d.ts +2 -0
  131. package/dist/apps/docs/index.d.ts.map +1 -0
  132. package/dist/apps/docs/index.js +2 -0
  133. package/dist/apps/docs/index.js.map +1 -0
  134. package/dist/{internal/OpenApi.logo.d.ts → apps/docs/logo.d.ts} +1 -1
  135. package/dist/apps/docs/logo.d.ts.map +1 -0
  136. package/dist/{internal/OpenApi.logo.js → apps/docs/logo.js} +1 -1
  137. package/dist/apps/docs/logo.js.map +1 -0
  138. package/dist/apps/docs/reference.d.ts +21 -0
  139. package/dist/apps/docs/reference.d.ts.map +1 -0
  140. package/dist/apps/docs/reference.js +112 -0
  141. package/dist/apps/docs/reference.js.map +1 -0
  142. package/dist/apps/index.d.ts +5 -0
  143. package/dist/apps/index.d.ts.map +1 -0
  144. package/dist/apps/index.js +5 -0
  145. package/dist/apps/index.js.map +1 -0
  146. package/dist/apps/mcp.d.ts +76 -0
  147. package/dist/apps/mcp.d.ts.map +1 -0
  148. package/dist/apps/mcp.js +188 -0
  149. package/dist/apps/mcp.js.map +1 -0
  150. package/dist/apps/metadata.d.ts +7 -0
  151. package/dist/apps/metadata.d.ts.map +1 -0
  152. package/dist/apps/metadata.js +15 -0
  153. package/dist/apps/metadata.js.map +1 -0
  154. package/dist/apps/platform/App.d.ts +5262 -0
  155. package/dist/apps/platform/App.d.ts.map +1 -0
  156. package/dist/apps/platform/App.js +84 -0
  157. package/dist/apps/platform/App.js.map +1 -0
  158. package/dist/apps/platform/Billing.d.ts +279 -0
  159. package/dist/apps/platform/Billing.d.ts.map +1 -0
  160. package/dist/apps/platform/Billing.js +477 -0
  161. package/dist/apps/platform/Billing.js.map +1 -0
  162. package/dist/apps/platform/index.d.ts +3 -0
  163. package/dist/apps/platform/index.d.ts.map +1 -0
  164. package/dist/apps/platform/index.js +3 -0
  165. package/dist/apps/platform/index.js.map +1 -0
  166. package/dist/apps/platform/routes/api-keys.d.ts +973 -0
  167. package/dist/apps/platform/routes/api-keys.d.ts.map +1 -0
  168. package/dist/apps/platform/routes/api-keys.js +430 -0
  169. package/dist/apps/platform/routes/api-keys.js.map +1 -0
  170. package/dist/apps/platform/routes/billing.d.ts +1520 -0
  171. package/dist/apps/platform/routes/billing.d.ts.map +1 -0
  172. package/dist/apps/platform/routes/billing.js +795 -0
  173. package/dist/apps/platform/routes/billing.js.map +1 -0
  174. package/dist/apps/platform/routes/invitations.d.ts +851 -0
  175. package/dist/apps/platform/routes/invitations.d.ts.map +1 -0
  176. package/dist/apps/platform/routes/invitations.js +471 -0
  177. package/dist/apps/platform/routes/invitations.js.map +1 -0
  178. package/dist/apps/platform/routes/me.d.ts +95 -0
  179. package/dist/apps/platform/routes/me.d.ts.map +1 -0
  180. package/dist/apps/platform/routes/me.js +81 -0
  181. package/dist/apps/platform/routes/me.js.map +1 -0
  182. package/dist/apps/platform/routes/members.d.ts +554 -0
  183. package/dist/apps/platform/routes/members.d.ts.map +1 -0
  184. package/dist/apps/platform/routes/members.js +251 -0
  185. package/dist/apps/platform/routes/members.js.map +1 -0
  186. package/dist/apps/platform/routes/orgs.d.ts +596 -0
  187. package/dist/apps/platform/routes/orgs.d.ts.map +1 -0
  188. package/dist/apps/platform/routes/orgs.js +276 -0
  189. package/dist/apps/platform/routes/orgs.js.map +1 -0
  190. package/dist/apps/platform/routes/projects.d.ts +740 -0
  191. package/dist/apps/platform/routes/projects.d.ts.map +1 -0
  192. package/dist/apps/platform/routes/projects.js +286 -0
  193. package/dist/apps/platform/routes/projects.js.map +1 -0
  194. package/dist/apps/platform/routes/scopes.d.ts +107 -0
  195. package/dist/apps/platform/routes/scopes.d.ts.map +1 -0
  196. package/dist/apps/platform/routes/scopes.js +66 -0
  197. package/dist/apps/platform/routes/scopes.js.map +1 -0
  198. package/dist/apps/platform/routes/usage.d.ts +588 -0
  199. package/dist/apps/platform/routes/usage.d.ts.map +1 -0
  200. package/dist/apps/platform/routes/usage.js +391 -0
  201. package/dist/apps/platform/routes/usage.js.map +1 -0
  202. package/dist/apps/relay/App.d.ts +44 -0
  203. package/dist/apps/relay/App.d.ts.map +1 -0
  204. package/dist/apps/relay/App.js +211 -0
  205. package/dist/apps/relay/App.js.map +1 -0
  206. package/dist/apps/relay/Sponsorships.d.ts +107 -0
  207. package/dist/apps/relay/Sponsorships.d.ts.map +1 -0
  208. package/dist/apps/relay/Sponsorships.js +194 -0
  209. package/dist/apps/relay/Sponsorships.js.map +1 -0
  210. package/dist/apps/relay/index.d.ts +3 -0
  211. package/dist/apps/relay/index.d.ts.map +1 -0
  212. package/dist/apps/relay/index.js +3 -0
  213. package/dist/apps/relay/index.js.map +1 -0
  214. package/dist/cli/Cli.d.ts +9 -2
  215. package/dist/cli/Cli.d.ts.map +1 -1
  216. package/dist/cli/Cli.js +12 -4
  217. package/dist/cli/Cli.js.map +1 -1
  218. package/dist/cli/commands/admin.d.ts +5 -1
  219. package/dist/cli/commands/admin.d.ts.map +1 -1
  220. package/dist/cli/commands/admin.js +82 -3
  221. package/dist/cli/commands/admin.js.map +1 -1
  222. package/dist/db/Db.d.ts +17 -0
  223. package/dist/db/Db.d.ts.map +1 -1
  224. package/dist/db/Db.js +254 -0
  225. package/dist/db/Db.js.map +1 -1
  226. package/dist/db/Schema.d.ts +159 -0
  227. package/dist/db/Schema.d.ts.map +1 -1
  228. package/dist/db/Schema.js +187 -2
  229. package/dist/db/Schema.js.map +1 -1
  230. package/dist/db/tables/billingSettings.d.ts +55 -0
  231. package/dist/db/tables/billingSettings.d.ts.map +1 -0
  232. package/dist/db/tables/billingSettings.js +70 -0
  233. package/dist/db/tables/billingSettings.js.map +1 -0
  234. package/dist/db/tables/invitations.d.ts +84 -0
  235. package/dist/db/tables/invitations.d.ts.map +1 -0
  236. package/dist/db/tables/invitations.js +165 -0
  237. package/dist/db/tables/invitations.js.map +1 -0
  238. package/dist/db/tables/memberships.d.ts +108 -0
  239. package/dist/db/tables/memberships.d.ts.map +1 -0
  240. package/dist/db/tables/memberships.js +153 -0
  241. package/dist/db/tables/memberships.js.map +1 -0
  242. package/dist/db/tables/organizations.d.ts +48 -0
  243. package/dist/db/tables/organizations.d.ts.map +1 -1
  244. package/dist/db/tables/organizations.js +97 -2
  245. package/dist/db/tables/organizations.js.map +1 -1
  246. package/dist/db/tables/projects.d.ts +65 -0
  247. package/dist/db/tables/projects.d.ts.map +1 -0
  248. package/dist/db/tables/projects.js +75 -0
  249. package/dist/db/tables/projects.js.map +1 -0
  250. package/dist/db/tables/requestUsage.d.ts +121 -0
  251. package/dist/db/tables/requestUsage.d.ts.map +1 -0
  252. package/dist/db/tables/requestUsage.js +150 -0
  253. package/dist/db/tables/requestUsage.js.map +1 -0
  254. package/dist/db/tables/sponsoredTransactions.d.ts +239 -0
  255. package/dist/db/tables/sponsoredTransactions.d.ts.map +1 -0
  256. package/dist/db/tables/sponsoredTransactions.js +314 -0
  257. package/dist/db/tables/sponsoredTransactions.js.map +1 -0
  258. package/dist/db/tables/stripeCustomers.d.ts +81 -0
  259. package/dist/db/tables/stripeCustomers.d.ts.map +1 -0
  260. package/dist/db/tables/stripeCustomers.js +120 -0
  261. package/dist/db/tables/stripeCustomers.js.map +1 -0
  262. package/dist/db/tables/users.d.ts +48 -0
  263. package/dist/db/tables/users.d.ts.map +1 -0
  264. package/dist/db/tables/users.js +65 -0
  265. package/dist/db/tables/users.js.map +1 -0
  266. package/dist/handlers/internal/executionError.d.ts.map +1 -1
  267. package/dist/handlers/internal/executionError.js +2 -0
  268. package/dist/handlers/internal/executionError.js.map +1 -1
  269. package/dist/handlers/internal/multisig.d.ts +2 -6
  270. package/dist/handlers/internal/multisig.d.ts.map +1 -1
  271. package/dist/handlers/internal/multisig.js +11 -36
  272. package/dist/handlers/internal/multisig.js.map +1 -1
  273. package/dist/handlers/internal/sponsorship.d.ts +35 -2
  274. package/dist/handlers/internal/sponsorship.d.ts.map +1 -1
  275. package/dist/handlers/internal/sponsorship.js +53 -14
  276. package/dist/handlers/internal/sponsorship.js.map +1 -1
  277. package/dist/handlers/internal/utils.d.ts +10 -3
  278. package/dist/handlers/internal/utils.d.ts.map +1 -1
  279. package/dist/handlers/internal/utils.js +14 -6
  280. package/dist/handlers/internal/utils.js.map +1 -1
  281. package/dist/handlers/relay.d.ts +6 -2
  282. package/dist/handlers/relay.d.ts.map +1 -1
  283. package/dist/handlers/relay.js +16 -2
  284. package/dist/handlers/relay.js.map +1 -1
  285. package/dist/index.d.ts +1 -0
  286. package/dist/index.d.ts.map +1 -1
  287. package/dist/index.js +1 -0
  288. package/dist/index.js.map +1 -1
  289. package/dist/internal/Auth.d.ts +222 -6
  290. package/dist/internal/Auth.d.ts.map +1 -1
  291. package/dist/internal/Auth.js +625 -74
  292. package/dist/internal/Auth.js.map +1 -1
  293. package/dist/internal/Cache.d.ts.map +1 -1
  294. package/dist/internal/Cache.js +4 -1
  295. package/dist/internal/Cache.js.map +1 -1
  296. package/dist/internal/EdgeCache.d.ts +10 -0
  297. package/dist/internal/EdgeCache.d.ts.map +1 -1
  298. package/dist/internal/EdgeCache.js +39 -8
  299. package/dist/internal/EdgeCache.js.map +1 -1
  300. package/dist/internal/Fees.d.ts +27 -0
  301. package/dist/internal/Fees.d.ts.map +1 -0
  302. package/dist/internal/Fees.js +48 -0
  303. package/dist/internal/Fees.js.map +1 -0
  304. package/dist/internal/FundingRoutes.d.ts +183 -0
  305. package/dist/internal/FundingRoutes.d.ts.map +1 -0
  306. package/dist/internal/FundingRoutes.js +490 -0
  307. package/dist/internal/FundingRoutes.js.map +1 -0
  308. package/dist/internal/Id.d.ts +15 -0
  309. package/dist/internal/Id.d.ts.map +1 -0
  310. package/dist/internal/Id.js +22 -0
  311. package/dist/internal/Id.js.map +1 -0
  312. package/dist/internal/Log.d.ts +9 -3
  313. package/dist/internal/Log.d.ts.map +1 -1
  314. package/dist/internal/Log.js +7 -0
  315. package/dist/internal/Log.js.map +1 -1
  316. package/dist/internal/OpenApi.d.ts +10 -20
  317. package/dist/internal/OpenApi.d.ts.map +1 -1
  318. package/dist/internal/OpenApi.js +8 -58
  319. package/dist/internal/OpenApi.js.map +1 -1
  320. package/dist/internal/RateLimit.d.ts +25 -0
  321. package/dist/internal/RateLimit.d.ts.map +1 -1
  322. package/dist/internal/RateLimit.js +51 -0
  323. package/dist/internal/RateLimit.js.map +1 -1
  324. package/dist/internal/Response.d.ts +1 -1
  325. package/dist/internal/Response.d.ts.map +1 -1
  326. package/dist/internal/Schema.d.ts +8 -0
  327. package/dist/internal/Schema.d.ts.map +1 -1
  328. package/dist/internal/Schema.js +7 -0
  329. package/dist/internal/Schema.js.map +1 -1
  330. package/dist/internal/Store.d.ts +7 -1
  331. package/dist/internal/Store.d.ts.map +1 -1
  332. package/dist/internal/Store.js +16 -2
  333. package/dist/internal/Store.js.map +1 -1
  334. package/dist/internal/Viem.d.ts +84 -0
  335. package/dist/internal/Viem.d.ts.map +1 -1
  336. package/dist/internal/Webhooks.d.ts +1 -1
  337. package/dist/internal/Webhooks.d.ts.map +1 -1
  338. package/dist/internal/Webhooks.js +3 -3
  339. package/dist/internal/Webhooks.js.map +1 -1
  340. package/package.json +33 -6
  341. package/src/ApiKey.ts +10 -2
  342. package/src/ApiKeys.test.ts +155 -1
  343. package/src/ApiKeys.ts +203 -1
  344. package/src/App.test.ts +64 -75741
  345. package/src/App.ts +218 -420
  346. package/src/Client.test-d.ts +657 -15
  347. package/src/Scope.ts +26 -4
  348. package/src/Webhooks.ts +2 -2
  349. package/src/admin/apps/api-keys.test.ts +51 -0
  350. package/src/admin/apps/api-keys.ts +84 -0
  351. package/src/admin/apps/scopes.test.ts +11 -1
  352. package/src/admin/apps/scopes.ts +3 -0
  353. package/src/admin/apps/verified-tokens.ts +1 -1
  354. package/src/admin/ui/src/components/Page.tsx +2 -3
  355. package/src/admin/ui/src/routes/__root.tsx +2 -2
  356. package/src/admin/ui/src/routes/api-keys.tsx +2 -2
  357. package/src/admin/ui.generated.ts +13 -13
  358. package/src/analytics/Analytics.test.ts +527 -0
  359. package/src/analytics/Analytics.ts +318 -0
  360. package/src/analytics/tables/requestEvents.ts +523 -0
  361. package/src/apps/composition.test.ts +66 -0
  362. package/src/apps/data/App.test.ts +72 -0
  363. package/src/apps/data/App.ts +330 -0
  364. package/src/apps/data/index.ts +2 -0
  365. package/src/apps/{activities.test.ts → data/routes/activities.test.ts} +3 -3
  366. package/src/apps/{activities.ts → data/routes/activities.ts} +15 -15
  367. package/src/apps/{addresses.ts → data/routes/addresses.ts} +6 -6
  368. package/src/apps/{balances.test.ts → data/routes/balances.test.ts} +2 -2
  369. package/src/apps/{balances.ts → data/routes/balances.ts} +46 -12
  370. package/src/apps/{blocks.test.ts → data/routes/blocks.test.ts} +3 -3
  371. package/src/apps/{blocks.ts → data/routes/blocks.ts} +14 -14
  372. package/src/apps/{coingecko.test.ts → data/routes/coingecko.test.ts} +2 -2
  373. package/src/apps/{coingecko.ts → data/routes/coingecko.ts} +14 -14
  374. package/src/apps/{exchanges.test.ts → data/routes/exchanges.test.ts} +1 -1
  375. package/src/apps/{exchanges.ts → data/routes/exchanges.ts} +20 -20
  376. package/src/apps/{fee-amm.test.ts → data/routes/fee-amm.test.ts} +3 -3
  377. package/src/apps/{fee-amm.ts → data/routes/fee-amm.ts} +18 -18
  378. package/src/apps/data/routes/funding.test.ts +214 -0
  379. package/src/apps/data/routes/funding.ts +584 -0
  380. package/src/apps/{indexer.test.ts → data/routes/indexer.test.ts} +26 -5
  381. package/src/apps/{indexer.ts → data/routes/indexer.ts} +13 -10
  382. package/src/apps/{receipts.test.ts → data/routes/receipts.test.ts} +3 -3
  383. package/src/apps/{receipts.ts → data/routes/receipts.ts} +15 -22
  384. package/src/apps/{rpc.test.ts → data/routes/rpc.test.ts} +122 -7
  385. package/src/apps/{rpc.ts → data/routes/rpc.ts} +38 -8
  386. package/src/apps/{tokenlist.test.ts → data/routes/tokenlist.test.ts} +3 -3
  387. package/src/apps/{tokenlist.ts → data/routes/tokenlist.ts} +9 -9
  388. package/src/apps/{tokens.test.ts → data/routes/tokens.test.ts} +2 -2
  389. package/src/apps/{tokens.ts → data/routes/tokens.ts} +20 -20
  390. package/src/apps/{transactions.test.ts → data/routes/transactions.test.ts} +3 -3
  391. package/src/apps/{transactions.ts → data/routes/transactions.ts} +15 -15
  392. package/src/apps/{transfers.test.ts → data/routes/transfers.test.ts} +2 -2
  393. package/src/apps/{transfers.ts → data/routes/transfers.ts} +15 -15
  394. package/src/apps/{verified-tokens.test.ts → data/routes/verified-tokens.test.ts} +3 -3
  395. package/src/apps/{verified-tokens.ts → data/routes/verified-tokens.ts} +10 -10
  396. package/src/apps/{webhooks.test.ts → data/routes/webhooks.test.ts} +7 -7
  397. package/src/apps/{webhooks.ts → data/routes/webhooks.ts} +50 -43
  398. package/src/apps/docs/App.ts +65 -0
  399. package/src/apps/docs/index.ts +1 -0
  400. package/src/apps/docs/reference.ts +132 -0
  401. package/src/apps/index.ts +4 -0
  402. package/src/apps/mcp.test.ts +206 -0
  403. package/src/apps/mcp.ts +250 -0
  404. package/src/apps/metadata.ts +23 -0
  405. package/src/apps/platform/App.ts +169 -0
  406. package/src/apps/platform/Billing.test.ts +530 -0
  407. package/src/apps/platform/Billing.ts +682 -0
  408. package/src/apps/platform/index.ts +2 -0
  409. package/src/apps/platform/routes/api-keys.test.ts +493 -0
  410. package/src/apps/platform/routes/api-keys.ts +543 -0
  411. package/src/apps/platform/routes/billing.test.ts +1100 -0
  412. package/src/apps/platform/routes/billing.ts +966 -0
  413. package/src/apps/platform/routes/invitations.test.ts +409 -0
  414. package/src/apps/platform/routes/invitations.ts +587 -0
  415. package/src/apps/platform/routes/me.test.ts +351 -0
  416. package/src/apps/platform/routes/me.ts +106 -0
  417. package/src/apps/platform/routes/members.test.ts +195 -0
  418. package/src/apps/platform/routes/members.ts +313 -0
  419. package/src/apps/platform/routes/orgs.test.ts +313 -0
  420. package/src/apps/platform/routes/orgs.ts +365 -0
  421. package/src/apps/platform/routes/projects.test.ts +181 -0
  422. package/src/apps/platform/routes/projects.ts +367 -0
  423. package/src/apps/platform/routes/scopes.test.ts +29 -0
  424. package/src/apps/platform/routes/scopes.ts +85 -0
  425. package/src/apps/platform/routes/usage.test.ts +422 -0
  426. package/src/apps/platform/routes/usage.ts +527 -0
  427. package/src/apps/relay/App.test.ts +933 -0
  428. package/src/apps/relay/App.ts +236 -0
  429. package/src/apps/relay/Sponsorships.test.ts +375 -0
  430. package/src/apps/relay/Sponsorships.ts +306 -0
  431. package/src/apps/relay/index.ts +2 -0
  432. package/src/cli/Cli.test.ts +25 -2
  433. package/src/cli/Cli.ts +18 -3
  434. package/src/cli/commands/admin.test.ts +83 -0
  435. package/src/cli/commands/admin.ts +91 -3
  436. package/src/db/Db.ts +296 -0
  437. package/src/db/Schema.ts +241 -2
  438. package/src/db/tables/billingSettings.test.ts +87 -0
  439. package/src/db/tables/billingSettings.ts +108 -0
  440. package/src/db/tables/invitations.ts +198 -0
  441. package/src/db/tables/memberships.ts +208 -0
  442. package/src/db/tables/organizations.test.ts +38 -2
  443. package/src/db/tables/organizations.ts +117 -2
  444. package/src/db/tables/projects.ts +107 -0
  445. package/src/db/tables/requestUsage.ts +231 -0
  446. package/src/db/tables/sponsoredTransactions.test.ts +325 -0
  447. package/src/db/tables/sponsoredTransactions.ts +464 -0
  448. package/src/db/tables/stripeCustomers.test.ts +109 -0
  449. package/src/db/tables/stripeCustomers.ts +162 -0
  450. package/src/db/tables/users.ts +85 -0
  451. package/src/handlers/internal/executionError.ts +2 -0
  452. package/src/handlers/internal/multisig.test.ts +84 -10
  453. package/src/handlers/internal/multisig.ts +15 -50
  454. package/src/handlers/internal/sponsorship.ts +106 -20
  455. package/src/handlers/internal/utils.ts +19 -7
  456. package/src/handlers/{internal/relay.localnet.test.ts → relay.test.ts} +2 -2
  457. package/src/handlers/relay.ts +23 -5
  458. package/src/index.ts +1 -0
  459. package/src/internal/Auth.test.ts +491 -25
  460. package/src/internal/Auth.ts +839 -84
  461. package/src/internal/Cache.ts +5 -1
  462. package/src/internal/Cursor.test.ts +3 -2
  463. package/src/internal/EdgeCache.test.ts +27 -4
  464. package/src/internal/EdgeCache.ts +47 -9
  465. package/src/internal/Fees.test.ts +32 -0
  466. package/src/internal/Fees.ts +51 -0
  467. package/src/internal/FundingRoutes.ts +583 -0
  468. package/src/internal/Id.test.ts +17 -0
  469. package/src/internal/Id.ts +24 -0
  470. package/src/internal/Log.test.ts +2 -1
  471. package/src/internal/Log.ts +16 -3
  472. package/src/internal/MetricSink.ts +1 -1
  473. package/src/internal/OpenApi.ts +12 -60
  474. package/src/internal/RateLimit.test.ts +84 -0
  475. package/src/internal/RateLimit.ts +70 -0
  476. package/src/internal/Response.ts +1 -1
  477. package/src/internal/Schema.ts +8 -0
  478. package/src/internal/Store.test.ts +52 -0
  479. package/src/internal/Store.ts +24 -5
  480. package/src/internal/Tidx.local.test.ts +1 -1
  481. package/src/internal/VerifiedTokens.test.ts +1 -1
  482. package/src/internal/WebhookDestination.test.ts +1 -1
  483. package/src/internal/Webhooks.test.ts +7 -7
  484. package/src/internal/Webhooks.ts +4 -4
  485. package/dist/apps/activities.js.map +0 -1
  486. package/dist/apps/addresses.js.map +0 -1
  487. package/dist/apps/balances.d.ts.map +0 -1
  488. package/dist/apps/balances.js.map +0 -1
  489. package/dist/apps/blocks.d.ts.map +0 -1
  490. package/dist/apps/blocks.js.map +0 -1
  491. package/dist/apps/coingecko.d.ts.map +0 -1
  492. package/dist/apps/coingecko.js.map +0 -1
  493. package/dist/apps/exchanges.d.ts.map +0 -1
  494. package/dist/apps/exchanges.js.map +0 -1
  495. package/dist/apps/fee-amm.d.ts.map +0 -1
  496. package/dist/apps/fee-amm.js.map +0 -1
  497. package/dist/apps/indexer.d.ts.map +0 -1
  498. package/dist/apps/indexer.js.map +0 -1
  499. package/dist/apps/receipts.d.ts.map +0 -1
  500. package/dist/apps/receipts.js.map +0 -1
  501. package/dist/apps/rpc.d.ts.map +0 -1
  502. package/dist/apps/rpc.js.map +0 -1
  503. package/dist/apps/tokenlist.d.ts.map +0 -1
  504. package/dist/apps/tokenlist.js.map +0 -1
  505. package/dist/apps/tokens.d.ts.map +0 -1
  506. package/dist/apps/tokens.js.map +0 -1
  507. package/dist/apps/transactions.d.ts.map +0 -1
  508. package/dist/apps/transactions.js.map +0 -1
  509. package/dist/apps/transfers.d.ts.map +0 -1
  510. package/dist/apps/transfers.js.map +0 -1
  511. package/dist/apps/verified-tokens.d.ts.map +0 -1
  512. package/dist/apps/verified-tokens.js.map +0 -1
  513. package/dist/apps/webhooks.js.map +0 -1
  514. package/dist/internal/OpenApi.fonts.d.ts.map +0 -1
  515. package/dist/internal/OpenApi.fonts.js.map +0 -1
  516. package/dist/internal/OpenApi.logo.d.ts.map +0 -1
  517. package/dist/internal/OpenApi.logo.js.map +0 -1
  518. package/src/handlers/internal/multisig.localnet.test.ts +0 -146
  519. /package/src/{internal/OpenApi.fonts.ts → apps/docs/fonts.ts} +0 -0
  520. /package/src/{internal/OpenApi.logo.ts → apps/docs/logo.ts} +0 -0
@@ -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
+ }
@@ -0,0 +1,198 @@
1
+ import type { Selectable } from 'kysely'
2
+
3
+ import * as Id from '../../internal/Id.js'
4
+ import type * as Db from '../Db.js'
5
+ import type * as db_Schema from '../Schema.js'
6
+ import * as Memberships from './memberships.js'
7
+
8
+ /** Columns of the `invitations` table, derived from `Schema.Invitation`. */
9
+ export type Table = db_Schema.Invitation
10
+
11
+ /** A stored invitation row. */
12
+ export type Record = Selectable<Table>
13
+
14
+ /**
15
+ * Inserts an invitation and returns the stored record. Emails are stored
16
+ * lowercase so matching against verified emails is case-insensitive. Re-inviting
17
+ * a live (pending) (org, email) refreshes that row — role, inviter, expiry —
18
+ * rather than accumulating duplicates, matching the `invitations_pending_unique`
19
+ * partial index.
20
+ *
21
+ * @param db - The database.
22
+ * @param input - The invitation to insert.
23
+ * @returns The stored record.
24
+ */
25
+ export function create(db: Db.Db, input: create.Input): Promise<Record> {
26
+ const now = new Date().toISOString()
27
+ return db.kysely
28
+ .insertInto('invitations')
29
+ .values({
30
+ acceptedAt: null,
31
+ createdAt: now,
32
+ email: input.email.toLowerCase(),
33
+ expiresAt: input.expiresAt,
34
+ id: Id.generate('inv'),
35
+ invitedBy: input.invitedBy,
36
+ orgId: input.orgId,
37
+ revokedAt: null,
38
+ role: input.role,
39
+ })
40
+ .onConflict((oc) =>
41
+ oc
42
+ .columns(['orgId', 'email'])
43
+ .where('acceptedAt', 'is', null)
44
+ .where('revokedAt', 'is', null)
45
+ .doUpdateSet({
46
+ createdAt: now,
47
+ expiresAt: input.expiresAt,
48
+ invitedBy: input.invitedBy,
49
+ role: input.role,
50
+ }),
51
+ )
52
+ .returningAll()
53
+ .executeTakeFirstOrThrow()
54
+ }
55
+
56
+ export declare namespace create {
57
+ /** Fields accepted when inserting an invitation. */
58
+ type Input = {
59
+ /** Invitee email. */
60
+ email: string
61
+ /** When the invitation expires (ISO 8601). */
62
+ expiresAt: string
63
+ /** User id (`usr_…`) creating the invitation. */
64
+ invitedBy: string
65
+ /** Organization id (`org_…`) the invitation joins. */
66
+ orgId: string
67
+ /** Role granted on accept. */
68
+ role: Memberships.Role
69
+ }
70
+ }
71
+
72
+ /**
73
+ * Reads an invitation by id.
74
+ *
75
+ * @param db - The database.
76
+ * @param id - The invitation id (`inv_…`).
77
+ * @returns The record, or `undefined` when absent.
78
+ */
79
+ export function get(db: Db.Db, id: string): Promise<Record | undefined> {
80
+ return db.kysely.selectFrom('invitations').selectAll().where('id', '=', id).executeTakeFirst()
81
+ }
82
+
83
+ /**
84
+ * Lists an organization's pending invitations (unaccepted, unrevoked,
85
+ * unexpired), newest first.
86
+ *
87
+ * @param db - The database.
88
+ * @param orgId - The organization id (`org_…`).
89
+ * @returns The records.
90
+ */
91
+ export function listByOrg(db: Db.Db, orgId: string): Promise<Record[]> {
92
+ return pending(db).where('orgId', '=', orgId).orderBy('createdAt', 'desc').execute()
93
+ }
94
+
95
+ /**
96
+ * Lists pending invitations addressed to an email, joined with the inviting
97
+ * organization's name, newest first.
98
+ *
99
+ * @param db - The database.
100
+ * @param email - The invitee email (matched lowercase).
101
+ * @returns The records with `orgName` from the organization row.
102
+ */
103
+ export function listByEmail(db: Db.Db, email: string): Promise<(Record & { orgName: string })[]> {
104
+ return db.kysely
105
+ .selectFrom('invitations')
106
+ .innerJoin('organizations', 'organizations.id', 'invitations.orgId')
107
+ .where('invitations.acceptedAt', 'is', null)
108
+ .where('invitations.revokedAt', 'is', null)
109
+ .where('invitations.expiresAt', '>', new Date().toISOString())
110
+ .where('invitations.email', '=', email.toLowerCase())
111
+ .selectAll('invitations')
112
+ .select('organizations.name as orgName')
113
+ .orderBy('invitations.createdAt', 'desc')
114
+ .execute()
115
+ }
116
+
117
+ /**
118
+ * Revokes a pending invitation.
119
+ *
120
+ * @param db - The database.
121
+ * @param id - The invitation id (`inv_…`).
122
+ * @returns The revoked record, or `undefined` when absent or already settled.
123
+ */
124
+ export function revoke(db: Db.Db, id: string): Promise<Record | undefined> {
125
+ const now = new Date().toISOString()
126
+ return db.kysely
127
+ .updateTable('invitations')
128
+ .set({ revokedAt: now })
129
+ .where('id', '=', id)
130
+ .where('acceptedAt', 'is', null)
131
+ .where('revokedAt', 'is', null)
132
+ .returningAll()
133
+ .executeTakeFirst()
134
+ }
135
+
136
+ /**
137
+ * Accepts a pending invitation into a membership, atomically: the conditional
138
+ * update settles the invitation exactly once (double accepts lose the race).
139
+ * A new member is inserted at the invited role; an existing member is only ever
140
+ * elevated to it — accepting never demotes (which could strip the last owner).
141
+ *
142
+ * @param db - The database.
143
+ * @param id - The invitation id (`inv_…`).
144
+ * @param userId - The accepting user id (`usr_…`).
145
+ * @returns The accepted record, or `undefined` when absent, settled, or expired.
146
+ */
147
+ export function accept(db: Db.Db, id: string, userId: string): Promise<Record | undefined> {
148
+ const now = new Date().toISOString()
149
+ return db.kysely.transaction().execute(async (trx) => {
150
+ const invitation = await trx
151
+ .updateTable('invitations')
152
+ .set({ acceptedAt: now })
153
+ .where('id', '=', id)
154
+ .where('acceptedAt', 'is', null)
155
+ .where('revokedAt', 'is', null)
156
+ .where('expiresAt', '>', now)
157
+ .returningAll()
158
+ .executeTakeFirst()
159
+ if (!invitation) return undefined
160
+ const existing = await trx
161
+ .selectFrom('memberships')
162
+ .selectAll()
163
+ .where('orgId', '=', invitation.orgId)
164
+ .where('userId', '=', userId)
165
+ .executeTakeFirst()
166
+ if (!existing)
167
+ await trx
168
+ .insertInto('memberships')
169
+ .values({
170
+ createdAt: now,
171
+ orgId: invitation.orgId,
172
+ role: invitation.role,
173
+ updatedAt: now,
174
+ userId,
175
+ })
176
+ // A concurrent accept/join could insert first; keep that membership.
177
+ .onConflict((oc) => oc.columns(['orgId', 'userId']).doNothing())
178
+ .execute()
179
+ else if (Memberships.rank[invitation.role] > Memberships.rank[existing.role])
180
+ await trx
181
+ .updateTable('memberships')
182
+ .set({ role: invitation.role, updatedAt: now })
183
+ .where('orgId', '=', invitation.orgId)
184
+ .where('userId', '=', userId)
185
+ .execute()
186
+ return invitation
187
+ })
188
+ }
189
+
190
+ /** Base query selecting pending invitations (unaccepted, unrevoked, unexpired). */
191
+ function pending(db: Db.Db) {
192
+ return db.kysely
193
+ .selectFrom('invitations')
194
+ .selectAll()
195
+ .where('acceptedAt', 'is', null)
196
+ .where('revokedAt', 'is', null)
197
+ .where('expiresAt', '>', new Date().toISOString())
198
+ }
@@ -0,0 +1,208 @@
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 `memberships` table, derived from `Schema.Membership`. */
7
+ export type Table = db_Schema.Membership
8
+
9
+ /** A stored membership row. */
10
+ export type Record = Selectable<Table>
11
+
12
+ /** A membership role. */
13
+ export type Role = Record['role']
14
+
15
+ /** Fixed membership roles, least to most privileged. */
16
+ export const roles = ['member', 'admin', 'owner'] as const satisfies readonly Role[]
17
+
18
+ /** Privilege rank per role; higher ranks satisfy lower minimums. */
19
+ export const rank = { admin: 1, member: 0, owner: 2 } as const satisfies { [role in Role]: number }
20
+
21
+ /**
22
+ * Inserts a membership and returns the stored record.
23
+ *
24
+ * @param db - The database.
25
+ * @param input - The membership to insert.
26
+ * @returns The stored record.
27
+ */
28
+ export function create(db: Db.Db, input: create.Input): Promise<Record> {
29
+ const now = new Date().toISOString()
30
+ return db.kysely
31
+ .insertInto('memberships')
32
+ .values({
33
+ createdAt: now,
34
+ orgId: input.orgId,
35
+ role: input.role,
36
+ updatedAt: now,
37
+ userId: input.userId,
38
+ })
39
+ .returningAll()
40
+ .executeTakeFirstOrThrow()
41
+ }
42
+
43
+ export declare namespace create {
44
+ /** Fields accepted when inserting a membership. */
45
+ type Input = {
46
+ /** Organization id (`org_…`). */
47
+ orgId: string
48
+ /** Granted role. */
49
+ role: Role
50
+ /** Member user id (`usr_…`). */
51
+ userId: string
52
+ }
53
+ }
54
+
55
+ /**
56
+ * Reads one user's membership in an organization.
57
+ *
58
+ * @param db - The database.
59
+ * @param orgId - The organization id (`org_…`).
60
+ * @param userId - The member user id (`usr_…`).
61
+ * @returns The record, or `undefined` when absent.
62
+ */
63
+ export function get(db: Db.Db, orgId: string, userId: string): Promise<Record | undefined> {
64
+ return db.kysely
65
+ .selectFrom('memberships')
66
+ .selectAll()
67
+ .where('orgId', '=', orgId)
68
+ .where('userId', '=', userId)
69
+ .executeTakeFirst()
70
+ }
71
+
72
+ /**
73
+ * Lists an organization's memberships, oldest first (stable member ordering).
74
+ *
75
+ * @param db - The database.
76
+ * @param orgId - The organization id (`org_…`).
77
+ * @returns The records.
78
+ */
79
+ export function listByOrg(db: Db.Db, orgId: string): Promise<Record[]> {
80
+ return db.kysely
81
+ .selectFrom('memberships')
82
+ .selectAll()
83
+ .where('orgId', '=', orgId)
84
+ .orderBy('createdAt', 'asc')
85
+ .execute()
86
+ }
87
+
88
+ /**
89
+ * Lists an organization's memberships joined with member identity (address,
90
+ * email), oldest first.
91
+ *
92
+ * @param db - The database.
93
+ * @param orgId - The organization id (`org_…`).
94
+ * @returns The records with `address`/`email` from the member's user row.
95
+ */
96
+ export function listByOrgDetailed(
97
+ db: Db.Db,
98
+ orgId: string,
99
+ ): Promise<(Record & { address: string | null; email: string | null })[]> {
100
+ return db.kysely
101
+ .selectFrom('memberships')
102
+ .innerJoin('users', 'users.id', 'memberships.userId')
103
+ .where('memberships.orgId', '=', orgId)
104
+ .selectAll('memberships')
105
+ .select(['users.address', 'users.email'])
106
+ .orderBy('memberships.createdAt', 'asc')
107
+ .execute()
108
+ }
109
+
110
+ /**
111
+ * Changes a member's role atomically, refusing to demote the last owner. The
112
+ * org's owner rows are locked for the transaction, so a concurrent demotion or
113
+ * removal cannot race two guards into leaving the organization ownerless.
114
+ *
115
+ * @param db - The database.
116
+ * @param orgId - The organization id (`org_…`).
117
+ * @param userId - The member user id (`usr_…`).
118
+ * @param input - The fields to update.
119
+ * @returns The updated record, or an `error` when absent or the last owner.
120
+ */
121
+ export function update(
122
+ db: Db.Db,
123
+ orgId: string,
124
+ userId: string,
125
+ input: update.Input,
126
+ ): Promise<update.Result> {
127
+ const now = new Date().toISOString()
128
+ return db.kysely.transaction().execute(async (trx) => {
129
+ const owners = await trx
130
+ .selectFrom('memberships')
131
+ .select('userId')
132
+ .where('orgId', '=', orgId)
133
+ .where('role', '=', 'owner')
134
+ .forUpdate()
135
+ .execute()
136
+ const current = await trx
137
+ .selectFrom('memberships')
138
+ .selectAll()
139
+ .where('orgId', '=', orgId)
140
+ .where('userId', '=', userId)
141
+ .executeTakeFirst()
142
+ if (!current) return { error: 'not_found' as const }
143
+ if (current.role === 'owner' && input.role !== 'owner' && owners.length <= 1)
144
+ return { error: 'last_owner' as const }
145
+ const record = await trx
146
+ .updateTable('memberships')
147
+ .set({ role: input.role, updatedAt: now })
148
+ .where('orgId', '=', orgId)
149
+ .where('userId', '=', userId)
150
+ .returningAll()
151
+ .executeTakeFirstOrThrow()
152
+ return { record }
153
+ })
154
+ }
155
+
156
+ export declare namespace update {
157
+ /** Mutable membership fields. */
158
+ type Input = {
159
+ /** New role. */
160
+ role: Role
161
+ }
162
+
163
+ /** Outcome of a guarded role change. */
164
+ type Result =
165
+ | { error: 'last_owner' | 'not_found'; record?: undefined }
166
+ | { error?: undefined; record: Record }
167
+ }
168
+
169
+ /**
170
+ * Deletes a membership atomically, refusing to remove the last owner. Locks
171
+ * the org's owner rows for the transaction (see {@link update}) so concurrent
172
+ * removals cannot race the organization ownerless.
173
+ *
174
+ * @param db - The database.
175
+ * @param orgId - The organization id (`org_…`).
176
+ * @param userId - The member user id (`usr_…`).
177
+ * @returns `removed` on success, otherwise why the removal was refused.
178
+ */
179
+ export function remove(db: Db.Db, orgId: string, userId: string): Promise<remove.Result> {
180
+ return db.kysely.transaction().execute(async (trx) => {
181
+ const owners = await trx
182
+ .selectFrom('memberships')
183
+ .select('userId')
184
+ .where('orgId', '=', orgId)
185
+ .where('role', '=', 'owner')
186
+ .forUpdate()
187
+ .execute()
188
+ const current = await trx
189
+ .selectFrom('memberships')
190
+ .selectAll()
191
+ .where('orgId', '=', orgId)
192
+ .where('userId', '=', userId)
193
+ .executeTakeFirst()
194
+ if (!current) return 'not_found'
195
+ if (current.role === 'owner' && owners.length <= 1) return 'last_owner'
196
+ await trx
197
+ .deleteFrom('memberships')
198
+ .where('orgId', '=', orgId)
199
+ .where('userId', '=', userId)
200
+ .execute()
201
+ return 'removed'
202
+ })
203
+ }
204
+
205
+ export declare namespace remove {
206
+ /** Outcome of a guarded removal. */
207
+ type Result = 'last_owner' | 'not_found' | 'removed'
208
+ }
@@ -1,8 +1,9 @@
1
1
  import { nanoid } from 'nanoid'
2
2
 
3
- import { postgresUrl } from '../../../test/containers.js'
3
+ import * as Webhooks from '../../internal/Webhooks.js'
4
4
  import * as Db from '../Db.js'
5
5
  import * as Organizations from './organizations.js'
6
+ import { postgresUrl } from '../../../test/containers.js'
6
7
 
7
8
  const create = () => Db.postgres({ connectionString: postgresUrl, schema: `t_${nanoid()}` })
8
9
 
@@ -11,7 +12,7 @@ function redact(record: Organizations.Record) {
11
12
  return {
12
13
  ...record,
13
14
  createdAt: '<timestamp>',
14
- 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,
15
16
  updatedAt: '<timestamp>',
16
17
  }
17
18
  }
@@ -29,6 +30,7 @@ describe('create', () => {
29
30
  "id": "org_<id>",
30
31
  "name": "Acme, Inc.",
31
32
  "updatedAt": "<timestamp>",
33
+ "userId": null,
32
34
  }
33
35
  `)
34
36
  await db.close()
@@ -49,6 +51,7 @@ describe('create', () => {
49
51
  "id": "org_explicit",
50
52
  "name": "Explicit",
51
53
  "updatedAt": "<timestamp>",
54
+ "userId": null,
52
55
  }
53
56
  `)
54
57
  await db.close()
@@ -122,3 +125,36 @@ describe('upsert', () => {
122
125
  await db.close()
123
126
  })
124
127
  })
128
+
129
+ describe('deleteOrganization', () => {
130
+ test('behavior: deletes api-key-owned webhook subscriptions', async () => {
131
+ const db = create()
132
+ await db.migrate()
133
+ await Organizations.create(db, { id: 'org_1', name: 'One' })
134
+ await Organizations.create(db, { id: 'org_2', name: 'Two' })
135
+ const deletedOrg = { orgId: 'org_1', type: 'api_key' } as const
136
+ const keptOrg = { orgId: 'org_2', type: 'api_key' } as const
137
+ const keptMpp = { payer: 'did:pkh:eip155:1:0xabc', type: 'mpp' } as const
138
+ await Webhooks.createSubscription(db, subscription(deletedOrg))
139
+ const keptApiKey = await Webhooks.createSubscription(db, subscription(keptOrg))
140
+ const keptMppSubscription = await Webhooks.createSubscription(db, subscription(keptMpp))
141
+
142
+ expect(await Organizations.deleteOrganization(db, 'org_1')).toBe(true)
143
+
144
+ expect(await Webhooks.listSubscriptions(db, deletedOrg)).toEqual([])
145
+ const active = await Webhooks.listActive(db, 4217, 'token:transfer')
146
+ expect(active.map(({ id }) => id).sort()).toEqual(
147
+ [keptApiKey.id, keptMppSubscription.id].sort(),
148
+ )
149
+ await db.close()
150
+ })
151
+ })
152
+
153
+ function subscription(owner: Webhooks.Owner): Webhooks.CreateInput {
154
+ return {
155
+ chainId: 4217,
156
+ destination: { type: 'url', url: 'https://hooks.example.com/endpoint' },
157
+ eventType: 'token:transfer',
158
+ owner,
159
+ }
160
+ }