tapimo 0.11.0 → 0.12.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 (339) hide show
  1. package/dist/App.d.ts +2296 -1010
  2. package/dist/App.d.ts.map +1 -1
  3. package/dist/App.js +2 -2
  4. package/dist/App.js.map +1 -1
  5. package/dist/Assets.d.ts +15 -0
  6. package/dist/Assets.d.ts.map +1 -1
  7. package/dist/Assets.js +7 -0
  8. package/dist/Assets.js.map +1 -1
  9. package/dist/admin/App.d.ts +53 -4
  10. package/dist/admin/App.d.ts.map +1 -1
  11. package/dist/admin/apps/api-keys.d.ts +4 -4
  12. package/dist/admin/apps/earn-vaults.d.ts +114 -0
  13. package/dist/admin/apps/earn-vaults.d.ts.map +1 -1
  14. package/dist/admin/apps/earn-vaults.js +59 -6
  15. package/dist/admin/apps/earn-vaults.js.map +1 -1
  16. package/dist/admin/apps/organizations.d.ts.map +1 -1
  17. package/dist/admin/apps/organizations.js +4 -2
  18. package/dist/admin/apps/organizations.js.map +1 -1
  19. package/dist/admin/apps/verified-tokens.d.ts +9 -9
  20. package/dist/admin/ui.generated.d.ts +1 -1
  21. package/dist/admin/ui.generated.d.ts.map +1 -1
  22. package/dist/admin/ui.generated.js +151 -151
  23. package/dist/admin/ui.generated.js.map +1 -1
  24. package/dist/apps/data/App.d.ts +1699 -749
  25. package/dist/apps/data/App.d.ts.map +1 -1
  26. package/dist/apps/data/App.js +56 -7
  27. package/dist/apps/data/App.js.map +1 -1
  28. package/dist/apps/data/ZoneSelection.d.ts +62 -0
  29. package/dist/apps/data/ZoneSelection.d.ts.map +1 -0
  30. package/dist/apps/data/ZoneSelection.js +44 -0
  31. package/dist/apps/data/ZoneSelection.js.map +1 -0
  32. package/dist/apps/data/routes/activities.d.ts +2367 -4
  33. package/dist/apps/data/routes/activities.d.ts.map +1 -1
  34. package/dist/apps/data/routes/activities.js +368 -87
  35. package/dist/apps/data/routes/activities.js.map +1 -1
  36. package/dist/apps/data/routes/addresses.d.ts +471 -340
  37. package/dist/apps/data/routes/addresses.d.ts.map +1 -1
  38. package/dist/apps/data/routes/balances.d.ts +28 -28
  39. package/dist/apps/data/routes/balances.js +1 -1
  40. package/dist/apps/data/routes/balances.js.map +1 -1
  41. package/dist/apps/data/routes/blocks.d.ts +4 -4
  42. package/dist/apps/data/routes/blocks.js +1 -1
  43. package/dist/apps/data/routes/blocks.js.map +1 -1
  44. package/dist/apps/data/routes/coingecko.d.ts +176 -176
  45. package/dist/apps/data/routes/earn.d.ts +131 -6
  46. package/dist/apps/data/routes/earn.d.ts.map +1 -1
  47. package/dist/apps/data/routes/earn.js +157 -25
  48. package/dist/apps/data/routes/earn.js.map +1 -1
  49. package/dist/apps/data/routes/exchanges.d.ts +825 -119
  50. package/dist/apps/data/routes/exchanges.d.ts.map +1 -1
  51. package/dist/apps/data/routes/exchanges.js +16 -13
  52. package/dist/apps/data/routes/exchanges.js.map +1 -1
  53. package/dist/apps/data/routes/fee-amm.d.ts.map +1 -1
  54. package/dist/apps/data/routes/fee-amm.js +9 -11
  55. package/dist/apps/data/routes/fee-amm.js.map +1 -1
  56. package/dist/apps/data/routes/indexer.d.ts +34 -34
  57. package/dist/apps/data/routes/receipts.d.ts +230 -230
  58. package/dist/apps/data/routes/receipts.js +1 -1
  59. package/dist/apps/data/routes/receipts.js.map +1 -1
  60. package/dist/apps/data/routes/rpc.js +24 -10
  61. package/dist/apps/data/routes/rpc.js.map +1 -1
  62. package/dist/apps/data/routes/tokenlist.d.ts.map +1 -1
  63. package/dist/apps/data/routes/tokenlist.js +16 -15
  64. package/dist/apps/data/routes/tokenlist.js.map +1 -1
  65. package/dist/apps/data/routes/tokens.d.ts +14 -0
  66. package/dist/apps/data/routes/tokens.d.ts.map +1 -1
  67. package/dist/apps/data/routes/tokens.js +34 -15
  68. package/dist/apps/data/routes/tokens.js.map +1 -1
  69. package/dist/apps/data/routes/transactions.js +1 -1
  70. package/dist/apps/data/routes/transactions.js.map +1 -1
  71. package/dist/apps/data/routes/transfers.d.ts.map +1 -1
  72. package/dist/apps/data/routes/transfers.js +7 -9
  73. package/dist/apps/data/routes/transfers.js.map +1 -1
  74. package/dist/apps/data/routes/webhooks.d.ts +4 -4
  75. package/dist/apps/data/routes/webhooks.d.ts.map +1 -1
  76. package/dist/apps/data/routes/webhooks.js +2 -2
  77. package/dist/apps/data/routes/webhooks.js.map +1 -1
  78. package/dist/apps/data/routes/zones.d.ts +30 -30
  79. package/dist/apps/funding/App.d.ts +27 -27
  80. package/dist/apps/funding/routes/chains.d.ts.map +1 -1
  81. package/dist/apps/funding/routes/chains.js +5 -4
  82. package/dist/apps/funding/routes/chains.js.map +1 -1
  83. package/dist/apps/funding/routes/deposit-addresses.d.ts +16 -16
  84. package/dist/apps/funding/routes/deposit-addresses.d.ts.map +1 -1
  85. package/dist/apps/funding/routes/deposit-addresses.js +8 -8
  86. package/dist/apps/funding/routes/deposit-addresses.js.map +1 -1
  87. package/dist/apps/funding/routes/providers.d.ts +13 -13
  88. package/dist/apps/funding/routes/providers.d.ts.map +1 -1
  89. package/dist/apps/funding/routes/providers.js +3 -2
  90. package/dist/apps/funding/routes/providers.js.map +1 -1
  91. package/dist/apps/funding/routes/quotes.d.ts.map +1 -1
  92. package/dist/apps/funding/routes/quotes.js +2 -2
  93. package/dist/apps/funding/routes/quotes.js.map +1 -1
  94. package/dist/apps/management/App.d.ts +627 -293
  95. package/dist/apps/management/App.d.ts.map +1 -1
  96. package/dist/apps/management/Billing.d.ts.map +1 -1
  97. package/dist/apps/management/Billing.js +19 -6
  98. package/dist/apps/management/Billing.js.map +1 -1
  99. package/dist/apps/management/routes/api-keys.d.ts +114 -54
  100. package/dist/apps/management/routes/api-keys.d.ts.map +1 -1
  101. package/dist/apps/management/routes/api-keys.js +76 -30
  102. package/dist/apps/management/routes/api-keys.js.map +1 -1
  103. package/dist/apps/management/routes/billing.d.ts +103 -30
  104. package/dist/apps/management/routes/billing.d.ts.map +1 -1
  105. package/dist/apps/management/routes/billing.js +91 -32
  106. package/dist/apps/management/routes/billing.js.map +1 -1
  107. package/dist/apps/management/routes/faucet.d.ts +33 -33
  108. package/dist/apps/management/routes/faucet.d.ts.map +1 -1
  109. package/dist/apps/management/routes/faucet.js +23 -12
  110. package/dist/apps/management/routes/faucet.js.map +1 -1
  111. package/dist/apps/management/routes/invitations.d.ts +46 -13
  112. package/dist/apps/management/routes/invitations.d.ts.map +1 -1
  113. package/dist/apps/management/routes/invitations.js +79 -29
  114. package/dist/apps/management/routes/invitations.js.map +1 -1
  115. package/dist/apps/management/routes/invite-links.d.ts +86 -41
  116. package/dist/apps/management/routes/invite-links.d.ts.map +1 -1
  117. package/dist/apps/management/routes/invite-links.js +100 -41
  118. package/dist/apps/management/routes/invite-links.js.map +1 -1
  119. package/dist/apps/management/routes/me.d.ts +5 -5
  120. package/dist/apps/management/routes/me.d.ts.map +1 -1
  121. package/dist/apps/management/routes/me.js +14 -10
  122. package/dist/apps/management/routes/me.js.map +1 -1
  123. package/dist/apps/management/routes/members.d.ts +93 -72
  124. package/dist/apps/management/routes/members.d.ts.map +1 -1
  125. package/dist/apps/management/routes/members.js +37 -18
  126. package/dist/apps/management/routes/members.js.map +1 -1
  127. package/dist/apps/management/routes/orgs.d.ts +48 -8
  128. package/dist/apps/management/routes/orgs.d.ts.map +1 -1
  129. package/dist/apps/management/routes/orgs.js +48 -19
  130. package/dist/apps/management/routes/orgs.js.map +1 -1
  131. package/dist/apps/management/routes/projects.d.ts +90 -28
  132. package/dist/apps/management/routes/projects.d.ts.map +1 -1
  133. package/dist/apps/management/routes/projects.js +72 -21
  134. package/dist/apps/management/routes/projects.js.map +1 -1
  135. package/dist/apps/management/routes/scopes.d.ts.map +1 -1
  136. package/dist/apps/management/routes/scopes.js +8 -5
  137. package/dist/apps/management/routes/scopes.js.map +1 -1
  138. package/dist/apps/management/routes/usage.d.ts +78 -78
  139. package/dist/apps/management/routes/usage.d.ts.map +1 -1
  140. package/dist/apps/management/routes/usage.js +41 -31
  141. package/dist/apps/management/routes/usage.js.map +1 -1
  142. package/dist/apps/management/routes/verified-token-requests.d.ts +34 -6
  143. package/dist/apps/management/routes/verified-token-requests.d.ts.map +1 -1
  144. package/dist/apps/management/routes/verified-token-requests.js +36 -17
  145. package/dist/apps/management/routes/verified-token-requests.js.map +1 -1
  146. package/dist/apps/management/routes/webhooks.d.ts +147 -130
  147. package/dist/apps/management/routes/webhooks.d.ts.map +1 -1
  148. package/dist/apps/management/routes/webhooks.js +80 -35
  149. package/dist/apps/management/routes/webhooks.js.map +1 -1
  150. package/dist/apps/mpp/internal/Adapter.d.ts +1 -1
  151. package/dist/apps/mpp/internal/Adapter.d.ts.map +1 -1
  152. package/dist/apps/mpp/internal/Adapter.js +12 -0
  153. package/dist/apps/mpp/internal/Adapter.js.map +1 -1
  154. package/dist/db/Db.d.ts +2 -0
  155. package/dist/db/Db.d.ts.map +1 -1
  156. package/dist/db/Db.js +40 -0
  157. package/dist/db/Db.js.map +1 -1
  158. package/dist/db/Schema.d.ts +23 -1
  159. package/dist/db/Schema.d.ts.map +1 -1
  160. package/dist/db/Schema.js +19 -3
  161. package/dist/db/Schema.js.map +1 -1
  162. package/dist/db/tables/earnVaults.d.ts +5 -1
  163. package/dist/db/tables/earnVaults.d.ts.map +1 -1
  164. package/dist/db/tables/earnVaults.js +5 -0
  165. package/dist/db/tables/earnVaults.js.map +1 -1
  166. package/dist/db/tables/webhookDeliveries.d.ts.map +1 -1
  167. package/dist/db/tables/webhookDeliveries.js +10 -0
  168. package/dist/db/tables/webhookDeliveries.js.map +1 -1
  169. package/dist/db/tables/webhookQueueCompletions.d.ts +15 -0
  170. package/dist/db/tables/webhookQueueCompletions.d.ts.map +1 -0
  171. package/dist/db/tables/webhookQueueCompletions.js +32 -0
  172. package/dist/db/tables/webhookQueueCompletions.js.map +1 -0
  173. package/dist/db/tables/webhookQueueEvents.d.ts +14 -20
  174. package/dist/db/tables/webhookQueueEvents.d.ts.map +1 -1
  175. package/dist/db/tables/webhookQueueEvents.js +68 -47
  176. package/dist/db/tables/webhookQueueEvents.js.map +1 -1
  177. package/dist/db/tables/webhookSubscriptions.d.ts +16 -5
  178. package/dist/db/tables/webhookSubscriptions.d.ts.map +1 -1
  179. package/dist/db/tables/webhookSubscriptions.js +29 -5
  180. package/dist/db/tables/webhookSubscriptions.js.map +1 -1
  181. package/dist/internal/Auth.d.ts +8 -1
  182. package/dist/internal/Auth.d.ts.map +1 -1
  183. package/dist/internal/Auth.js +13 -3
  184. package/dist/internal/Auth.js.map +1 -1
  185. package/dist/internal/Cache.d.ts.map +1 -1
  186. package/dist/internal/Cache.js +11 -7
  187. package/dist/internal/Cache.js.map +1 -1
  188. package/dist/internal/EarnVaults.d.ts +41 -2
  189. package/dist/internal/EarnVaults.d.ts.map +1 -1
  190. package/dist/internal/EarnVaults.js +167 -0
  191. package/dist/internal/EarnVaults.js.map +1 -1
  192. package/dist/internal/OpenApi.d.ts +26 -22
  193. package/dist/internal/OpenApi.d.ts.map +1 -1
  194. package/dist/internal/OpenApi.js +128 -39
  195. package/dist/internal/OpenApi.js.map +1 -1
  196. package/dist/internal/Response.d.ts +13 -8
  197. package/dist/internal/Response.d.ts.map +1 -1
  198. package/dist/internal/Response.js +16 -5
  199. package/dist/internal/Response.js.map +1 -1
  200. package/dist/internal/Schema.d.ts +5 -7
  201. package/dist/internal/Schema.d.ts.map +1 -1
  202. package/dist/internal/Schema.js +6 -7
  203. package/dist/internal/Schema.js.map +1 -1
  204. package/dist/internal/Store.d.ts +15 -6
  205. package/dist/internal/Store.d.ts.map +1 -1
  206. package/dist/internal/Store.js +45 -15
  207. package/dist/internal/Store.js.map +1 -1
  208. package/dist/internal/Tidx.d.ts +2 -0
  209. package/dist/internal/Tidx.d.ts.map +1 -1
  210. package/dist/internal/Tidx.js +16 -4
  211. package/dist/internal/Tidx.js.map +1 -1
  212. package/dist/internal/Webhooks.d.ts +29 -12
  213. package/dist/internal/Webhooks.d.ts.map +1 -1
  214. package/dist/internal/Webhooks.js +37 -20
  215. package/dist/internal/Webhooks.js.map +1 -1
  216. package/dist/internal/funding/Deposit.d.ts +2 -2
  217. package/dist/internal/funding/Deposit.d.ts.map +1 -1
  218. package/dist/internal/funding/Deposit.js +9 -8
  219. package/dist/internal/funding/Deposit.js.map +1 -1
  220. package/dist/internal/funding/DepositAddress.d.ts +4 -4
  221. package/dist/internal/funding/DepositAddress.d.ts.map +1 -1
  222. package/dist/internal/funding/DepositAddress.js +9 -8
  223. package/dist/internal/funding/DepositAddress.js.map +1 -1
  224. package/dist/internal/funding/Provider.d.ts.map +1 -1
  225. package/dist/internal/funding/Provider.js +13 -12
  226. package/dist/internal/funding/Provider.js.map +1 -1
  227. package/dist/internal/funding/providers/stargate.d.ts +19 -0
  228. package/dist/internal/funding/providers/stargate.d.ts.map +1 -0
  229. package/dist/internal/funding/providers/stargate.js +143 -0
  230. package/dist/internal/funding/providers/stargate.js.map +1 -0
  231. package/dist/providers/index.d.ts +7 -0
  232. package/dist/providers/index.d.ts.map +1 -1
  233. package/dist/providers/index.js +3 -0
  234. package/dist/providers/index.js.map +1 -1
  235. package/package.json +2 -2
  236. package/src/App.test.ts +48 -0
  237. package/src/App.ts +4 -2
  238. package/src/Assets.test.ts +18 -0
  239. package/src/Assets.ts +22 -0
  240. package/src/Client.test-d.ts +265 -16
  241. package/src/admin/apps/earn-vaults.test.ts +143 -0
  242. package/src/admin/apps/earn-vaults.ts +83 -6
  243. package/src/admin/apps/organizations.ts +6 -2
  244. package/src/admin/ui/src/routes/api-keys.tsx +36 -0
  245. package/src/admin/ui.generated.ts +151 -189
  246. package/src/apps/data/App.test.ts +31 -0
  247. package/src/apps/data/App.ts +56 -8
  248. package/src/apps/data/ZoneSelection.test.ts +196 -0
  249. package/src/apps/data/ZoneSelection.ts +108 -0
  250. package/src/apps/data/routes/activities.test.ts +328 -1
  251. package/src/apps/data/routes/activities.ts +498 -93
  252. package/src/apps/data/routes/balances.ts +1 -1
  253. package/src/apps/data/routes/blocks.ts +1 -1
  254. package/src/apps/data/routes/earn.test.ts +183 -23
  255. package/src/apps/data/routes/earn.ts +225 -29
  256. package/src/apps/data/routes/earn.zone-access.test.ts +46 -0
  257. package/src/apps/data/routes/exchanges.test.ts +31 -1
  258. package/src/apps/data/routes/exchanges.ts +19 -15
  259. package/src/apps/data/routes/fee-amm.ts +18 -11
  260. package/src/apps/data/routes/receipts.ts +1 -1
  261. package/src/apps/data/routes/rpc.test.ts +75 -0
  262. package/src/apps/data/routes/rpc.ts +23 -9
  263. package/src/apps/data/routes/tokenlist.ts +21 -18
  264. package/src/apps/data/routes/tokens.test.ts +29 -0
  265. package/src/apps/data/routes/tokens.ts +38 -15
  266. package/src/apps/data/routes/transactions.ts +1 -1
  267. package/src/apps/data/routes/transfers.test.ts +3 -3
  268. package/src/apps/data/routes/transfers.ts +12 -9
  269. package/src/apps/data/routes/webhooks.ts +6 -2
  270. package/src/apps/data/routes/zones.test.ts +19 -13
  271. package/src/apps/funding/routes/chains.test.ts +211 -7
  272. package/src/apps/funding/routes/chains.ts +19 -12
  273. package/src/apps/funding/routes/deposit-addresses.test.ts +394 -0
  274. package/src/apps/funding/routes/deposit-addresses.ts +84 -72
  275. package/src/apps/funding/routes/providers.ts +9 -5
  276. package/src/apps/funding/routes/quotes.test.ts +46 -0
  277. package/src/apps/funding/routes/quotes.ts +8 -5
  278. package/src/apps/funding/routes/transfers.test.ts +35 -32
  279. package/src/apps/management/Billing.test.ts +26 -0
  280. package/src/apps/management/Billing.ts +24 -7
  281. package/src/apps/management/routes/api-keys.test.ts +178 -0
  282. package/src/apps/management/routes/api-keys.ts +203 -144
  283. package/src/apps/management/routes/billing.test.ts +209 -0
  284. package/src/apps/management/routes/billing.ts +254 -177
  285. package/src/apps/management/routes/faucet.test.ts +39 -0
  286. package/src/apps/management/routes/faucet.ts +75 -41
  287. package/src/apps/management/routes/invitations.test.ts +216 -0
  288. package/src/apps/management/routes/invitations.ts +186 -109
  289. package/src/apps/management/routes/invite-links.test.ts +263 -1
  290. package/src/apps/management/routes/invite-links.ts +295 -214
  291. package/src/apps/management/routes/me.test.ts +49 -0
  292. package/src/apps/management/routes/me.ts +42 -32
  293. package/src/apps/management/routes/members.test.ts +124 -1
  294. package/src/apps/management/routes/members.ts +81 -51
  295. package/src/apps/management/routes/orgs.test.ts +176 -0
  296. package/src/apps/management/routes/orgs.ts +101 -59
  297. package/src/apps/management/routes/projects.test.ts +183 -0
  298. package/src/apps/management/routes/projects.ts +124 -60
  299. package/src/apps/management/routes/scopes.test.ts +44 -0
  300. package/src/apps/management/routes/scopes.ts +37 -28
  301. package/src/apps/management/routes/usage.test.ts +92 -0
  302. package/src/apps/management/routes/usage.ts +186 -151
  303. package/src/apps/management/routes/verified-token-requests.test.ts +114 -0
  304. package/src/apps/management/routes/verified-token-requests.ts +166 -133
  305. package/src/apps/management/routes/webhooks.test.ts +234 -0
  306. package/src/apps/management/routes/webhooks.ts +191 -78
  307. package/src/apps/mpp/internal/Adapter.ts +13 -1
  308. package/src/db/Db.test.ts +41 -0
  309. package/src/db/Db.ts +62 -0
  310. package/src/db/Schema.ts +28 -3
  311. package/src/db/tables/earnVaults.test.ts +22 -0
  312. package/src/db/tables/earnVaults.ts +11 -1
  313. package/src/db/tables/webhookDeliveries.ts +13 -0
  314. package/src/db/tables/webhookQueueCompletions.ts +55 -0
  315. package/src/db/tables/webhookQueueEvents.ts +95 -61
  316. package/src/db/tables/webhookSubscriptions.ts +45 -6
  317. package/src/internal/Auth.ts +23 -5
  318. package/src/internal/Cache.ts +14 -7
  319. package/src/internal/EarnVaults.capabilities.test.ts +33 -0
  320. package/src/internal/EarnVaults.test.ts +79 -0
  321. package/src/internal/EarnVaults.ts +277 -0
  322. package/src/internal/OpenApi.ts +165 -49
  323. package/src/internal/Response.test.ts +95 -0
  324. package/src/internal/Response.ts +40 -2
  325. package/src/internal/Schema.test.ts +16 -0
  326. package/src/internal/Schema.ts +7 -7
  327. package/src/internal/Store.test.ts +186 -0
  328. package/src/internal/Store.ts +57 -17
  329. package/src/internal/Tidx.test.ts +19 -0
  330. package/src/internal/Tidx.ts +16 -4
  331. package/src/internal/Webhooks.test.ts +122 -8
  332. package/src/internal/Webhooks.ts +59 -29
  333. package/src/internal/funding/Deposit.ts +71 -64
  334. package/src/internal/funding/DepositAddress.ts +50 -43
  335. package/src/internal/funding/Provider.ts +164 -142
  336. package/src/internal/funding/providers/index.test.ts +185 -0
  337. package/src/internal/funding/providers/stargate.ts +184 -0
  338. package/src/providers/index.test.ts +1 -0
  339. package/src/providers/index.ts +9 -0
@@ -21,6 +21,55 @@ function createApp() {
21
21
  })
22
22
  }
23
23
 
24
+ test('publishes a generator-ready OpenAPI contract', async () => {
25
+ const spec = await (await createApp().request('/openapi.json')).json()
26
+ const operation = spec.paths['/v1/me'].get
27
+
28
+ expect(spec.components.schemas.User.properties.email.examples).toEqual(['dev@example.com'])
29
+
30
+ expect({
31
+ component: spec.components.schemas.User ? 'User' : undefined,
32
+ errors: {
33
+ 400: operation.responses[400].content['application/json'].schema,
34
+ 403: operation.responses[403].content['application/json'].schema,
35
+ 404: operation.responses[404].content['application/json'].schema,
36
+ },
37
+ formats: {
38
+ address: spec.components.schemas.User.properties.address.pattern,
39
+ createdAt: spec.components.schemas.User.properties.createdAt.format,
40
+ email: spec.components.schemas.User.properties.email.format,
41
+ updatedAt: spec.components.schemas.User.properties.updatedAt.format,
42
+ },
43
+ operationId: operation.operationId,
44
+ response: operation.responses[200].content['application/json'].schema,
45
+ }).toMatchInlineSnapshot(`
46
+ {
47
+ "component": "User",
48
+ "errors": {
49
+ "400": {
50
+ "$ref": "#/components/schemas/ApiKeyMalformedError",
51
+ },
52
+ "403": {
53
+ "$ref": "#/components/schemas/ForbiddenError",
54
+ },
55
+ "404": {
56
+ "$ref": "#/components/schemas/UserNotFoundError",
57
+ },
58
+ },
59
+ "formats": {
60
+ "address": "^0x[0-9a-fA-F]{40}$",
61
+ "createdAt": "date-time",
62
+ "email": "email",
63
+ "updatedAt": "date-time",
64
+ },
65
+ "operationId": "getMe",
66
+ "response": {
67
+ "$ref": "#/components/schemas/User",
68
+ },
69
+ }
70
+ `)
71
+ })
72
+
24
73
  describe('SIWE verification', () => {
25
74
  test('surfaces configured RPC failures as dependency errors', async () => {
26
75
  const account = Account.fromHeadlessWebAuthn(P256.randomPrivateKey(), {
@@ -12,37 +12,43 @@ import * as Users from '../../../db/tables/users.js'
12
12
  /** Zod schemas owned by the current-user resource. */
13
13
  export namespace schema {
14
14
  /** The signed-in developer. */
15
- export const User = Schema.describe(
16
- z.object({
17
- address: z
18
- .optional(z.string())
19
- .check(
20
- z.describe('Wallet address bound to this user, when wallet sign-in established it.'),
21
- z.meta({ examples: ['0x0000000000000000000000000000000000000001'] }),
22
- ),
23
- createdAt: z
24
- .string()
25
- .check(
26
- z.describe('When the user was created (ISO 8601).'),
27
- z.meta({ examples: ['2026-01-01T00:00:00.000Z'] }),
28
- ),
29
- email: z
30
- .optional(z.string())
31
- .check(z.describe('Verified email from the wallet identity token, when present.')),
32
- id: z
33
- .string()
34
- .check(
35
- z.describe('Opaque user id (`usr_…`).'),
36
- z.meta({ examples: ['usr_1a2b3c4d5e6f7g8h9j0k1m2n'] }),
37
- ),
38
- updatedAt: z
39
- .string()
40
- .check(
41
- z.describe('When the user was last updated (ISO 8601).'),
42
- z.meta({ examples: ['2026-01-01T00:00:00.000Z'] }),
43
- ),
44
- }),
45
- 'The signed-in developer.',
15
+ export const User = OpenApi.component(
16
+ Schema.describe(
17
+ z.object({
18
+ address: z
19
+ .optional(z.templateLiteral(['0x', z.string().check(z.regex(/^[0-9a-fA-F]{40}$/))]))
20
+ .check(
21
+ z.describe('Wallet address bound to this user, when wallet sign-in established it.'),
22
+ z.meta({ examples: ['0x0000000000000000000000000000000000000001'] }),
23
+ ),
24
+ createdAt: z.iso
25
+ .datetime()
26
+ .check(
27
+ z.describe('When the user was created (ISO 8601).'),
28
+ z.meta({ examples: ['2026-01-01T00:00:00.000Z'] }),
29
+ ),
30
+ email: z
31
+ .optional(z.email())
32
+ .check(
33
+ z.describe('Verified email from the wallet identity token, when present.'),
34
+ z.meta({ examples: ['dev@example.com'] }),
35
+ ),
36
+ id: z
37
+ .string()
38
+ .check(
39
+ z.describe('Opaque user id (`usr_…`).'),
40
+ z.meta({ examples: ['usr_1a2b3c4d5e6f7g8h9j0k1m2n'] }),
41
+ ),
42
+ updatedAt: z.iso
43
+ .datetime()
44
+ .check(
45
+ z.describe('When the user was last updated (ISO 8601).'),
46
+ z.meta({ examples: ['2026-01-01T00:00:00.000Z'] }),
47
+ ),
48
+ }),
49
+ 'The signed-in developer.',
50
+ ),
51
+ 'User',
46
52
  )
47
53
  }
48
54
 
@@ -58,8 +64,12 @@ export function me() {
58
64
  operationId: 'getMe',
59
65
  responses: OpenApi.responses({
60
66
  errors: {
67
+ 400: { codes: [], description: 'Malformed API key.' },
61
68
  403: 'The caller is not a signed-in user.',
62
- 404: 'No user exists for this session.',
69
+ 404: {
70
+ codes: ['user_not_found'],
71
+ description: 'No user exists for this session.',
72
+ },
63
73
  },
64
74
  success: { description: 'The authenticated user.', schema: schema.User },
65
75
  }),
@@ -44,6 +44,129 @@ async function team(
44
44
  return { member, org, owner }
45
45
  }
46
46
 
47
+ test('publishes generator-ready OpenAPI contracts', async () => {
48
+ const spec = await (await createApp().request('/openapi.json')).json()
49
+ const collection = spec.paths['/v1/orgs/{orgId}/members']
50
+ const detail = spec.paths['/v1/orgs/{orgId}/members/{userId}']
51
+
52
+ expect(spec.components.schemas.MemberList.properties.data.examples).toEqual([[]])
53
+
54
+ expect({
55
+ components: ['Member', 'MemberList', 'RemoveMemberResponse', 'UpdateMemberRequest'].filter(
56
+ (name) => spec.components.schemas[name],
57
+ ),
58
+ list: {
59
+ errors: {
60
+ 400: collection.get.responses[400].content['application/json'].schema,
61
+ 403: collection.get.responses[403].content['application/json'].schema,
62
+ 404: collection.get.responses[404].content['application/json'].schema,
63
+ },
64
+ operationId: collection.get.operationId,
65
+ response: collection.get.responses[200].content['application/json'].schema,
66
+ },
67
+ memberFormats: {
68
+ address: spec.components.schemas.Member.properties.address.pattern,
69
+ createdAt: spec.components.schemas.Member.properties.createdAt.format,
70
+ email: spec.components.schemas.Member.properties.email.format,
71
+ },
72
+ remove: {
73
+ errors: {
74
+ 400: detail.delete.responses[400].content['application/json'].schema,
75
+ 403: detail.delete.responses[403].content['application/json'].schema,
76
+ 404: detail.delete.responses[404].content['application/json'].schema,
77
+ 409: detail.delete.responses[409].content['application/json'].schema,
78
+ },
79
+ operationId: detail.delete.operationId,
80
+ response: detail.delete.responses[200].content['application/json'].schema,
81
+ },
82
+ update: {
83
+ errors: {
84
+ 400: detail.patch.responses[400].content['application/json'].schema,
85
+ 403: detail.patch.responses[403].content['application/json'].schema,
86
+ 404: detail.patch.responses[404].content['application/json'].schema,
87
+ 409: detail.patch.responses[409].content['application/json'].schema,
88
+ },
89
+ operationId: detail.patch.operationId,
90
+ request: detail.patch.requestBody.content['application/json'].schema,
91
+ response: detail.patch.responses[200].content['application/json'].schema,
92
+ },
93
+ }).toMatchInlineSnapshot(`
94
+ {
95
+ "components": [
96
+ "Member",
97
+ "MemberList",
98
+ "RemoveMemberResponse",
99
+ "UpdateMemberRequest",
100
+ ],
101
+ "list": {
102
+ "errors": {
103
+ "400": {
104
+ "$ref": "#/components/schemas/ApiKeyMalformedOrParamInvalidError",
105
+ },
106
+ "403": {
107
+ "$ref": "#/components/schemas/ForbiddenError",
108
+ },
109
+ "404": {
110
+ "$ref": "#/components/schemas/OrganizationNotFoundError",
111
+ },
112
+ },
113
+ "operationId": "listMembers",
114
+ "response": {
115
+ "$ref": "#/components/schemas/MemberList",
116
+ },
117
+ },
118
+ "memberFormats": {
119
+ "address": "^0x[0-9a-fA-F]{40}$",
120
+ "createdAt": "date-time",
121
+ "email": "email",
122
+ },
123
+ "remove": {
124
+ "errors": {
125
+ "400": {
126
+ "$ref": "#/components/schemas/ApiKeyMalformedOrParamInvalidError",
127
+ },
128
+ "403": {
129
+ "$ref": "#/components/schemas/ForbiddenError",
130
+ },
131
+ "404": {
132
+ "$ref": "#/components/schemas/MemberNotFoundOrOrganizationNotFoundError",
133
+ },
134
+ "409": {
135
+ "$ref": "#/components/schemas/LastOwnerError",
136
+ },
137
+ },
138
+ "operationId": "removeMember",
139
+ "response": {
140
+ "$ref": "#/components/schemas/RemoveMemberResponse",
141
+ },
142
+ },
143
+ "update": {
144
+ "errors": {
145
+ "400": {
146
+ "$ref": "#/components/schemas/ApiKeyMalformedOrBodyInvalidOrParamInvalidError",
147
+ },
148
+ "403": {
149
+ "$ref": "#/components/schemas/ForbiddenError",
150
+ },
151
+ "404": {
152
+ "$ref": "#/components/schemas/MemberNotFoundOrOrganizationNotFoundError",
153
+ },
154
+ "409": {
155
+ "$ref": "#/components/schemas/LastOwnerError",
156
+ },
157
+ },
158
+ "operationId": "updateMember",
159
+ "request": {
160
+ "$ref": "#/components/schemas/UpdateMemberRequest",
161
+ },
162
+ "response": {
163
+ "$ref": "#/components/schemas/Member",
164
+ },
165
+ },
166
+ }
167
+ `)
168
+ })
169
+
47
170
  describe('members', () => {
48
171
  test('lists the team with roles and identity', async () => {
49
172
  const db = TestApp.database()
@@ -140,7 +263,7 @@ describe('members', () => {
140
263
  app.request(`/v1/orgs/${org.id}/members/${owner.userId}`, json('PATCH', second.cookie, { role: 'member' })), // prettier-ignore
141
264
  app.request(`/v1/orgs/${org.id}/members/${second.userId}`, json('PATCH', owner.cookie, { role: 'member' })), // prettier-ignore
142
265
  ])
143
- expect([a.status, b.status].sort()).toEqual([200, 409])
266
+ expect([a.status, b.status].sort((left, right) => left - right)).toEqual([200, 409])
144
267
  const owners = (await Memberships.listByOrg(db, org.id)).filter(({ role }) => role === 'owner')
145
268
  expect(owners).toHaveLength(1)
146
269
  })
@@ -18,35 +18,38 @@ export namespace schema {
18
18
  .check(z.describe('Membership role.'), z.meta({ examples: ['admin'] }))
19
19
 
20
20
  /** One organization member: the membership plus the member's identity. */
21
- export const Member = Schema.describe(
22
- z.object({
23
- address: z
24
- .optional(z.string())
25
- .check(
26
- z.describe("The member's wallet address, when wallet sign-in established it."),
27
- z.meta({ examples: ['0x20f414b3cbcf1fd8e8f5e1c1c2e3d4a5b6c7d8e9'] }),
28
- ),
29
- createdAt: z
30
- .string()
31
- .check(
32
- z.describe('When the member joined (ISO 8601).'),
33
- z.meta({ examples: ['2026-01-01T00:00:00.000Z'] }),
34
- ),
35
- email: z
36
- .optional(z.string())
37
- .check(
38
- z.describe("The member's verified email, when known."),
39
- z.meta({ examples: ['dev@example.com'] }),
40
- ),
41
- role: Role,
42
- userId: z
43
- .string()
44
- .check(
45
- z.describe('Member user id (`usr_…`).'),
46
- z.meta({ examples: ['usr_1a2b3c4d5e6f7g8h9j0k1m2n'] }),
47
- ),
48
- }),
49
- "One organization member: the membership plus the member's identity.",
21
+ export const Member = OpenApi.component(
22
+ Schema.describe(
23
+ z.object({
24
+ address: z
25
+ .optional(z.templateLiteral(['0x', z.string().check(z.regex(/^[0-9a-fA-F]{40}$/))]))
26
+ .check(
27
+ z.describe("The member's wallet address, when wallet sign-in established it."),
28
+ z.meta({ examples: ['0x20f414b3cbcf1fd8e8f5e1c1c2e3d4a5b6c7d8e9'] }),
29
+ ),
30
+ createdAt: z.iso
31
+ .datetime()
32
+ .check(
33
+ z.describe('When the member joined (ISO 8601).'),
34
+ z.meta({ examples: ['2026-01-01T00:00:00.000Z'] }),
35
+ ),
36
+ email: z
37
+ .optional(z.email())
38
+ .check(
39
+ z.describe("The member's verified email, when known."),
40
+ z.meta({ examples: ['dev@example.com'] }),
41
+ ),
42
+ role: Role,
43
+ userId: z
44
+ .string()
45
+ .check(
46
+ z.describe('Member user id (`usr_…`).'),
47
+ z.meta({ examples: ['usr_1a2b3c4d5e6f7g8h9j0k1m2n'] }),
48
+ ),
49
+ }),
50
+ "One organization member: the membership plus the member's identity.",
51
+ ),
52
+ 'Member',
50
53
  )
51
54
 
52
55
  /** Path parameters addressing an organization's member collection. */
@@ -80,34 +83,43 @@ export namespace schema {
80
83
  .check(z.describe('Path parameters for one member.'))
81
84
 
82
85
  /** Request body changing a member's role. */
83
- export const MemberBody = z
84
- .object({ role: Role })
85
- .check(z.describe("Fields for changing a member's role."))
86
+ export const UpdateMemberRequest = OpenApi.component(
87
+ z.object({ role: Role }).check(z.describe("Fields for changing a member's role.")),
88
+ 'UpdateMemberRequest',
89
+ )
86
90
 
87
91
  /** Schemas for the listMembers operation. */
88
92
  export namespace listMembers {
89
93
  /** Non-paginated list of an organization's members. */
90
- export const Response = Schema.describe(
91
- z.object({
92
- data: z.array(Member).check(z.describe('The members, oldest first.')),
93
- }),
94
- "A non-paginated list of an organization's members.",
94
+ export const Response = OpenApi.component(
95
+ Schema.describe(
96
+ z.object({
97
+ data: z
98
+ .array(Member)
99
+ .check(z.describe('The members, oldest first.'), z.meta({ examples: [[]] })),
100
+ }),
101
+ "A non-paginated list of an organization's members.",
102
+ ),
103
+ 'MemberList',
95
104
  )
96
105
  }
97
106
 
98
107
  /** Schemas for the removeMember operation. */
99
108
  export namespace removeMember {
100
109
  /** Confirmation that the member was removed. */
101
- export const Response = z
102
- .object({
103
- userId: z
104
- .string()
105
- .check(
106
- z.describe('User id (`usr_…`) of the removed member.'),
107
- z.meta({ examples: ['usr_1a2b3c4d5e6f7g8h9j0k1m2n'] }),
108
- ),
109
- })
110
- .check(z.describe('Confirmation that the member was removed.'))
110
+ export const Response = OpenApi.component(
111
+ z
112
+ .object({
113
+ userId: z
114
+ .string()
115
+ .check(
116
+ z.describe('User id (`usr_…`) of the removed member.'),
117
+ z.meta({ examples: ['usr_1a2b3c4d5e6f7g8h9j0k1m2n'] }),
118
+ ),
119
+ })
120
+ .check(z.describe('Confirmation that the member was removed.')),
121
+ 'RemoveMemberResponse',
122
+ )
111
123
  }
112
124
  }
113
125
 
@@ -129,7 +141,13 @@ export function members() {
129
141
  OpenApi.describeRoute({
130
142
  operationId: 'listMembers',
131
143
  responses: OpenApi.responses({
132
- errors: { 404: 'No accessible organization was found for this id.' },
144
+ errors: {
145
+ 400: { codes: ['param_invalid'], description: 'Malformed API key or invalid path.' },
146
+ 404: {
147
+ codes: ['organization_not_found'],
148
+ description: 'No accessible organization was found for this id.',
149
+ },
150
+ },
133
151
  success: {
134
152
  description: 'The members, oldest first.',
135
153
  schema: schema.listMembers.Response,
@@ -169,7 +187,7 @@ export function members() {
169
187
  code: 'param_invalid',
170
188
  message: 'Check the path parameters and try again.',
171
189
  }),
172
- OpenApi.validate('json', schema.MemberBody, {
190
+ OpenApi.validate('json', schema.UpdateMemberRequest, {
173
191
  code: 'body_invalid',
174
192
  message: 'Check the request body and try again.',
175
193
  }),
@@ -178,7 +196,14 @@ export function members() {
178
196
  operationId: 'updateMember',
179
197
  responses: OpenApi.responses({
180
198
  errors: {
181
- 404: 'No accessible member was found for this id.',
199
+ 400: {
200
+ codes: ['body_invalid', 'param_invalid'],
201
+ description: 'Malformed API key, invalid path, or invalid body.',
202
+ },
203
+ 404: {
204
+ codes: ['member_not_found', 'organization_not_found'],
205
+ description: 'No accessible organization or member was found for this id.',
206
+ },
182
207
  409: {
183
208
  codes: ['last_owner'],
184
209
  description: 'The organization would be left without an owner.',
@@ -192,6 +217,7 @@ export function members() {
192
217
  async (c) => {
193
218
  if (Auth.narrowAccess) return Auth.accessError(c)
194
219
  if (Auth.narrowScope) return Auth.ensureOrgError(c)
220
+ if (Auth.narrowOrgRole) return Auth.ensureOrgRoleError(c)
195
221
  if (OpenApi.narrowValidation)
196
222
  return OpenApi.validationError(c, {
197
223
  code: 'param_invalid',
@@ -237,7 +263,11 @@ export function members() {
237
263
  operationId: 'removeMember',
238
264
  responses: OpenApi.responses({
239
265
  errors: {
240
- 404: 'No accessible member was found for this id.',
266
+ 400: { codes: ['param_invalid'], description: 'Malformed API key or invalid path.' },
267
+ 404: {
268
+ codes: ['member_not_found', 'organization_not_found'],
269
+ description: 'No accessible organization or member was found for this id.',
270
+ },
241
271
  409: {
242
272
  codes: ['last_owner'],
243
273
  description: 'The organization would be left without an owner.',
@@ -79,7 +79,183 @@ async function session(app: TestApp.signIn.App) {
79
79
  return cookie!
80
80
  }
81
81
 
82
+ test('publishes generator-ready OpenAPI contracts', async () => {
83
+ const spec = await (await createApp().request('/openapi.json')).json()
84
+ const collection = spec.paths['/v1/orgs']
85
+ const detail = spec.paths['/v1/orgs/{orgId}']
86
+
87
+ expect({
88
+ components: [
89
+ 'CreateOrganizationRequest',
90
+ 'DeleteOrganizationResponse',
91
+ 'Organization',
92
+ 'OrganizationList',
93
+ 'UpdateOrganizationRequest',
94
+ ].filter((name) => spec.components.schemas[name]),
95
+ create: {
96
+ errors: {
97
+ 400: collection.post.responses[400].content['application/json'].schema,
98
+ 403: collection.post.responses[403].content['application/json'].schema,
99
+ },
100
+ operationId: collection.post.operationId,
101
+ request: collection.post.requestBody.content['application/json'].schema,
102
+ response: collection.post.responses[200].content['application/json'].schema,
103
+ },
104
+ delete: {
105
+ errors: {
106
+ 400: detail.delete.responses[400].content['application/json'].schema,
107
+ 403: detail.delete.responses[403].content['application/json'].schema,
108
+ 404: detail.delete.responses[404].content['application/json'].schema,
109
+ 409: detail.delete.responses[409].content['application/json'].schema,
110
+ },
111
+ operationId: detail.delete.operationId,
112
+ response: detail.delete.responses[200].content['application/json'].schema,
113
+ },
114
+ get: {
115
+ errors: {
116
+ 400: detail.get.responses[400].content['application/json'].schema,
117
+ 403: detail.get.responses[403].content['application/json'].schema,
118
+ 404: detail.get.responses[404].content['application/json'].schema,
119
+ },
120
+ operationId: detail.get.operationId,
121
+ response: detail.get.responses[200].content['application/json'].schema,
122
+ },
123
+ list: {
124
+ errors: {
125
+ 400: collection.get.responses[400].content['application/json'].schema,
126
+ 403: collection.get.responses[403].content['application/json'].schema,
127
+ },
128
+ operationId: collection.get.operationId,
129
+ response: collection.get.responses[200].content['application/json'].schema,
130
+ },
131
+ timestamps: {
132
+ createdAt: spec.components.schemas.Organization.properties.createdAt.format,
133
+ updatedAt: spec.components.schemas.Organization.properties.updatedAt.format,
134
+ },
135
+ update: {
136
+ errors: {
137
+ 400: detail.patch.responses[400].content['application/json'].schema,
138
+ 403: detail.patch.responses[403].content['application/json'].schema,
139
+ 404: detail.patch.responses[404].content['application/json'].schema,
140
+ },
141
+ operationId: detail.patch.operationId,
142
+ request: detail.patch.requestBody.content['application/json'].schema,
143
+ response: detail.patch.responses[200].content['application/json'].schema,
144
+ },
145
+ }).toMatchInlineSnapshot(`
146
+ {
147
+ "components": [
148
+ "CreateOrganizationRequest",
149
+ "DeleteOrganizationResponse",
150
+ "Organization",
151
+ "OrganizationList",
152
+ "UpdateOrganizationRequest",
153
+ ],
154
+ "create": {
155
+ "errors": {
156
+ "400": {
157
+ "$ref": "#/components/schemas/ApiKeyMalformedOrBodyInvalidError",
158
+ },
159
+ "403": {
160
+ "$ref": "#/components/schemas/ForbiddenError",
161
+ },
162
+ },
163
+ "operationId": "createOrganization",
164
+ "request": {
165
+ "$ref": "#/components/schemas/CreateOrganizationRequest",
166
+ },
167
+ "response": {
168
+ "$ref": "#/components/schemas/Organization",
169
+ },
170
+ },
171
+ "delete": {
172
+ "errors": {
173
+ "400": {
174
+ "$ref": "#/components/schemas/ApiKeyMalformedOrParamInvalidError",
175
+ },
176
+ "403": {
177
+ "$ref": "#/components/schemas/ForbiddenError",
178
+ },
179
+ "404": {
180
+ "$ref": "#/components/schemas/OrganizationNotFoundError",
181
+ },
182
+ "409": {
183
+ "$ref": "#/components/schemas/SponsorshipsUnreportedError",
184
+ },
185
+ },
186
+ "operationId": "deleteOrganization",
187
+ "response": {
188
+ "$ref": "#/components/schemas/DeleteOrganizationResponse",
189
+ },
190
+ },
191
+ "get": {
192
+ "errors": {
193
+ "400": {
194
+ "$ref": "#/components/schemas/ApiKeyMalformedOrParamInvalidError",
195
+ },
196
+ "403": {
197
+ "$ref": "#/components/schemas/ForbiddenError",
198
+ },
199
+ "404": {
200
+ "$ref": "#/components/schemas/OrganizationNotFoundError",
201
+ },
202
+ },
203
+ "operationId": "getOrganization",
204
+ "response": {
205
+ "$ref": "#/components/schemas/Organization",
206
+ },
207
+ },
208
+ "list": {
209
+ "errors": {
210
+ "400": {
211
+ "$ref": "#/components/schemas/ApiKeyMalformedError",
212
+ },
213
+ "403": {
214
+ "$ref": "#/components/schemas/ForbiddenError",
215
+ },
216
+ },
217
+ "operationId": "listOrganizations",
218
+ "response": {
219
+ "$ref": "#/components/schemas/OrganizationList",
220
+ },
221
+ },
222
+ "timestamps": {
223
+ "createdAt": "date-time",
224
+ "updatedAt": "date-time",
225
+ },
226
+ "update": {
227
+ "errors": {
228
+ "400": {
229
+ "$ref": "#/components/schemas/ApiKeyMalformedOrBodyInvalidOrParamInvalidError",
230
+ },
231
+ "403": {
232
+ "$ref": "#/components/schemas/ForbiddenError",
233
+ },
234
+ "404": {
235
+ "$ref": "#/components/schemas/OrganizationNotFoundError",
236
+ },
237
+ },
238
+ "operationId": "updateOrganization",
239
+ "request": {
240
+ "$ref": "#/components/schemas/UpdateOrganizationRequest",
241
+ },
242
+ "response": {
243
+ "$ref": "#/components/schemas/Organization",
244
+ },
245
+ },
246
+ }
247
+ `)
248
+ })
249
+
82
250
  describe('GET /orgs', () => {
251
+ test('rejects malformed API key credentials', async () => {
252
+ const response = await createApp().request('/v1/orgs', {
253
+ headers: { authorization: 'Basic wrong' },
254
+ })
255
+ expect(response.status).toBe(400)
256
+ expect(await response.json()).toMatchObject({ error: { code: 'api_key_malformed' } })
257
+ })
258
+
83
259
  test('lists all organizations for the super admin', async () => {
84
260
  const response = await (await seeded()).request('/v1/orgs', as(secret))
85
261
  expect(response.status).toBe(200)