@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,110 @@
1
+ /**
2
+ * Email Verification — Token generation, validation, and verification flow.
3
+ *
4
+ * Manages the signup verification lifecycle:
5
+ * 1. Generate signed token on signup
6
+ * 2. Store token + expiry in the auth database
7
+ * 3. Verify token when user clicks the link
8
+ * 4. Mark user as verified, send welcome email, grant credits
9
+ */
10
+
11
+ import crypto from "node:crypto";
12
+ import type { Pool } from "pg";
13
+ import type { IEmailVerifier } from "./require-verified.js";
14
+
15
+ const TOKEN_EXPIRY_MS = 24 * 60 * 60 * 1000; // 24 hours
16
+
17
+ // ---------------------------------------------------------------------------
18
+ // Schema
19
+ // ---------------------------------------------------------------------------
20
+
21
+ /** Add email verification columns to the better-auth user table. */
22
+ export async function initVerificationSchema(pool: Pool): Promise<void> {
23
+ // raw SQL: better-auth manages its own schema outside Drizzle
24
+ await pool.query(`ALTER TABLE "user" ADD COLUMN IF NOT EXISTS email_verified BOOLEAN NOT NULL DEFAULT false`);
25
+ // raw SQL: better-auth manages its own schema outside Drizzle
26
+ await pool.query(`ALTER TABLE "user" ADD COLUMN IF NOT EXISTS verification_token TEXT`);
27
+ // raw SQL: better-auth manages its own schema outside Drizzle
28
+ await pool.query(`ALTER TABLE "user" ADD COLUMN IF NOT EXISTS verification_expires TEXT`);
29
+ }
30
+
31
+ // ---------------------------------------------------------------------------
32
+ // Token operations
33
+ // ---------------------------------------------------------------------------
34
+
35
+ export interface VerificationToken {
36
+ token: string;
37
+ expiresAt: string; // ISO-8601
38
+ }
39
+
40
+ /**
41
+ * Generate a verification token and store it against a user.
42
+ */
43
+ export async function generateVerificationToken(pool: Pool, userId: string): Promise<VerificationToken> {
44
+ const token = crypto.randomBytes(32).toString("hex");
45
+ const expiresAt = new Date(Date.now() + TOKEN_EXPIRY_MS).toISOString();
46
+
47
+ // raw SQL: better-auth manages its own schema outside Drizzle
48
+ await pool.query(`UPDATE "user" SET verification_token = $1, verification_expires = $2 WHERE id = $3`, [
49
+ token,
50
+ expiresAt,
51
+ userId,
52
+ ]);
53
+
54
+ return { token, expiresAt };
55
+ }
56
+
57
+ /**
58
+ * Verify a token: check it exists, hasn't expired, and mark the user as verified.
59
+ */
60
+ export async function verifyToken(pool: Pool, token: string): Promise<{ userId: string; email: string } | null> {
61
+ if (!token || token.length !== 64) return null;
62
+
63
+ // raw SQL: better-auth manages its own schema outside Drizzle
64
+ const { rows } = await pool.query(
65
+ `SELECT id, email, verification_token, verification_expires, email_verified FROM "user" WHERE verification_token = $1`,
66
+ [token],
67
+ );
68
+
69
+ const row = rows[0];
70
+ if (!row) return null;
71
+ if (row.email_verified === true) return null;
72
+
73
+ const expiresAt = new Date(row.verification_expires).getTime();
74
+ if (Date.now() > expiresAt) return null;
75
+
76
+ // raw SQL: better-auth manages its own schema outside Drizzle
77
+ await pool.query(
78
+ `UPDATE "user" SET email_verified = true, "emailVerified" = true, verification_token = NULL, verification_expires = NULL WHERE id = $1`,
79
+ [row.id],
80
+ );
81
+
82
+ return { userId: row.id, email: row.email };
83
+ }
84
+
85
+ /**
86
+ * Check whether a user has verified their email.
87
+ */
88
+ export async function isEmailVerified(pool: Pool, userId: string): Promise<boolean> {
89
+ // raw SQL: better-auth manages its own schema outside Drizzle
90
+ const { rows } = await pool.query(`SELECT email_verified FROM "user" WHERE id = $1`, [userId]);
91
+ return rows[0]?.email_verified === true;
92
+ }
93
+
94
+ /**
95
+ * Get a user's email by their ID.
96
+ */
97
+ export async function getUserEmail(pool: Pool, userId: string): Promise<string | null> {
98
+ // raw SQL: better-auth manages its own schema outside Drizzle
99
+ const { rows } = await pool.query(`SELECT email FROM "user" WHERE id = $1`, [userId]);
100
+ return rows[0]?.email ?? null;
101
+ }
102
+
103
+ /** PostgreSQL-backed implementation of IEmailVerifier for the auth database. */
104
+ export class PgEmailVerifier implements IEmailVerifier {
105
+ constructor(private readonly pool: Pool) {}
106
+
107
+ async isVerified(userId: string): Promise<boolean> {
108
+ return isEmailVerified(this.pool, userId);
109
+ }
110
+ }
package/src/index.ts ADDED
@@ -0,0 +1,51 @@
1
+ // Database
2
+ export type { PlatformDb, PlatformSchema } from "./db/index.js";
3
+ export { createDb, schema } from "./db/index.js";
4
+
5
+ // Admin
6
+ export * from "./admin/index.js";
7
+
8
+ // Auth
9
+ export * from "./auth/index.js";
10
+
11
+ // Billing (selective — ITenantCustomerRepository/TenantCustomerRow also in credits)
12
+ export {
13
+ PaymentMethodOwnershipError,
14
+ noOpReplayGuard,
15
+ DrizzleWebhookSeenRepository,
16
+ type SavedPaymentMethod,
17
+ type CheckoutOpts,
18
+ type CheckoutSession,
19
+ type ChargeOpts,
20
+ type ChargeResult,
21
+ type SetupResult,
22
+ type PortalOpts,
23
+ type WebhookResult,
24
+ type IPaymentProcessor,
25
+ type Invoice,
26
+ type IWebhookSeenRepository,
27
+ } from "./billing/index.js";
28
+
29
+ // Config
30
+ export { config, billingConfigSchema, type PlatformConfig } from "./config/index.js";
31
+
32
+ // Credits
33
+ export * from "./credits/index.js";
34
+
35
+ // Email
36
+ export * from "./email/index.js";
37
+
38
+ // Metering
39
+ export * from "./metering/index.js";
40
+
41
+ // Middleware
42
+ export * from "./middleware/index.js";
43
+
44
+ // Security
45
+ export * from "./security/index.js";
46
+
47
+ // Tenancy
48
+ export * from "./tenancy/index.js";
49
+
50
+ // tRPC
51
+ export * from "./trpc/index.js";
@@ -0,0 +1,239 @@
1
+ import crypto from "node:crypto";
2
+ import type { PGlite } from "@electric-sql/pglite";
3
+ import { afterEach, beforeEach, describe, expect, it } from "vitest";
4
+ import type { PlatformDb } from "../db/index.js";
5
+ import { meterEvents, usageSummaries } from "../db/schema/meter-events.js";
6
+ import { createTestDb } from "../test/db.js";
7
+ import { Credit } from "../credits/credit.js";
8
+ import { DrizzleMeterAggregator } from "./aggregator.js";
9
+ import { DrizzleUsageSummaryRepository } from "./drizzle-usage-summary-repository.js";
10
+
11
+ const WINDOW_MS = 60_000; // 1-minute windows
12
+
13
+ describe("DrizzleMeterAggregator edge cases", () => {
14
+ let db: PlatformDb;
15
+ let pool: PGlite;
16
+ let aggregator: DrizzleMeterAggregator;
17
+
18
+ beforeEach(async () => {
19
+ const testDb = await createTestDb();
20
+ db = testDb.db;
21
+ pool = testDb.pool;
22
+ aggregator = new DrizzleMeterAggregator(new DrizzleUsageSummaryRepository(db), { windowMs: WINDOW_MS });
23
+ });
24
+
25
+ afterEach(async () => {
26
+ aggregator.stop();
27
+ await pool.close();
28
+ });
29
+
30
+ async function insertEvent(overrides: {
31
+ tenant?: string;
32
+ cost?: number;
33
+ charge?: number;
34
+ capability?: string;
35
+ provider?: string;
36
+ timestamp: number;
37
+ id?: string;
38
+ }) {
39
+ await db.insert(meterEvents).values({
40
+ id: overrides.id ?? crypto.randomUUID(),
41
+ tenant: overrides.tenant ?? "tenant-1",
42
+ cost: overrides.cost ?? Credit.fromDollars(0.001).toRaw(),
43
+ charge: overrides.charge ?? Credit.fromDollars(0.002).toRaw(),
44
+ capability: overrides.capability ?? "embeddings",
45
+ provider: overrides.provider ?? "openai",
46
+ timestamp: overrides.timestamp,
47
+ });
48
+ }
49
+
50
+ it("sums events correctly within a single completed window", async () => {
51
+ const baseTime = 0;
52
+ await insertEvent({ timestamp: baseTime + 10_000, cost: 1_000_000, charge: 2_000_000 });
53
+ await insertEvent({ timestamp: baseTime + 20_000, cost: 3_000_000, charge: 4_000_000 });
54
+ await insertEvent({ timestamp: baseTime + 30_000, cost: 5_000_000, charge: 6_000_000 });
55
+
56
+ const inserted = await aggregator.aggregate(WINDOW_MS + 1);
57
+
58
+ expect(inserted).toBe(1);
59
+
60
+ const summaries = await db.select().from(usageSummaries);
61
+ const real = summaries.filter((s) => s.tenant !== "__sentinel__");
62
+ expect(real).toHaveLength(1);
63
+ expect(real[0].tenant).toBe("tenant-1");
64
+ expect(real[0].eventCount).toBe(3);
65
+ expect(real[0].totalCost).toBe(9_000_000);
66
+ expect(real[0].totalCharge).toBe(12_000_000);
67
+ expect(real[0].windowStart).toBe(0);
68
+ expect(real[0].windowEnd).toBe(WINDOW_MS);
69
+ });
70
+
71
+ it("returns zero and inserts sentinel when no events exist", async () => {
72
+ const inserted = await aggregator.aggregate(WINDOW_MS + 1);
73
+
74
+ expect(inserted).toBe(0);
75
+
76
+ const summaries = await db.select().from(usageSummaries);
77
+ expect(summaries).toHaveLength(1);
78
+ expect(summaries[0].tenant).toBe("__sentinel__");
79
+ expect(summaries[0].eventCount).toBe(0);
80
+ });
81
+
82
+ it("rejects duplicate event IDs via primary key constraint", async () => {
83
+ const eventId = crypto.randomUUID();
84
+ await insertEvent({ timestamp: 10_000, id: eventId });
85
+
86
+ await expect(insertEvent({ timestamp: 10_000, id: eventId })).rejects.toThrow();
87
+
88
+ const inserted = await aggregator.aggregate(WINDOW_MS + 1);
89
+ expect(inserted).toBe(1);
90
+
91
+ const summaries = await db.select().from(usageSummaries);
92
+ const real = summaries.filter((s) => s.tenant !== "__sentinel__");
93
+ expect(real).toHaveLength(1);
94
+ expect(real[0].eventCount).toBe(1);
95
+ });
96
+
97
+ it("handles very large cost totals without overflow", async () => {
98
+ const largeCost = 1_000_000_000_000;
99
+ const largeCharge = 2_000_000_000_000;
100
+
101
+ for (let i = 0; i < 5; i++) {
102
+ await insertEvent({ timestamp: 10_000 + i * 1000, cost: largeCost, charge: largeCharge });
103
+ }
104
+
105
+ const inserted = await aggregator.aggregate(WINDOW_MS + 1);
106
+ expect(inserted).toBe(1);
107
+
108
+ const summaries = await db.select().from(usageSummaries);
109
+ const real = summaries.filter((s) => s.tenant !== "__sentinel__");
110
+ expect(real).toHaveLength(1);
111
+ expect(real[0].totalCost).toBe(5_000_000_000_000);
112
+ expect(real[0].totalCharge).toBe(10_000_000_000_000);
113
+ expect(real[0].eventCount).toBe(5);
114
+ });
115
+
116
+ it("isolates aggregation per tenant", async () => {
117
+ await insertEvent({ tenant: "tenant-A", timestamp: 10_000, cost: 1_000_000, charge: 2_000_000 });
118
+ await insertEvent({ tenant: "tenant-A", timestamp: 20_000, cost: 3_000_000, charge: 4_000_000 });
119
+ await insertEvent({ tenant: "tenant-B", timestamp: 15_000, cost: 5_000_000, charge: 6_000_000 });
120
+
121
+ const inserted = await aggregator.aggregate(WINDOW_MS + 1);
122
+ expect(inserted).toBe(2);
123
+
124
+ const summaries = await db.select().from(usageSummaries);
125
+ const real = summaries.filter((s) => s.tenant !== "__sentinel__");
126
+ expect(real).toHaveLength(2);
127
+
128
+ const tenantA = real.find((s) => s.tenant === "tenant-A");
129
+ expect(tenantA).toEqual(
130
+ expect.objectContaining({
131
+ tenant: "tenant-A",
132
+ eventCount: 2,
133
+ totalCost: 4_000_000,
134
+ totalCharge: 6_000_000,
135
+ }),
136
+ );
137
+ const tenantB = real.find((s) => s.tenant === "tenant-B");
138
+ expect(tenantB).toEqual(
139
+ expect.objectContaining({
140
+ tenant: "tenant-B",
141
+ eventCount: 1,
142
+ totalCost: 5_000_000,
143
+ totalCharge: 6_000_000,
144
+ }),
145
+ );
146
+ });
147
+
148
+ it("includes events at window start, excludes events at window end", async () => {
149
+ await insertEvent({ timestamp: 0, cost: 1_000_000, charge: 1_000_000 });
150
+ await insertEvent({ timestamp: 59_999, cost: 2_000_000, charge: 2_000_000 });
151
+ await insertEvent({ timestamp: 60_000, cost: 100_000_000, charge: 100_000_000 });
152
+
153
+ const inserted = await aggregator.aggregate(2 * WINDOW_MS + 1);
154
+
155
+ const summaries = await db.select().from(usageSummaries);
156
+ const real = summaries.filter((s) => s.tenant !== "__sentinel__");
157
+
158
+ const window0 = real.find((s) => s.windowStart === 0);
159
+ expect(window0).toEqual(
160
+ expect.objectContaining({
161
+ windowStart: 0,
162
+ eventCount: 2,
163
+ totalCost: 3_000_000,
164
+ }),
165
+ );
166
+
167
+ const window1 = real.find((s) => s.windowStart === 60_000);
168
+ expect(window1).toEqual(
169
+ expect.objectContaining({
170
+ windowStart: 60_000,
171
+ eventCount: 1,
172
+ totalCost: 100_000_000,
173
+ }),
174
+ );
175
+
176
+ // window0: 1 tenant/capability/provider group = 1 row
177
+ // window1: 1 tenant/capability/provider group = 1 row
178
+ expect(inserted).toBe(2);
179
+ });
180
+
181
+ it("aggregates zero-cost events correctly", async () => {
182
+ await insertEvent({ timestamp: 10_000, cost: 0, charge: 0 });
183
+ await insertEvent({ timestamp: 20_000, cost: 0, charge: 0 });
184
+
185
+ const inserted = await aggregator.aggregate(WINDOW_MS + 1);
186
+ expect(inserted).toBe(1);
187
+
188
+ const summaries = await db.select().from(usageSummaries);
189
+ const real = summaries.filter((s) => s.tenant !== "__sentinel__");
190
+ expect(real).toHaveLength(1);
191
+ expect(real[0].totalCost).toBe(0);
192
+ expect(real[0].totalCharge).toBe(0);
193
+ expect(real[0].eventCount).toBe(2);
194
+ });
195
+
196
+ it("handles a single event exactly at window boundary", async () => {
197
+ // Event at timestamp 0 (window start) — should be in window [0, WINDOW_MS)
198
+ await insertEvent({ timestamp: 0, cost: 1_000_000, charge: 2_000_000 });
199
+
200
+ const inserted = await aggregator.aggregate(WINDOW_MS + 1);
201
+ expect(inserted).toBe(1);
202
+
203
+ const summaries = await db.select().from(usageSummaries);
204
+ const real = summaries.filter((s) => s.tenant !== "__sentinel__");
205
+ expect(real).toHaveLength(1);
206
+ expect(real[0].eventCount).toBe(1);
207
+ expect(real[0].totalCost).toBe(1_000_000);
208
+ });
209
+
210
+ it("handles event one unit before window end", async () => {
211
+ // Event at WINDOW_MS - 1 should be included in first window
212
+ await insertEvent({ timestamp: WINDOW_MS - 1, cost: 500_000, charge: 600_000 });
213
+
214
+ const inserted = await aggregator.aggregate(WINDOW_MS + 1);
215
+ expect(inserted).toBe(1);
216
+
217
+ const summaries = await db.select().from(usageSummaries);
218
+ const real = summaries.filter((s) => s.tenant !== "__sentinel__");
219
+ expect(real).toHaveLength(1);
220
+ expect(real[0].windowStart).toBe(0);
221
+ expect(real[0].eventCount).toBe(1);
222
+ expect(real[0].totalCost).toBe(500_000);
223
+ });
224
+
225
+ it("handles event exactly at window end (goes to next window)", async () => {
226
+ // Event at exactly WINDOW_MS should be in window [WINDOW_MS, 2*WINDOW_MS)
227
+ await insertEvent({ timestamp: WINDOW_MS, cost: 700_000, charge: 800_000 });
228
+
229
+ await aggregator.aggregate(2 * WINDOW_MS + 1);
230
+
231
+ const summaries = await db.select().from(usageSummaries);
232
+ const real = summaries.filter((s) => s.tenant !== "__sentinel__");
233
+ // Should be in second window, not first
234
+ expect(real).toHaveLength(1);
235
+ expect(real[0].windowStart).toBe(WINDOW_MS);
236
+ expect(real[0].eventCount).toBe(1);
237
+ expect(real[0].totalCost).toBe(700_000);
238
+ });
239
+ });
@@ -0,0 +1,160 @@
1
+ import crypto from "node:crypto";
2
+ import type { IUsageSummaryRepository } from "./drizzle-usage-summary-repository.js";
3
+ import type { UsageSummary } from "./types.js";
4
+
5
+ export interface IMeterAggregator {
6
+ aggregate(now?: number): Promise<number>;
7
+ start(intervalMs?: number): void;
8
+ stop(): void;
9
+ querySummaries(tenant: string, opts?: { since?: number; until?: number; limit?: number }): Promise<UsageSummary[]>;
10
+ getTenantTotal(
11
+ tenant: string,
12
+ since: number,
13
+ ): Promise<{ totalCost: number; totalCharge: number; eventCount: number }>;
14
+ }
15
+
16
+ /**
17
+ * Background aggregator that rolls up raw meter events into per-tenant
18
+ * usage summaries over fixed time windows.
19
+ *
20
+ * Designed to run periodically (e.g., every minute). Each run aggregates
21
+ * events from a completed window that haven't been summarized yet.
22
+ */
23
+ export class DrizzleMeterAggregator implements IMeterAggregator {
24
+ private readonly windowMs: number;
25
+ private timer: ReturnType<typeof setInterval> | null = null;
26
+
27
+ constructor(
28
+ private readonly repo: IUsageSummaryRepository,
29
+ opts: { windowMs?: number } = {},
30
+ ) {
31
+ this.windowMs = opts.windowMs ?? 60_000; // 1 minute default
32
+ }
33
+
34
+ /**
35
+ * Aggregate all events in completed windows up to `now`.
36
+ * Returns the number of summary rows inserted.
37
+ */
38
+ async aggregate(now: number = Date.now()): Promise<number> {
39
+ let lastEnd = await this.repo.getLastWindowEnd();
40
+
41
+ // Calculate the current window boundary. We only aggregate *completed* windows.
42
+ const currentWindowStart = Math.floor(now / this.windowMs) * this.windowMs;
43
+
44
+ if (lastEnd >= currentWindowStart) {
45
+ // Nothing new to aggregate.
46
+ return 0;
47
+ }
48
+
49
+ let totalInserted = 0;
50
+
51
+ // Process one window at a time to respect fixed time window boundaries.
52
+ // On first run (lastEnd === 0), align to the first window boundary at or
53
+ // before currentWindowStart so we don't create thousands of empty sentinels.
54
+ if (lastEnd === 0) {
55
+ // Find earliest event to anchor the first window.
56
+ const earliest = await this.repo.getEarliestEventTimestamp(currentWindowStart);
57
+ if (earliest != null) {
58
+ lastEnd = Math.floor(earliest / this.windowMs) * this.windowMs;
59
+ } else {
60
+ // No events at all -- insert a single sentinel to mark we've processed up to now.
61
+ await this.repo.insertSummary({
62
+ id: crypto.randomUUID(),
63
+ tenant: "__sentinel__",
64
+ capability: "__none__",
65
+ provider: "__none__",
66
+ eventCount: 0,
67
+ totalCost: 0,
68
+ totalCharge: 0,
69
+ totalDuration: 0,
70
+ windowStart: 0,
71
+ windowEnd: currentWindowStart,
72
+ });
73
+ return 0;
74
+ }
75
+ }
76
+
77
+ while (lastEnd < currentWindowStart) {
78
+ const windowStart = lastEnd;
79
+ const windowEnd = Math.min(lastEnd + this.windowMs, currentWindowStart);
80
+
81
+ // Group events by tenant + capability + provider within this single window.
82
+ const rows = await this.repo.getAggregatedEvents(windowStart, windowEnd);
83
+
84
+ if (rows.length === 0) {
85
+ // Advance past this empty window by inserting a sentinel with zero counts.
86
+ await this.repo.insertSummary({
87
+ id: crypto.randomUUID(),
88
+ tenant: "__sentinel__",
89
+ capability: "__none__",
90
+ provider: "__none__",
91
+ eventCount: 0,
92
+ totalCost: 0,
93
+ totalCharge: 0,
94
+ totalDuration: 0,
95
+ windowStart,
96
+ windowEnd,
97
+ });
98
+ } else {
99
+ await this.repo.insertSummariesBatch(
100
+ rows.map((s) => ({
101
+ id: crypto.randomUUID(),
102
+ tenant: s.tenant,
103
+ capability: s.capability,
104
+ provider: s.provider,
105
+ eventCount: s.eventCount,
106
+ totalCost: s.totalCost,
107
+ totalCharge: s.totalCharge,
108
+ totalDuration: s.totalDuration,
109
+ windowStart,
110
+ windowEnd,
111
+ })),
112
+ );
113
+ totalInserted += rows.length;
114
+ }
115
+
116
+ lastEnd = windowEnd;
117
+ }
118
+
119
+ return totalInserted;
120
+ }
121
+
122
+ /** Start periodic aggregation. */
123
+ start(intervalMs?: number): void {
124
+ if (this.timer) {
125
+ return; // Already running -- avoid leaking a second interval timer.
126
+ }
127
+ const interval = intervalMs ?? this.windowMs;
128
+ this.timer = setInterval(() => this.aggregate(), interval);
129
+ if (this.timer.unref) {
130
+ this.timer.unref();
131
+ }
132
+ }
133
+
134
+ /** Stop periodic aggregation. */
135
+ stop(): void {
136
+ if (this.timer) {
137
+ clearInterval(this.timer);
138
+ this.timer = null;
139
+ }
140
+ }
141
+
142
+ /** Query usage summaries for a tenant within a time range. */
143
+ async querySummaries(
144
+ tenant: string,
145
+ opts: { since?: number; until?: number; limit?: number } = {},
146
+ ): Promise<UsageSummary[]> {
147
+ return this.repo.querySummaries(tenant, opts);
148
+ }
149
+
150
+ /** Get a tenant's total usage across all capabilities since a given time. */
151
+ async getTenantTotal(
152
+ tenant: string,
153
+ since: number,
154
+ ): Promise<{ totalCost: number; totalCharge: number; eventCount: number }> {
155
+ return this.repo.getTenantTotal(tenant, since);
156
+ }
157
+ }
158
+
159
+ // Backward-compat alias.
160
+ export { DrizzleMeterAggregator as MeterAggregator };
@@ -0,0 +1,134 @@
1
+ import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import { afterEach, beforeEach, describe, expect, it } from "vitest";
5
+ import { Credit } from "../credits/credit.js";
6
+ import { MeterDLQ } from "./dlq.js";
7
+
8
+ describe("MeterDLQ", () => {
9
+ let tmpDir: string;
10
+ let dlqPath: string;
11
+
12
+ beforeEach(() => {
13
+ tmpDir = path.join(os.tmpdir(), `wopr-dlq-test-${Date.now()}`);
14
+ dlqPath = path.join(tmpDir, "meter-dlq.jsonl");
15
+ });
16
+
17
+ afterEach(() => {
18
+ if (existsSync(tmpDir)) {
19
+ rmSync(tmpDir, { recursive: true, force: true });
20
+ }
21
+ });
22
+
23
+ it("creates the directory when it does not exist", () => {
24
+ // tmpDir doesn't exist yet — constructor should create it
25
+ expect(existsSync(tmpDir)).toBe(false);
26
+ new MeterDLQ(dlqPath);
27
+ expect(existsSync(tmpDir)).toBe(true);
28
+ });
29
+
30
+ it("does not throw when directory already exists", () => {
31
+ mkdirSync(tmpDir, { recursive: true });
32
+ expect(() => new MeterDLQ(dlqPath)).not.toThrow();
33
+ });
34
+
35
+ it("readAll returns empty array when DLQ file does not exist", () => {
36
+ new MeterDLQ(dlqPath);
37
+ expect(existsSync(dlqPath)).toBe(false);
38
+ const dlq = new MeterDLQ(dlqPath);
39
+ expect(dlq.readAll()).toEqual([]);
40
+ });
41
+
42
+ it("readAll returns empty array when DLQ file is empty", () => {
43
+ mkdirSync(tmpDir, { recursive: true });
44
+ writeFileSync(dlqPath, "");
45
+ const dlq = new MeterDLQ(dlqPath);
46
+ expect(dlq.readAll()).toEqual([]);
47
+ });
48
+
49
+ it("readAll returns empty array when DLQ file contains only whitespace", () => {
50
+ mkdirSync(tmpDir, { recursive: true });
51
+ writeFileSync(dlqPath, "\n\n \n");
52
+ const dlq = new MeterDLQ(dlqPath);
53
+ expect(dlq.readAll()).toEqual([]);
54
+ });
55
+
56
+ it("append writes a DLQ entry and readAll returns it", () => {
57
+ const dlq = new MeterDLQ(dlqPath);
58
+ const event = {
59
+ id: "evt-1",
60
+ tenant: "tenant-1",
61
+ capability: "tts",
62
+ provider: "elevenlabs",
63
+ cost: Credit.fromDollars(0.01),
64
+ charge: Credit.fromDollars(0.02),
65
+ timestamp: Date.now(),
66
+ };
67
+
68
+ dlq.append(event, "Stripe API 500", 3);
69
+
70
+ const entries = dlq.readAll();
71
+ expect(entries).toHaveLength(1);
72
+ expect(entries[0].id).toBe("evt-1");
73
+ expect(entries[0].dlq_error).toBe("Stripe API 500");
74
+ expect(entries[0].dlq_retries).toBe(3);
75
+ });
76
+
77
+ it("count returns 0 for empty DLQ", () => {
78
+ const dlq = new MeterDLQ(dlqPath);
79
+ expect(dlq.count()).toBe(0);
80
+ });
81
+
82
+ it("count returns correct count after appending", () => {
83
+ const dlq = new MeterDLQ(dlqPath);
84
+ const event = {
85
+ id: "evt-1",
86
+ tenant: "t1",
87
+ capability: "tts",
88
+ provider: "elevenlabs",
89
+ cost: Credit.fromDollars(0.01),
90
+ charge: Credit.fromDollars(0.02),
91
+ timestamp: Date.now(),
92
+ };
93
+ dlq.append(event, "err", 1);
94
+ dlq.append({ ...event, id: "evt-2" }, "err2", 2);
95
+ expect(dlq.count()).toBe(2);
96
+ });
97
+
98
+ it("count counts malformed lines (no JSON parsing)", () => {
99
+ mkdirSync(tmpDir, { recursive: true });
100
+ const goodLine = JSON.stringify({ id: "evt-1", dlq_error: "e", dlq_retries: 1, dlq_timestamp: 0 });
101
+ writeFileSync(dlqPath, `${goodLine}\nnot-valid-json\n`);
102
+ const dlq = new MeterDLQ(dlqPath);
103
+ // count should return 2 (line count), not 1 (parsed count)
104
+ // This verifies count() does NOT use JSON.parse
105
+ expect(dlq.count()).toBe(2);
106
+ });
107
+
108
+ it("count returns correct count when file has no trailing newline", () => {
109
+ mkdirSync(tmpDir, { recursive: true });
110
+ const line1 = JSON.stringify({ id: "evt-1", dlq_error: "e", dlq_retries: 1, dlq_timestamp: 0 });
111
+ const line2 = JSON.stringify({ id: "evt-2", dlq_error: "e", dlq_retries: 1, dlq_timestamp: 0 });
112
+ // Write two entries with no trailing newline
113
+ writeFileSync(dlqPath, `${line1}\n${line2}`);
114
+ const dlq = new MeterDLQ(dlqPath);
115
+ expect(dlq.count()).toBe(2);
116
+ });
117
+
118
+ it("count returns 0 when file is deleted between calls (ENOENT)", () => {
119
+ mkdirSync(tmpDir, { recursive: true });
120
+ const dlq = new MeterDLQ(dlqPath);
121
+ // File doesn't exist at all — should not throw, return 0
122
+ expect(dlq.count()).toBe(0);
123
+ });
124
+
125
+ it("readAll skips malformed lines", () => {
126
+ mkdirSync(tmpDir, { recursive: true });
127
+ const goodLine = JSON.stringify({ id: "evt-1", dlq_error: "e", dlq_retries: 1, dlq_timestamp: 0 });
128
+ writeFileSync(dlqPath, `${goodLine}\nnot-valid-json\n`);
129
+ const dlq = new MeterDLQ(dlqPath);
130
+ const entries = dlq.readAll();
131
+ expect(entries).toHaveLength(1);
132
+ expect(entries[0].id).toBe("evt-1");
133
+ });
134
+ });