@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,102 @@
1
+ import { appendFileSync, existsSync, mkdirSync, readFileSync } from "node:fs";
2
+ import { dirname } from "node:path";
3
+ import type { MeterEvent } from "./types.js";
4
+
5
+ /**
6
+ * Dead-Letter Queue for meter events that failed to flush after max retries.
7
+ *
8
+ * Events written here are permanently failed and require manual intervention
9
+ * to recover. This ensures we never silently drop billing events.
10
+ */
11
+ export class MeterDLQ {
12
+ private readonly dlqPath: string;
13
+
14
+ constructor(dlqPath: string) {
15
+ this.dlqPath = dlqPath;
16
+ this.ensureDir();
17
+ }
18
+
19
+ private ensureDir(): void {
20
+ const dir = dirname(this.dlqPath);
21
+ if (!existsSync(dir)) {
22
+ mkdirSync(dir, { recursive: true });
23
+ }
24
+ }
25
+
26
+ /**
27
+ * Append a failed event to the DLQ with failure metadata.
28
+ */
29
+ append(event: MeterEvent & { id: string }, error: string, retries: number): void {
30
+ const dlqEntry = {
31
+ ...event,
32
+ dlq_timestamp: Date.now(),
33
+ dlq_error: error,
34
+ dlq_retries: retries,
35
+ };
36
+
37
+ const line = `${JSON.stringify(dlqEntry)}\n`;
38
+ appendFileSync(this.dlqPath, line, { encoding: "utf8", flag: "a" });
39
+ }
40
+
41
+ /**
42
+ * Read all events from the DLQ for manual recovery.
43
+ */
44
+ readAll(): Array<
45
+ MeterEvent & {
46
+ id: string;
47
+ dlq_timestamp: number;
48
+ dlq_error: string;
49
+ dlq_retries: number;
50
+ }
51
+ > {
52
+ if (!existsSync(this.dlqPath)) {
53
+ return [];
54
+ }
55
+
56
+ const content = readFileSync(this.dlqPath, "utf8");
57
+ if (!content.trim()) {
58
+ return [];
59
+ }
60
+
61
+ const entries: Array<
62
+ MeterEvent & {
63
+ id: string;
64
+ dlq_timestamp: number;
65
+ dlq_error: string;
66
+ dlq_retries: number;
67
+ }
68
+ > = [];
69
+ for (const line of content.trim().split("\n")) {
70
+ try {
71
+ entries.push(JSON.parse(line));
72
+ } catch {
73
+ // Skip malformed lines (e.g., from incomplete writes during error conditions).
74
+ }
75
+ }
76
+ return entries;
77
+ }
78
+
79
+ /**
80
+ * Get the number of events in the DLQ.
81
+ */
82
+ count(): number {
83
+ let content: string;
84
+ try {
85
+ content = readFileSync(this.dlqPath, "utf8");
86
+ } catch (err) {
87
+ if ((err as NodeJS.ErrnoException).code === "ENOENT") {
88
+ return 0;
89
+ }
90
+ throw err;
91
+ }
92
+ if (!content) {
93
+ return 0;
94
+ }
95
+ let n = 0;
96
+ for (let i = 0; i < content.length; i++) {
97
+ if (content.charCodeAt(i) === 10) n++;
98
+ }
99
+ if (content.length > 0 && content.charCodeAt(content.length - 1) !== 10) n++;
100
+ return n;
101
+ }
102
+ }
@@ -0,0 +1,167 @@
1
+ import { and, count, desc, eq, gte, lt, lte, max, min, sql, sum } from "drizzle-orm";
2
+ import type { PlatformDb } from "../db/index.js";
3
+ import { meterEvents, usageSummaries } from "../db/schema/meter-events.js";
4
+ import type { UsageSummary } from "./types.js";
5
+
6
+ export interface AggregatedWindowRow {
7
+ tenant: string;
8
+ capability: string;
9
+ provider: string;
10
+ eventCount: number;
11
+ totalCost: number;
12
+ totalCharge: number;
13
+ totalDuration: number;
14
+ }
15
+
16
+ export interface UsageSummaryInsert {
17
+ id: string;
18
+ tenant: string;
19
+ capability: string;
20
+ provider: string;
21
+ eventCount: number;
22
+ totalCost: number;
23
+ totalCharge: number;
24
+ totalDuration: number;
25
+ windowStart: number;
26
+ windowEnd: number;
27
+ }
28
+
29
+ export interface IUsageSummaryRepository {
30
+ /** Get the maximum windowEnd across all usage summaries. Returns 0 if none. */
31
+ getLastWindowEnd(): Promise<number>;
32
+
33
+ /** Get the earliest meter event timestamp before the given time. Returns null if none. */
34
+ getEarliestEventTimestamp(before: number): Promise<number | null>;
35
+
36
+ /** Get aggregated event groups for a time window [start, end). */
37
+ getAggregatedEvents(windowStart: number, windowEnd: number): Promise<AggregatedWindowRow[]>;
38
+
39
+ /** Insert a single usage summary row. */
40
+ insertSummary(values: UsageSummaryInsert): Promise<void>;
41
+
42
+ /** Insert multiple usage summary rows in a transaction. */
43
+ insertSummariesBatch(rows: UsageSummaryInsert[]): Promise<void>;
44
+
45
+ /** Query usage summaries for a tenant within a time range. */
46
+ querySummaries(tenant: string, opts?: { since?: number; until?: number; limit?: number }): Promise<UsageSummary[]>;
47
+
48
+ /** Get a tenant's total usage since a given time. */
49
+ getTenantTotal(
50
+ tenant: string,
51
+ since: number,
52
+ ): Promise<{ totalCost: number; totalCharge: number; eventCount: number }>;
53
+ }
54
+
55
+ export class DrizzleUsageSummaryRepository implements IUsageSummaryRepository {
56
+ constructor(private readonly db: PlatformDb) {}
57
+
58
+ async getLastWindowEnd(): Promise<number> {
59
+ const row = (await this.db.select({ lastEnd: max(usageSummaries.windowEnd) }).from(usageSummaries))[0];
60
+ return row?.lastEnd ?? 0;
61
+ }
62
+
63
+ async getEarliestEventTimestamp(before: number): Promise<number | null> {
64
+ const row = (
65
+ await this.db
66
+ .select({ ts: min(meterEvents.timestamp) })
67
+ .from(meterEvents)
68
+ .where(lt(meterEvents.timestamp, before))
69
+ )[0];
70
+ return row?.ts ?? null;
71
+ }
72
+
73
+ async getAggregatedEvents(windowStart: number, windowEnd: number): Promise<AggregatedWindowRow[]> {
74
+ const rows = await this.db
75
+ .select({
76
+ tenant: meterEvents.tenant,
77
+ capability: meterEvents.capability,
78
+ provider: meterEvents.provider,
79
+ eventCount: count(),
80
+ totalCost: sum(meterEvents.cost),
81
+ totalCharge: sum(meterEvents.charge),
82
+ totalDuration: sql<number>`COALESCE(SUM(${meterEvents.duration}), 0)`,
83
+ })
84
+ .from(meterEvents)
85
+ .where(and(gte(meterEvents.timestamp, windowStart), lt(meterEvents.timestamp, windowEnd)))
86
+ .groupBy(meterEvents.tenant, meterEvents.capability, meterEvents.provider);
87
+
88
+ return rows.map((r) => ({
89
+ tenant: r.tenant,
90
+ capability: r.capability,
91
+ provider: r.provider,
92
+ eventCount: r.eventCount,
93
+ totalCost: Number(r.totalCost),
94
+ totalCharge: Number(r.totalCharge),
95
+ totalDuration: Number(r.totalDuration),
96
+ }));
97
+ }
98
+
99
+ async insertSummary(values: UsageSummaryInsert): Promise<void> {
100
+ await this.db.insert(usageSummaries).values(values);
101
+ }
102
+
103
+ async insertSummariesBatch(rows: UsageSummaryInsert[]): Promise<void> {
104
+ if (rows.length === 0) return;
105
+ const CHUNK_SIZE = 1000; // safe limit: each row has ~10 columns, 1000*10 < 65535 param limit
106
+ await this.db.transaction(async (tx) => {
107
+ for (let i = 0; i < rows.length; i += CHUNK_SIZE) {
108
+ await tx.insert(usageSummaries).values(rows.slice(i, i + CHUNK_SIZE));
109
+ }
110
+ });
111
+ }
112
+
113
+ async querySummaries(
114
+ tenant: string,
115
+ opts: { since?: number; until?: number; limit?: number } = {},
116
+ ): Promise<UsageSummary[]> {
117
+ const conditions = [eq(usageSummaries.tenant, tenant)];
118
+ if (opts.since != null) {
119
+ conditions.push(gte(usageSummaries.windowStart, opts.since));
120
+ }
121
+ if (opts.until != null) {
122
+ conditions.push(lte(usageSummaries.windowEnd, opts.until));
123
+ }
124
+ const limit = Math.min(Math.max(1, opts.limit ?? 100), 1000);
125
+
126
+ return this.db
127
+ .select({
128
+ tenant: usageSummaries.tenant,
129
+ capability: usageSummaries.capability,
130
+ provider: usageSummaries.provider,
131
+ event_count: usageSummaries.eventCount,
132
+ total_cost: usageSummaries.totalCost,
133
+ total_charge: usageSummaries.totalCharge,
134
+ total_duration: usageSummaries.totalDuration,
135
+ window_start: usageSummaries.windowStart,
136
+ window_end: usageSummaries.windowEnd,
137
+ })
138
+ .from(usageSummaries)
139
+ .where(and(...conditions))
140
+ .orderBy(desc(usageSummaries.windowStart))
141
+ .limit(limit);
142
+ }
143
+
144
+ async getTenantTotal(
145
+ tenant: string,
146
+ since: number,
147
+ ): Promise<{ totalCost: number; totalCharge: number; eventCount: number }> {
148
+ const row = (
149
+ await this.db
150
+ .select({
151
+ totalCost: sql<number>`COALESCE(SUM(${usageSummaries.totalCost}), 0)`,
152
+ totalCharge: sql<number>`COALESCE(SUM(${usageSummaries.totalCharge}), 0)`,
153
+ eventCount: sql<number>`COALESCE(SUM(${usageSummaries.eventCount}), 0)`,
154
+ })
155
+ .from(usageSummaries)
156
+ .where(and(eq(usageSummaries.tenant, tenant), gte(usageSummaries.windowStart, since)))
157
+ )[0];
158
+
159
+ return {
160
+ totalCost: Number(row?.totalCost ?? 0),
161
+ totalCharge: Number(row?.totalCharge ?? 0),
162
+ eventCount: Number(row?.eventCount ?? 0),
163
+ };
164
+ }
165
+ }
166
+
167
+ export { DrizzleUsageSummaryRepository as UsageSummaryRepository };
@@ -0,0 +1,202 @@
1
+ import { mkdirSync, rmSync } from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import type { PGlite } from "@electric-sql/pglite";
5
+ import { afterEach, beforeEach, describe, expect, it } from "vitest";
6
+ import type { PlatformDb } from "../db/index.js";
7
+ import { beginTestTransaction, createTestDb, endTestTransaction, rollbackTestTransaction } from "../test/db.js";
8
+ import { Credit } from "../credits/credit.js";
9
+ import { MeterDLQ } from "./dlq.js";
10
+ import { DrizzleMeterEmitter } from "./emitter.js";
11
+ import { DrizzleMeterEventRepository } from "./meter-event-repository.js";
12
+ import type { MeterEvent } from "./types.js";
13
+
14
+ function makeTempDir(): string {
15
+ const dir = join(tmpdir(), `emitter-test-${crypto.randomUUID()}`);
16
+ mkdirSync(dir, { recursive: true });
17
+ return dir;
18
+ }
19
+
20
+ function makeEvent(overrides: Partial<MeterEvent> = {}): MeterEvent {
21
+ return {
22
+ tenant: "t1",
23
+ cost: Credit.fromCents(1),
24
+ charge: Credit.fromCents(2),
25
+ capability: "llm",
26
+ provider: "openai",
27
+ timestamp: Date.now(),
28
+ ...overrides,
29
+ };
30
+ }
31
+
32
+ describe("DrizzleMeterEmitter — happy path", () => {
33
+ let pool: PGlite;
34
+ let db: PlatformDb;
35
+ let emitter: DrizzleMeterEmitter;
36
+ let tempDir: string;
37
+
38
+ beforeEach(async () => {
39
+ ({ db, pool } = await createTestDb());
40
+ await beginTestTransaction(pool);
41
+ await rollbackTestTransaction(pool);
42
+ tempDir = makeTempDir();
43
+ const repo = new DrizzleMeterEventRepository(db);
44
+ emitter = new DrizzleMeterEmitter(repo, {
45
+ flushIntervalMs: 60_000,
46
+ batchSize: 100,
47
+ walPath: join(tempDir, "wal.jsonl"),
48
+ dlqPath: join(tempDir, "dlq.jsonl"),
49
+ maxRetries: 3,
50
+ });
51
+ await emitter.ready;
52
+ });
53
+
54
+ afterEach(async () => {
55
+ // Flush remaining events before close() to drain the buffer synchronously
56
+ await emitter.flush();
57
+ emitter.close();
58
+ await endTestTransaction(pool);
59
+ await pool.close();
60
+ try {
61
+ rmSync(tempDir, { recursive: true, force: true });
62
+ } catch {
63
+ // ignore cleanup errors
64
+ }
65
+ });
66
+
67
+ it("writes a meter event to the database after flush", async () => {
68
+ emitter.emit(makeEvent({ tenant: "t1", capability: "voice" }));
69
+ expect(emitter.pending).toBe(1);
70
+
71
+ const flushed = await emitter.flush();
72
+ expect(flushed).toBe(1);
73
+ expect(emitter.pending).toBe(0);
74
+
75
+ const rows = await emitter.queryEvents("t1");
76
+ expect(rows).toHaveLength(1);
77
+ expect(rows[0].tenant).toBe("t1");
78
+ expect(rows[0].capability).toBe("voice");
79
+ expect(rows[0].cost).toBe(Credit.fromCents(1).toRaw());
80
+ expect(rows[0].charge).toBe(Credit.fromCents(2).toRaw());
81
+ });
82
+
83
+ it("persists multiple events in one flush", async () => {
84
+ emitter.emit(makeEvent({ tenant: "t1" }));
85
+ emitter.emit(makeEvent({ tenant: "t1" }));
86
+ emitter.emit(makeEvent({ tenant: "t2" }));
87
+
88
+ const flushed = await emitter.flush();
89
+ expect(flushed).toBe(3);
90
+
91
+ const t1Rows = await emitter.queryEvents("t1");
92
+ expect(t1Rows).toHaveLength(2);
93
+
94
+ const t2Rows = await emitter.queryEvents("t2");
95
+ expect(t2Rows).toHaveLength(1);
96
+ });
97
+
98
+ it("flush returns 0 when buffer is empty", async () => {
99
+ expect(await emitter.flush()).toBe(0);
100
+ });
101
+
102
+ it("silently drops events emitted after close", async () => {
103
+ emitter.close();
104
+ emitter.emit(makeEvent());
105
+ expect(emitter.pending).toBe(0);
106
+ });
107
+
108
+ it("persists sessionId, duration, usage, tier, and metadata", async () => {
109
+ emitter.emit(
110
+ makeEvent({
111
+ sessionId: "sess-1",
112
+ duration: 5000,
113
+ usage: { units: 100, unitType: "tokens" },
114
+ tier: "wopr",
115
+ metadata: { model: "gpt-4" },
116
+ }),
117
+ );
118
+ await emitter.flush();
119
+
120
+ const rows = await emitter.queryEvents("t1");
121
+ expect(rows[0].session_id).toBe("sess-1");
122
+ expect(rows[0].duration).toBe(5000);
123
+ expect(rows[0].usage_units).toBe(100);
124
+ expect(rows[0].usage_unit_type).toBe("tokens");
125
+ expect(rows[0].tier).toBe("wopr");
126
+ expect(JSON.parse(rows[0].metadata as string)).toEqual({ model: "gpt-4" });
127
+ });
128
+ });
129
+
130
+ describe("DrizzleMeterEmitter — DLQ failure paths", () => {
131
+ it("moves events to DLQ after maxRetries failures", async () => {
132
+ const { db: failDb, pool: failPool } = await createTestDb();
133
+ const failTempDir = makeTempDir();
134
+ const dlqPath = join(failTempDir, "dlq.jsonl");
135
+
136
+ const failRepo = new DrizzleMeterEventRepository(failDb);
137
+ const em = new DrizzleMeterEmitter(failRepo, {
138
+ flushIntervalMs: 60_000,
139
+ walPath: join(failTempDir, "wal.jsonl"),
140
+ dlqPath,
141
+ maxRetries: 1,
142
+ });
143
+ await em.ready;
144
+
145
+ em.emit(makeEvent());
146
+
147
+ // Drop the table so flush fails
148
+ await failPool.query("DROP TABLE meter_events CASCADE");
149
+ await em.flush();
150
+
151
+ // maxRetries=1: first failure hits the limit => DLQ
152
+ const dlq = new MeterDLQ(dlqPath);
153
+ expect(dlq.count()).toBe(1);
154
+ expect(em.pending).toBe(0);
155
+
156
+ em.close();
157
+ await failPool.close();
158
+ try {
159
+ rmSync(failTempDir, { recursive: true, force: true });
160
+ } catch {
161
+ // ignore
162
+ }
163
+ });
164
+
165
+ it("retries failed events before moving to DLQ", async () => {
166
+ const { db: failDb, pool: failPool } = await createTestDb();
167
+ const failTempDir = makeTempDir();
168
+ const dlqPath = join(failTempDir, "dlq.jsonl");
169
+
170
+ const failRepo2 = new DrizzleMeterEventRepository(failDb);
171
+ const em = new DrizzleMeterEmitter(failRepo2, {
172
+ flushIntervalMs: 60_000,
173
+ walPath: join(failTempDir, "wal.jsonl"),
174
+ dlqPath,
175
+ maxRetries: 2,
176
+ });
177
+ await em.ready;
178
+
179
+ em.emit(makeEvent());
180
+
181
+ // First flush fails
182
+ await failPool.query("DROP TABLE meter_events CASCADE");
183
+ await em.flush();
184
+
185
+ // After first failure (maxRetries=2): still in buffer for retry
186
+ expect(em.pending).toBe(1);
187
+
188
+ // DLQ should be empty — not yet reached maxRetries
189
+ const dlq = new MeterDLQ(dlqPath);
190
+ expect(dlq.count()).toBe(0);
191
+
192
+ // Clear the buffer before close to avoid a DLQ write race during cleanup
193
+ (em as unknown as { buffer: unknown[] }).buffer = [];
194
+ em.close();
195
+ await failPool.close();
196
+ try {
197
+ rmSync(failTempDir, { recursive: true, force: true });
198
+ } catch {
199
+ // ignore
200
+ }
201
+ });
202
+ });
@@ -0,0 +1,227 @@
1
+ import { config } from "../config/index.js";
2
+ import { Credit } from "../credits/credit.js";
3
+ import { MeterDLQ } from "./dlq.js";
4
+ import type { IMeterEventRepository } from "./meter-event-repository.js";
5
+ import type { MeterEvent, MeterEventRow } from "./types.js";
6
+ import { MeterWAL } from "./wal.js";
7
+
8
+ const DEFAULT_WAL_PATH = process.env.METER_WAL_PATH ?? "./data/meter-wal.jsonl";
9
+ const DEFAULT_DLQ_PATH = process.env.METER_DLQ_PATH ?? "./data/meter-dlq.jsonl";
10
+ const DEFAULT_MAX_RETRIES = config.billing.meterMaxRetries;
11
+
12
+ export interface IMeterEmitter {
13
+ emit(event: MeterEvent): void;
14
+ flush(): Promise<number>;
15
+ readonly pending: number;
16
+ close(): void;
17
+ queryEvents(tenant: string, limit?: number): Promise<MeterEventRow[]>;
18
+ }
19
+
20
+ /**
21
+ * Fire-and-forget meter event emitter with fail-closed durability.
22
+ *
23
+ * Buffers events in memory and flushes them to the database in batches,
24
+ * ensuring zero latency impact on the observed API calls.
25
+ *
26
+ * FAIL-CLOSED POLICY:
27
+ * - Events are written to WAL (write-ahead log) on disk BEFORE buffering
28
+ * - If flush fails, events are retried up to MAX_RETRIES times
29
+ * - After MAX_RETRIES, events move to DLQ (dead-letter queue) for manual recovery
30
+ * - On startup, unflushed WAL events are replayed idempotently
31
+ */
32
+ export class DrizzleMeterEmitter implements IMeterEmitter {
33
+ private buffer: Array<MeterEvent & { id: string }> = [];
34
+ private flushTimer: ReturnType<typeof setInterval> | null = null;
35
+ private readonly flushIntervalMs: number;
36
+ private readonly batchSize: number;
37
+ private readonly maxRetries: number;
38
+ private closed = false;
39
+ private readonly wal: MeterWAL;
40
+ private readonly dlq: MeterDLQ;
41
+ private readonly retryCount = new Map<string, number>();
42
+ /** Resolves when the initial WAL replay has completed. */
43
+ readonly ready: Promise<void>;
44
+
45
+ constructor(
46
+ private readonly repo: IMeterEventRepository,
47
+ opts: {
48
+ flushIntervalMs?: number;
49
+ batchSize?: number;
50
+ walPath?: string;
51
+ dlqPath?: string;
52
+ maxRetries?: number;
53
+ } = {},
54
+ ) {
55
+ this.flushIntervalMs = opts.flushIntervalMs ?? 1000;
56
+ this.batchSize = opts.batchSize ?? 100;
57
+ this.maxRetries = opts.maxRetries ?? DEFAULT_MAX_RETRIES;
58
+
59
+ this.wal = new MeterWAL(opts.walPath ?? DEFAULT_WAL_PATH);
60
+ this.dlq = new MeterDLQ(opts.dlqPath ?? DEFAULT_DLQ_PATH);
61
+
62
+ // Replay any unflushed WAL events from a previous session.
63
+ this.ready = this.replayWAL();
64
+
65
+ this.flushTimer = setInterval(() => this.flush(), this.flushIntervalMs);
66
+ // Do not keep the process alive just for metering flushes.
67
+ if (this.flushTimer.unref) {
68
+ this.flushTimer.unref();
69
+ }
70
+ }
71
+
72
+ /**
73
+ * Replay unflushed events from the WAL on startup.
74
+ * Idempotent: skips events already in the database.
75
+ * Returns a promise that resolves when the replay is complete.
76
+ */
77
+ private async replayWAL(): Promise<void> {
78
+ const walEvents = this.wal.readAll();
79
+ if (walEvents.length === 0) return;
80
+
81
+ await this.replayWALAsync(walEvents);
82
+ }
83
+
84
+ /**
85
+ * Reconstitute Credit fields on WAL events after JSON deserialization.
86
+ * Credit.toJSON() serializes to a raw number, so after JSON.parse the
87
+ * cost/charge fields are plain numbers — convert them back to Credit.
88
+ */
89
+ private reconstituteCreditFields(events: Array<MeterEvent & { id: string }>): Array<MeterEvent & { id: string }> {
90
+ return events.map((e) => ({
91
+ ...e,
92
+ cost: e.cost instanceof Credit ? e.cost : Credit.fromRaw(e.cost as unknown as number),
93
+ charge: e.charge instanceof Credit ? e.charge : Credit.fromRaw(e.charge as unknown as number),
94
+ }));
95
+ }
96
+
97
+ private async replayWALAsync(walEvents: Array<MeterEvent & { id: string }>): Promise<void> {
98
+ // Reconstitute Credit objects from raw numbers after JSON deserialization.
99
+ walEvents = this.reconstituteCreditFields(walEvents);
100
+ // Check which events are already in the database.
101
+ const existingIds = new Set<string>();
102
+ for (const e of walEvents) {
103
+ if (await this.repo.existsById(e.id)) {
104
+ existingIds.add(e.id);
105
+ }
106
+ }
107
+
108
+ // Replay only new events.
109
+ const toReplay = walEvents.filter((e) => !existingIds.has(e.id));
110
+ if (toReplay.length > 0) {
111
+ this.buffer.push(...toReplay);
112
+ const flushed = await this.flush();
113
+ if (flushed === 0) {
114
+ // Flush failed -- events remain in WAL and buffer for retry.
115
+ return;
116
+ }
117
+ }
118
+
119
+ // Remove already-persisted events from WAL (idempotent cleanup).
120
+ if (existingIds.size > 0) {
121
+ await this.wal.remove(existingIds);
122
+ }
123
+ }
124
+
125
+ /** Emit a meter event. Non-blocking -- buffers in memory after WAL write. */
126
+ emit(event: MeterEvent): void {
127
+ if (this.closed) return;
128
+
129
+ // FAIL-CLOSED: Write to WAL first, then buffer.
130
+ // append() is synchronous (appendFileSync), so the WAL write completes
131
+ // before buffer.push() — crash safety is guaranteed.
132
+ const eventWithId = this.wal.append(event);
133
+ this.buffer.push(eventWithId);
134
+
135
+ if (this.buffer.length >= this.batchSize) {
136
+ void this.flush();
137
+ }
138
+ }
139
+
140
+ /** Flush buffered events to the database with retry and DLQ logic. */
141
+ async flush(): Promise<number> {
142
+ if (this.buffer.length === 0) return 0;
143
+ const batch = this.buffer.splice(0);
144
+
145
+ try {
146
+ await this.repo.insertBatch(
147
+ batch.map((e) => ({
148
+ id: e.id,
149
+ tenant: e.tenant,
150
+ cost: e.cost.toRaw(),
151
+ charge: e.charge.toRaw(),
152
+ capability: e.capability,
153
+ provider: e.provider,
154
+ timestamp: e.timestamp,
155
+ sessionId: e.sessionId ?? null,
156
+ duration: e.duration ?? null,
157
+ usageUnits: e.usage?.units ?? null,
158
+ usageUnitType: e.usage?.unitType ?? null,
159
+ tier: e.tier ?? null,
160
+ metadata: e.metadata ? JSON.stringify(e.metadata) : null,
161
+ })),
162
+ );
163
+
164
+ // Success: remove from WAL and reset retry counters.
165
+ const flushedIds = new Set(batch.map((e) => e.id));
166
+ await this.wal.remove(flushedIds);
167
+ for (const id of flushedIds) {
168
+ this.retryCount.delete(id);
169
+ }
170
+
171
+ return batch.length;
172
+ } catch (error) {
173
+ // Failure: track retries and move to DLQ if max exceeded.
174
+ const toRetry: Array<MeterEvent & { id: string }> = [];
175
+ const toDLQ: Array<MeterEvent & { id: string }> = [];
176
+
177
+ for (const event of batch) {
178
+ const retries = (this.retryCount.get(event.id) ?? 0) + 1;
179
+ this.retryCount.set(event.id, retries);
180
+
181
+ if (retries >= this.maxRetries) {
182
+ // Max retries exceeded -- move to DLQ.
183
+ toDLQ.push(event);
184
+ this.dlq.append(event, String(error), retries);
185
+ this.retryCount.delete(event.id);
186
+ } else {
187
+ // Retry on next flush.
188
+ toRetry.push(event);
189
+ }
190
+ }
191
+
192
+ // Remove DLQ events from WAL (they're now in DLQ).
193
+ if (toDLQ.length > 0) {
194
+ const dlqIds = new Set(toDLQ.map((e) => e.id));
195
+ await this.wal.remove(dlqIds);
196
+ }
197
+
198
+ // Re-add retry events to buffer.
199
+ this.buffer.unshift(...toRetry);
200
+
201
+ return 0;
202
+ }
203
+ }
204
+
205
+ /** Number of events currently buffered. */
206
+ get pending(): number {
207
+ return this.buffer.length;
208
+ }
209
+
210
+ /** Stop the flush timer and flush remaining events. */
211
+ close(): void {
212
+ this.closed = true;
213
+ if (this.flushTimer) {
214
+ clearInterval(this.flushTimer);
215
+ this.flushTimer = null;
216
+ }
217
+ void this.flush();
218
+ }
219
+
220
+ /** Query persisted events (for testing / diagnostics). */
221
+ async queryEvents(tenant: string, limit = 50): Promise<MeterEventRow[]> {
222
+ return this.repo.queryByTenant(tenant, limit);
223
+ }
224
+ }
225
+
226
+ // Backward-compat alias.
227
+ export { DrizzleMeterEmitter as MeterEmitter };