tapimo 0.1.0 → 0.2.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 (385) hide show
  1. package/README.md +28 -15
  2. package/dist/ApiKey.d.ts +3 -1
  3. package/dist/ApiKey.d.ts.map +1 -1
  4. package/dist/ApiKey.js +1 -0
  5. package/dist/ApiKey.js.map +1 -1
  6. package/dist/ApiKeys.d.ts +71 -3
  7. package/dist/ApiKeys.d.ts.map +1 -1
  8. package/dist/ApiKeys.js +117 -1
  9. package/dist/ApiKeys.js.map +1 -1
  10. package/dist/App.d.ts +7139 -4197
  11. package/dist/App.d.ts.map +1 -1
  12. package/dist/App.js +133 -329
  13. package/dist/App.js.map +1 -1
  14. package/dist/Scope.d.ts +11 -4
  15. package/dist/Scope.d.ts.map +1 -1
  16. package/dist/Scope.js +12 -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 +126 -53
  23. package/dist/admin/App.d.ts.map +1 -1
  24. package/dist/admin/apps/api-keys.d.ts +104 -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 +4 -1
  29. package/dist/admin/apps/scopes.d.ts.map +1 -1
  30. package/dist/admin/apps/verified-tokens.d.ts +115 -115
  31. package/dist/admin/apps/verified-tokens.js +1 -1
  32. package/dist/admin/apps/verified-tokens.js.map +1 -1
  33. package/dist/admin/ui.generated.d.ts +1 -1
  34. package/dist/admin/ui.generated.js +1 -1
  35. package/dist/apps/data/App.d.ts +19176 -0
  36. package/dist/apps/data/App.d.ts.map +1 -0
  37. package/dist/apps/data/App.js +236 -0
  38. package/dist/apps/data/App.js.map +1 -0
  39. package/dist/apps/data/index.d.ts +3 -0
  40. package/dist/apps/data/index.d.ts.map +1 -0
  41. package/dist/apps/data/index.js +3 -0
  42. package/dist/apps/data/index.js.map +1 -0
  43. package/dist/apps/{activities.d.ts → data/routes/activities.d.ts} +32 -32
  44. package/dist/apps/{activities.d.ts.map → data/routes/activities.d.ts.map} +1 -1
  45. package/dist/apps/{activities.js → data/routes/activities.js} +13 -13
  46. package/dist/apps/data/routes/activities.js.map +1 -0
  47. package/dist/apps/{addresses.d.ts → data/routes/addresses.d.ts} +22 -22
  48. package/dist/apps/{addresses.d.ts.map → data/routes/addresses.d.ts.map} +1 -1
  49. package/dist/apps/{addresses.js → data/routes/addresses.js} +5 -5
  50. package/dist/apps/data/routes/addresses.js.map +1 -0
  51. package/dist/apps/{balances.d.ts → data/routes/balances.d.ts} +49 -49
  52. package/dist/apps/data/routes/balances.d.ts.map +1 -0
  53. package/dist/apps/{balances.js → data/routes/balances.js} +42 -11
  54. package/dist/apps/data/routes/balances.js.map +1 -0
  55. package/dist/apps/{blocks.d.ts → data/routes/blocks.d.ts} +47 -47
  56. package/dist/apps/data/routes/blocks.d.ts.map +1 -0
  57. package/dist/apps/{blocks.js → data/routes/blocks.js} +13 -13
  58. package/dist/apps/data/routes/blocks.js.map +1 -0
  59. package/dist/apps/{coingecko.d.ts → data/routes/coingecko.d.ts} +183 -183
  60. package/dist/apps/data/routes/coingecko.d.ts.map +1 -0
  61. package/dist/apps/{coingecko.js → data/routes/coingecko.js} +13 -13
  62. package/dist/apps/data/routes/coingecko.js.map +1 -0
  63. package/dist/apps/{exchanges.d.ts → data/routes/exchanges.d.ts} +460 -460
  64. package/dist/apps/data/routes/exchanges.d.ts.map +1 -0
  65. package/dist/apps/{exchanges.js → data/routes/exchanges.js} +19 -19
  66. package/dist/apps/data/routes/exchanges.js.map +1 -0
  67. package/dist/apps/{fee-amm.d.ts → data/routes/fee-amm.d.ts} +94 -94
  68. package/dist/apps/data/routes/fee-amm.d.ts.map +1 -0
  69. package/dist/apps/{fee-amm.js → data/routes/fee-amm.js} +17 -17
  70. package/dist/apps/data/routes/fee-amm.js.map +1 -0
  71. package/dist/apps/{indexer.d.ts → data/routes/indexer.d.ts} +4 -4
  72. package/dist/apps/data/routes/indexer.d.ts.map +1 -0
  73. package/dist/apps/{indexer.js → data/routes/indexer.js} +13 -9
  74. package/dist/apps/data/routes/indexer.js.map +1 -0
  75. package/dist/apps/{receipts.d.ts → data/routes/receipts.d.ts} +49 -49
  76. package/dist/apps/data/routes/receipts.d.ts.map +1 -0
  77. package/dist/apps/{receipts.js → data/routes/receipts.js} +12 -12
  78. package/dist/apps/data/routes/receipts.js.map +1 -0
  79. package/dist/apps/{rpc.d.ts → data/routes/rpc.d.ts} +4 -4
  80. package/dist/apps/data/routes/rpc.d.ts.map +1 -0
  81. package/dist/apps/{rpc.js → data/routes/rpc.js} +6 -6
  82. package/dist/apps/data/routes/rpc.js.map +1 -0
  83. package/dist/apps/{tokenlist.d.ts → data/routes/tokenlist.d.ts} +4 -4
  84. package/dist/apps/data/routes/tokenlist.d.ts.map +1 -0
  85. package/dist/apps/{tokenlist.js → data/routes/tokenlist.js} +8 -8
  86. package/dist/apps/data/routes/tokenlist.js.map +1 -0
  87. package/dist/apps/{tokens.d.ts → data/routes/tokens.d.ts} +468 -468
  88. package/dist/apps/data/routes/tokens.d.ts.map +1 -0
  89. package/dist/apps/{tokens.js → data/routes/tokens.js} +19 -19
  90. package/dist/apps/data/routes/tokens.js.map +1 -0
  91. package/dist/apps/{transactions.d.ts → data/routes/transactions.d.ts} +304 -304
  92. package/dist/apps/data/routes/transactions.d.ts.map +1 -0
  93. package/dist/apps/{transactions.js → data/routes/transactions.js} +13 -13
  94. package/dist/apps/data/routes/transactions.js.map +1 -0
  95. package/dist/apps/{transfers.d.ts → data/routes/transfers.d.ts} +6 -6
  96. package/dist/apps/data/routes/transfers.d.ts.map +1 -0
  97. package/dist/apps/{transfers.js → data/routes/transfers.js} +13 -13
  98. package/dist/apps/data/routes/transfers.js.map +1 -0
  99. package/dist/apps/{verified-tokens.d.ts → data/routes/verified-tokens.d.ts} +42 -42
  100. package/dist/apps/data/routes/verified-tokens.d.ts.map +1 -0
  101. package/dist/apps/{verified-tokens.js → data/routes/verified-tokens.js} +9 -9
  102. package/dist/apps/data/routes/verified-tokens.js.map +1 -0
  103. package/dist/apps/{webhooks.d.ts → data/routes/webhooks.d.ts} +62 -62
  104. package/dist/apps/{webhooks.d.ts.map → data/routes/webhooks.d.ts.map} +1 -1
  105. package/dist/apps/{webhooks.js → data/routes/webhooks.js} +30 -26
  106. package/dist/apps/data/routes/webhooks.js.map +1 -0
  107. package/dist/apps/docs/App.d.ts +27 -0
  108. package/dist/apps/docs/App.d.ts.map +1 -0
  109. package/dist/apps/docs/App.js +122 -0
  110. package/dist/apps/docs/App.js.map +1 -0
  111. package/dist/{internal/OpenApi.fonts.d.ts → apps/docs/fonts.d.ts} +1 -1
  112. package/dist/apps/docs/fonts.d.ts.map +1 -0
  113. package/dist/{internal/OpenApi.fonts.js → apps/docs/fonts.js} +1 -1
  114. package/dist/apps/docs/fonts.js.map +1 -0
  115. package/dist/apps/docs/index.d.ts +2 -0
  116. package/dist/apps/docs/index.d.ts.map +1 -0
  117. package/dist/apps/docs/index.js +2 -0
  118. package/dist/apps/docs/index.js.map +1 -0
  119. package/dist/{internal/OpenApi.logo.d.ts → apps/docs/logo.d.ts} +1 -1
  120. package/dist/apps/docs/logo.d.ts.map +1 -0
  121. package/dist/{internal/OpenApi.logo.js → apps/docs/logo.js} +1 -1
  122. package/dist/apps/docs/logo.js.map +1 -0
  123. package/dist/apps/index.d.ts +4 -0
  124. package/dist/apps/index.d.ts.map +1 -0
  125. package/dist/apps/index.js +4 -0
  126. package/dist/apps/index.js.map +1 -0
  127. package/dist/apps/metadata.d.ts +7 -0
  128. package/dist/apps/metadata.d.ts.map +1 -0
  129. package/dist/apps/metadata.js +15 -0
  130. package/dist/apps/metadata.js.map +1 -0
  131. package/dist/apps/platform/App.d.ts +2985 -0
  132. package/dist/apps/platform/App.d.ts.map +1 -0
  133. package/dist/apps/platform/App.js +74 -0
  134. package/dist/apps/platform/App.js.map +1 -0
  135. package/dist/apps/platform/index.d.ts +2 -0
  136. package/dist/apps/platform/index.d.ts.map +1 -0
  137. package/dist/apps/platform/index.js +2 -0
  138. package/dist/apps/platform/index.js.map +1 -0
  139. package/dist/apps/platform/routes/api-keys.d.ts +681 -0
  140. package/dist/apps/platform/routes/api-keys.d.ts.map +1 -0
  141. package/dist/apps/platform/routes/api-keys.js +328 -0
  142. package/dist/apps/platform/routes/api-keys.js.map +1 -0
  143. package/dist/apps/platform/routes/invitations.d.ts +733 -0
  144. package/dist/apps/platform/routes/invitations.d.ts.map +1 -0
  145. package/dist/apps/platform/routes/invitations.js +413 -0
  146. package/dist/apps/platform/routes/invitations.js.map +1 -0
  147. package/dist/apps/platform/routes/me.d.ts +95 -0
  148. package/dist/apps/platform/routes/me.d.ts.map +1 -0
  149. package/dist/apps/platform/routes/me.js +81 -0
  150. package/dist/apps/platform/routes/me.js.map +1 -0
  151. package/dist/apps/platform/routes/members.d.ts +554 -0
  152. package/dist/apps/platform/routes/members.d.ts.map +1 -0
  153. package/dist/apps/platform/routes/members.js +251 -0
  154. package/dist/apps/platform/routes/members.js.map +1 -0
  155. package/dist/apps/platform/routes/orgs.d.ts +582 -0
  156. package/dist/apps/platform/routes/orgs.d.ts.map +1 -0
  157. package/dist/apps/platform/routes/orgs.js +265 -0
  158. package/dist/apps/platform/routes/orgs.js.map +1 -0
  159. package/dist/apps/platform/routes/projects.d.ts +740 -0
  160. package/dist/apps/platform/routes/projects.d.ts.map +1 -0
  161. package/dist/apps/platform/routes/projects.js +286 -0
  162. package/dist/apps/platform/routes/projects.js.map +1 -0
  163. package/dist/apps/relay/App.d.ts +41 -0
  164. package/dist/apps/relay/App.d.ts.map +1 -0
  165. package/dist/apps/relay/App.js +103 -0
  166. package/dist/apps/relay/App.js.map +1 -0
  167. package/dist/apps/relay/Sponsorships.d.ts +107 -0
  168. package/dist/apps/relay/Sponsorships.d.ts.map +1 -0
  169. package/dist/apps/relay/Sponsorships.js +193 -0
  170. package/dist/apps/relay/Sponsorships.js.map +1 -0
  171. package/dist/apps/relay/index.d.ts +3 -0
  172. package/dist/apps/relay/index.d.ts.map +1 -0
  173. package/dist/apps/relay/index.js +3 -0
  174. package/dist/apps/relay/index.js.map +1 -0
  175. package/dist/cli/Cli.d.ts +1 -1
  176. package/dist/cli/Cli.js +1 -1
  177. package/dist/cli/commands/admin.d.ts +1 -1
  178. package/dist/cli/commands/admin.js +2 -2
  179. package/dist/cli/commands/admin.js.map +1 -1
  180. package/dist/db/Db.d.ts +10 -0
  181. package/dist/db/Db.d.ts.map +1 -1
  182. package/dist/db/Db.js +128 -0
  183. package/dist/db/Db.js.map +1 -1
  184. package/dist/db/Schema.d.ts +80 -0
  185. package/dist/db/Schema.d.ts.map +1 -1
  186. package/dist/db/Schema.js +99 -0
  187. package/dist/db/Schema.js.map +1 -1
  188. package/dist/db/tables/invitations.d.ts +84 -0
  189. package/dist/db/tables/invitations.d.ts.map +1 -0
  190. package/dist/db/tables/invitations.js +165 -0
  191. package/dist/db/tables/invitations.js.map +1 -0
  192. package/dist/db/tables/memberships.d.ts +108 -0
  193. package/dist/db/tables/memberships.d.ts.map +1 -0
  194. package/dist/db/tables/memberships.js +153 -0
  195. package/dist/db/tables/memberships.js.map +1 -0
  196. package/dist/db/tables/organizations.d.ts +44 -0
  197. package/dist/db/tables/organizations.d.ts.map +1 -1
  198. package/dist/db/tables/organizations.js +89 -0
  199. package/dist/db/tables/organizations.js.map +1 -1
  200. package/dist/db/tables/projects.d.ts +65 -0
  201. package/dist/db/tables/projects.d.ts.map +1 -0
  202. package/dist/db/tables/projects.js +75 -0
  203. package/dist/db/tables/projects.js.map +1 -0
  204. package/dist/db/tables/sponsoredTransactions.d.ts +120 -0
  205. package/dist/db/tables/sponsoredTransactions.d.ts.map +1 -0
  206. package/dist/db/tables/sponsoredTransactions.js +161 -0
  207. package/dist/db/tables/sponsoredTransactions.js.map +1 -0
  208. package/dist/db/tables/users.d.ts +48 -0
  209. package/dist/db/tables/users.d.ts.map +1 -0
  210. package/dist/db/tables/users.js +65 -0
  211. package/dist/db/tables/users.js.map +1 -0
  212. package/dist/handlers/internal/executionError.d.ts.map +1 -1
  213. package/dist/handlers/internal/executionError.js +2 -0
  214. package/dist/handlers/internal/executionError.js.map +1 -1
  215. package/dist/handlers/internal/multisig.d.ts +2 -6
  216. package/dist/handlers/internal/multisig.d.ts.map +1 -1
  217. package/dist/handlers/internal/multisig.js +11 -36
  218. package/dist/handlers/internal/multisig.js.map +1 -1
  219. package/dist/handlers/internal/sponsorship.d.ts +32 -2
  220. package/dist/handlers/internal/sponsorship.d.ts.map +1 -1
  221. package/dist/handlers/internal/sponsorship.js +34 -9
  222. package/dist/handlers/internal/sponsorship.js.map +1 -1
  223. package/dist/handlers/relay.d.ts +6 -2
  224. package/dist/handlers/relay.d.ts.map +1 -1
  225. package/dist/handlers/relay.js +16 -2
  226. package/dist/handlers/relay.js.map +1 -1
  227. package/dist/internal/Auth.d.ts +208 -6
  228. package/dist/internal/Auth.d.ts.map +1 -1
  229. package/dist/internal/Auth.js +500 -21
  230. package/dist/internal/Auth.js.map +1 -1
  231. package/dist/internal/Log.d.ts +7 -3
  232. package/dist/internal/Log.d.ts.map +1 -1
  233. package/dist/internal/Log.js +4 -0
  234. package/dist/internal/Log.js.map +1 -1
  235. package/dist/internal/OpenApi.d.ts +0 -19
  236. package/dist/internal/OpenApi.d.ts.map +1 -1
  237. package/dist/internal/OpenApi.js +0 -57
  238. package/dist/internal/OpenApi.js.map +1 -1
  239. package/dist/internal/Store.d.ts +7 -1
  240. package/dist/internal/Store.d.ts.map +1 -1
  241. package/dist/internal/Store.js +16 -2
  242. package/dist/internal/Store.js.map +1 -1
  243. package/dist/internal/Viem.d.ts +84 -0
  244. package/dist/internal/Viem.d.ts.map +1 -1
  245. package/dist/internal/Webhooks.d.ts +1 -1
  246. package/dist/internal/Webhooks.d.ts.map +1 -1
  247. package/package.json +31 -5
  248. package/src/ApiKey.ts +1 -0
  249. package/src/ApiKeys.test.ts +104 -1
  250. package/src/ApiKeys.ts +154 -1
  251. package/src/App.test.ts +11187 -179
  252. package/src/App.ts +200 -420
  253. package/src/Client.test-d.ts +306 -15
  254. package/src/Scope.ts +13 -4
  255. package/src/Webhooks.ts +2 -2
  256. package/src/admin/apps/api-keys.test.ts +51 -0
  257. package/src/admin/apps/api-keys.ts +84 -0
  258. package/src/admin/apps/scopes.test.ts +5 -1
  259. package/src/admin/apps/verified-tokens.ts +1 -1
  260. package/src/admin/ui.generated.ts +1 -1
  261. package/src/apps/composition.test.ts +66 -0
  262. package/src/apps/data/App.ts +295 -0
  263. package/src/apps/data/index.ts +2 -0
  264. package/src/apps/{activities.test.ts → data/routes/activities.test.ts} +3 -3
  265. package/src/apps/{activities.ts → data/routes/activities.ts} +15 -15
  266. package/src/apps/{addresses.ts → data/routes/addresses.ts} +6 -6
  267. package/src/apps/{balances.test.ts → data/routes/balances.test.ts} +2 -2
  268. package/src/apps/{balances.ts → data/routes/balances.ts} +46 -12
  269. package/src/apps/{blocks.test.ts → data/routes/blocks.test.ts} +3 -3
  270. package/src/apps/{blocks.ts → data/routes/blocks.ts} +14 -14
  271. package/src/apps/{coingecko.test.ts → data/routes/coingecko.test.ts} +2 -2
  272. package/src/apps/{coingecko.ts → data/routes/coingecko.ts} +14 -14
  273. package/src/apps/{exchanges.test.ts → data/routes/exchanges.test.ts} +1 -1
  274. package/src/apps/{exchanges.ts → data/routes/exchanges.ts} +20 -20
  275. package/src/apps/{fee-amm.test.ts → data/routes/fee-amm.test.ts} +3 -3
  276. package/src/apps/{fee-amm.ts → data/routes/fee-amm.ts} +18 -18
  277. package/src/apps/{indexer.test.ts → data/routes/indexer.test.ts} +26 -5
  278. package/src/apps/{indexer.ts → data/routes/indexer.ts} +13 -10
  279. package/src/apps/{receipts.test.ts → data/routes/receipts.test.ts} +3 -3
  280. package/src/apps/{receipts.ts → data/routes/receipts.ts} +13 -13
  281. package/src/apps/{rpc.test.ts → data/routes/rpc.test.ts} +44 -5
  282. package/src/apps/{rpc.ts → data/routes/rpc.ts} +7 -7
  283. package/src/apps/{tokenlist.test.ts → data/routes/tokenlist.test.ts} +3 -3
  284. package/src/apps/{tokenlist.ts → data/routes/tokenlist.ts} +9 -9
  285. package/src/apps/{tokens.test.ts → data/routes/tokens.test.ts} +2 -2
  286. package/src/apps/{tokens.ts → data/routes/tokens.ts} +20 -20
  287. package/src/apps/{transactions.test.ts → data/routes/transactions.test.ts} +3 -3
  288. package/src/apps/{transactions.ts → data/routes/transactions.ts} +15 -15
  289. package/src/apps/{transfers.test.ts → data/routes/transfers.test.ts} +2 -2
  290. package/src/apps/{transfers.ts → data/routes/transfers.ts} +15 -15
  291. package/src/apps/{verified-tokens.test.ts → data/routes/verified-tokens.test.ts} +3 -3
  292. package/src/apps/{verified-tokens.ts → data/routes/verified-tokens.ts} +10 -10
  293. package/src/apps/{webhooks.test.ts → data/routes/webhooks.test.ts} +6 -6
  294. package/src/apps/{webhooks.ts → data/routes/webhooks.ts} +39 -35
  295. package/src/apps/docs/App.ts +136 -0
  296. package/src/apps/docs/index.ts +1 -0
  297. package/src/apps/index.ts +3 -0
  298. package/src/apps/metadata.ts +23 -0
  299. package/src/apps/platform/App.ts +130 -0
  300. package/src/apps/platform/index.ts +1 -0
  301. package/src/apps/platform/routes/api-keys.test.ts +330 -0
  302. package/src/apps/platform/routes/api-keys.ts +427 -0
  303. package/src/apps/platform/routes/invitations.test.ts +361 -0
  304. package/src/apps/platform/routes/invitations.ts +519 -0
  305. package/src/apps/platform/routes/me.test.ts +121 -0
  306. package/src/apps/platform/routes/me.ts +106 -0
  307. package/src/apps/platform/routes/members.test.ts +195 -0
  308. package/src/apps/platform/routes/members.ts +313 -0
  309. package/src/apps/platform/routes/orgs.test.ts +313 -0
  310. package/src/apps/platform/routes/orgs.ts +343 -0
  311. package/src/apps/platform/routes/projects.test.ts +181 -0
  312. package/src/apps/platform/routes/projects.ts +367 -0
  313. package/src/apps/relay/App.test.ts +484 -0
  314. package/src/apps/relay/App.ts +128 -0
  315. package/src/apps/relay/Sponsorships.test.ts +375 -0
  316. package/src/apps/relay/Sponsorships.ts +305 -0
  317. package/src/apps/relay/index.ts +2 -0
  318. package/src/cli/Cli.ts +1 -1
  319. package/src/cli/commands/admin.ts +2 -2
  320. package/src/db/Db.ts +159 -0
  321. package/src/db/Schema.ts +137 -0
  322. package/src/db/tables/invitations.ts +198 -0
  323. package/src/db/tables/memberships.ts +208 -0
  324. package/src/db/tables/organizations.test.ts +37 -1
  325. package/src/db/tables/organizations.ts +105 -0
  326. package/src/db/tables/projects.ts +107 -0
  327. package/src/db/tables/sponsoredTransactions.test.ts +218 -0
  328. package/src/db/tables/sponsoredTransactions.ts +236 -0
  329. package/src/db/tables/users.ts +85 -0
  330. package/src/handlers/internal/executionError.ts +2 -0
  331. package/src/handlers/internal/multisig.test.ts +84 -10
  332. package/src/handlers/internal/multisig.ts +15 -50
  333. package/src/handlers/internal/sponsorship.ts +74 -15
  334. package/src/handlers/{internal/relay.localnet.test.ts → relay.test.ts} +2 -2
  335. package/src/handlers/relay.ts +23 -5
  336. package/src/internal/Auth.test.ts +316 -0
  337. package/src/internal/Auth.ts +691 -25
  338. package/src/internal/Cursor.test.ts +3 -2
  339. package/src/internal/Log.test.ts +1 -1
  340. package/src/internal/Log.ts +11 -3
  341. package/src/internal/MetricSink.ts +1 -1
  342. package/src/internal/OpenApi.ts +0 -59
  343. package/src/internal/Store.test.ts +52 -0
  344. package/src/internal/Store.ts +24 -5
  345. package/src/internal/Tidx.local.test.ts +1 -1
  346. package/src/internal/VerifiedTokens.test.ts +1 -1
  347. package/src/internal/WebhookDestination.test.ts +1 -1
  348. package/src/internal/Webhooks.test.ts +4 -4
  349. package/src/internal/Webhooks.ts +1 -1
  350. package/dist/apps/activities.js.map +0 -1
  351. package/dist/apps/addresses.js.map +0 -1
  352. package/dist/apps/balances.d.ts.map +0 -1
  353. package/dist/apps/balances.js.map +0 -1
  354. package/dist/apps/blocks.d.ts.map +0 -1
  355. package/dist/apps/blocks.js.map +0 -1
  356. package/dist/apps/coingecko.d.ts.map +0 -1
  357. package/dist/apps/coingecko.js.map +0 -1
  358. package/dist/apps/exchanges.d.ts.map +0 -1
  359. package/dist/apps/exchanges.js.map +0 -1
  360. package/dist/apps/fee-amm.d.ts.map +0 -1
  361. package/dist/apps/fee-amm.js.map +0 -1
  362. package/dist/apps/indexer.d.ts.map +0 -1
  363. package/dist/apps/indexer.js.map +0 -1
  364. package/dist/apps/receipts.d.ts.map +0 -1
  365. package/dist/apps/receipts.js.map +0 -1
  366. package/dist/apps/rpc.d.ts.map +0 -1
  367. package/dist/apps/rpc.js.map +0 -1
  368. package/dist/apps/tokenlist.d.ts.map +0 -1
  369. package/dist/apps/tokenlist.js.map +0 -1
  370. package/dist/apps/tokens.d.ts.map +0 -1
  371. package/dist/apps/tokens.js.map +0 -1
  372. package/dist/apps/transactions.d.ts.map +0 -1
  373. package/dist/apps/transactions.js.map +0 -1
  374. package/dist/apps/transfers.d.ts.map +0 -1
  375. package/dist/apps/transfers.js.map +0 -1
  376. package/dist/apps/verified-tokens.d.ts.map +0 -1
  377. package/dist/apps/verified-tokens.js.map +0 -1
  378. package/dist/apps/webhooks.js.map +0 -1
  379. package/dist/internal/OpenApi.fonts.d.ts.map +0 -1
  380. package/dist/internal/OpenApi.fonts.js.map +0 -1
  381. package/dist/internal/OpenApi.logo.d.ts.map +0 -1
  382. package/dist/internal/OpenApi.logo.js.map +0 -1
  383. package/src/handlers/internal/multisig.localnet.test.ts +0 -146
  384. /package/src/{internal/OpenApi.fonts.ts → apps/docs/fonts.ts} +0 -0
  385. /package/src/{internal/OpenApi.logo.ts → apps/docs/logo.ts} +0 -0
@@ -50,11 +50,11 @@ const migrate = incur_Cli.create('migrate', {
50
50
  })
51
51
 
52
52
  /**
53
- * Operator commands that run against a deployment's infrastructure rather
53
+ * Super admin commands that run against a deployment's infrastructure rather
54
54
  * than the hosted API (database migrations, …).
55
55
  */
56
56
  export const admin = incur_Cli
57
57
  .create('admin', {
58
- description: 'Operator commands for a Tempo API deployment.',
58
+ description: 'Super admin commands for a Tempo API deployment.',
59
59
  })
60
60
  .command(migrate)
package/src/db/Db.ts CHANGED
@@ -5,11 +5,17 @@ import {
5
5
  type Dialect,
6
6
  Kysely,
7
7
  PostgresDialect,
8
+ type RawBuilder,
8
9
  sql,
9
10
  } from 'kysely'
10
11
  import * as pg from 'pg'
11
12
 
13
+ import type * as Invitations from './tables/invitations.js'
14
+ import type * as Memberships from './tables/memberships.js'
12
15
  import type * as Organizations from './tables/organizations.js'
16
+ import type * as Projects from './tables/projects.js'
17
+ import type * as SponsoredTransactions from './tables/sponsoredTransactions.js'
18
+ import type * as Users from './tables/users.js'
13
19
  import type * as VerifiedTokens from './tables/verifiedTokens.js'
14
20
  import type * as WebhookDeliveries from './tables/webhookDeliveries.js'
15
21
  import type * as WebhookSubscriptions from './tables/webhookSubscriptions.js'
@@ -26,7 +32,12 @@ export type Database = {
26
32
  /** SQL as applied; null on rows written before the column existed. */
27
33
  sql: string | null
28
34
  }
35
+ invitations: Invitations.Table
36
+ memberships: Memberships.Table
29
37
  organizations: Organizations.Table
38
+ projects: Projects.Table
39
+ sponsored_transactions: SponsoredTransactions.Table
40
+ users: Users.Table
30
41
  verified_token_lists: VerifiedTokens.ListTable
31
42
  verified_tokens: VerifiedTokens.Table
32
43
  webhook_deliveries: WebhookDeliveries.Table
@@ -235,8 +246,156 @@ export const migrations: readonly Migration[] = [
235
246
  .on('webhookDeliveries')
236
247
  .column('expiresAt'),
237
248
  },
249
+ {
250
+ name: '0012_users',
251
+ up: (db) =>
252
+ db.schema
253
+ .createTable('users')
254
+ .ifNotExists()
255
+ .addColumn('id', 'text', (c) => c.primaryKey())
256
+ .addColumn('address', 'text', (c) => c.notNull().unique())
257
+ .addColumn('email', 'text')
258
+ .addColumn('createdAt', 'text', (c) => c.notNull())
259
+ .addColumn('updatedAt', 'text', (c) => c.notNull()),
260
+ },
261
+ {
262
+ name: '0013_organizations_user_id',
263
+ // The schema builder cannot express `ADD COLUMN IF NOT EXISTS`; raw SQL
264
+ // (snake_case: the CamelCase plugin does not map raw statements).
265
+ up: (db) => raw(db, sql`ALTER TABLE organizations ADD COLUMN IF NOT EXISTS user_id text`),
266
+ },
267
+ {
268
+ name: '0014_projects',
269
+ up: (db) =>
270
+ db.schema
271
+ .createTable('projects')
272
+ .ifNotExists()
273
+ .addColumn('id', 'text', (c) => c.primaryKey())
274
+ .addColumn('orgId', 'text', (c) => c.notNull())
275
+ .addColumn('name', 'text', (c) => c.notNull())
276
+ .addColumn('createdAt', 'text', (c) => c.notNull())
277
+ .addColumn('updatedAt', 'text', (c) => c.notNull()),
278
+ },
279
+ {
280
+ name: '0015_projects_org_index',
281
+ up: (db) =>
282
+ db.schema.createIndex('projects_org_idx').ifNotExists().on('projects').column('orgId'),
283
+ },
284
+ {
285
+ name: '0016_memberships',
286
+ up: (db) =>
287
+ db.schema
288
+ .createTable('memberships')
289
+ .ifNotExists()
290
+ .addColumn('orgId', 'text', (c) => c.notNull())
291
+ .addColumn('userId', 'text', (c) => c.notNull())
292
+ .addColumn('role', 'text', (c) => c.notNull())
293
+ .addColumn('createdAt', 'text', (c) => c.notNull())
294
+ .addColumn('updatedAt', 'text', (c) => c.notNull())
295
+ .addPrimaryKeyConstraint('memberships_pk', ['orgId', 'userId']),
296
+ },
297
+ {
298
+ name: '0017_memberships_user_index',
299
+ up: (db) =>
300
+ db.schema.createIndex('memberships_user_idx').ifNotExists().on('memberships').column('userId'), // prettier-ignore
301
+ },
302
+ {
303
+ name: '0018_memberships_owner_backfill',
304
+ // Raw SQL (snake_case: the CamelCase plugin does not map raw statements).
305
+ up: (db) =>
306
+ raw(
307
+ db,
308
+ sql`INSERT INTO memberships (org_id, user_id, role, created_at, updated_at) SELECT id, user_id, 'owner', created_at, updated_at FROM organizations WHERE user_id IS NOT NULL ON CONFLICT DO NOTHING`,
309
+ ),
310
+ },
311
+ {
312
+ name: '0019_invitations',
313
+ up: (db) =>
314
+ db.schema
315
+ .createTable('invitations')
316
+ .ifNotExists()
317
+ .addColumn('id', 'text', (c) => c.primaryKey())
318
+ .addColumn('orgId', 'text', (c) => c.notNull())
319
+ .addColumn('email', 'text', (c) => c.notNull())
320
+ .addColumn('role', 'text', (c) => c.notNull())
321
+ .addColumn('invitedBy', 'text', (c) => c.notNull())
322
+ .addColumn('createdAt', 'text', (c) => c.notNull())
323
+ .addColumn('expiresAt', 'text', (c) => c.notNull())
324
+ .addColumn('acceptedAt', 'text')
325
+ .addColumn('revokedAt', 'text'),
326
+ },
327
+ {
328
+ name: '0020_invitations_org_index',
329
+ up: (db) =>
330
+ db.schema.createIndex('invitations_org_idx').ifNotExists().on('invitations').column('orgId'),
331
+ },
332
+ {
333
+ name: '0021_invitations_email_index',
334
+ up: (db) =>
335
+ db.schema.createIndex('invitations_email_idx').ifNotExists().on('invitations').column('email'), // prettier-ignore
336
+ },
337
+ {
338
+ name: '0022_users_address_nullable',
339
+ // Raw SQL; a no-op when the column is already nullable.
340
+ up: (db) => raw(db, sql`ALTER TABLE users ALTER COLUMN address DROP NOT NULL`),
341
+ },
342
+ {
343
+ name: '0023_invitations_pending_unique',
344
+ // Raw SQL: a partial unique index (snake_case; the CamelCase plugin does
345
+ // not map raw statements). At most one live (unaccepted, unrevoked)
346
+ // invitation per (org, email); a re-invite upserts the existing row.
347
+ up: (db) =>
348
+ raw(
349
+ db,
350
+ sql`CREATE UNIQUE INDEX IF NOT EXISTS invitations_pending_unique ON invitations (org_id, email) WHERE accepted_at IS NULL AND revoked_at IS NULL`,
351
+ ),
352
+ },
353
+ {
354
+ name: '0024_sponsored_transactions',
355
+ up: (db) =>
356
+ db.schema
357
+ .createTable('sponsoredTransactions')
358
+ .ifNotExists()
359
+ .addColumn('id', 'text', (c) => c.primaryKey())
360
+ .addColumn('orgId', 'text', (c) => c.notNull())
361
+ .addColumn('projectId', 'text', (c) => c.notNull())
362
+ .addColumn('apiKeyId', 'text', (c) => c.notNull())
363
+ .addColumn('environment', 'text', (c) => c.notNull())
364
+ .addColumn('chainId', 'integer', (c) => c.notNull())
365
+ .addColumn('transactionHash', 'text')
366
+ .addColumn('signPayload', 'text', (c) => c.notNull())
367
+ .addColumn('transaction', 'text', (c) => c.notNull())
368
+ .addColumn('status', 'text', (c) => c.notNull())
369
+ .addColumn('feeAmount', 'text')
370
+ .addColumn('feeToken', 'text')
371
+ .addColumn('billable', 'boolean', (c) => c.notNull())
372
+ .addColumn('createdAt', 'text', (c) => c.notNull())
373
+ .addColumn('finalizedAt', 'text')
374
+ .addUniqueConstraint('sponsored_transactions_sign_payload_key', ['signPayload']),
375
+ },
376
+ {
377
+ name: '0025_sponsored_transactions_status_index',
378
+ up: (db) =>
379
+ db.schema.createIndex('sponsored_transactions_status_idx').ifNotExists().on('sponsoredTransactions').columns(['status', 'createdAt']), // prettier-ignore
380
+ },
381
+ {
382
+ name: '0026_sponsored_transactions_org_index',
383
+ up: (db) =>
384
+ db.schema.createIndex('sponsored_transactions_org_idx').ifNotExists().on('sponsoredTransactions').columns(['orgId', 'createdAt']), // prettier-ignore
385
+ },
238
386
  ]
239
387
 
388
+ /**
389
+ * Adapts a raw statement to the migration contract (argument-less
390
+ * `compile`/`execute`) for DDL the schema builders cannot express.
391
+ */
392
+ function raw(db: Kysely<Database>, statement: RawBuilder<unknown>) {
393
+ return {
394
+ compile: () => statement.compile(db),
395
+ execute: () => statement.execute(db),
396
+ }
397
+ }
398
+
240
399
  /**
241
400
  * Builds a {@link Db} for any Kysely {@link Dialect}. All backends share
242
401
  * migrations, repositories, and the required {@link CamelCasePlugin} mapping.
package/src/db/Schema.ts CHANGED
@@ -8,6 +8,49 @@ 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
+ /** A pending, expiring offer of organization membership. */
12
+ export const Invitation = z.object({
13
+ acceptedAt: z
14
+ .nullable(z.string())
15
+ .check(z.describe('When the invitation was accepted (ISO 8601), or null while pending.')),
16
+ createdAt: z.string().check(z.describe('When the invitation was created (ISO 8601).')),
17
+ email: z
18
+ .string()
19
+ .check(z.describe('Invitee email (stored lowercase); matched against verified emails.')),
20
+ expiresAt: z.string().check(z.describe('When the invitation expires (ISO 8601).')),
21
+ id: z
22
+ .string()
23
+ .check(
24
+ z.describe('Opaque invitation id (`inv_…`).'),
25
+ z.meta({ examples: ['inv_1a2b3c4d5e6f7g8h9j0k1m2n'] }),
26
+ ),
27
+ invitedBy: z.string().check(z.describe('User id (`usr_…`) that created the invitation.')),
28
+ orgId: z.string().check(z.describe('Organization id (`org_…`) the invitation joins.')),
29
+ revokedAt: z
30
+ .nullable(z.string())
31
+ .check(z.describe('When the invitation was revoked (ISO 8601), or null.')),
32
+ role: z
33
+ .enum(['admin', 'member', 'owner'])
34
+ .check(z.describe('Role granted when the invitation is accepted.')),
35
+ })
36
+
37
+ /** Columns of the `invitations` table. */
38
+ export type Invitation = z.output<typeof Invitation>
39
+
40
+ /** A user's role within an organization. */
41
+ export const Membership = z.object({
42
+ createdAt: z.string().check(z.describe('When the membership was created (ISO 8601).')),
43
+ orgId: z.string().check(z.describe('Organization id (`org_…`).')),
44
+ role: z
45
+ .enum(['admin', 'member', 'owner'])
46
+ .check(z.describe("The member's role within the organization.")),
47
+ updatedAt: z.string().check(z.describe('When the membership was last updated (ISO 8601).')),
48
+ userId: z.string().check(z.describe('Member user id (`usr_…`).')),
49
+ })
50
+
51
+ /** Columns of the `memberships` table. */
52
+ export type Membership = z.output<typeof Membership>
53
+
11
54
  /** An organization, the top-level tenant that owns API keys. */
12
55
  export const Organization = z.object({
13
56
  createdAt: z.string().check(z.describe('When the organization was created (ISO 8601).')),
@@ -24,11 +67,105 @@ export const Organization = z.object({
24
67
  .string()
25
68
  .check(z.describe('Human-readable organization name.'), z.meta({ examples: ['Acme, Inc.'] })),
26
69
  updatedAt: z.string().check(z.describe('When the organization was last updated (ISO 8601).')),
70
+ userId: z
71
+ .nullable(z.string())
72
+ .check(
73
+ z.describe('Owning user id (`usr_…`), or null for organizations created by the super admin.'),
74
+ ),
27
75
  })
28
76
 
29
77
  /** Columns of the `organizations` table. */
30
78
  export type Organization = z.output<typeof Organization>
31
79
 
80
+ /** A project: an app or integration within an organization. */
81
+ export const Project = z.object({
82
+ createdAt: z.string().check(z.describe('When the project was created (ISO 8601).')),
83
+ id: z
84
+ .string()
85
+ .check(
86
+ z.describe('Opaque project id (`prj_…`).'),
87
+ z.meta({ examples: ['prj_1a2b3c4d5e6f7g8h9j0k1m2n'] }),
88
+ ),
89
+ name: z
90
+ .string()
91
+ .check(z.describe('Human-readable project name.'), z.meta({ examples: ['Checkout'] })),
92
+ orgId: z.string().check(z.describe('Owning organization id (`org_…`).')),
93
+ updatedAt: z.string().check(z.describe('When the project was last updated (ISO 8601).')),
94
+ })
95
+
96
+ /** Columns of the `projects` table. */
97
+ export type Project = z.output<typeof Project>
98
+
99
+ /** A transaction sponsored by the managed fee payer — the billable unit of fee payer spend. */
100
+ export const SponsoredTransaction = z.object({
101
+ apiKeyId: z.string().check(z.describe('API key id (`key_…`) that requested sponsorship.')),
102
+ billable: z
103
+ .boolean()
104
+ .check(z.describe('Whether the sponsorship accrues billable spend; false for sandbox.')),
105
+ chainId: z.number().check(z.describe('Chain the sponsored transaction targets.')),
106
+ createdAt: z.string().check(z.describe('When the sponsorship was recorded (ISO 8601).')),
107
+ environment: z
108
+ .enum(['production', 'sandbox'])
109
+ .check(z.describe('Key environment the sponsorship was requested under.')),
110
+ feeAmount: z
111
+ .nullable(z.string())
112
+ .check(z.describe('Actual fee paid in fee-token base units, or null until finalized.')),
113
+ feeToken: z
114
+ .nullable(z.string())
115
+ .check(z.describe('Fee token address, or null when the chain default applied.')),
116
+ finalizedAt: z
117
+ .nullable(z.string())
118
+ .check(z.describe('When the sponsorship reached a terminal status (ISO 8601), or null.')),
119
+ id: z
120
+ .string()
121
+ .check(
122
+ z.describe('Opaque sponsored-transaction id (`stx_…`).'),
123
+ z.meta({ examples: ['stx_1a2b3c4d5e6f7g8h9j0k1m2n'] }),
124
+ ),
125
+ orgId: z.string().check(z.describe('Organization id (`org_…`) the spend attributes to.')),
126
+ projectId: z.string().check(z.describe('Project id (`prj_…`) the spend attributes to.')),
127
+ signPayload: z
128
+ .string()
129
+ .check(z.describe('Fee-payer sign payload — a stable identity for the sponsored envelope.')),
130
+ status: z
131
+ .enum(['failed', 'finalized', 'pending'])
132
+ .check(z.describe('Pending until a receipt finalizes it or the pending TTL fails it.')),
133
+ transaction: z
134
+ .string()
135
+ .check(z.describe('Serialized sponsored transaction, kept for durable observability.')),
136
+ transactionHash: z
137
+ .nullable(z.string())
138
+ .check(z.describe('Transaction hash, or null for fill intents until reconciled.')),
139
+ })
140
+
141
+ /** Columns of the `sponsored_transactions` table. */
142
+ export type SponsoredTransaction = z.output<typeof SponsoredTransaction>
143
+
144
+ /** A developer identity. */
145
+ export const User = z.object({
146
+ address: z
147
+ .nullable(z.string())
148
+ .check(
149
+ z.describe(
150
+ 'Wallet address that signs in as this user (stored lowercase), or null for non-wallet identities.',
151
+ ),
152
+ ),
153
+ createdAt: z.string().check(z.describe('When the user was created (ISO 8601).')),
154
+ email: z
155
+ .nullable(z.string())
156
+ .check(z.describe('Verified email from the wallet identity token, or null.')),
157
+ id: z
158
+ .string()
159
+ .check(
160
+ z.describe('Opaque user id (`usr_…`).'),
161
+ z.meta({ examples: ['usr_1a2b3c4d5e6f7g8h9j0k1m2n'] }),
162
+ ),
163
+ updatedAt: z.string().check(z.describe('When the user was last updated (ISO 8601).')),
164
+ })
165
+
166
+ /** Columns of the `users` table. */
167
+ export type User = z.output<typeof User>
168
+
32
169
  /**
33
170
  * A verified token: one row per curated token, ordered by `position` (list
34
171
  * order is canonical and surfaced to clients). Addresses are stored lowercase;
@@ -0,0 +1,198 @@
1
+ import type { Selectable } from 'kysely'
2
+ import { nanoid } from 'nanoid'
3
+
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: `inv_${nanoid()}`,
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
+ }