@wopr-network/platform-core 0.1.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 (694) hide show
  1. package/biome.json +61 -0
  2. package/dist/admin/admin-audit-log-repository.d.ts +33 -0
  3. package/dist/admin/admin-audit-log-repository.js +102 -0
  4. package/dist/admin/audit-log.d.ts +49 -0
  5. package/dist/admin/audit-log.js +63 -0
  6. package/dist/admin/index.d.ts +6 -0
  7. package/dist/admin/index.js +3 -0
  8. package/dist/admin/role-store.d.ts +37 -0
  9. package/dist/admin/role-store.js +106 -0
  10. package/dist/auth/api-key-repository.d.ts +11 -0
  11. package/dist/auth/api-key-repository.js +33 -0
  12. package/dist/auth/api-key-repository.test.d.ts +1 -0
  13. package/dist/auth/api-key-repository.test.js +46 -0
  14. package/dist/auth/auth.test.d.ts +1 -0
  15. package/dist/auth/auth.test.js +140 -0
  16. package/dist/auth/better-auth.d.ts +42 -0
  17. package/dist/auth/better-auth.js +196 -0
  18. package/dist/auth/index.d.ts +186 -0
  19. package/dist/auth/index.js +422 -0
  20. package/dist/auth/login-history-repository.d.ts +14 -0
  21. package/dist/auth/login-history-repository.js +15 -0
  22. package/dist/auth/login-history-repository.test.d.ts +1 -0
  23. package/dist/auth/login-history-repository.test.js +47 -0
  24. package/dist/auth/middleware.d.ts +55 -0
  25. package/dist/auth/middleware.js +101 -0
  26. package/dist/auth/middleware.test.d.ts +1 -0
  27. package/dist/auth/middleware.test.js +213 -0
  28. package/dist/auth/scoped-tokens.test.d.ts +1 -0
  29. package/dist/auth/scoped-tokens.test.js +306 -0
  30. package/dist/auth/tenant-access.test.d.ts +1 -0
  31. package/dist/auth/tenant-access.test.js +62 -0
  32. package/dist/auth/user-creator.d.ts +9 -0
  33. package/dist/auth/user-creator.js +47 -0
  34. package/dist/auth/user-creator.test.d.ts +1 -0
  35. package/dist/auth/user-creator.test.js +78 -0
  36. package/dist/auth/user-role-repository.d.ts +31 -0
  37. package/dist/auth/user-role-repository.js +53 -0
  38. package/dist/auth/user-role-repository.test.d.ts +1 -0
  39. package/dist/auth/user-role-repository.test.js +122 -0
  40. package/dist/billing/drizzle-webhook-seen-repository.d.ts +10 -0
  41. package/dist/billing/drizzle-webhook-seen-repository.js +28 -0
  42. package/dist/billing/index.d.ts +7 -0
  43. package/dist/billing/index.js +7 -0
  44. package/dist/billing/payment-processor.d.ts +127 -0
  45. package/dist/billing/payment-processor.js +8 -0
  46. package/dist/billing/payment-processor.test.d.ts +1 -0
  47. package/dist/billing/payment-processor.test.js +71 -0
  48. package/dist/billing/payram/cents-credits-boundary.test.d.ts +1 -0
  49. package/dist/billing/payram/cents-credits-boundary.test.js +75 -0
  50. package/dist/billing/payram/charge-store.d.ts +41 -0
  51. package/dist/billing/payram/charge-store.js +72 -0
  52. package/dist/billing/payram/charge-store.test.d.ts +1 -0
  53. package/dist/billing/payram/charge-store.test.js +64 -0
  54. package/dist/billing/payram/checkout.d.ts +15 -0
  55. package/dist/billing/payram/checkout.js +24 -0
  56. package/dist/billing/payram/checkout.test.d.ts +1 -0
  57. package/dist/billing/payram/checkout.test.js +74 -0
  58. package/dist/billing/payram/client.d.ts +7 -0
  59. package/dist/billing/payram/client.js +15 -0
  60. package/dist/billing/payram/client.test.d.ts +1 -0
  61. package/dist/billing/payram/client.test.js +52 -0
  62. package/dist/billing/payram/index.d.ts +8 -0
  63. package/dist/billing/payram/index.js +4 -0
  64. package/dist/billing/payram/types.d.ts +40 -0
  65. package/dist/billing/payram/types.js +1 -0
  66. package/dist/billing/payram/webhook.d.ts +19 -0
  67. package/dist/billing/payram/webhook.js +67 -0
  68. package/dist/billing/payram/webhook.test.d.ts +7 -0
  69. package/dist/billing/payram/webhook.test.js +248 -0
  70. package/dist/billing/stripe/cents-credits-boundary.test.d.ts +1 -0
  71. package/dist/billing/stripe/cents-credits-boundary.test.js +62 -0
  72. package/dist/billing/stripe/checkout.d.ts +20 -0
  73. package/dist/billing/stripe/checkout.js +63 -0
  74. package/dist/billing/stripe/checkout.test.d.ts +1 -0
  75. package/dist/billing/stripe/checkout.test.js +148 -0
  76. package/dist/billing/stripe/client.d.ts +14 -0
  77. package/dist/billing/stripe/client.js +33 -0
  78. package/dist/billing/stripe/client.test.d.ts +1 -0
  79. package/dist/billing/stripe/client.test.js +58 -0
  80. package/dist/billing/stripe/credit-prices.d.ts +63 -0
  81. package/dist/billing/stripe/credit-prices.js +81 -0
  82. package/dist/billing/stripe/credit-prices.test.d.ts +1 -0
  83. package/dist/billing/stripe/credit-prices.test.js +87 -0
  84. package/dist/billing/stripe/index.d.ts +14 -0
  85. package/dist/billing/stripe/index.js +8 -0
  86. package/dist/billing/stripe/payment-methods-detach-all.test.d.ts +1 -0
  87. package/dist/billing/stripe/payment-methods-detach-all.test.js +40 -0
  88. package/dist/billing/stripe/payment-methods.d.ts +25 -0
  89. package/dist/billing/stripe/payment-methods.js +53 -0
  90. package/dist/billing/stripe/payment-methods.test.d.ts +1 -0
  91. package/dist/billing/stripe/payment-methods.test.js +122 -0
  92. package/dist/billing/stripe/portal.d.ts +10 -0
  93. package/dist/billing/stripe/portal.js +16 -0
  94. package/dist/billing/stripe/portal.test.d.ts +1 -0
  95. package/dist/billing/stripe/portal.test.js +48 -0
  96. package/dist/billing/stripe/setup-intent.d.ts +16 -0
  97. package/dist/billing/stripe/setup-intent.js +22 -0
  98. package/dist/billing/stripe/setup-intent.test.d.ts +1 -0
  99. package/dist/billing/stripe/setup-intent.test.js +58 -0
  100. package/dist/billing/stripe/stripe-payment-processor.d.ts +49 -0
  101. package/dist/billing/stripe/stripe-payment-processor.js +166 -0
  102. package/dist/billing/stripe/stripe-payment-processor.test.d.ts +1 -0
  103. package/dist/billing/stripe/stripe-payment-processor.test.js +413 -0
  104. package/dist/billing/stripe/tenant-store.d.ts +56 -0
  105. package/dist/billing/stripe/tenant-store.js +119 -0
  106. package/dist/billing/stripe/tenant-store.test.d.ts +1 -0
  107. package/dist/billing/stripe/tenant-store.test.js +97 -0
  108. package/dist/billing/stripe/types.d.ts +49 -0
  109. package/dist/billing/stripe/types.js +1 -0
  110. package/dist/billing/webhook-seen-repository.d.ts +14 -0
  111. package/dist/billing/webhook-seen-repository.js +13 -0
  112. package/dist/config/billing-env.test.d.ts +1 -0
  113. package/dist/config/billing-env.test.js +48 -0
  114. package/dist/config/index.d.ts +46 -0
  115. package/dist/config/index.js +38 -0
  116. package/dist/config/logger.d.ts +2 -0
  117. package/dist/config/logger.js +11 -0
  118. package/dist/config/provider-endpoints.d.ts +6 -0
  119. package/dist/config/provider-endpoints.js +12 -0
  120. package/dist/credits/auto-topup-charge.d.ts +27 -0
  121. package/dist/credits/auto-topup-charge.js +139 -0
  122. package/dist/credits/auto-topup-charge.test.d.ts +1 -0
  123. package/dist/credits/auto-topup-charge.test.js +242 -0
  124. package/dist/credits/auto-topup-event-log-repository.d.ts +16 -0
  125. package/dist/credits/auto-topup-event-log-repository.js +18 -0
  126. package/dist/credits/auto-topup-event-log-repository.test.d.ts +1 -0
  127. package/dist/credits/auto-topup-event-log-repository.test.js +83 -0
  128. package/dist/credits/auto-topup-schedule.d.ts +27 -0
  129. package/dist/credits/auto-topup-schedule.js +66 -0
  130. package/dist/credits/auto-topup-schedule.test.d.ts +1 -0
  131. package/dist/credits/auto-topup-schedule.test.js +145 -0
  132. package/dist/credits/auto-topup-settings-repository.d.ts +54 -0
  133. package/dist/credits/auto-topup-settings-repository.js +184 -0
  134. package/dist/credits/auto-topup-settings-repository.test.d.ts +1 -0
  135. package/dist/credits/auto-topup-settings-repository.test.js +104 -0
  136. package/dist/credits/auto-topup-usage.d.ts +22 -0
  137. package/dist/credits/auto-topup-usage.js +56 -0
  138. package/dist/credits/auto-topup-usage.test.d.ts +1 -0
  139. package/dist/credits/auto-topup-usage.test.js +181 -0
  140. package/dist/credits/credit-expiry-cron.d.ts +19 -0
  141. package/dist/credits/credit-expiry-cron.js +50 -0
  142. package/dist/credits/credit-expiry-cron.test.d.ts +1 -0
  143. package/dist/credits/credit-expiry-cron.test.js +67 -0
  144. package/dist/credits/credit-ledger-extra.test.d.ts +1 -0
  145. package/dist/credits/credit-ledger-extra.test.js +40 -0
  146. package/dist/credits/credit-ledger.bench.d.ts +1 -0
  147. package/dist/credits/credit-ledger.bench.js +33 -0
  148. package/dist/credits/credit-ledger.d.ts +130 -0
  149. package/dist/credits/credit-ledger.js +293 -0
  150. package/dist/credits/credit-ledger.test.d.ts +4 -0
  151. package/dist/credits/credit-ledger.test.js +203 -0
  152. package/dist/credits/credit-transaction-repository.d.ts +17 -0
  153. package/dist/credits/credit-transaction-repository.js +35 -0
  154. package/dist/credits/credit-transaction-repository.test.d.ts +1 -0
  155. package/dist/credits/credit-transaction-repository.test.js +232 -0
  156. package/dist/credits/credit.d.ts +75 -0
  157. package/dist/credits/credit.js +139 -0
  158. package/dist/credits/credit.test.d.ts +1 -0
  159. package/dist/credits/credit.test.js +196 -0
  160. package/dist/credits/dividend-cron.d.ts +29 -0
  161. package/dist/credits/dividend-cron.js +88 -0
  162. package/dist/credits/dividend-cron.test.d.ts +1 -0
  163. package/dist/credits/dividend-cron.test.js +128 -0
  164. package/dist/credits/dividend-repository.d.ts +29 -0
  165. package/dist/credits/dividend-repository.js +126 -0
  166. package/dist/credits/dividend-repository.test.d.ts +1 -0
  167. package/dist/credits/dividend-repository.test.js +176 -0
  168. package/dist/credits/index.d.ts +9 -0
  169. package/dist/credits/index.js +5 -0
  170. package/dist/credits/repository-types.d.ts +29 -0
  171. package/dist/credits/repository-types.js +1 -0
  172. package/dist/credits/signup-grant.d.ts +12 -0
  173. package/dist/credits/signup-grant.js +35 -0
  174. package/dist/credits/signup-grant.test.d.ts +1 -0
  175. package/dist/credits/signup-grant.test.js +51 -0
  176. package/dist/credits/tenant-customer-repository.d.ts +30 -0
  177. package/dist/credits/tenant-customer-repository.js +5 -0
  178. package/dist/db/auth-user-repository.d.ts +46 -0
  179. package/dist/db/auth-user-repository.js +90 -0
  180. package/dist/db/credit-column.d.ts +27 -0
  181. package/dist/db/credit-column.js +13 -0
  182. package/dist/db/index.d.ts +14 -0
  183. package/dist/db/index.js +8 -0
  184. package/dist/db/schema/account-deletion-requests.d.ts +203 -0
  185. package/dist/db/schema/account-deletion-requests.js +36 -0
  186. package/dist/db/schema/account-export-requests.d.ts +148 -0
  187. package/dist/db/schema/account-export-requests.js +19 -0
  188. package/dist/db/schema/admin-audit.d.ts +194 -0
  189. package/dist/db/schema/admin-audit.js +21 -0
  190. package/dist/db/schema/admin-users.d.ts +177 -0
  191. package/dist/db/schema/admin-users.js +23 -0
  192. package/dist/db/schema/affiliate-fraud.d.ts +160 -0
  193. package/dist/db/schema/affiliate-fraud.js +18 -0
  194. package/dist/db/schema/affiliate.d.ts +277 -0
  195. package/dist/db/schema/affiliate.js +32 -0
  196. package/dist/db/schema/coupon-codes.d.ts +143 -0
  197. package/dist/db/schema/coupon-codes.js +17 -0
  198. package/dist/db/schema/credit-auto-topup-settings.d.ts +232 -0
  199. package/dist/db/schema/credit-auto-topup-settings.js +27 -0
  200. package/dist/db/schema/credit-auto-topup.d.ts +130 -0
  201. package/dist/db/schema/credit-auto-topup.js +21 -0
  202. package/dist/db/schema/credits.d.ts +283 -0
  203. package/dist/db/schema/credits.js +38 -0
  204. package/dist/db/schema/dividend-distributions.d.ts +130 -0
  205. package/dist/db/schema/dividend-distributions.js +19 -0
  206. package/dist/db/schema/email-notifications.d.ts +99 -0
  207. package/dist/db/schema/email-notifications.js +21 -0
  208. package/dist/db/schema/index.d.ts +33 -0
  209. package/dist/db/schema/index.js +33 -0
  210. package/dist/db/schema/meter-events.d.ts +599 -0
  211. package/dist/db/schema/meter-events.js +55 -0
  212. package/dist/db/schema/notification-preferences.d.ts +165 -0
  213. package/dist/db/schema/notification-preferences.js +18 -0
  214. package/dist/db/schema/notification-queue.d.ts +236 -0
  215. package/dist/db/schema/notification-queue.js +40 -0
  216. package/dist/db/schema/org-memberships.d.ts +63 -0
  217. package/dist/db/schema/org-memberships.js +15 -0
  218. package/dist/db/schema/organization-members.d.ts +235 -0
  219. package/dist/db/schema/organization-members.js +27 -0
  220. package/dist/db/schema/payram.d.ts +164 -0
  221. package/dist/db/schema/payram.js +21 -0
  222. package/dist/db/schema/platform-api-keys.d.ts +143 -0
  223. package/dist/db/schema/platform-api-keys.js +20 -0
  224. package/dist/db/schema/promotion-redemptions.d.ts +143 -0
  225. package/dist/db/schema/promotion-redemptions.js +18 -0
  226. package/dist/db/schema/promotions.d.ts +445 -0
  227. package/dist/db/schema/promotions.js +48 -0
  228. package/dist/db/schema/provider-credentials.d.ts +201 -0
  229. package/dist/db/schema/provider-credentials.js +36 -0
  230. package/dist/db/schema/rate-limit-entries.d.ts +75 -0
  231. package/dist/db/schema/rate-limit-entries.js +7 -0
  232. package/dist/db/schema/secret-audit-log.d.ts +109 -0
  233. package/dist/db/schema/secret-audit-log.js +15 -0
  234. package/dist/db/schema/session-usage.d.ts +194 -0
  235. package/dist/db/schema/session-usage.js +19 -0
  236. package/dist/db/schema/spending-limits.d.ts +92 -0
  237. package/dist/db/schema/spending-limits.js +8 -0
  238. package/dist/db/schema/tenant-addons.d.ts +58 -0
  239. package/dist/db/schema/tenant-addons.js +9 -0
  240. package/dist/db/schema/tenant-api-keys.d.ts +131 -0
  241. package/dist/db/schema/tenant-api-keys.js +21 -0
  242. package/dist/db/schema/tenant-capability-settings.d.ts +79 -0
  243. package/dist/db/schema/tenant-capability-settings.js +12 -0
  244. package/dist/db/schema/tenant-customers.d.ts +303 -0
  245. package/dist/db/schema/tenant-customers.js +25 -0
  246. package/dist/db/schema/tenants.d.ts +126 -0
  247. package/dist/db/schema/tenants.js +18 -0
  248. package/dist/db/schema/user-roles.d.ts +98 -0
  249. package/dist/db/schema/user-roles.js +18 -0
  250. package/dist/db/schema/webhook-seen-events.d.ts +58 -0
  251. package/dist/db/schema/webhook-seen-events.js +9 -0
  252. package/dist/email/billing-emails.d.ts +51 -0
  253. package/dist/email/billing-emails.js +163 -0
  254. package/dist/email/billing-emails.test.d.ts +1 -0
  255. package/dist/email/billing-emails.test.js +162 -0
  256. package/dist/email/client.d.ts +51 -0
  257. package/dist/email/client.js +102 -0
  258. package/dist/email/client.test.d.ts +1 -0
  259. package/dist/email/client.test.js +120 -0
  260. package/dist/email/drizzle-billing-email-repository.d.ts +21 -0
  261. package/dist/email/drizzle-billing-email-repository.js +36 -0
  262. package/dist/email/drizzle-billing-email-repository.test.d.ts +1 -0
  263. package/dist/email/drizzle-billing-email-repository.test.js +42 -0
  264. package/dist/email/index.d.ts +33 -0
  265. package/dist/email/index.js +22 -0
  266. package/dist/email/notification-preferences-store.d.ts +12 -0
  267. package/dist/email/notification-preferences-store.js +82 -0
  268. package/dist/email/notification-preferences-store.test.d.ts +1 -0
  269. package/dist/email/notification-preferences-store.test.js +86 -0
  270. package/dist/email/notification-queue-store.d.ts +25 -0
  271. package/dist/email/notification-queue-store.js +97 -0
  272. package/dist/email/notification-queue-store.test.d.ts +1 -0
  273. package/dist/email/notification-queue-store.test.js +177 -0
  274. package/dist/email/notification-repository-types.d.ts +70 -0
  275. package/dist/email/notification-repository-types.js +6 -0
  276. package/dist/email/notification-service.d.ts +41 -0
  277. package/dist/email/notification-service.js +196 -0
  278. package/dist/email/notification-service.test.d.ts +1 -0
  279. package/dist/email/notification-service.test.js +160 -0
  280. package/dist/email/notification-templates.d.ts +18 -0
  281. package/dist/email/notification-templates.js +574 -0
  282. package/dist/email/notification-templates.test.d.ts +1 -0
  283. package/dist/email/notification-templates.test.js +238 -0
  284. package/dist/email/notification-worker.d.ts +24 -0
  285. package/dist/email/notification-worker.js +109 -0
  286. package/dist/email/notification-worker.test.d.ts +1 -0
  287. package/dist/email/notification-worker.test.js +153 -0
  288. package/dist/email/require-verified.d.ts +25 -0
  289. package/dist/email/require-verified.js +52 -0
  290. package/dist/email/require-verified.test.d.ts +1 -0
  291. package/dist/email/require-verified.test.js +62 -0
  292. package/dist/email/resend-adapter.d.ts +47 -0
  293. package/dist/email/resend-adapter.js +137 -0
  294. package/dist/email/resend-adapter.test.d.ts +1 -0
  295. package/dist/email/resend-adapter.test.js +190 -0
  296. package/dist/email/templates.d.ts +22 -0
  297. package/dist/email/templates.js +359 -0
  298. package/dist/email/templates.test.d.ts +1 -0
  299. package/dist/email/templates.test.js +170 -0
  300. package/dist/email/verification.d.ts +42 -0
  301. package/dist/email/verification.js +83 -0
  302. package/dist/email/verification.test.d.ts +1 -0
  303. package/dist/email/verification.test.js +141 -0
  304. package/dist/index.d.ts +13 -0
  305. package/dist/index.js +23 -0
  306. package/dist/metering/aggregator.d.ts +54 -0
  307. package/dist/metering/aggregator.js +123 -0
  308. package/dist/metering/aggregator.test.d.ts +1 -0
  309. package/dist/metering/aggregator.test.js +179 -0
  310. package/dist/metering/dlq.d.ts +31 -0
  311. package/dist/metering/dlq.js +82 -0
  312. package/dist/metering/dlq.test.d.ts +1 -0
  313. package/dist/metering/dlq.test.js +117 -0
  314. package/dist/metering/drizzle-usage-summary-repository.d.ts +67 -0
  315. package/dist/metering/drizzle-usage-summary-repository.js +98 -0
  316. package/dist/metering/emitter.d.ts +66 -0
  317. package/dist/metering/emitter.js +185 -0
  318. package/dist/metering/emitter.test.d.ts +1 -0
  319. package/dist/metering/emitter.test.js +171 -0
  320. package/dist/metering/index.d.ts +11 -0
  321. package/dist/metering/index.js +5 -0
  322. package/dist/metering/load-test.bench.d.ts +1 -0
  323. package/dist/metering/load-test.bench.js +103 -0
  324. package/dist/metering/meter-event-repository.d.ts +33 -0
  325. package/dist/metering/meter-event-repository.js +58 -0
  326. package/dist/metering/meter-repositories.test.d.ts +1 -0
  327. package/dist/metering/meter-repositories.test.js +419 -0
  328. package/dist/metering/metering.test.d.ts +1 -0
  329. package/dist/metering/metering.test.js +1046 -0
  330. package/dist/metering/reconciliation-cron.d.ts +37 -0
  331. package/dist/metering/reconciliation-cron.js +85 -0
  332. package/dist/metering/reconciliation-cron.test.d.ts +1 -0
  333. package/dist/metering/reconciliation-cron.test.js +162 -0
  334. package/dist/metering/reconciliation-repository.d.ts +27 -0
  335. package/dist/metering/reconciliation-repository.js +43 -0
  336. package/dist/metering/reconciliation-repository.test.d.ts +1 -0
  337. package/dist/metering/reconciliation-repository.test.js +160 -0
  338. package/dist/metering/types.d.ts +88 -0
  339. package/dist/metering/types.js +1 -0
  340. package/dist/metering/wal.d.ts +49 -0
  341. package/dist/metering/wal.js +124 -0
  342. package/dist/metering/wal.test.d.ts +1 -0
  343. package/dist/metering/wal.test.js +175 -0
  344. package/dist/middleware/csrf.d.ts +24 -0
  345. package/dist/middleware/csrf.js +80 -0
  346. package/dist/middleware/csrf.test.d.ts +1 -0
  347. package/dist/middleware/csrf.test.js +152 -0
  348. package/dist/middleware/drizzle-rate-limit-repository.d.ts +9 -0
  349. package/dist/middleware/drizzle-rate-limit-repository.js +52 -0
  350. package/dist/middleware/drizzle-rate-limit-repository.test.d.ts +1 -0
  351. package/dist/middleware/drizzle-rate-limit-repository.test.js +74 -0
  352. package/dist/middleware/get-client-ip.d.ts +22 -0
  353. package/dist/middleware/get-client-ip.js +51 -0
  354. package/dist/middleware/get-client-ip.test.d.ts +1 -0
  355. package/dist/middleware/get-client-ip.test.js +40 -0
  356. package/dist/middleware/index.d.ts +5 -0
  357. package/dist/middleware/index.js +4 -0
  358. package/dist/middleware/rate-limit-repository.d.ts +19 -0
  359. package/dist/middleware/rate-limit-repository.js +1 -0
  360. package/dist/middleware/rate-limit.d.ts +57 -0
  361. package/dist/middleware/rate-limit.js +109 -0
  362. package/dist/middleware/rate-limit.test.d.ts +1 -0
  363. package/dist/middleware/rate-limit.test.js +247 -0
  364. package/dist/security/credential-vault/audit-repository.d.ts +27 -0
  365. package/dist/security/credential-vault/audit-repository.js +42 -0
  366. package/dist/security/credential-vault/audit-repository.test.d.ts +1 -0
  367. package/dist/security/credential-vault/audit-repository.test.js +78 -0
  368. package/dist/security/credential-vault/credential-repository.d.ts +94 -0
  369. package/dist/security/credential-vault/credential-repository.js +145 -0
  370. package/dist/security/credential-vault/credential-repository.test.d.ts +1 -0
  371. package/dist/security/credential-vault/credential-repository.test.js +206 -0
  372. package/dist/security/credential-vault/index.d.ts +12 -0
  373. package/dist/security/credential-vault/index.js +6 -0
  374. package/dist/security/credential-vault/key-rotation.d.ts +18 -0
  375. package/dist/security/credential-vault/key-rotation.js +52 -0
  376. package/dist/security/credential-vault/key-rotation.test.d.ts +1 -0
  377. package/dist/security/credential-vault/key-rotation.test.js +95 -0
  378. package/dist/security/credential-vault/migrate-plaintext.d.ts +15 -0
  379. package/dist/security/credential-vault/migrate-plaintext.js +80 -0
  380. package/dist/security/credential-vault/migrate-plaintext.test.d.ts +1 -0
  381. package/dist/security/credential-vault/migrate-plaintext.test.js +111 -0
  382. package/dist/security/credential-vault/migration-check.d.ts +15 -0
  383. package/dist/security/credential-vault/migration-check.js +71 -0
  384. package/dist/security/credential-vault/migration-check.test.d.ts +1 -0
  385. package/dist/security/credential-vault/migration-check.test.js +457 -0
  386. package/dist/security/credential-vault/store.d.ts +106 -0
  387. package/dist/security/credential-vault/store.js +181 -0
  388. package/dist/security/credential-vault/store.test.d.ts +1 -0
  389. package/dist/security/credential-vault/store.test.js +482 -0
  390. package/dist/security/encryption.d.ts +22 -0
  391. package/dist/security/encryption.js +53 -0
  392. package/dist/security/encryption.test.d.ts +1 -0
  393. package/dist/security/encryption.test.js +95 -0
  394. package/dist/security/host-validation.d.ts +11 -0
  395. package/dist/security/host-validation.js +108 -0
  396. package/dist/security/host-validation.test.d.ts +1 -0
  397. package/dist/security/host-validation.test.js +106 -0
  398. package/dist/security/index.d.ts +11 -0
  399. package/dist/security/index.js +11 -0
  400. package/dist/security/key-audit.d.ts +16 -0
  401. package/dist/security/key-audit.js +35 -0
  402. package/dist/security/key-audit.test.d.ts +1 -0
  403. package/dist/security/key-audit.test.js +50 -0
  404. package/dist/security/key-injection.d.ts +28 -0
  405. package/dist/security/key-injection.js +57 -0
  406. package/dist/security/key-injection.test.d.ts +1 -0
  407. package/dist/security/key-injection.test.js +97 -0
  408. package/dist/security/key-validation.d.ts +16 -0
  409. package/dist/security/key-validation.js +78 -0
  410. package/dist/security/key-validation.test.d.ts +1 -0
  411. package/dist/security/key-validation.test.js +87 -0
  412. package/dist/security/redirect-allowlist.d.ts +6 -0
  413. package/dist/security/redirect-allowlist.js +36 -0
  414. package/dist/security/redirect-allowlist.test.d.ts +1 -0
  415. package/dist/security/redirect-allowlist.test.js +55 -0
  416. package/dist/security/tenant-keys/capability-settings-store.d.ts +22 -0
  417. package/dist/security/tenant-keys/capability-settings-store.js +33 -0
  418. package/dist/security/tenant-keys/capability-settings-store.test.d.ts +1 -0
  419. package/dist/security/tenant-keys/capability-settings-store.test.js +77 -0
  420. package/dist/security/tenant-keys/index.d.ts +10 -0
  421. package/dist/security/tenant-keys/index.js +5 -0
  422. package/dist/security/tenant-keys/key-resolution-repository.d.ts +15 -0
  423. package/dist/security/tenant-keys/key-resolution-repository.js +18 -0
  424. package/dist/security/tenant-keys/key-resolution-repository.test.d.ts +1 -0
  425. package/dist/security/tenant-keys/key-resolution-repository.test.js +72 -0
  426. package/dist/security/tenant-keys/key-resolution.d.ts +39 -0
  427. package/dist/security/tenant-keys/key-resolution.js +59 -0
  428. package/dist/security/tenant-keys/key-resolution.test.d.ts +1 -0
  429. package/dist/security/tenant-keys/key-resolution.test.js +97 -0
  430. package/dist/security/tenant-keys/org-key-resolution.d.ts +30 -0
  431. package/dist/security/tenant-keys/org-key-resolution.js +50 -0
  432. package/dist/security/tenant-keys/org-key-resolution.test.d.ts +1 -0
  433. package/dist/security/tenant-keys/org-key-resolution.test.js +103 -0
  434. package/dist/security/tenant-keys/tenant-key-repository.d.ts +36 -0
  435. package/dist/security/tenant-keys/tenant-key-repository.js +96 -0
  436. package/dist/security/tenant-keys/tenant-key-repository.test.d.ts +1 -0
  437. package/dist/security/tenant-keys/tenant-key-repository.test.js +114 -0
  438. package/dist/security/types.d.ts +35 -0
  439. package/dist/security/types.js +15 -0
  440. package/dist/tenancy/drizzle-org-repository.d.ts +40 -0
  441. package/dist/tenancy/drizzle-org-repository.js +126 -0
  442. package/dist/tenancy/index.d.ts +6 -0
  443. package/dist/tenancy/index.js +3 -0
  444. package/dist/tenancy/org-member-repository.d.ts +57 -0
  445. package/dist/tenancy/org-member-repository.js +99 -0
  446. package/dist/tenancy/org-repository.test.d.ts +1 -0
  447. package/dist/tenancy/org-repository.test.js +143 -0
  448. package/dist/tenancy/org-service.d.ts +70 -0
  449. package/dist/tenancy/org-service.js +223 -0
  450. package/dist/tenancy/org-service.test.d.ts +1 -0
  451. package/dist/tenancy/org-service.test.js +550 -0
  452. package/dist/test/db.d.ts +33 -0
  453. package/dist/test/db.js +65 -0
  454. package/dist/trpc/index.d.ts +1 -0
  455. package/dist/trpc/index.js +1 -0
  456. package/dist/trpc/init.d.ts +49 -0
  457. package/dist/trpc/init.js +108 -0
  458. package/dist/trpc/init.test.d.ts +1 -0
  459. package/dist/trpc/init.test.js +154 -0
  460. package/drizzle/migrations/0000_slippery_mandrill.sql +559 -0
  461. package/drizzle/migrations/meta/0000_snapshot.json +4374 -0
  462. package/drizzle/migrations/meta/_journal.json +13 -0
  463. package/drizzle.config.ts +41 -0
  464. package/package.json +64 -0
  465. package/src/admin/admin-audit-log-repository.ts +135 -0
  466. package/src/admin/audit-log.ts +111 -0
  467. package/src/admin/index.ts +6 -0
  468. package/src/admin/role-store.ts +134 -0
  469. package/src/auth/api-key-repository.test.ts +63 -0
  470. package/src/auth/api-key-repository.ts +46 -0
  471. package/src/auth/auth.test.ts +166 -0
  472. package/src/auth/better-auth.ts +216 -0
  473. package/src/auth/index.ts +520 -0
  474. package/src/auth/login-history-repository.test.ts +54 -0
  475. package/src/auth/login-history-repository.ts +28 -0
  476. package/src/auth/middleware.test.ts +264 -0
  477. package/src/auth/middleware.ts +117 -0
  478. package/src/auth/scoped-tokens.test.ts +362 -0
  479. package/src/auth/tenant-access.test.ts +69 -0
  480. package/src/auth/user-creator.test.ts +98 -0
  481. package/src/auth/user-creator.ts +54 -0
  482. package/src/auth/user-role-repository.test.ts +149 -0
  483. package/src/auth/user-role-repository.ts +67 -0
  484. package/src/billing/drizzle-webhook-seen-repository.ts +34 -0
  485. package/src/billing/index.ts +22 -0
  486. package/src/billing/payment-processor.test.ts +93 -0
  487. package/src/billing/payment-processor.ts +150 -0
  488. package/src/billing/payram/cents-credits-boundary.test.ts +84 -0
  489. package/src/billing/payram/charge-store.test.ts +84 -0
  490. package/src/billing/payram/charge-store.ts +109 -0
  491. package/src/billing/payram/checkout.test.ts +99 -0
  492. package/src/billing/payram/checkout.ts +40 -0
  493. package/src/billing/payram/client.test.ts +62 -0
  494. package/src/billing/payram/client.ts +21 -0
  495. package/src/billing/payram/index.ts +14 -0
  496. package/src/billing/payram/types.ts +44 -0
  497. package/src/billing/payram/webhook.test.ts +318 -0
  498. package/src/billing/payram/webhook.ts +97 -0
  499. package/src/billing/stripe/cents-credits-boundary.test.ts +70 -0
  500. package/src/billing/stripe/checkout.test.ts +186 -0
  501. package/src/billing/stripe/checkout.ts +82 -0
  502. package/src/billing/stripe/client.test.ts +64 -0
  503. package/src/billing/stripe/client.ts +39 -0
  504. package/src/billing/stripe/credit-prices.test.ts +114 -0
  505. package/src/billing/stripe/credit-prices.ts +113 -0
  506. package/src/billing/stripe/index.ts +14 -0
  507. package/src/billing/stripe/payment-methods-detach-all.test.ts +53 -0
  508. package/src/billing/stripe/payment-methods.test.ts +157 -0
  509. package/src/billing/stripe/payment-methods.ts +76 -0
  510. package/src/billing/stripe/portal.test.ts +63 -0
  511. package/src/billing/stripe/portal.ts +25 -0
  512. package/src/billing/stripe/setup-intent.test.ts +78 -0
  513. package/src/billing/stripe/setup-intent.ts +34 -0
  514. package/src/billing/stripe/stripe-payment-processor.test.ts +517 -0
  515. package/src/billing/stripe/stripe-payment-processor.ts +255 -0
  516. package/src/billing/stripe/tenant-store.test.ts +124 -0
  517. package/src/billing/stripe/tenant-store.ts +151 -0
  518. package/src/billing/stripe/types.ts +53 -0
  519. package/src/billing/webhook-seen-repository.ts +24 -0
  520. package/src/config/billing-env.test.ts +54 -0
  521. package/src/config/index.ts +44 -0
  522. package/src/config/logger.ts +12 -0
  523. package/src/config/provider-endpoints.ts +14 -0
  524. package/src/credits/auto-topup-charge.test.ts +292 -0
  525. package/src/credits/auto-topup-charge.ts +171 -0
  526. package/src/credits/auto-topup-event-log-repository.test.ts +99 -0
  527. package/src/credits/auto-topup-event-log-repository.ts +30 -0
  528. package/src/credits/auto-topup-schedule.test.ts +179 -0
  529. package/src/credits/auto-topup-schedule.ts +93 -0
  530. package/src/credits/auto-topup-settings-repository.test.ts +123 -0
  531. package/src/credits/auto-topup-settings-repository.ts +245 -0
  532. package/src/credits/auto-topup-usage.test.ts +220 -0
  533. package/src/credits/auto-topup-usage.ts +68 -0
  534. package/src/credits/credit-expiry-cron.test.ts +125 -0
  535. package/src/credits/credit-expiry-cron.ts +76 -0
  536. package/src/credits/credit-ledger-extra.test.ts +57 -0
  537. package/src/credits/credit-ledger.bench.ts +56 -0
  538. package/src/credits/credit-ledger.test.ts +276 -0
  539. package/src/credits/credit-ledger.ts +450 -0
  540. package/src/credits/credit-transaction-repository.test.ts +274 -0
  541. package/src/credits/credit-transaction-repository.ts +62 -0
  542. package/src/credits/credit.test.ts +234 -0
  543. package/src/credits/credit.ts +160 -0
  544. package/src/credits/dividend-cron.test.ts +158 -0
  545. package/src/credits/dividend-cron.ts +127 -0
  546. package/src/credits/dividend-repository.test.ts +223 -0
  547. package/src/credits/dividend-repository.ts +182 -0
  548. package/src/credits/index.ts +25 -0
  549. package/src/credits/repository-types.ts +33 -0
  550. package/src/credits/signup-grant.test.ts +63 -0
  551. package/src/credits/signup-grant.ts +44 -0
  552. package/src/credits/tenant-customer-repository.ts +28 -0
  553. package/src/db/auth-user-repository.ts +124 -0
  554. package/src/db/credit-column.ts +17 -0
  555. package/src/db/index.ts +21 -0
  556. package/src/db/schema/account-deletion-requests.ts +41 -0
  557. package/src/db/schema/account-export-requests.ts +24 -0
  558. package/src/db/schema/admin-audit.ts +26 -0
  559. package/src/db/schema/admin-users.ts +31 -0
  560. package/src/db/schema/affiliate-fraud.ts +23 -0
  561. package/src/db/schema/affiliate.ts +38 -0
  562. package/src/db/schema/coupon-codes.ts +22 -0
  563. package/src/db/schema/credit-auto-topup-settings.ts +32 -0
  564. package/src/db/schema/credit-auto-topup.ts +26 -0
  565. package/src/db/schema/credits.ts +44 -0
  566. package/src/db/schema/dividend-distributions.ts +24 -0
  567. package/src/db/schema/email-notifications.ts +26 -0
  568. package/src/db/schema/index.ts +33 -0
  569. package/src/db/schema/meter-events.ts +70 -0
  570. package/src/db/schema/notification-preferences.ts +19 -0
  571. package/src/db/schema/notification-queue.ts +45 -0
  572. package/src/db/schema/org-memberships.ts +20 -0
  573. package/src/db/schema/organization-members.ts +37 -0
  574. package/src/db/schema/payram.ts +26 -0
  575. package/src/db/schema/platform-api-keys.ts +25 -0
  576. package/src/db/schema/promotion-redemptions.ts +23 -0
  577. package/src/db/schema/promotions.ts +57 -0
  578. package/src/db/schema/provider-credentials.ts +41 -0
  579. package/src/db/schema/rate-limit-entries.ts +12 -0
  580. package/src/db/schema/secret-audit-log.ts +20 -0
  581. package/src/db/schema/session-usage.ts +24 -0
  582. package/src/db/schema/spending-limits.ts +9 -0
  583. package/src/db/schema/tenant-addons.ts +14 -0
  584. package/src/db/schema/tenant-api-keys.ts +26 -0
  585. package/src/db/schema/tenant-capability-settings.ts +17 -0
  586. package/src/db/schema/tenant-customers.ts +35 -0
  587. package/src/db/schema/tenants.ts +23 -0
  588. package/src/db/schema/user-roles.ts +23 -0
  589. package/src/db/schema/webhook-seen-events.ts +14 -0
  590. package/src/email/billing-emails.test.ts +198 -0
  591. package/src/email/billing-emails.ts +211 -0
  592. package/src/email/client.test.ts +149 -0
  593. package/src/email/client.ts +137 -0
  594. package/src/email/drizzle-billing-email-repository.test.ts +52 -0
  595. package/src/email/drizzle-billing-email-repository.ts +59 -0
  596. package/src/email/index.ts +57 -0
  597. package/src/email/notification-preferences-store.test.ts +102 -0
  598. package/src/email/notification-preferences-store.ts +90 -0
  599. package/src/email/notification-queue-store.test.ts +215 -0
  600. package/src/email/notification-queue-store.ts +127 -0
  601. package/src/email/notification-repository-types.ts +101 -0
  602. package/src/email/notification-service.test.ts +178 -0
  603. package/src/email/notification-service.ts +265 -0
  604. package/src/email/notification-templates.test.ts +261 -0
  605. package/src/email/notification-templates.ts +727 -0
  606. package/src/email/notification-worker.test.ts +189 -0
  607. package/src/email/notification-worker.ts +133 -0
  608. package/src/email/require-verified.ts +65 -0
  609. package/src/email/resend-adapter.test.ts +253 -0
  610. package/src/email/resend-adapter.ts +157 -0
  611. package/src/email/templates.test.ts +217 -0
  612. package/src/email/templates.ts +469 -0
  613. package/src/email/verification.test.ts +185 -0
  614. package/src/email/verification.ts +110 -0
  615. package/src/index.ts +51 -0
  616. package/src/metering/aggregator.test.ts +239 -0
  617. package/src/metering/aggregator.ts +160 -0
  618. package/src/metering/dlq.test.ts +134 -0
  619. package/src/metering/dlq.ts +102 -0
  620. package/src/metering/drizzle-usage-summary-repository.ts +167 -0
  621. package/src/metering/emitter.test.ts +202 -0
  622. package/src/metering/emitter.ts +227 -0
  623. package/src/metering/index.ts +21 -0
  624. package/src/metering/load-test.bench.ts +130 -0
  625. package/src/metering/meter-event-repository.ts +87 -0
  626. package/src/metering/meter-repositories.test.ts +491 -0
  627. package/src/metering/metering.test.ts +1317 -0
  628. package/src/metering/reconciliation-cron.test.ts +202 -0
  629. package/src/metering/reconciliation-cron.ts +134 -0
  630. package/src/metering/reconciliation-repository.test.ts +196 -0
  631. package/src/metering/reconciliation-repository.ts +83 -0
  632. package/src/metering/types.ts +93 -0
  633. package/src/metering/wal.test.ts +222 -0
  634. package/src/metering/wal.ts +139 -0
  635. package/src/middleware/csrf.test.ts +178 -0
  636. package/src/middleware/csrf.ts +101 -0
  637. package/src/middleware/drizzle-rate-limit-repository.test.ts +97 -0
  638. package/src/middleware/drizzle-rate-limit-repository.ts +57 -0
  639. package/src/middleware/get-client-ip.test.ts +49 -0
  640. package/src/middleware/get-client-ip.ts +62 -0
  641. package/src/middleware/index.ts +12 -0
  642. package/src/middleware/rate-limit-repository.ts +22 -0
  643. package/src/middleware/rate-limit.test.ts +338 -0
  644. package/src/middleware/rate-limit.ts +169 -0
  645. package/src/security/credential-vault/audit-repository.test.ts +91 -0
  646. package/src/security/credential-vault/audit-repository.ts +64 -0
  647. package/src/security/credential-vault/credential-repository.test.ts +264 -0
  648. package/src/security/credential-vault/credential-repository.ts +233 -0
  649. package/src/security/credential-vault/index.ts +26 -0
  650. package/src/security/credential-vault/key-rotation.test.ts +139 -0
  651. package/src/security/credential-vault/key-rotation.ts +70 -0
  652. package/src/security/credential-vault/migrate-plaintext.test.ts +138 -0
  653. package/src/security/credential-vault/migrate-plaintext.ts +101 -0
  654. package/src/security/credential-vault/migration-check.test.ts +533 -0
  655. package/src/security/credential-vault/migration-check.ts +88 -0
  656. package/src/security/credential-vault/store.test.ts +569 -0
  657. package/src/security/credential-vault/store.ts +284 -0
  658. package/src/security/encryption.test.ts +114 -0
  659. package/src/security/encryption.ts +65 -0
  660. package/src/security/host-validation.test.ts +136 -0
  661. package/src/security/host-validation.ts +116 -0
  662. package/src/security/index.ts +59 -0
  663. package/src/security/key-audit.test.ts +57 -0
  664. package/src/security/key-audit.ts +45 -0
  665. package/src/security/key-injection.test.ts +131 -0
  666. package/src/security/key-injection.ts +71 -0
  667. package/src/security/key-validation.test.ts +111 -0
  668. package/src/security/key-validation.ts +84 -0
  669. package/src/security/redirect-allowlist.test.ts +70 -0
  670. package/src/security/redirect-allowlist.ts +35 -0
  671. package/src/security/tenant-keys/capability-settings-store.test.ts +98 -0
  672. package/src/security/tenant-keys/capability-settings-store.ts +53 -0
  673. package/src/security/tenant-keys/index.ts +10 -0
  674. package/src/security/tenant-keys/key-resolution-repository.test.ts +95 -0
  675. package/src/security/tenant-keys/key-resolution-repository.ts +31 -0
  676. package/src/security/tenant-keys/key-resolution.test.ts +173 -0
  677. package/src/security/tenant-keys/key-resolution.ts +87 -0
  678. package/src/security/tenant-keys/org-key-resolution.test.ts +217 -0
  679. package/src/security/tenant-keys/org-key-resolution.ts +76 -0
  680. package/src/security/tenant-keys/tenant-key-repository.test.ts +143 -0
  681. package/src/security/tenant-keys/tenant-key-repository.ts +130 -0
  682. package/src/security/types.ts +43 -0
  683. package/src/tenancy/drizzle-org-repository.ts +169 -0
  684. package/src/tenancy/index.ts +6 -0
  685. package/src/tenancy/org-member-repository.ts +159 -0
  686. package/src/tenancy/org-repository.test.ts +172 -0
  687. package/src/tenancy/org-service.test.ts +634 -0
  688. package/src/tenancy/org-service.ts +290 -0
  689. package/src/test/db.ts +97 -0
  690. package/src/trpc/index.ts +11 -0
  691. package/src/trpc/init.test.ts +196 -0
  692. package/src/trpc/init.ts +138 -0
  693. package/tsconfig.json +20 -0
  694. package/vitest.config.ts +8 -0
@@ -0,0 +1,202 @@
1
+ import crypto from "node:crypto";
2
+ import type { PGlite } from "@electric-sql/pglite";
3
+ import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
4
+ import type { PlatformDb } from "../db/index.js";
5
+ import { usageSummaries } from "../db/schema/meter-events.js";
6
+ import { createTestDb, truncateAllTables } from "../test/db.js";
7
+ import { Credit } from "../credits/credit.js";
8
+ import { CreditLedger } from "../credits/credit-ledger.js";
9
+ import { runReconciliation } from "./reconciliation-cron.js";
10
+ import { DrizzleAdapterUsageRepository, DrizzleUsageSummaryRepository } from "./reconciliation-repository.js";
11
+
12
+ /** Today's date as YYYY-MM-DD (UTC). We use "today" as targetDate since the
13
+ * credit ledger inserts use `now()` for createdAt, so debits land in today's window. */
14
+ const TODAY = new Date().toISOString().slice(0, 10);
15
+ const DAY_START = new Date(`${TODAY}T00:00:00Z`).getTime();
16
+ const DAY_END = DAY_START + 24 * 60 * 60 * 1000;
17
+
18
+ describe("runReconciliation", () => {
19
+ let pool: PGlite;
20
+ let db: PlatformDb;
21
+ let ledger: CreditLedger;
22
+ let usageSummaryRepo: DrizzleUsageSummaryRepository;
23
+ let adapterUsageRepo: DrizzleAdapterUsageRepository;
24
+
25
+ beforeAll(async () => {
26
+ const t = await createTestDb();
27
+ pool = t.pool;
28
+ db = t.db;
29
+ ledger = new CreditLedger(db);
30
+ usageSummaryRepo = new DrizzleUsageSummaryRepository(db);
31
+ adapterUsageRepo = new DrizzleAdapterUsageRepository(db);
32
+ });
33
+
34
+ afterAll(async () => {
35
+ await pool.close();
36
+ });
37
+
38
+ beforeEach(async () => {
39
+ await truncateAllTables(pool);
40
+ });
41
+
42
+ /** Insert a usage_summaries row directly. */
43
+ async function insertSummary(opts: {
44
+ tenant: string;
45
+ totalCharge: number;
46
+ windowStart?: number;
47
+ windowEnd?: number;
48
+ capability?: string;
49
+ provider?: string;
50
+ }) {
51
+ await db.insert(usageSummaries).values({
52
+ id: crypto.randomUUID(),
53
+ tenant: opts.tenant,
54
+ capability: opts.capability ?? "chat",
55
+ provider: opts.provider ?? "openai",
56
+ eventCount: 1,
57
+ totalCost: 0,
58
+ totalCharge: opts.totalCharge,
59
+ totalDuration: 0,
60
+ windowStart: opts.windowStart ?? DAY_START,
61
+ windowEnd: opts.windowEnd ?? DAY_END - 1,
62
+ });
63
+ }
64
+
65
+ it("returns empty result when no metering or ledger data", async () => {
66
+ const result = await runReconciliation({ usageSummaryRepo, adapterUsageRepo, targetDate: TODAY });
67
+ expect(result.tenantsChecked).toBe(0);
68
+ expect(result.discrepancies).toEqual([]);
69
+ expect(result.flagged).toEqual([]);
70
+ expect(result.date).toBe(TODAY);
71
+ });
72
+
73
+ it("no discrepancy when metered charge matches ledger debit", async () => {
74
+ const charge = Credit.fromCents(50);
75
+ await insertSummary({ tenant: "t1", totalCharge: charge.toRaw() });
76
+
77
+ await ledger.credit("t1", Credit.fromCents(500), "purchase");
78
+ await ledger.debit("t1", charge, "adapter_usage", "chat usage");
79
+
80
+ const result = await runReconciliation({ usageSummaryRepo, adapterUsageRepo, targetDate: TODAY });
81
+ expect(result.tenantsChecked).toBe(1);
82
+ expect(result.discrepancies).toEqual([]);
83
+ });
84
+
85
+ it("detects drift when metered charge exceeds ledger debit", async () => {
86
+ await insertSummary({ tenant: "t1", totalCharge: Credit.fromCents(100).toRaw() });
87
+
88
+ await ledger.credit("t1", Credit.fromCents(500), "purchase");
89
+ await ledger.debit("t1", Credit.fromCents(80), "adapter_usage", "chat usage");
90
+
91
+ const result = await runReconciliation({ usageSummaryRepo, adapterUsageRepo, targetDate: TODAY });
92
+ expect(result.tenantsChecked).toBe(1);
93
+ expect(result.discrepancies).toHaveLength(1);
94
+ expect(result.discrepancies[0].tenantId).toBe("t1");
95
+ // drift = metered(100c) - ledger(80c) = 20c, in raw units
96
+ expect(result.discrepancies[0].driftRaw).toBe(Credit.fromCents(20).toRaw());
97
+ });
98
+
99
+ it("flags tenant when drift exceeds flag threshold", async () => {
100
+ await insertSummary({ tenant: "t1", totalCharge: Credit.fromCents(200).toRaw() });
101
+
102
+ // No ledger debit at all — simulating a missed deduction
103
+ const onFlagForReview = vi.fn();
104
+ const result = await runReconciliation({
105
+ usageSummaryRepo,
106
+ adapterUsageRepo,
107
+ targetDate: TODAY,
108
+ flagThresholdRaw: Credit.fromCents(100).toRaw(), // $1.00
109
+ onFlagForReview,
110
+ });
111
+
112
+ expect(result.flagged).toContain("t1");
113
+ expect(onFlagForReview).toHaveBeenCalledWith("t1", Credit.fromCents(200).toRaw());
114
+ });
115
+
116
+ it("ignores non-adapter_usage debits (bot_runtime, etc.)", async () => {
117
+ await insertSummary({ tenant: "t1", totalCharge: Credit.fromCents(20).toRaw() });
118
+
119
+ await ledger.credit("t1", Credit.fromCents(500), "purchase");
120
+ // Debit as bot_runtime — should NOT count toward reconciliation
121
+ await ledger.debit("t1", Credit.fromCents(20), "bot_runtime", "daily runtime");
122
+
123
+ const result = await runReconciliation({ usageSummaryRepo, adapterUsageRepo, targetDate: TODAY });
124
+ // Metered 20c, ledger adapter_usage = 0 => drift = 20c
125
+ expect(result.discrepancies).toHaveLength(1);
126
+ expect(result.discrepancies[0].driftRaw).toBe(Credit.fromCents(20).toRaw());
127
+ });
128
+
129
+ it("ignores __sentinel__ rows from usage_summaries", async () => {
130
+ // Insert a sentinel row (should be ignored)
131
+ await db.insert(usageSummaries).values({
132
+ id: crypto.randomUUID(),
133
+ tenant: "__sentinel__",
134
+ capability: "__none__",
135
+ provider: "__none__",
136
+ eventCount: 0,
137
+ totalCost: 0,
138
+ totalCharge: 0,
139
+ totalDuration: 0,
140
+ windowStart: DAY_START,
141
+ windowEnd: DAY_END - 1,
142
+ });
143
+
144
+ const result = await runReconciliation({ usageSummaryRepo, adapterUsageRepo, targetDate: TODAY });
145
+ expect(result.tenantsChecked).toBe(0);
146
+ expect(result.discrepancies).toEqual([]);
147
+ });
148
+
149
+ it("handles multiple tenants independently", async () => {
150
+ // t1: balanced
151
+ await insertSummary({ tenant: "t1", totalCharge: Credit.fromCents(50).toRaw() });
152
+ await ledger.credit("t1", Credit.fromCents(500), "purchase");
153
+ await ledger.debit("t1", Credit.fromCents(50), "adapter_usage", "chat");
154
+
155
+ // t2: drifted
156
+ await insertSummary({ tenant: "t2", totalCharge: Credit.fromCents(100).toRaw() });
157
+ await ledger.credit("t2", Credit.fromCents(500), "purchase");
158
+ await ledger.debit("t2", Credit.fromCents(60), "adapter_usage", "chat");
159
+
160
+ const result = await runReconciliation({ usageSummaryRepo, adapterUsageRepo, targetDate: TODAY });
161
+ expect(result.tenantsChecked).toBe(2);
162
+ expect(result.discrepancies).toHaveLength(1);
163
+ expect(result.discrepancies[0].tenantId).toBe("t2");
164
+ expect(result.discrepancies[0].driftRaw).toBe(Credit.fromCents(40).toRaw());
165
+ });
166
+
167
+ it("uses yesterday as default targetDate", async () => {
168
+ const yesterday = new Date();
169
+ yesterday.setDate(yesterday.getDate() - 1);
170
+ const expectedDate = yesterday.toISOString().slice(0, 10);
171
+
172
+ const result = await runReconciliation({ usageSummaryRepo, adapterUsageRepo });
173
+ expect(result.date).toBe(expectedDate);
174
+ expect(result.tenantsChecked).toBe(0);
175
+ });
176
+
177
+ it("ignores metering data outside the target date window", async () => {
178
+ // Summary from yesterday — should not appear in today's reconciliation
179
+ const yesterday = new Date(`${TODAY}T00:00:00Z`).getTime() - 24 * 60 * 60 * 1000;
180
+ await insertSummary({
181
+ tenant: "t1",
182
+ totalCharge: Credit.fromCents(100).toRaw(),
183
+ windowStart: yesterday,
184
+ windowEnd: yesterday + 24 * 60 * 60 * 1000 - 1,
185
+ });
186
+
187
+ const result = await runReconciliation({ usageSummaryRepo, adapterUsageRepo, targetDate: TODAY });
188
+ expect(result.tenantsChecked).toBe(0);
189
+ });
190
+
191
+ it("handles over-billed drift (ledger > metered)", async () => {
192
+ // Metered 50c but debited 80c (over-billed)
193
+ await insertSummary({ tenant: "t1", totalCharge: Credit.fromCents(50).toRaw() });
194
+
195
+ await ledger.credit("t1", Credit.fromCents(500), "purchase");
196
+ await ledger.debit("t1", Credit.fromCents(80), "adapter_usage", "chat usage");
197
+
198
+ const result = await runReconciliation({ usageSummaryRepo, adapterUsageRepo, targetDate: TODAY });
199
+ expect(result.discrepancies).toHaveLength(1);
200
+ expect(result.discrepancies[0].driftRaw).toBe(Credit.fromCents(-30).toRaw());
201
+ });
202
+ });
@@ -0,0 +1,134 @@
1
+ import { logger } from "../config/logger.js";
2
+ import { Credit } from "../credits/credit.js";
3
+ import type { IAdapterUsageRepository, IUsageSummaryRepository } from "./reconciliation-repository.js";
4
+
5
+ export interface ReconciliationConfig {
6
+ usageSummaryRepo: IUsageSummaryRepository;
7
+ adapterUsageRepo: IAdapterUsageRepository;
8
+ /** The date to reconcile, as YYYY-MM-DD. Defaults to yesterday. */
9
+ targetDate?: string;
10
+ /** Raw nano-dollar threshold below which drift is ignored. Default: 1 cent in raw units. */
11
+ driftThresholdRaw?: number;
12
+ /** If drift exceeds this raw amount, flag the account for review. Default: $1.00 in raw units. */
13
+ flagThresholdRaw?: number;
14
+ /** Callback invoked for each tenant exceeding the flag threshold. */
15
+ onFlagForReview?: (tenantId: string, driftRaw: number) => void | Promise<void>;
16
+ }
17
+
18
+ export interface ReconciliationResult {
19
+ /** Date reconciled (YYYY-MM-DD). */
20
+ date: string;
21
+ /** Number of tenants checked. */
22
+ tenantsChecked: number;
23
+ /** Tenants with drift exceeding driftThresholdRaw. */
24
+ discrepancies: Array<{
25
+ tenantId: string;
26
+ /** Total charge from metering (raw nano-dollars). */
27
+ meteredChargeRaw: number;
28
+ /** Total debits from ledger for adapter_usage (raw nano-dollars, absolute value). */
29
+ ledgerDebitRaw: number;
30
+ /** meteredChargeRaw - ledgerDebitRaw (positive = under-billed, negative = over-billed). */
31
+ driftRaw: number;
32
+ }>;
33
+ /** Tenants flagged for review (drift > flagThresholdRaw). */
34
+ flagged: string[];
35
+ }
36
+
37
+ /**
38
+ * Reconcile metered usage charges against ledger adapter_usage debits
39
+ * for a single day. Logs warnings for any per-tenant drift exceeding the
40
+ * threshold and optionally flags accounts for review.
41
+ */
42
+ export async function runReconciliation(cfg: ReconciliationConfig): Promise<ReconciliationResult> {
43
+ const yesterday = new Date();
44
+ yesterday.setDate(yesterday.getDate() - 1);
45
+ const targetDate = cfg.targetDate ?? yesterday.toISOString().slice(0, 10);
46
+
47
+ const driftThresholdRaw = cfg.driftThresholdRaw ?? Credit.fromCents(1).toRaw();
48
+ const flagThresholdRaw = cfg.flagThresholdRaw ?? Credit.fromCents(100).toRaw();
49
+
50
+ // Convert YYYY-MM-DD to epoch ms range [dayStart, dayEnd)
51
+ const dayStart = new Date(`${targetDate}T00:00:00Z`).getTime();
52
+ const dayEnd = dayStart + 24 * 60 * 60 * 1000;
53
+
54
+ const result: ReconciliationResult = {
55
+ date: targetDate,
56
+ tenantsChecked: 0,
57
+ discrepancies: [],
58
+ flagged: [],
59
+ };
60
+
61
+ // 1. Sum metered charges per tenant from usage_summaries for the day window.
62
+ // Filter out __sentinel__ rows.
63
+ const meteredRows = await cfg.usageSummaryRepo.getAggregatedChargesByWindow(dayStart, dayEnd);
64
+
65
+ // 2. Sum ledger adapter_usage debits per tenant for the same day.
66
+ // credit_transactions.created_at is a text column storing Postgres now() values
67
+ // (e.g. "2026-02-28 17:00:00+00"). Cast to timestamptz for reliable range comparison.
68
+ const dayStartIso = new Date(dayStart).toISOString();
69
+ const dayEndIso = new Date(dayEnd).toISOString();
70
+
71
+ const ledgerRows = await cfg.adapterUsageRepo.getAggregatedAdapterUsageDebits(dayStartIso, dayEndIso);
72
+
73
+ // 3. Build union of tenant IDs from both sources so ledger-only tenants
74
+ // (e.g. debits with no metering summary — negative drift / over-billed) are also checked.
75
+ const meteredMap = new Map<string, number>();
76
+ for (const row of meteredRows) {
77
+ meteredMap.set(row.tenant, row.totalChargeRaw);
78
+ }
79
+
80
+ const ledgerMap = new Map<string, number>();
81
+ for (const row of ledgerRows) {
82
+ ledgerMap.set(row.tenantId, row.totalDebitRaw);
83
+ }
84
+
85
+ const allTenants = new Set([...meteredMap.keys(), ...ledgerMap.keys()]);
86
+
87
+ if (allTenants.size === 0) {
88
+ return result;
89
+ }
90
+
91
+ // 4. Compare per-tenant across the union
92
+ result.tenantsChecked = allTenants.size;
93
+
94
+ for (const tenantId of allTenants) {
95
+ const meteredChargeRaw = meteredMap.get(tenantId) ?? 0;
96
+ const ledgerDebitRaw = ledgerMap.get(tenantId) ?? 0;
97
+ const driftRaw = meteredChargeRaw - ledgerDebitRaw;
98
+ const absDrift = Math.abs(driftRaw);
99
+
100
+ if (absDrift > driftThresholdRaw) {
101
+ result.discrepancies.push({
102
+ tenantId,
103
+ meteredChargeRaw,
104
+ ledgerDebitRaw,
105
+ driftRaw,
106
+ });
107
+
108
+ logger.warn("Metering/ledger drift detected", {
109
+ tenantId,
110
+ meteredCharge: Credit.fromRaw(meteredChargeRaw).toDisplayString(),
111
+ ledgerDebit: Credit.fromRaw(ledgerDebitRaw).toDisplayString(),
112
+ drift: Credit.fromRaw(absDrift).toDisplayString(),
113
+ direction: driftRaw > 0 ? "under-billed" : "over-billed",
114
+ date: targetDate,
115
+ });
116
+
117
+ if (absDrift > flagThresholdRaw) {
118
+ result.flagged.push(tenantId);
119
+ if (cfg.onFlagForReview) {
120
+ try {
121
+ await cfg.onFlagForReview(tenantId, driftRaw);
122
+ } catch (err) {
123
+ logger.error("onFlagForReview callback failed for tenant", {
124
+ tenantId,
125
+ error: err instanceof Error ? err.message : String(err),
126
+ });
127
+ }
128
+ }
129
+ }
130
+ }
131
+ }
132
+
133
+ return result;
134
+ }
@@ -0,0 +1,196 @@
1
+ import crypto from "node:crypto";
2
+ import type { PGlite } from "@electric-sql/pglite";
3
+ import { afterAll, beforeAll, beforeEach, describe, expect, it } from "vitest";
4
+ import type { PlatformDb } from "../db/index.js";
5
+ import { createTestDb, seedUsageSummary, truncateAllTables } from "../test/db.js";
6
+ import { Credit } from "../credits/credit.js";
7
+ import { CreditLedger } from "../credits/credit-ledger.js";
8
+ import { DrizzleAdapterUsageRepository, DrizzleUsageSummaryRepository } from "./reconciliation-repository.js";
9
+
10
+ let pool: PGlite;
11
+ let db: PlatformDb;
12
+
13
+ beforeAll(async () => {
14
+ ({ db, pool } = await createTestDb());
15
+ });
16
+
17
+ afterAll(async () => {
18
+ await pool.close();
19
+ });
20
+
21
+ /** Epoch ms for a given date string. */
22
+ function epochMs(dateStr: string): number {
23
+ return new Date(dateStr).getTime();
24
+ }
25
+
26
+ describe("DrizzleUsageSummaryRepository", () => {
27
+ let repo: DrizzleUsageSummaryRepository;
28
+
29
+ beforeEach(async () => {
30
+ await truncateAllTables(pool);
31
+ repo = new DrizzleUsageSummaryRepository(db);
32
+ });
33
+
34
+ it("returns empty array when no usage summaries exist", async () => {
35
+ const result = await repo.getAggregatedChargesByWindow(0, Date.now());
36
+ expect(result).toEqual([]);
37
+ });
38
+
39
+ it("aggregates charges per tenant within [windowStart, windowEnd)", async () => {
40
+ const dayStart = epochMs("2026-02-15T00:00:00Z");
41
+ const dayEnd = epochMs("2026-02-16T00:00:00Z");
42
+
43
+ // Two summaries for t1 within window
44
+ await seedUsageSummary(db, {
45
+ id: crypto.randomUUID(),
46
+ tenant: "t1",
47
+ totalCharge: 5000,
48
+ windowStart: dayStart,
49
+ windowEnd: dayStart + 3600000,
50
+ });
51
+ await seedUsageSummary(db, {
52
+ id: crypto.randomUUID(),
53
+ tenant: "t1",
54
+ totalCharge: 3000,
55
+ windowStart: dayStart + 3600000,
56
+ windowEnd: dayStart + 7200000,
57
+ });
58
+
59
+ // One summary for t2 within window
60
+ await seedUsageSummary(db, {
61
+ id: crypto.randomUUID(),
62
+ tenant: "t2",
63
+ totalCharge: 1000,
64
+ windowStart: dayStart,
65
+ windowEnd: dayStart + 3600000,
66
+ });
67
+
68
+ const result = await repo.getAggregatedChargesByWindow(dayStart, dayEnd);
69
+
70
+ expect(result).toHaveLength(2);
71
+ const t1 = result.find((r) => r.tenant === "t1");
72
+ const t2 = result.find((r) => r.tenant === "t2");
73
+ expect(t1?.totalChargeRaw).toBe(8000); // 5000 + 3000
74
+ expect(t2?.totalChargeRaw).toBe(1000);
75
+ });
76
+
77
+ it("excludes __sentinel__ rows", async () => {
78
+ const dayStart = epochMs("2026-02-15T00:00:00Z");
79
+
80
+ await seedUsageSummary(db, {
81
+ id: crypto.randomUUID(),
82
+ tenant: "__sentinel__",
83
+ totalCharge: 999999,
84
+ windowStart: dayStart,
85
+ windowEnd: dayStart + 3600000,
86
+ });
87
+ await seedUsageSummary(db, {
88
+ id: crypto.randomUUID(),
89
+ tenant: "t1",
90
+ totalCharge: 100,
91
+ windowStart: dayStart,
92
+ windowEnd: dayStart + 3600000,
93
+ });
94
+
95
+ const result = await repo.getAggregatedChargesByWindow(dayStart, dayStart + 86400000);
96
+ expect(result).toHaveLength(1);
97
+ expect(result[0].tenant).toBe("t1");
98
+ });
99
+
100
+ it("excludes summaries outside the window", async () => {
101
+ const dayStart = epochMs("2026-02-15T00:00:00Z");
102
+ const dayEnd = epochMs("2026-02-16T00:00:00Z");
103
+
104
+ // Before window
105
+ await seedUsageSummary(db, {
106
+ id: crypto.randomUUID(),
107
+ tenant: "t1",
108
+ totalCharge: 100,
109
+ windowStart: epochMs("2026-02-14T00:00:00Z"),
110
+ windowEnd: epochMs("2026-02-14T01:00:00Z"),
111
+ });
112
+
113
+ // After window
114
+ await seedUsageSummary(db, {
115
+ id: crypto.randomUUID(),
116
+ tenant: "t1",
117
+ totalCharge: 200,
118
+ windowStart: dayEnd,
119
+ windowEnd: dayEnd + 3600000,
120
+ });
121
+
122
+ const result = await repo.getAggregatedChargesByWindow(dayStart, dayEnd);
123
+ expect(result).toEqual([]);
124
+ });
125
+ });
126
+
127
+ describe("DrizzleAdapterUsageRepository", () => {
128
+ let repo: DrizzleAdapterUsageRepository;
129
+ let ledger: CreditLedger;
130
+
131
+ beforeEach(async () => {
132
+ await truncateAllTables(pool);
133
+ repo = new DrizzleAdapterUsageRepository(db);
134
+ ledger = new CreditLedger(db);
135
+ });
136
+
137
+ it("returns empty array when no adapter_usage debits exist", async () => {
138
+ const today = new Date().toISOString().slice(0, 10);
139
+ const startIso = `${today}T00:00:00Z`;
140
+ const endIso = new Date(new Date(startIso).getTime() + 86400000).toISOString();
141
+ const result = await repo.getAggregatedAdapterUsageDebits(startIso, endIso);
142
+ expect(result).toEqual([]);
143
+ });
144
+
145
+ it("aggregates adapter_usage debits per tenant within [startIso, endIso)", async () => {
146
+ // Fund tenants
147
+ await ledger.credit("t1", Credit.fromCents(1000), "purchase");
148
+ await ledger.credit("t2", Credit.fromCents(1000), "purchase");
149
+
150
+ await ledger.debit("t1", Credit.fromCents(30), "adapter_usage", "t1-debit-1");
151
+ await ledger.debit("t1", Credit.fromCents(20), "adapter_usage", "t1-debit-2");
152
+ await ledger.debit("t2", Credit.fromCents(50), "adapter_usage", "t2-debit-1");
153
+
154
+ // Query window covering today
155
+ const today = new Date().toISOString().slice(0, 10);
156
+ const startIso = `${today}T00:00:00Z`;
157
+ const endIso = new Date(new Date(startIso).getTime() + 86400000).toISOString();
158
+
159
+ const result = await repo.getAggregatedAdapterUsageDebits(startIso, endIso);
160
+ expect(result).toHaveLength(2);
161
+
162
+ const t1 = result.find((r) => r.tenantId === "t1");
163
+ const t2 = result.find((r) => r.tenantId === "t2");
164
+
165
+ expect(t1?.totalDebitRaw).toBe(Credit.fromCents(50).toRaw()); // 30 + 20
166
+ expect(t2?.totalDebitRaw).toBe(Credit.fromCents(50).toRaw());
167
+ });
168
+
169
+ it("excludes non-adapter_usage debit types", async () => {
170
+ await ledger.credit("t1", Credit.fromCents(1000), "purchase");
171
+ await ledger.debit("t1", Credit.fromCents(30), "adapter_usage", "adapter debit");
172
+ await ledger.debit("t1", Credit.fromCents(20), "bot_runtime", "runtime debit");
173
+
174
+ const today = new Date().toISOString().slice(0, 10);
175
+ const startIso = `${today}T00:00:00Z`;
176
+ const endIso = new Date(new Date(startIso).getTime() + 86400000).toISOString();
177
+
178
+ const result = await repo.getAggregatedAdapterUsageDebits(startIso, endIso);
179
+ expect(result).toHaveLength(1);
180
+ expect(result[0].totalDebitRaw).toBe(Credit.fromCents(30).toRaw());
181
+ });
182
+
183
+ it("excludes credit transactions (positive amounts are not debits)", async () => {
184
+ await ledger.credit("t1", Credit.fromCents(1000), "purchase");
185
+ await ledger.debit("t1", Credit.fromCents(10), "adapter_usage", "real debit");
186
+
187
+ const today = new Date().toISOString().slice(0, 10);
188
+ const startIso = `${today}T00:00:00Z`;
189
+ const endIso = new Date(new Date(startIso).getTime() + 86400000).toISOString();
190
+
191
+ const result = await repo.getAggregatedAdapterUsageDebits(startIso, endIso);
192
+ expect(result).toHaveLength(1);
193
+ // Only the 10-cent adapter_usage debit
194
+ expect(result[0].totalDebitRaw).toBe(Credit.fromCents(10).toRaw());
195
+ });
196
+ });
@@ -0,0 +1,83 @@
1
+ import { and, eq, gte, lt, ne, sql } from "drizzle-orm";
2
+ import type { PlatformDb } from "../db/index.js";
3
+ import { creditTransactions } from "../db/schema/credits.js";
4
+ import { usageSummaries } from "../db/schema/meter-events.js";
5
+
6
+ // ---------------------------------------------------------------------------
7
+ // IUsageSummaryRepository
8
+ // ---------------------------------------------------------------------------
9
+
10
+ export interface AggregatedCharge {
11
+ tenant: string;
12
+ totalChargeRaw: number;
13
+ }
14
+
15
+ export interface IUsageSummaryRepository {
16
+ /** Sum metered charges per tenant for windows overlapping [windowStart, windowEnd). */
17
+ getAggregatedChargesByWindow(windowStart: number, windowEnd: number): Promise<AggregatedCharge[]>;
18
+ }
19
+
20
+ export class DrizzleUsageSummaryRepository implements IUsageSummaryRepository {
21
+ constructor(private readonly db: PlatformDb) {}
22
+
23
+ async getAggregatedChargesByWindow(windowStart: number, windowEnd: number): Promise<AggregatedCharge[]> {
24
+ const rows = await this.db
25
+ .select({
26
+ tenant: usageSummaries.tenant,
27
+ // raw SQL: Drizzle cannot express COALESCE with SUM aggregation
28
+ totalCharge: sql<number>`COALESCE(SUM(${usageSummaries.totalCharge}), 0)`,
29
+ })
30
+ .from(usageSummaries)
31
+ .where(
32
+ and(
33
+ gte(usageSummaries.windowStart, windowStart),
34
+ lt(usageSummaries.windowEnd, windowEnd),
35
+ ne(usageSummaries.tenant, "__sentinel__"),
36
+ ),
37
+ )
38
+ .groupBy(usageSummaries.tenant);
39
+
40
+ return rows.map((r) => ({ tenant: r.tenant, totalChargeRaw: Number(r.totalCharge) }));
41
+ }
42
+ }
43
+
44
+ // ---------------------------------------------------------------------------
45
+ // IAdapterUsageRepository
46
+ // ---------------------------------------------------------------------------
47
+
48
+ export interface AggregatedDebit {
49
+ tenantId: string;
50
+ totalDebitRaw: number;
51
+ }
52
+
53
+ export interface IAdapterUsageRepository {
54
+ /** Sum adapter_usage debits per tenant within [startIso, endIso). */
55
+ getAggregatedAdapterUsageDebits(startIso: string, endIso: string): Promise<AggregatedDebit[]>;
56
+ }
57
+
58
+ export class DrizzleAdapterUsageRepository implements IAdapterUsageRepository {
59
+ constructor(private readonly db: PlatformDb) {}
60
+
61
+ async getAggregatedAdapterUsageDebits(startIso: string, endIso: string): Promise<AggregatedDebit[]> {
62
+ const rows = await this.db
63
+ .select({
64
+ tenantId: creditTransactions.tenantId,
65
+ // amount_credits stores negative values for debits; ABS gives the raw positive debit amount.
66
+ // Use the raw column name in sql to bypass the custom creditColumn type serializer.
67
+ // raw SQL: Drizzle cannot express ABS with COALESCE and SUM
68
+ totalDebitRaw: sql<number>`COALESCE(SUM(ABS(amount_credits)), 0)`,
69
+ })
70
+ .from(creditTransactions)
71
+ .where(
72
+ and(
73
+ eq(creditTransactions.type, "adapter_usage"),
74
+ // raw SQL: Drizzle cannot express timestamptz cast for text column date comparison
75
+ sql`${creditTransactions.createdAt}::timestamptz >= ${startIso}::timestamptz`,
76
+ sql`${creditTransactions.createdAt}::timestamptz < ${endIso}::timestamptz`,
77
+ ),
78
+ )
79
+ .groupBy(creditTransactions.tenantId);
80
+
81
+ return rows.map((r) => ({ tenantId: r.tenantId, totalDebitRaw: Number(r.totalDebitRaw) }));
82
+ }
83
+ }