tapimo 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (313) hide show
  1. package/README.md +31 -3
  2. package/dist/ApiKey.d.ts +1 -0
  3. package/dist/ApiKey.d.ts.map +1 -1
  4. package/dist/ApiKey.js +5 -2
  5. package/dist/ApiKey.js.map +1 -1
  6. package/dist/ApiKeys.d.ts +28 -1
  7. package/dist/ApiKeys.d.ts.map +1 -1
  8. package/dist/ApiKeys.js +34 -1
  9. package/dist/ApiKeys.js.map +1 -1
  10. package/dist/App.d.ts +7167 -4507
  11. package/dist/App.d.ts.map +1 -1
  12. package/dist/App.js +8 -0
  13. package/dist/App.js.map +1 -1
  14. package/dist/Scope.d.ts +11 -2
  15. package/dist/Scope.d.ts.map +1 -1
  16. package/dist/Scope.js +14 -3
  17. package/dist/Scope.js.map +1 -1
  18. package/dist/admin/App.d.ts +128 -118
  19. package/dist/admin/App.d.ts.map +1 -1
  20. package/dist/admin/apps/api-keys.d.ts +7 -0
  21. package/dist/admin/apps/api-keys.d.ts.map +1 -1
  22. package/dist/admin/apps/scopes.d.ts +8 -0
  23. package/dist/admin/apps/scopes.d.ts.map +1 -1
  24. package/dist/admin/apps/scopes.js +3 -0
  25. package/dist/admin/apps/scopes.js.map +1 -1
  26. package/dist/admin/apps/verified-tokens.d.ts +57 -57
  27. package/dist/admin/ui.generated.d.ts +1 -1
  28. package/dist/admin/ui.generated.js +13 -13
  29. package/dist/admin/ui.generated.js.map +1 -1
  30. package/dist/analytics/Analytics.d.ts +151 -0
  31. package/dist/analytics/Analytics.d.ts.map +1 -0
  32. package/dist/analytics/Analytics.js +212 -0
  33. package/dist/analytics/Analytics.js.map +1 -0
  34. package/dist/analytics/tables/requestEvents.d.ts +215 -0
  35. package/dist/analytics/tables/requestEvents.d.ts.map +1 -0
  36. package/dist/analytics/tables/requestEvents.js +264 -0
  37. package/dist/analytics/tables/requestEvents.js.map +1 -0
  38. package/dist/apps/data/App.d.ts +1024 -624
  39. package/dist/apps/data/App.d.ts.map +1 -1
  40. package/dist/apps/data/App.js +36 -0
  41. package/dist/apps/data/App.js.map +1 -1
  42. package/dist/apps/data/routes/balances.d.ts +45 -45
  43. package/dist/apps/data/routes/coingecko.d.ts +176 -176
  44. package/dist/apps/data/routes/exchanges.d.ts +455 -455
  45. package/dist/apps/data/routes/fee-amm.d.ts +101 -101
  46. package/dist/apps/data/routes/funding.d.ts +765 -0
  47. package/dist/apps/data/routes/funding.d.ts.map +1 -0
  48. package/dist/apps/data/routes/funding.js +383 -0
  49. package/dist/apps/data/routes/funding.js.map +1 -0
  50. package/dist/apps/data/routes/receipts.d.ts.map +1 -1
  51. package/dist/apps/data/routes/receipts.js +2 -8
  52. package/dist/apps/data/routes/receipts.js.map +1 -1
  53. package/dist/apps/data/routes/rpc.d.ts +29 -36
  54. package/dist/apps/data/routes/rpc.d.ts.map +1 -1
  55. package/dist/apps/data/routes/rpc.js +29 -1
  56. package/dist/apps/data/routes/rpc.js.map +1 -1
  57. package/dist/apps/data/routes/tokenlist.d.ts +36 -36
  58. package/dist/apps/data/routes/transfers.d.ts +56 -56
  59. package/dist/apps/data/routes/verified-tokens.d.ts +36 -36
  60. package/dist/apps/data/routes/webhooks.d.ts +45 -45
  61. package/dist/apps/data/routes/webhooks.d.ts.map +1 -1
  62. package/dist/apps/data/routes/webhooks.js +8 -8
  63. package/dist/apps/data/routes/webhooks.js.map +1 -1
  64. package/dist/apps/docs/App.d.ts +4 -2
  65. package/dist/apps/docs/App.d.ts.map +1 -1
  66. package/dist/apps/docs/App.js +27 -95
  67. package/dist/apps/docs/App.js.map +1 -1
  68. package/dist/apps/docs/reference.d.ts +21 -0
  69. package/dist/apps/docs/reference.d.ts.map +1 -0
  70. package/dist/apps/docs/reference.js +112 -0
  71. package/dist/apps/docs/reference.js.map +1 -0
  72. package/dist/apps/index.d.ts +1 -0
  73. package/dist/apps/index.d.ts.map +1 -1
  74. package/dist/apps/index.js +1 -0
  75. package/dist/apps/index.js.map +1 -1
  76. package/dist/apps/mcp.d.ts +76 -0
  77. package/dist/apps/mcp.d.ts.map +1 -0
  78. package/dist/apps/mcp.js +188 -0
  79. package/dist/apps/mcp.js.map +1 -0
  80. package/dist/apps/platform/App.d.ts +3005 -728
  81. package/dist/apps/platform/App.d.ts.map +1 -1
  82. package/dist/apps/platform/App.js +13 -3
  83. package/dist/apps/platform/App.js.map +1 -1
  84. package/dist/apps/platform/Billing.d.ts +279 -0
  85. package/dist/apps/platform/Billing.d.ts.map +1 -0
  86. package/dist/apps/platform/Billing.js +477 -0
  87. package/dist/apps/platform/Billing.js.map +1 -0
  88. package/dist/apps/platform/index.d.ts +1 -0
  89. package/dist/apps/platform/index.d.ts.map +1 -1
  90. package/dist/apps/platform/index.js +1 -0
  91. package/dist/apps/platform/index.js.map +1 -1
  92. package/dist/apps/platform/routes/api-keys.d.ts +341 -49
  93. package/dist/apps/platform/routes/api-keys.d.ts.map +1 -1
  94. package/dist/apps/platform/routes/api-keys.js +115 -13
  95. package/dist/apps/platform/routes/api-keys.js.map +1 -1
  96. package/dist/apps/platform/routes/billing.d.ts +1520 -0
  97. package/dist/apps/platform/routes/billing.d.ts.map +1 -0
  98. package/dist/apps/platform/routes/billing.js +795 -0
  99. package/dist/apps/platform/routes/billing.js.map +1 -0
  100. package/dist/apps/platform/routes/invitations.d.ts +201 -83
  101. package/dist/apps/platform/routes/invitations.d.ts.map +1 -1
  102. package/dist/apps/platform/routes/invitations.js +58 -0
  103. package/dist/apps/platform/routes/invitations.js.map +1 -1
  104. package/dist/apps/platform/routes/me.d.ts +17 -17
  105. package/dist/apps/platform/routes/orgs.d.ts +50 -36
  106. package/dist/apps/platform/routes/orgs.d.ts.map +1 -1
  107. package/dist/apps/platform/routes/orgs.js +14 -3
  108. package/dist/apps/platform/routes/orgs.js.map +1 -1
  109. package/dist/apps/platform/routes/projects.d.ts +97 -97
  110. package/dist/apps/platform/routes/scopes.d.ts +107 -0
  111. package/dist/apps/platform/routes/scopes.d.ts.map +1 -0
  112. package/dist/apps/platform/routes/scopes.js +66 -0
  113. package/dist/apps/platform/routes/scopes.js.map +1 -0
  114. package/dist/apps/platform/routes/usage.d.ts +588 -0
  115. package/dist/apps/platform/routes/usage.d.ts.map +1 -0
  116. package/dist/apps/platform/routes/usage.js +391 -0
  117. package/dist/apps/platform/routes/usage.js.map +1 -0
  118. package/dist/apps/relay/App.d.ts +4 -1
  119. package/dist/apps/relay/App.d.ts.map +1 -1
  120. package/dist/apps/relay/App.js +118 -10
  121. package/dist/apps/relay/App.js.map +1 -1
  122. package/dist/apps/relay/Sponsorships.d.ts.map +1 -1
  123. package/dist/apps/relay/Sponsorships.js +2 -1
  124. package/dist/apps/relay/Sponsorships.js.map +1 -1
  125. package/dist/cli/Cli.d.ts +8 -1
  126. package/dist/cli/Cli.d.ts.map +1 -1
  127. package/dist/cli/Cli.js +11 -3
  128. package/dist/cli/Cli.js.map +1 -1
  129. package/dist/cli/commands/admin.d.ts +4 -0
  130. package/dist/cli/commands/admin.d.ts.map +1 -1
  131. package/dist/cli/commands/admin.js +80 -1
  132. package/dist/cli/commands/admin.js.map +1 -1
  133. package/dist/db/Db.d.ts +7 -0
  134. package/dist/db/Db.d.ts.map +1 -1
  135. package/dist/db/Db.js +126 -0
  136. package/dist/db/Db.js.map +1 -1
  137. package/dist/db/Schema.d.ts +79 -0
  138. package/dist/db/Schema.d.ts.map +1 -1
  139. package/dist/db/Schema.js +88 -2
  140. package/dist/db/Schema.js.map +1 -1
  141. package/dist/db/tables/billingSettings.d.ts +55 -0
  142. package/dist/db/tables/billingSettings.d.ts.map +1 -0
  143. package/dist/db/tables/billingSettings.js +70 -0
  144. package/dist/db/tables/billingSettings.js.map +1 -0
  145. package/dist/db/tables/invitations.js +2 -2
  146. package/dist/db/tables/invitations.js.map +1 -1
  147. package/dist/db/tables/organizations.d.ts +6 -2
  148. package/dist/db/tables/organizations.d.ts.map +1 -1
  149. package/dist/db/tables/organizations.js +10 -4
  150. package/dist/db/tables/organizations.js.map +1 -1
  151. package/dist/db/tables/projects.js +2 -2
  152. package/dist/db/tables/projects.js.map +1 -1
  153. package/dist/db/tables/requestUsage.d.ts +121 -0
  154. package/dist/db/tables/requestUsage.d.ts.map +1 -0
  155. package/dist/db/tables/requestUsage.js +150 -0
  156. package/dist/db/tables/requestUsage.js.map +1 -0
  157. package/dist/db/tables/sponsoredTransactions.d.ts +119 -0
  158. package/dist/db/tables/sponsoredTransactions.d.ts.map +1 -1
  159. package/dist/db/tables/sponsoredTransactions.js +155 -2
  160. package/dist/db/tables/sponsoredTransactions.js.map +1 -1
  161. package/dist/db/tables/stripeCustomers.d.ts +81 -0
  162. package/dist/db/tables/stripeCustomers.d.ts.map +1 -0
  163. package/dist/db/tables/stripeCustomers.js +120 -0
  164. package/dist/db/tables/stripeCustomers.js.map +1 -0
  165. package/dist/db/tables/users.js +2 -2
  166. package/dist/db/tables/users.js.map +1 -1
  167. package/dist/handlers/internal/sponsorship.d.ts +5 -2
  168. package/dist/handlers/internal/sponsorship.d.ts.map +1 -1
  169. package/dist/handlers/internal/sponsorship.js +19 -5
  170. package/dist/handlers/internal/sponsorship.js.map +1 -1
  171. package/dist/handlers/internal/utils.d.ts +10 -3
  172. package/dist/handlers/internal/utils.d.ts.map +1 -1
  173. package/dist/handlers/internal/utils.js +14 -6
  174. package/dist/handlers/internal/utils.js.map +1 -1
  175. package/dist/index.d.ts +1 -0
  176. package/dist/index.d.ts.map +1 -1
  177. package/dist/index.js +1 -0
  178. package/dist/index.js.map +1 -1
  179. package/dist/internal/Auth.d.ts +18 -4
  180. package/dist/internal/Auth.d.ts.map +1 -1
  181. package/dist/internal/Auth.js +148 -76
  182. package/dist/internal/Auth.js.map +1 -1
  183. package/dist/internal/Cache.d.ts.map +1 -1
  184. package/dist/internal/Cache.js +4 -1
  185. package/dist/internal/Cache.js.map +1 -1
  186. package/dist/internal/EdgeCache.d.ts +10 -0
  187. package/dist/internal/EdgeCache.d.ts.map +1 -1
  188. package/dist/internal/EdgeCache.js +39 -8
  189. package/dist/internal/EdgeCache.js.map +1 -1
  190. package/dist/internal/Fees.d.ts +27 -0
  191. package/dist/internal/Fees.d.ts.map +1 -0
  192. package/dist/internal/Fees.js +48 -0
  193. package/dist/internal/Fees.js.map +1 -0
  194. package/dist/internal/FundingRoutes.d.ts +183 -0
  195. package/dist/internal/FundingRoutes.d.ts.map +1 -0
  196. package/dist/internal/FundingRoutes.js +490 -0
  197. package/dist/internal/FundingRoutes.js.map +1 -0
  198. package/dist/internal/Id.d.ts +15 -0
  199. package/dist/internal/Id.d.ts.map +1 -0
  200. package/dist/internal/Id.js +22 -0
  201. package/dist/internal/Id.js.map +1 -0
  202. package/dist/internal/Log.d.ts +2 -0
  203. package/dist/internal/Log.d.ts.map +1 -1
  204. package/dist/internal/Log.js +3 -0
  205. package/dist/internal/Log.js.map +1 -1
  206. package/dist/internal/OpenApi.d.ts +10 -1
  207. package/dist/internal/OpenApi.d.ts.map +1 -1
  208. package/dist/internal/OpenApi.js +8 -1
  209. package/dist/internal/OpenApi.js.map +1 -1
  210. package/dist/internal/RateLimit.d.ts +25 -0
  211. package/dist/internal/RateLimit.d.ts.map +1 -1
  212. package/dist/internal/RateLimit.js +51 -0
  213. package/dist/internal/RateLimit.js.map +1 -1
  214. package/dist/internal/Response.d.ts +1 -1
  215. package/dist/internal/Response.d.ts.map +1 -1
  216. package/dist/internal/Schema.d.ts +8 -0
  217. package/dist/internal/Schema.d.ts.map +1 -1
  218. package/dist/internal/Schema.js +7 -0
  219. package/dist/internal/Schema.js.map +1 -1
  220. package/dist/internal/Webhooks.d.ts.map +1 -1
  221. package/dist/internal/Webhooks.js +3 -3
  222. package/dist/internal/Webhooks.js.map +1 -1
  223. package/package.json +4 -3
  224. package/src/ApiKey.ts +9 -2
  225. package/src/ApiKeys.test.ts +51 -0
  226. package/src/ApiKeys.ts +50 -1
  227. package/src/App.test.ts +33 -86718
  228. package/src/App.ts +18 -0
  229. package/src/Client.test-d.ts +351 -0
  230. package/src/Scope.ts +16 -3
  231. package/src/admin/apps/scopes.test.ts +6 -0
  232. package/src/admin/apps/scopes.ts +3 -0
  233. package/src/admin/ui/src/components/Page.tsx +2 -3
  234. package/src/admin/ui/src/routes/__root.tsx +2 -2
  235. package/src/admin/ui/src/routes/api-keys.tsx +2 -2
  236. package/src/admin/ui.generated.ts +13 -13
  237. package/src/analytics/Analytics.test.ts +527 -0
  238. package/src/analytics/Analytics.ts +318 -0
  239. package/src/analytics/tables/requestEvents.ts +523 -0
  240. package/src/apps/data/App.test.ts +72 -0
  241. package/src/apps/data/App.ts +35 -0
  242. package/src/apps/data/routes/funding.test.ts +214 -0
  243. package/src/apps/data/routes/funding.ts +584 -0
  244. package/src/apps/data/routes/receipts.ts +2 -9
  245. package/src/apps/data/routes/rpc.test.ts +78 -2
  246. package/src/apps/data/routes/rpc.ts +31 -1
  247. package/src/apps/data/routes/webhooks.test.ts +1 -1
  248. package/src/apps/data/routes/webhooks.ts +11 -8
  249. package/src/apps/docs/App.ts +29 -100
  250. package/src/apps/docs/reference.ts +132 -0
  251. package/src/apps/index.ts +1 -0
  252. package/src/apps/mcp.test.ts +206 -0
  253. package/src/apps/mcp.ts +250 -0
  254. package/src/apps/platform/App.ts +43 -4
  255. package/src/apps/platform/Billing.test.ts +530 -0
  256. package/src/apps/platform/Billing.ts +682 -0
  257. package/src/apps/platform/index.ts +1 -0
  258. package/src/apps/platform/routes/api-keys.test.ts +172 -9
  259. package/src/apps/platform/routes/api-keys.ts +136 -20
  260. package/src/apps/platform/routes/billing.test.ts +1100 -0
  261. package/src/apps/platform/routes/billing.ts +966 -0
  262. package/src/apps/platform/routes/invitations.test.ts +55 -7
  263. package/src/apps/platform/routes/invitations.ts +68 -0
  264. package/src/apps/platform/routes/me.test.ts +230 -0
  265. package/src/apps/platform/routes/orgs.ts +26 -4
  266. package/src/apps/platform/routes/scopes.test.ts +29 -0
  267. package/src/apps/platform/routes/scopes.ts +85 -0
  268. package/src/apps/platform/routes/usage.test.ts +422 -0
  269. package/src/apps/platform/routes/usage.ts +527 -0
  270. package/src/apps/relay/App.test.ts +457 -8
  271. package/src/apps/relay/App.ts +120 -12
  272. package/src/apps/relay/Sponsorships.test.ts +1 -1
  273. package/src/apps/relay/Sponsorships.ts +2 -1
  274. package/src/cli/Cli.test.ts +25 -2
  275. package/src/cli/Cli.ts +17 -2
  276. package/src/cli/commands/admin.test.ts +83 -0
  277. package/src/cli/commands/admin.ts +89 -1
  278. package/src/db/Db.ts +137 -0
  279. package/src/db/Schema.ts +104 -2
  280. package/src/db/tables/billingSettings.test.ts +87 -0
  281. package/src/db/tables/billingSettings.ts +108 -0
  282. package/src/db/tables/invitations.ts +2 -2
  283. package/src/db/tables/organizations.test.ts +1 -1
  284. package/src/db/tables/organizations.ts +15 -5
  285. package/src/db/tables/projects.ts +2 -2
  286. package/src/db/tables/requestUsage.ts +231 -0
  287. package/src/db/tables/sponsoredTransactions.test.ts +108 -1
  288. package/src/db/tables/sponsoredTransactions.ts +230 -2
  289. package/src/db/tables/stripeCustomers.test.ts +109 -0
  290. package/src/db/tables/stripeCustomers.ts +162 -0
  291. package/src/db/tables/users.ts +2 -2
  292. package/src/handlers/internal/sponsorship.ts +34 -7
  293. package/src/handlers/internal/utils.ts +19 -7
  294. package/src/index.ts +1 -0
  295. package/src/internal/Auth.test.ts +175 -25
  296. package/src/internal/Auth.ts +175 -86
  297. package/src/internal/Cache.ts +5 -1
  298. package/src/internal/EdgeCache.test.ts +27 -4
  299. package/src/internal/EdgeCache.ts +47 -9
  300. package/src/internal/Fees.test.ts +32 -0
  301. package/src/internal/Fees.ts +51 -0
  302. package/src/internal/FundingRoutes.ts +583 -0
  303. package/src/internal/Id.test.ts +17 -0
  304. package/src/internal/Id.ts +24 -0
  305. package/src/internal/Log.test.ts +1 -0
  306. package/src/internal/Log.ts +5 -0
  307. package/src/internal/OpenApi.ts +12 -1
  308. package/src/internal/RateLimit.test.ts +84 -0
  309. package/src/internal/RateLimit.ts +70 -0
  310. package/src/internal/Response.ts +1 -1
  311. package/src/internal/Schema.ts +8 -0
  312. package/src/internal/Webhooks.test.ts +3 -3
  313. package/src/internal/Webhooks.ts +3 -3
@@ -0,0 +1,109 @@
1
+ import { nanoid } from 'nanoid'
2
+
3
+ import { postgresUrl } from '../../../test/containers.js'
4
+ import * as Db from '../Db.js'
5
+ import * as StripeCustomers from './stripeCustomers.js'
6
+
7
+ const create = () => Db.postgres({ connectionString: postgresUrl, schema: `t_${nanoid()}` })
8
+
9
+ /** Redacts nondeterministic row fields (timestamps). */
10
+ function redact(record: StripeCustomers.Record) {
11
+ return { ...record, createdAt: '<timestamp>', updatedAt: '<timestamp>' }
12
+ }
13
+
14
+ describe('create', () => {
15
+ test('behavior: inserts with status none', async () => {
16
+ const db = create()
17
+ await db.migrate()
18
+ const record = await StripeCustomers.create(db, {
19
+ orgId: 'org_1',
20
+ stripeCustomerId: 'cus_a',
21
+ })
22
+ expect(redact(record)).toMatchInlineSnapshot(`
23
+ {
24
+ "createdAt": "<timestamp>",
25
+ "environment": "production",
26
+ "orgId": "org_1",
27
+ "status": "none",
28
+ "stripeCustomerId": "cus_a",
29
+ "updatedAt": "<timestamp>",
30
+ }
31
+ `)
32
+ await db.close()
33
+ })
34
+
35
+ test('behavior: a concurrent duplicate resolves to the winning row', async () => {
36
+ const db = create()
37
+ await db.migrate()
38
+ const winner = await StripeCustomers.create(db, { orgId: 'org_1', stripeCustomerId: 'cus_a' })
39
+ // Loser of the first-checkout race: same org, its own Stripe customer.
40
+ const resolved = await StripeCustomers.create(db, { orgId: 'org_1', stripeCustomerId: 'cus_b' }) // prettier-ignore
41
+ expect(resolved).toStrictEqual(winner)
42
+ expect(await StripeCustomers.getByCustomer(db, 'cus_b')).toBeUndefined()
43
+ await db.close()
44
+ })
45
+ })
46
+
47
+ describe('get', () => {
48
+ test('behavior: reads by org; undefined when absent', async () => {
49
+ const db = create()
50
+ await db.migrate()
51
+ const record = await StripeCustomers.create(db, { orgId: 'org_1', stripeCustomerId: 'cus_a' })
52
+ expect(await StripeCustomers.get(db, 'org_1')).toStrictEqual(record)
53
+ expect(await StripeCustomers.get(db, 'org_missing')).toBeUndefined()
54
+ await db.close()
55
+ })
56
+
57
+ test('behavior: production and sandbox rows coexist per org and read disjointly', async () => {
58
+ const db = create()
59
+ await db.migrate()
60
+ const production = await StripeCustomers.create(db, { orgId: 'org_1', stripeCustomerId: 'cus_live' }) // prettier-ignore
61
+ const sandbox = await StripeCustomers.create(db, { environment: 'sandbox', orgId: 'org_1', stripeCustomerId: 'cus_test' }) // prettier-ignore
62
+ expect(sandbox.environment).toBe('sandbox')
63
+ expect(await StripeCustomers.get(db, 'org_1')).toStrictEqual(production)
64
+ expect(await StripeCustomers.get(db, 'org_1', 'sandbox')).toStrictEqual(sandbox)
65
+ await db.close()
66
+ })
67
+ })
68
+
69
+ describe('getByCustomer', () => {
70
+ test('behavior: reads by Stripe customer id; undefined for unknown customers', async () => {
71
+ const db = create()
72
+ await db.migrate()
73
+ const record = await StripeCustomers.create(db, { orgId: 'org_1', stripeCustomerId: 'cus_a' })
74
+ expect(await StripeCustomers.getByCustomer(db, 'cus_a')).toStrictEqual(record)
75
+ expect(await StripeCustomers.getByCustomer(db, 'cus_unknown')).toBeUndefined()
76
+ await db.close()
77
+ })
78
+ })
79
+
80
+ describe('setStatus', () => {
81
+ test('behavior: persists the status and bumps updatedAt', async () => {
82
+ const db = create()
83
+ await db.migrate()
84
+ const record = await StripeCustomers.create(db, { orgId: 'org_1', stripeCustomerId: 'cus_a' })
85
+ const updated = await StripeCustomers.setStatus(db, 'org_1', 'active')
86
+ expect(updated?.status).toMatchInlineSnapshot(`"active"`)
87
+ expect(updated?.createdAt).toBe(record.createdAt)
88
+ await db.close()
89
+ })
90
+
91
+ test('behavior: undefined when the org has no billing account', async () => {
92
+ const db = create()
93
+ await db.migrate()
94
+ expect(await StripeCustomers.setStatus(db, 'org_missing', 'active')).toBeUndefined()
95
+ await db.close()
96
+ })
97
+ })
98
+
99
+ describe('deleteByOrg', () => {
100
+ test('behavior: deletes the row; false when absent', async () => {
101
+ const db = create()
102
+ await db.migrate()
103
+ await StripeCustomers.create(db, { orgId: 'org_1', stripeCustomerId: 'cus_a' })
104
+ expect(await StripeCustomers.deleteByOrg(db, 'org_1')).toBe(true)
105
+ expect(await StripeCustomers.get(db, 'org_1')).toBeUndefined()
106
+ expect(await StripeCustomers.deleteByOrg(db, 'org_1')).toBe(false)
107
+ await db.close()
108
+ })
109
+ })
@@ -0,0 +1,162 @@
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 `stripe_customers` table, derived from `Schema.StripeCustomer`. */
7
+ export type Table = db_Schema.StripeCustomer
8
+
9
+ /** A stored Stripe billing-source row. */
10
+ export type Record = Selectable<Table>
11
+
12
+ /** A billing status. */
13
+ export type Status = Record['status']
14
+
15
+ /** Billing statuses; `active` alone opens the production sponsorship gate. */
16
+ export const statuses = ['active', 'canceled', 'none', 'past_due'] as const satisfies readonly Status[] // prettier-ignore
17
+
18
+ /**
19
+ * Inserts a Stripe billing source (status `none`) and returns the stored record.
20
+ * Idempotent per organization: a concurrent first-checkout race resolves to
21
+ * the winner's row — the loser's Stripe customer stays orphaned in Stripe,
22
+ * which is harmless.
23
+ *
24
+ * @param db - The database.
25
+ * @param input - The Stripe source to insert.
26
+ * @returns The stored record.
27
+ */
28
+ export async function create(db: Db.Db, input: create.Input): Promise<Record> {
29
+ const now = new Date().toISOString()
30
+ const environment = input.environment ?? 'production'
31
+ const inserted = await db.kysely
32
+ .insertInto('stripe_customers')
33
+ .values({
34
+ createdAt: now,
35
+ environment,
36
+ orgId: input.orgId,
37
+ status: 'none',
38
+ stripeCustomerId: input.stripeCustomerId,
39
+ updatedAt: now,
40
+ })
41
+ // Untargeted so it holds against either the org pkey (before the pkey drop
42
+ // release) or the `(org_id, environment)` unique index (after).
43
+ .onConflict((oc) => oc.doNothing())
44
+ .returningAll()
45
+ .executeTakeFirst()
46
+ if (inserted) return inserted
47
+ return await db.kysely
48
+ .selectFrom('stripe_customers')
49
+ .selectAll()
50
+ .where('orgId', '=', input.orgId)
51
+ .where('environment', '=', environment)
52
+ .executeTakeFirstOrThrow()
53
+ }
54
+
55
+ export declare namespace create {
56
+ /** Fields accepted when inserting a Stripe billing source. */
57
+ type Input = {
58
+ /** Environment this source backs; defaults to `production`. */
59
+ environment?: Record['environment'] | undefined
60
+ /** Organization id (`org_…`) the Stripe source belongs to. */
61
+ orgId: string
62
+ /** Stripe customer id backing this account. */
63
+ stripeCustomerId: string
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Deletes an organization's Stripe billing source. The Stripe customer is never
69
+ * deleted; its invoice history stays in Stripe for audit.
70
+ *
71
+ * @param db - The database.
72
+ * @param orgId - The organization id (`org_…`).
73
+ * @returns Whether a Stripe source existed and was deleted.
74
+ */
75
+ export async function deleteByOrg(db: Db.Db, orgId: string): Promise<boolean> {
76
+ const result = await db.kysely
77
+ .deleteFrom('stripe_customers')
78
+ .where('orgId', '=', orgId)
79
+ .executeTakeFirst()
80
+ return result.numDeletedRows > 0n
81
+ }
82
+
83
+ /**
84
+ * Reads an organization's Stripe billing source.
85
+ *
86
+ * @param db - The database.
87
+ * @param orgId - The organization id (`org_…`).
88
+ * @param environment - The environment to read; defaults to `production`.
89
+ * @returns The stored record, or `undefined` when the org has none.
90
+ */
91
+ export function get(
92
+ db: Db.Db,
93
+ orgId: string,
94
+ environment: Record['environment'] = 'production',
95
+ ): Promise<Record | undefined> {
96
+ return db.kysely
97
+ .selectFrom('stripe_customers')
98
+ .selectAll()
99
+ .where('orgId', '=', orgId)
100
+ .where('environment', '=', environment)
101
+ .executeTakeFirst()
102
+ }
103
+
104
+ /**
105
+ * Lists every billing source in an environment — the driver set for the cron
106
+ * job that reconciles cached billing snapshots (e.g. API-key records) against
107
+ * this authoritative table.
108
+ *
109
+ * @param db - The database.
110
+ * @param environment - The environment to enumerate.
111
+ * @returns The stored records for that environment.
112
+ */
113
+ export function listByEnvironment(
114
+ db: Db.Db,
115
+ environment: Record['environment'],
116
+ ): Promise<Record[]> {
117
+ return db.kysely
118
+ .selectFrom('stripe_customers')
119
+ .selectAll()
120
+ .where('environment', '=', environment)
121
+ .execute()
122
+ }
123
+
124
+ /**
125
+ * Reads a Stripe billing source by its customer id — the webhook handler's
126
+ * lookup for inbound events.
127
+ *
128
+ * @param db - The database.
129
+ * @param stripeCustomerId - The Stripe customer id.
130
+ * @returns The stored record, or `undefined` for unknown customers.
131
+ */
132
+ export function getByCustomer(db: Db.Db, stripeCustomerId: string): Promise<Record | undefined> {
133
+ return db.kysely
134
+ .selectFrom('stripe_customers')
135
+ .selectAll()
136
+ .where('stripeCustomerId', '=', stripeCustomerId)
137
+ .executeTakeFirst()
138
+ }
139
+
140
+ /**
141
+ * Sets an organization's billing status.
142
+ *
143
+ * @param db - The database.
144
+ * @param orgId - The organization id (`org_…`).
145
+ * @param status - The derived status to persist.
146
+ * @param environment - The environment to update; defaults to `production`.
147
+ * @returns The updated record, or `undefined` when the org has no Stripe source.
148
+ */
149
+ export function setStatus(
150
+ db: Db.Db,
151
+ orgId: string,
152
+ status: Status,
153
+ environment: Record['environment'] = 'production',
154
+ ): Promise<Record | undefined> {
155
+ return db.kysely
156
+ .updateTable('stripe_customers')
157
+ .set({ status, updatedAt: new Date().toISOString() })
158
+ .where('orgId', '=', orgId)
159
+ .where('environment', '=', environment)
160
+ .returningAll()
161
+ .executeTakeFirst()
162
+ }
@@ -1,6 +1,6 @@
1
1
  import type { Selectable } from 'kysely'
2
- import { nanoid } from 'nanoid'
3
2
 
3
+ import * as Id from '../../internal/Id.js'
4
4
  import type * as Db from '../Db.js'
5
5
  import type * as db_Schema from '../Schema.js'
6
6
 
@@ -49,7 +49,7 @@ export async function upsertByAddress(db: Db.Db, input: upsertByAddress.Input):
49
49
  const now = new Date().toISOString()
50
50
  const inserted = await db.kysely
51
51
  .insertInto('users')
52
- .values({ address, createdAt: now, email: null, id: `usr_${nanoid()}`, updatedAt: now })
52
+ .values({ address, createdAt: now, email: null, id: Id.generate('usr'), updatedAt: now })
53
53
  .onConflict((oc) => oc.column('address').doNothing())
54
54
  .returningAll()
55
55
  .executeTakeFirst()
@@ -9,11 +9,30 @@ import * as Utils from './utils.js'
9
9
  /**
10
10
  * Sponsorship approval callback. The candidate always carries its `chainId`:
11
11
  * raw submissions deserialize it, and fills validate only prepared/filled
12
- * transactions.
12
+ * transactions. Anything but `true` refuses; raw submissions surface a named
13
+ * reason to the caller while fills fall back to an unsponsored fill.
13
14
  */
14
15
  export type Validate = (
15
16
  request: Transaction.TransactionRequest & { chainId?: number | Hex.Hex | undefined },
16
- ) => boolean | Promise<boolean>
17
+ ) => Validation | Promise<Validation>
18
+
19
+ /** A sponsorship verdict: `true` sponsors; `false` or a named reason refuses. */
20
+ export type Validation =
21
+ | boolean
22
+ | 'billing_past_due'
23
+ | 'billing_required'
24
+ | 'fee_token_unsupported'
25
+ | 'spend_limit_exceeded'
26
+ | 'tx_fee_limit_exceeded'
27
+
28
+ /** Refusal messages keyed by named validation reason. */
29
+ const refusalMessages = {
30
+ billing_past_due: 'Billing past due.',
31
+ billing_required: 'Billing required.',
32
+ fee_token_unsupported: 'Fee token unsupported.',
33
+ spend_limit_exceeded: 'Spend limit exceeded.',
34
+ tx_fee_limit_exceeded: 'Transaction fee limit exceeded.',
35
+ } as const satisfies Record<Exclude<Validation, boolean>, string>
17
36
 
18
37
  /** Returns sponsor metadata for `eth_fillTransaction` responses. */
19
38
  export function getSponsor(options: getSponsor.Options): getSponsor.ReturnType {
@@ -49,10 +68,13 @@ export declare namespace getSponsor {
49
68
  export async function shouldSponsor(options: shouldSponsor.Options) {
50
69
  const { sender, transaction, validate } = options
51
70
  if (!validate) return true
52
- return await validate({
71
+ // Named refusal reasons collapse to false: fills fall back to an
72
+ // unsponsored fill instead of surfacing the reason.
73
+ const verdict = await validate({
53
74
  ...transaction,
54
75
  from: sender,
55
76
  } as Transaction.TransactionRequest)
77
+ return verdict === true
56
78
  }
57
79
 
58
80
  export declare namespace shouldSponsor {
@@ -166,10 +188,15 @@ export async function handleRawTransaction(options: handleRawTransaction.Options
166
188
  feeToken_chain
167
189
  const transaction_sponsored = feeToken ? { ...transaction, feeToken } : transaction
168
190
 
169
- if (validate && !(await validate(transaction_sponsored as Transaction.TransactionRequest)))
170
- throw new RpcResponse.InvalidParamsError({
171
- message: 'Sponsorship rejected.',
172
- })
191
+ if (validate) {
192
+ const verdict = await validate(transaction_sponsored as Transaction.TransactionRequest)
193
+ if (verdict !== true)
194
+ throw new RpcResponse.InvalidParamsError(
195
+ verdict === false
196
+ ? { message: 'Sponsorship rejected.' }
197
+ : { data: { code: verdict }, message: refusalMessages[verdict] },
198
+ )
199
+ }
173
200
 
174
201
  const envelope = TxEnvelopeTempo.from(transaction_sponsored as never)
175
202
  const signPayload = TxEnvelopeTempo.getFeePayerSignPayload(envelope, { sender })
@@ -80,18 +80,30 @@ export function normalizeTempoTransaction(value: Record<string, unknown> | undef
80
80
 
81
81
  /** Returns a raw JSON-RPC error response object (not wrapped in a `Response`). */
82
82
  export function rpcErrorJson(request: RpcRequest.RpcRequest, error: unknown) {
83
- if (error instanceof RpcResponse.InvalidParamsError)
84
- return RpcResponse.from({ error }, { request })
85
-
86
- if (error instanceof RpcResponse.MethodNotSupportedError)
87
- return RpcResponse.from({ error }, { request })
83
+ // Serialize error instances to plain objects: `Error.message` is
84
+ // non-enumerable, so instances stringify onto the wire without it.
85
+ if (
86
+ error instanceof RpcResponse.InvalidParamsError ||
87
+ error instanceof RpcResponse.MethodNotSupportedError
88
+ )
89
+ return RpcResponse.from(
90
+ {
91
+ error: {
92
+ code: error.code,
93
+ message: error.message,
94
+ ...(error.data !== undefined ? { data: error.data } : {}),
95
+ },
96
+ },
97
+ { request },
98
+ )
88
99
 
89
100
  if ((error as { name?: string | undefined }).name === 'ZodError')
90
101
  return RpcResponse.from(
91
102
  {
92
- error: new RpcResponse.InvalidParamsError({
103
+ error: {
104
+ code: RpcResponse.InvalidParamsError.code,
93
105
  message: (error as Error).message,
94
- }),
106
+ },
95
107
  },
96
108
  { request },
97
109
  )
package/src/index.ts CHANGED
@@ -1,3 +1,4 @@
1
+ export * as Analytics from './analytics/Analytics.js'
1
2
  export * as ApiKey from './ApiKey.js'
2
3
  export * as ApiKeys from './ApiKeys.js'
3
4
  export * as App from './App.js'