@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.
- package/biome.json +61 -0
- package/dist/admin/admin-audit-log-repository.d.ts +33 -0
- package/dist/admin/admin-audit-log-repository.js +102 -0
- package/dist/admin/audit-log.d.ts +49 -0
- package/dist/admin/audit-log.js +63 -0
- package/dist/admin/index.d.ts +6 -0
- package/dist/admin/index.js +3 -0
- package/dist/admin/role-store.d.ts +37 -0
- package/dist/admin/role-store.js +106 -0
- package/dist/auth/api-key-repository.d.ts +11 -0
- package/dist/auth/api-key-repository.js +33 -0
- package/dist/auth/api-key-repository.test.d.ts +1 -0
- package/dist/auth/api-key-repository.test.js +46 -0
- package/dist/auth/auth.test.d.ts +1 -0
- package/dist/auth/auth.test.js +140 -0
- package/dist/auth/better-auth.d.ts +42 -0
- package/dist/auth/better-auth.js +196 -0
- package/dist/auth/index.d.ts +186 -0
- package/dist/auth/index.js +422 -0
- package/dist/auth/login-history-repository.d.ts +14 -0
- package/dist/auth/login-history-repository.js +15 -0
- package/dist/auth/login-history-repository.test.d.ts +1 -0
- package/dist/auth/login-history-repository.test.js +47 -0
- package/dist/auth/middleware.d.ts +55 -0
- package/dist/auth/middleware.js +101 -0
- package/dist/auth/middleware.test.d.ts +1 -0
- package/dist/auth/middleware.test.js +213 -0
- package/dist/auth/scoped-tokens.test.d.ts +1 -0
- package/dist/auth/scoped-tokens.test.js +306 -0
- package/dist/auth/tenant-access.test.d.ts +1 -0
- package/dist/auth/tenant-access.test.js +62 -0
- package/dist/auth/user-creator.d.ts +9 -0
- package/dist/auth/user-creator.js +47 -0
- package/dist/auth/user-creator.test.d.ts +1 -0
- package/dist/auth/user-creator.test.js +78 -0
- package/dist/auth/user-role-repository.d.ts +31 -0
- package/dist/auth/user-role-repository.js +53 -0
- package/dist/auth/user-role-repository.test.d.ts +1 -0
- package/dist/auth/user-role-repository.test.js +122 -0
- package/dist/billing/drizzle-webhook-seen-repository.d.ts +10 -0
- package/dist/billing/drizzle-webhook-seen-repository.js +28 -0
- package/dist/billing/index.d.ts +7 -0
- package/dist/billing/index.js +7 -0
- package/dist/billing/payment-processor.d.ts +127 -0
- package/dist/billing/payment-processor.js +8 -0
- package/dist/billing/payment-processor.test.d.ts +1 -0
- package/dist/billing/payment-processor.test.js +71 -0
- package/dist/billing/payram/cents-credits-boundary.test.d.ts +1 -0
- package/dist/billing/payram/cents-credits-boundary.test.js +75 -0
- package/dist/billing/payram/charge-store.d.ts +41 -0
- package/dist/billing/payram/charge-store.js +72 -0
- package/dist/billing/payram/charge-store.test.d.ts +1 -0
- package/dist/billing/payram/charge-store.test.js +64 -0
- package/dist/billing/payram/checkout.d.ts +15 -0
- package/dist/billing/payram/checkout.js +24 -0
- package/dist/billing/payram/checkout.test.d.ts +1 -0
- package/dist/billing/payram/checkout.test.js +74 -0
- package/dist/billing/payram/client.d.ts +7 -0
- package/dist/billing/payram/client.js +15 -0
- package/dist/billing/payram/client.test.d.ts +1 -0
- package/dist/billing/payram/client.test.js +52 -0
- package/dist/billing/payram/index.d.ts +8 -0
- package/dist/billing/payram/index.js +4 -0
- package/dist/billing/payram/types.d.ts +40 -0
- package/dist/billing/payram/types.js +1 -0
- package/dist/billing/payram/webhook.d.ts +19 -0
- package/dist/billing/payram/webhook.js +67 -0
- package/dist/billing/payram/webhook.test.d.ts +7 -0
- package/dist/billing/payram/webhook.test.js +248 -0
- package/dist/billing/stripe/cents-credits-boundary.test.d.ts +1 -0
- package/dist/billing/stripe/cents-credits-boundary.test.js +62 -0
- package/dist/billing/stripe/checkout.d.ts +20 -0
- package/dist/billing/stripe/checkout.js +63 -0
- package/dist/billing/stripe/checkout.test.d.ts +1 -0
- package/dist/billing/stripe/checkout.test.js +148 -0
- package/dist/billing/stripe/client.d.ts +14 -0
- package/dist/billing/stripe/client.js +33 -0
- package/dist/billing/stripe/client.test.d.ts +1 -0
- package/dist/billing/stripe/client.test.js +58 -0
- package/dist/billing/stripe/credit-prices.d.ts +63 -0
- package/dist/billing/stripe/credit-prices.js +81 -0
- package/dist/billing/stripe/credit-prices.test.d.ts +1 -0
- package/dist/billing/stripe/credit-prices.test.js +87 -0
- package/dist/billing/stripe/index.d.ts +14 -0
- package/dist/billing/stripe/index.js +8 -0
- package/dist/billing/stripe/payment-methods-detach-all.test.d.ts +1 -0
- package/dist/billing/stripe/payment-methods-detach-all.test.js +40 -0
- package/dist/billing/stripe/payment-methods.d.ts +25 -0
- package/dist/billing/stripe/payment-methods.js +53 -0
- package/dist/billing/stripe/payment-methods.test.d.ts +1 -0
- package/dist/billing/stripe/payment-methods.test.js +122 -0
- package/dist/billing/stripe/portal.d.ts +10 -0
- package/dist/billing/stripe/portal.js +16 -0
- package/dist/billing/stripe/portal.test.d.ts +1 -0
- package/dist/billing/stripe/portal.test.js +48 -0
- package/dist/billing/stripe/setup-intent.d.ts +16 -0
- package/dist/billing/stripe/setup-intent.js +22 -0
- package/dist/billing/stripe/setup-intent.test.d.ts +1 -0
- package/dist/billing/stripe/setup-intent.test.js +58 -0
- package/dist/billing/stripe/stripe-payment-processor.d.ts +49 -0
- package/dist/billing/stripe/stripe-payment-processor.js +166 -0
- package/dist/billing/stripe/stripe-payment-processor.test.d.ts +1 -0
- package/dist/billing/stripe/stripe-payment-processor.test.js +413 -0
- package/dist/billing/stripe/tenant-store.d.ts +56 -0
- package/dist/billing/stripe/tenant-store.js +119 -0
- package/dist/billing/stripe/tenant-store.test.d.ts +1 -0
- package/dist/billing/stripe/tenant-store.test.js +97 -0
- package/dist/billing/stripe/types.d.ts +49 -0
- package/dist/billing/stripe/types.js +1 -0
- package/dist/billing/webhook-seen-repository.d.ts +14 -0
- package/dist/billing/webhook-seen-repository.js +13 -0
- package/dist/config/billing-env.test.d.ts +1 -0
- package/dist/config/billing-env.test.js +48 -0
- package/dist/config/index.d.ts +46 -0
- package/dist/config/index.js +38 -0
- package/dist/config/logger.d.ts +2 -0
- package/dist/config/logger.js +11 -0
- package/dist/config/provider-endpoints.d.ts +6 -0
- package/dist/config/provider-endpoints.js +12 -0
- package/dist/credits/auto-topup-charge.d.ts +27 -0
- package/dist/credits/auto-topup-charge.js +139 -0
- package/dist/credits/auto-topup-charge.test.d.ts +1 -0
- package/dist/credits/auto-topup-charge.test.js +242 -0
- package/dist/credits/auto-topup-event-log-repository.d.ts +16 -0
- package/dist/credits/auto-topup-event-log-repository.js +18 -0
- package/dist/credits/auto-topup-event-log-repository.test.d.ts +1 -0
- package/dist/credits/auto-topup-event-log-repository.test.js +83 -0
- package/dist/credits/auto-topup-schedule.d.ts +27 -0
- package/dist/credits/auto-topup-schedule.js +66 -0
- package/dist/credits/auto-topup-schedule.test.d.ts +1 -0
- package/dist/credits/auto-topup-schedule.test.js +145 -0
- package/dist/credits/auto-topup-settings-repository.d.ts +54 -0
- package/dist/credits/auto-topup-settings-repository.js +184 -0
- package/dist/credits/auto-topup-settings-repository.test.d.ts +1 -0
- package/dist/credits/auto-topup-settings-repository.test.js +104 -0
- package/dist/credits/auto-topup-usage.d.ts +22 -0
- package/dist/credits/auto-topup-usage.js +56 -0
- package/dist/credits/auto-topup-usage.test.d.ts +1 -0
- package/dist/credits/auto-topup-usage.test.js +181 -0
- package/dist/credits/credit-expiry-cron.d.ts +19 -0
- package/dist/credits/credit-expiry-cron.js +50 -0
- package/dist/credits/credit-expiry-cron.test.d.ts +1 -0
- package/dist/credits/credit-expiry-cron.test.js +67 -0
- package/dist/credits/credit-ledger-extra.test.d.ts +1 -0
- package/dist/credits/credit-ledger-extra.test.js +40 -0
- package/dist/credits/credit-ledger.bench.d.ts +1 -0
- package/dist/credits/credit-ledger.bench.js +33 -0
- package/dist/credits/credit-ledger.d.ts +130 -0
- package/dist/credits/credit-ledger.js +293 -0
- package/dist/credits/credit-ledger.test.d.ts +4 -0
- package/dist/credits/credit-ledger.test.js +203 -0
- package/dist/credits/credit-transaction-repository.d.ts +17 -0
- package/dist/credits/credit-transaction-repository.js +35 -0
- package/dist/credits/credit-transaction-repository.test.d.ts +1 -0
- package/dist/credits/credit-transaction-repository.test.js +232 -0
- package/dist/credits/credit.d.ts +75 -0
- package/dist/credits/credit.js +139 -0
- package/dist/credits/credit.test.d.ts +1 -0
- package/dist/credits/credit.test.js +196 -0
- package/dist/credits/dividend-cron.d.ts +29 -0
- package/dist/credits/dividend-cron.js +88 -0
- package/dist/credits/dividend-cron.test.d.ts +1 -0
- package/dist/credits/dividend-cron.test.js +128 -0
- package/dist/credits/dividend-repository.d.ts +29 -0
- package/dist/credits/dividend-repository.js +126 -0
- package/dist/credits/dividend-repository.test.d.ts +1 -0
- package/dist/credits/dividend-repository.test.js +176 -0
- package/dist/credits/index.d.ts +9 -0
- package/dist/credits/index.js +5 -0
- package/dist/credits/repository-types.d.ts +29 -0
- package/dist/credits/repository-types.js +1 -0
- package/dist/credits/signup-grant.d.ts +12 -0
- package/dist/credits/signup-grant.js +35 -0
- package/dist/credits/signup-grant.test.d.ts +1 -0
- package/dist/credits/signup-grant.test.js +51 -0
- package/dist/credits/tenant-customer-repository.d.ts +30 -0
- package/dist/credits/tenant-customer-repository.js +5 -0
- package/dist/db/auth-user-repository.d.ts +46 -0
- package/dist/db/auth-user-repository.js +90 -0
- package/dist/db/credit-column.d.ts +27 -0
- package/dist/db/credit-column.js +13 -0
- package/dist/db/index.d.ts +14 -0
- package/dist/db/index.js +8 -0
- package/dist/db/schema/account-deletion-requests.d.ts +203 -0
- package/dist/db/schema/account-deletion-requests.js +36 -0
- package/dist/db/schema/account-export-requests.d.ts +148 -0
- package/dist/db/schema/account-export-requests.js +19 -0
- package/dist/db/schema/admin-audit.d.ts +194 -0
- package/dist/db/schema/admin-audit.js +21 -0
- package/dist/db/schema/admin-users.d.ts +177 -0
- package/dist/db/schema/admin-users.js +23 -0
- package/dist/db/schema/affiliate-fraud.d.ts +160 -0
- package/dist/db/schema/affiliate-fraud.js +18 -0
- package/dist/db/schema/affiliate.d.ts +277 -0
- package/dist/db/schema/affiliate.js +32 -0
- package/dist/db/schema/coupon-codes.d.ts +143 -0
- package/dist/db/schema/coupon-codes.js +17 -0
- package/dist/db/schema/credit-auto-topup-settings.d.ts +232 -0
- package/dist/db/schema/credit-auto-topup-settings.js +27 -0
- package/dist/db/schema/credit-auto-topup.d.ts +130 -0
- package/dist/db/schema/credit-auto-topup.js +21 -0
- package/dist/db/schema/credits.d.ts +283 -0
- package/dist/db/schema/credits.js +38 -0
- package/dist/db/schema/dividend-distributions.d.ts +130 -0
- package/dist/db/schema/dividend-distributions.js +19 -0
- package/dist/db/schema/email-notifications.d.ts +99 -0
- package/dist/db/schema/email-notifications.js +21 -0
- package/dist/db/schema/index.d.ts +33 -0
- package/dist/db/schema/index.js +33 -0
- package/dist/db/schema/meter-events.d.ts +599 -0
- package/dist/db/schema/meter-events.js +55 -0
- package/dist/db/schema/notification-preferences.d.ts +165 -0
- package/dist/db/schema/notification-preferences.js +18 -0
- package/dist/db/schema/notification-queue.d.ts +236 -0
- package/dist/db/schema/notification-queue.js +40 -0
- package/dist/db/schema/org-memberships.d.ts +63 -0
- package/dist/db/schema/org-memberships.js +15 -0
- package/dist/db/schema/organization-members.d.ts +235 -0
- package/dist/db/schema/organization-members.js +27 -0
- package/dist/db/schema/payram.d.ts +164 -0
- package/dist/db/schema/payram.js +21 -0
- package/dist/db/schema/platform-api-keys.d.ts +143 -0
- package/dist/db/schema/platform-api-keys.js +20 -0
- package/dist/db/schema/promotion-redemptions.d.ts +143 -0
- package/dist/db/schema/promotion-redemptions.js +18 -0
- package/dist/db/schema/promotions.d.ts +445 -0
- package/dist/db/schema/promotions.js +48 -0
- package/dist/db/schema/provider-credentials.d.ts +201 -0
- package/dist/db/schema/provider-credentials.js +36 -0
- package/dist/db/schema/rate-limit-entries.d.ts +75 -0
- package/dist/db/schema/rate-limit-entries.js +7 -0
- package/dist/db/schema/secret-audit-log.d.ts +109 -0
- package/dist/db/schema/secret-audit-log.js +15 -0
- package/dist/db/schema/session-usage.d.ts +194 -0
- package/dist/db/schema/session-usage.js +19 -0
- package/dist/db/schema/spending-limits.d.ts +92 -0
- package/dist/db/schema/spending-limits.js +8 -0
- package/dist/db/schema/tenant-addons.d.ts +58 -0
- package/dist/db/schema/tenant-addons.js +9 -0
- package/dist/db/schema/tenant-api-keys.d.ts +131 -0
- package/dist/db/schema/tenant-api-keys.js +21 -0
- package/dist/db/schema/tenant-capability-settings.d.ts +79 -0
- package/dist/db/schema/tenant-capability-settings.js +12 -0
- package/dist/db/schema/tenant-customers.d.ts +303 -0
- package/dist/db/schema/tenant-customers.js +25 -0
- package/dist/db/schema/tenants.d.ts +126 -0
- package/dist/db/schema/tenants.js +18 -0
- package/dist/db/schema/user-roles.d.ts +98 -0
- package/dist/db/schema/user-roles.js +18 -0
- package/dist/db/schema/webhook-seen-events.d.ts +58 -0
- package/dist/db/schema/webhook-seen-events.js +9 -0
- package/dist/email/billing-emails.d.ts +51 -0
- package/dist/email/billing-emails.js +163 -0
- package/dist/email/billing-emails.test.d.ts +1 -0
- package/dist/email/billing-emails.test.js +162 -0
- package/dist/email/client.d.ts +51 -0
- package/dist/email/client.js +102 -0
- package/dist/email/client.test.d.ts +1 -0
- package/dist/email/client.test.js +120 -0
- package/dist/email/drizzle-billing-email-repository.d.ts +21 -0
- package/dist/email/drizzle-billing-email-repository.js +36 -0
- package/dist/email/drizzle-billing-email-repository.test.d.ts +1 -0
- package/dist/email/drizzle-billing-email-repository.test.js +42 -0
- package/dist/email/index.d.ts +33 -0
- package/dist/email/index.js +22 -0
- package/dist/email/notification-preferences-store.d.ts +12 -0
- package/dist/email/notification-preferences-store.js +82 -0
- package/dist/email/notification-preferences-store.test.d.ts +1 -0
- package/dist/email/notification-preferences-store.test.js +86 -0
- package/dist/email/notification-queue-store.d.ts +25 -0
- package/dist/email/notification-queue-store.js +97 -0
- package/dist/email/notification-queue-store.test.d.ts +1 -0
- package/dist/email/notification-queue-store.test.js +177 -0
- package/dist/email/notification-repository-types.d.ts +70 -0
- package/dist/email/notification-repository-types.js +6 -0
- package/dist/email/notification-service.d.ts +41 -0
- package/dist/email/notification-service.js +196 -0
- package/dist/email/notification-service.test.d.ts +1 -0
- package/dist/email/notification-service.test.js +160 -0
- package/dist/email/notification-templates.d.ts +18 -0
- package/dist/email/notification-templates.js +574 -0
- package/dist/email/notification-templates.test.d.ts +1 -0
- package/dist/email/notification-templates.test.js +238 -0
- package/dist/email/notification-worker.d.ts +24 -0
- package/dist/email/notification-worker.js +109 -0
- package/dist/email/notification-worker.test.d.ts +1 -0
- package/dist/email/notification-worker.test.js +153 -0
- package/dist/email/require-verified.d.ts +25 -0
- package/dist/email/require-verified.js +52 -0
- package/dist/email/require-verified.test.d.ts +1 -0
- package/dist/email/require-verified.test.js +62 -0
- package/dist/email/resend-adapter.d.ts +47 -0
- package/dist/email/resend-adapter.js +137 -0
- package/dist/email/resend-adapter.test.d.ts +1 -0
- package/dist/email/resend-adapter.test.js +190 -0
- package/dist/email/templates.d.ts +22 -0
- package/dist/email/templates.js +359 -0
- package/dist/email/templates.test.d.ts +1 -0
- package/dist/email/templates.test.js +170 -0
- package/dist/email/verification.d.ts +42 -0
- package/dist/email/verification.js +83 -0
- package/dist/email/verification.test.d.ts +1 -0
- package/dist/email/verification.test.js +141 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +23 -0
- package/dist/metering/aggregator.d.ts +54 -0
- package/dist/metering/aggregator.js +123 -0
- package/dist/metering/aggregator.test.d.ts +1 -0
- package/dist/metering/aggregator.test.js +179 -0
- package/dist/metering/dlq.d.ts +31 -0
- package/dist/metering/dlq.js +82 -0
- package/dist/metering/dlq.test.d.ts +1 -0
- package/dist/metering/dlq.test.js +117 -0
- package/dist/metering/drizzle-usage-summary-repository.d.ts +67 -0
- package/dist/metering/drizzle-usage-summary-repository.js +98 -0
- package/dist/metering/emitter.d.ts +66 -0
- package/dist/metering/emitter.js +185 -0
- package/dist/metering/emitter.test.d.ts +1 -0
- package/dist/metering/emitter.test.js +171 -0
- package/dist/metering/index.d.ts +11 -0
- package/dist/metering/index.js +5 -0
- package/dist/metering/load-test.bench.d.ts +1 -0
- package/dist/metering/load-test.bench.js +103 -0
- package/dist/metering/meter-event-repository.d.ts +33 -0
- package/dist/metering/meter-event-repository.js +58 -0
- package/dist/metering/meter-repositories.test.d.ts +1 -0
- package/dist/metering/meter-repositories.test.js +419 -0
- package/dist/metering/metering.test.d.ts +1 -0
- package/dist/metering/metering.test.js +1046 -0
- package/dist/metering/reconciliation-cron.d.ts +37 -0
- package/dist/metering/reconciliation-cron.js +85 -0
- package/dist/metering/reconciliation-cron.test.d.ts +1 -0
- package/dist/metering/reconciliation-cron.test.js +162 -0
- package/dist/metering/reconciliation-repository.d.ts +27 -0
- package/dist/metering/reconciliation-repository.js +43 -0
- package/dist/metering/reconciliation-repository.test.d.ts +1 -0
- package/dist/metering/reconciliation-repository.test.js +160 -0
- package/dist/metering/types.d.ts +88 -0
- package/dist/metering/types.js +1 -0
- package/dist/metering/wal.d.ts +49 -0
- package/dist/metering/wal.js +124 -0
- package/dist/metering/wal.test.d.ts +1 -0
- package/dist/metering/wal.test.js +175 -0
- package/dist/middleware/csrf.d.ts +24 -0
- package/dist/middleware/csrf.js +80 -0
- package/dist/middleware/csrf.test.d.ts +1 -0
- package/dist/middleware/csrf.test.js +152 -0
- package/dist/middleware/drizzle-rate-limit-repository.d.ts +9 -0
- package/dist/middleware/drizzle-rate-limit-repository.js +52 -0
- package/dist/middleware/drizzle-rate-limit-repository.test.d.ts +1 -0
- package/dist/middleware/drizzle-rate-limit-repository.test.js +74 -0
- package/dist/middleware/get-client-ip.d.ts +22 -0
- package/dist/middleware/get-client-ip.js +51 -0
- package/dist/middleware/get-client-ip.test.d.ts +1 -0
- package/dist/middleware/get-client-ip.test.js +40 -0
- package/dist/middleware/index.d.ts +5 -0
- package/dist/middleware/index.js +4 -0
- package/dist/middleware/rate-limit-repository.d.ts +19 -0
- package/dist/middleware/rate-limit-repository.js +1 -0
- package/dist/middleware/rate-limit.d.ts +57 -0
- package/dist/middleware/rate-limit.js +109 -0
- package/dist/middleware/rate-limit.test.d.ts +1 -0
- package/dist/middleware/rate-limit.test.js +247 -0
- package/dist/security/credential-vault/audit-repository.d.ts +27 -0
- package/dist/security/credential-vault/audit-repository.js +42 -0
- package/dist/security/credential-vault/audit-repository.test.d.ts +1 -0
- package/dist/security/credential-vault/audit-repository.test.js +78 -0
- package/dist/security/credential-vault/credential-repository.d.ts +94 -0
- package/dist/security/credential-vault/credential-repository.js +145 -0
- package/dist/security/credential-vault/credential-repository.test.d.ts +1 -0
- package/dist/security/credential-vault/credential-repository.test.js +206 -0
- package/dist/security/credential-vault/index.d.ts +12 -0
- package/dist/security/credential-vault/index.js +6 -0
- package/dist/security/credential-vault/key-rotation.d.ts +18 -0
- package/dist/security/credential-vault/key-rotation.js +52 -0
- package/dist/security/credential-vault/key-rotation.test.d.ts +1 -0
- package/dist/security/credential-vault/key-rotation.test.js +95 -0
- package/dist/security/credential-vault/migrate-plaintext.d.ts +15 -0
- package/dist/security/credential-vault/migrate-plaintext.js +80 -0
- package/dist/security/credential-vault/migrate-plaintext.test.d.ts +1 -0
- package/dist/security/credential-vault/migrate-plaintext.test.js +111 -0
- package/dist/security/credential-vault/migration-check.d.ts +15 -0
- package/dist/security/credential-vault/migration-check.js +71 -0
- package/dist/security/credential-vault/migration-check.test.d.ts +1 -0
- package/dist/security/credential-vault/migration-check.test.js +457 -0
- package/dist/security/credential-vault/store.d.ts +106 -0
- package/dist/security/credential-vault/store.js +181 -0
- package/dist/security/credential-vault/store.test.d.ts +1 -0
- package/dist/security/credential-vault/store.test.js +482 -0
- package/dist/security/encryption.d.ts +22 -0
- package/dist/security/encryption.js +53 -0
- package/dist/security/encryption.test.d.ts +1 -0
- package/dist/security/encryption.test.js +95 -0
- package/dist/security/host-validation.d.ts +11 -0
- package/dist/security/host-validation.js +108 -0
- package/dist/security/host-validation.test.d.ts +1 -0
- package/dist/security/host-validation.test.js +106 -0
- package/dist/security/index.d.ts +11 -0
- package/dist/security/index.js +11 -0
- package/dist/security/key-audit.d.ts +16 -0
- package/dist/security/key-audit.js +35 -0
- package/dist/security/key-audit.test.d.ts +1 -0
- package/dist/security/key-audit.test.js +50 -0
- package/dist/security/key-injection.d.ts +28 -0
- package/dist/security/key-injection.js +57 -0
- package/dist/security/key-injection.test.d.ts +1 -0
- package/dist/security/key-injection.test.js +97 -0
- package/dist/security/key-validation.d.ts +16 -0
- package/dist/security/key-validation.js +78 -0
- package/dist/security/key-validation.test.d.ts +1 -0
- package/dist/security/key-validation.test.js +87 -0
- package/dist/security/redirect-allowlist.d.ts +6 -0
- package/dist/security/redirect-allowlist.js +36 -0
- package/dist/security/redirect-allowlist.test.d.ts +1 -0
- package/dist/security/redirect-allowlist.test.js +55 -0
- package/dist/security/tenant-keys/capability-settings-store.d.ts +22 -0
- package/dist/security/tenant-keys/capability-settings-store.js +33 -0
- package/dist/security/tenant-keys/capability-settings-store.test.d.ts +1 -0
- package/dist/security/tenant-keys/capability-settings-store.test.js +77 -0
- package/dist/security/tenant-keys/index.d.ts +10 -0
- package/dist/security/tenant-keys/index.js +5 -0
- package/dist/security/tenant-keys/key-resolution-repository.d.ts +15 -0
- package/dist/security/tenant-keys/key-resolution-repository.js +18 -0
- package/dist/security/tenant-keys/key-resolution-repository.test.d.ts +1 -0
- package/dist/security/tenant-keys/key-resolution-repository.test.js +72 -0
- package/dist/security/tenant-keys/key-resolution.d.ts +39 -0
- package/dist/security/tenant-keys/key-resolution.js +59 -0
- package/dist/security/tenant-keys/key-resolution.test.d.ts +1 -0
- package/dist/security/tenant-keys/key-resolution.test.js +97 -0
- package/dist/security/tenant-keys/org-key-resolution.d.ts +30 -0
- package/dist/security/tenant-keys/org-key-resolution.js +50 -0
- package/dist/security/tenant-keys/org-key-resolution.test.d.ts +1 -0
- package/dist/security/tenant-keys/org-key-resolution.test.js +103 -0
- package/dist/security/tenant-keys/tenant-key-repository.d.ts +36 -0
- package/dist/security/tenant-keys/tenant-key-repository.js +96 -0
- package/dist/security/tenant-keys/tenant-key-repository.test.d.ts +1 -0
- package/dist/security/tenant-keys/tenant-key-repository.test.js +114 -0
- package/dist/security/types.d.ts +35 -0
- package/dist/security/types.js +15 -0
- package/dist/tenancy/drizzle-org-repository.d.ts +40 -0
- package/dist/tenancy/drizzle-org-repository.js +126 -0
- package/dist/tenancy/index.d.ts +6 -0
- package/dist/tenancy/index.js +3 -0
- package/dist/tenancy/org-member-repository.d.ts +57 -0
- package/dist/tenancy/org-member-repository.js +99 -0
- package/dist/tenancy/org-repository.test.d.ts +1 -0
- package/dist/tenancy/org-repository.test.js +143 -0
- package/dist/tenancy/org-service.d.ts +70 -0
- package/dist/tenancy/org-service.js +223 -0
- package/dist/tenancy/org-service.test.d.ts +1 -0
- package/dist/tenancy/org-service.test.js +550 -0
- package/dist/test/db.d.ts +33 -0
- package/dist/test/db.js +65 -0
- package/dist/trpc/index.d.ts +1 -0
- package/dist/trpc/index.js +1 -0
- package/dist/trpc/init.d.ts +49 -0
- package/dist/trpc/init.js +108 -0
- package/dist/trpc/init.test.d.ts +1 -0
- package/dist/trpc/init.test.js +154 -0
- package/drizzle/migrations/0000_slippery_mandrill.sql +559 -0
- package/drizzle/migrations/meta/0000_snapshot.json +4374 -0
- package/drizzle/migrations/meta/_journal.json +13 -0
- package/drizzle.config.ts +41 -0
- package/package.json +64 -0
- package/src/admin/admin-audit-log-repository.ts +135 -0
- package/src/admin/audit-log.ts +111 -0
- package/src/admin/index.ts +6 -0
- package/src/admin/role-store.ts +134 -0
- package/src/auth/api-key-repository.test.ts +63 -0
- package/src/auth/api-key-repository.ts +46 -0
- package/src/auth/auth.test.ts +166 -0
- package/src/auth/better-auth.ts +216 -0
- package/src/auth/index.ts +520 -0
- package/src/auth/login-history-repository.test.ts +54 -0
- package/src/auth/login-history-repository.ts +28 -0
- package/src/auth/middleware.test.ts +264 -0
- package/src/auth/middleware.ts +117 -0
- package/src/auth/scoped-tokens.test.ts +362 -0
- package/src/auth/tenant-access.test.ts +69 -0
- package/src/auth/user-creator.test.ts +98 -0
- package/src/auth/user-creator.ts +54 -0
- package/src/auth/user-role-repository.test.ts +149 -0
- package/src/auth/user-role-repository.ts +67 -0
- package/src/billing/drizzle-webhook-seen-repository.ts +34 -0
- package/src/billing/index.ts +22 -0
- package/src/billing/payment-processor.test.ts +93 -0
- package/src/billing/payment-processor.ts +150 -0
- package/src/billing/payram/cents-credits-boundary.test.ts +84 -0
- package/src/billing/payram/charge-store.test.ts +84 -0
- package/src/billing/payram/charge-store.ts +109 -0
- package/src/billing/payram/checkout.test.ts +99 -0
- package/src/billing/payram/checkout.ts +40 -0
- package/src/billing/payram/client.test.ts +62 -0
- package/src/billing/payram/client.ts +21 -0
- package/src/billing/payram/index.ts +14 -0
- package/src/billing/payram/types.ts +44 -0
- package/src/billing/payram/webhook.test.ts +318 -0
- package/src/billing/payram/webhook.ts +97 -0
- package/src/billing/stripe/cents-credits-boundary.test.ts +70 -0
- package/src/billing/stripe/checkout.test.ts +186 -0
- package/src/billing/stripe/checkout.ts +82 -0
- package/src/billing/stripe/client.test.ts +64 -0
- package/src/billing/stripe/client.ts +39 -0
- package/src/billing/stripe/credit-prices.test.ts +114 -0
- package/src/billing/stripe/credit-prices.ts +113 -0
- package/src/billing/stripe/index.ts +14 -0
- package/src/billing/stripe/payment-methods-detach-all.test.ts +53 -0
- package/src/billing/stripe/payment-methods.test.ts +157 -0
- package/src/billing/stripe/payment-methods.ts +76 -0
- package/src/billing/stripe/portal.test.ts +63 -0
- package/src/billing/stripe/portal.ts +25 -0
- package/src/billing/stripe/setup-intent.test.ts +78 -0
- package/src/billing/stripe/setup-intent.ts +34 -0
- package/src/billing/stripe/stripe-payment-processor.test.ts +517 -0
- package/src/billing/stripe/stripe-payment-processor.ts +255 -0
- package/src/billing/stripe/tenant-store.test.ts +124 -0
- package/src/billing/stripe/tenant-store.ts +151 -0
- package/src/billing/stripe/types.ts +53 -0
- package/src/billing/webhook-seen-repository.ts +24 -0
- package/src/config/billing-env.test.ts +54 -0
- package/src/config/index.ts +44 -0
- package/src/config/logger.ts +12 -0
- package/src/config/provider-endpoints.ts +14 -0
- package/src/credits/auto-topup-charge.test.ts +292 -0
- package/src/credits/auto-topup-charge.ts +171 -0
- package/src/credits/auto-topup-event-log-repository.test.ts +99 -0
- package/src/credits/auto-topup-event-log-repository.ts +30 -0
- package/src/credits/auto-topup-schedule.test.ts +179 -0
- package/src/credits/auto-topup-schedule.ts +93 -0
- package/src/credits/auto-topup-settings-repository.test.ts +123 -0
- package/src/credits/auto-topup-settings-repository.ts +245 -0
- package/src/credits/auto-topup-usage.test.ts +220 -0
- package/src/credits/auto-topup-usage.ts +68 -0
- package/src/credits/credit-expiry-cron.test.ts +125 -0
- package/src/credits/credit-expiry-cron.ts +76 -0
- package/src/credits/credit-ledger-extra.test.ts +57 -0
- package/src/credits/credit-ledger.bench.ts +56 -0
- package/src/credits/credit-ledger.test.ts +276 -0
- package/src/credits/credit-ledger.ts +450 -0
- package/src/credits/credit-transaction-repository.test.ts +274 -0
- package/src/credits/credit-transaction-repository.ts +62 -0
- package/src/credits/credit.test.ts +234 -0
- package/src/credits/credit.ts +160 -0
- package/src/credits/dividend-cron.test.ts +158 -0
- package/src/credits/dividend-cron.ts +127 -0
- package/src/credits/dividend-repository.test.ts +223 -0
- package/src/credits/dividend-repository.ts +182 -0
- package/src/credits/index.ts +25 -0
- package/src/credits/repository-types.ts +33 -0
- package/src/credits/signup-grant.test.ts +63 -0
- package/src/credits/signup-grant.ts +44 -0
- package/src/credits/tenant-customer-repository.ts +28 -0
- package/src/db/auth-user-repository.ts +124 -0
- package/src/db/credit-column.ts +17 -0
- package/src/db/index.ts +21 -0
- package/src/db/schema/account-deletion-requests.ts +41 -0
- package/src/db/schema/account-export-requests.ts +24 -0
- package/src/db/schema/admin-audit.ts +26 -0
- package/src/db/schema/admin-users.ts +31 -0
- package/src/db/schema/affiliate-fraud.ts +23 -0
- package/src/db/schema/affiliate.ts +38 -0
- package/src/db/schema/coupon-codes.ts +22 -0
- package/src/db/schema/credit-auto-topup-settings.ts +32 -0
- package/src/db/schema/credit-auto-topup.ts +26 -0
- package/src/db/schema/credits.ts +44 -0
- package/src/db/schema/dividend-distributions.ts +24 -0
- package/src/db/schema/email-notifications.ts +26 -0
- package/src/db/schema/index.ts +33 -0
- package/src/db/schema/meter-events.ts +70 -0
- package/src/db/schema/notification-preferences.ts +19 -0
- package/src/db/schema/notification-queue.ts +45 -0
- package/src/db/schema/org-memberships.ts +20 -0
- package/src/db/schema/organization-members.ts +37 -0
- package/src/db/schema/payram.ts +26 -0
- package/src/db/schema/platform-api-keys.ts +25 -0
- package/src/db/schema/promotion-redemptions.ts +23 -0
- package/src/db/schema/promotions.ts +57 -0
- package/src/db/schema/provider-credentials.ts +41 -0
- package/src/db/schema/rate-limit-entries.ts +12 -0
- package/src/db/schema/secret-audit-log.ts +20 -0
- package/src/db/schema/session-usage.ts +24 -0
- package/src/db/schema/spending-limits.ts +9 -0
- package/src/db/schema/tenant-addons.ts +14 -0
- package/src/db/schema/tenant-api-keys.ts +26 -0
- package/src/db/schema/tenant-capability-settings.ts +17 -0
- package/src/db/schema/tenant-customers.ts +35 -0
- package/src/db/schema/tenants.ts +23 -0
- package/src/db/schema/user-roles.ts +23 -0
- package/src/db/schema/webhook-seen-events.ts +14 -0
- package/src/email/billing-emails.test.ts +198 -0
- package/src/email/billing-emails.ts +211 -0
- package/src/email/client.test.ts +149 -0
- package/src/email/client.ts +137 -0
- package/src/email/drizzle-billing-email-repository.test.ts +52 -0
- package/src/email/drizzle-billing-email-repository.ts +59 -0
- package/src/email/index.ts +57 -0
- package/src/email/notification-preferences-store.test.ts +102 -0
- package/src/email/notification-preferences-store.ts +90 -0
- package/src/email/notification-queue-store.test.ts +215 -0
- package/src/email/notification-queue-store.ts +127 -0
- package/src/email/notification-repository-types.ts +101 -0
- package/src/email/notification-service.test.ts +178 -0
- package/src/email/notification-service.ts +265 -0
- package/src/email/notification-templates.test.ts +261 -0
- package/src/email/notification-templates.ts +727 -0
- package/src/email/notification-worker.test.ts +189 -0
- package/src/email/notification-worker.ts +133 -0
- package/src/email/require-verified.ts +65 -0
- package/src/email/resend-adapter.test.ts +253 -0
- package/src/email/resend-adapter.ts +157 -0
- package/src/email/templates.test.ts +217 -0
- package/src/email/templates.ts +469 -0
- package/src/email/verification.test.ts +185 -0
- package/src/email/verification.ts +110 -0
- package/src/index.ts +51 -0
- package/src/metering/aggregator.test.ts +239 -0
- package/src/metering/aggregator.ts +160 -0
- package/src/metering/dlq.test.ts +134 -0
- package/src/metering/dlq.ts +102 -0
- package/src/metering/drizzle-usage-summary-repository.ts +167 -0
- package/src/metering/emitter.test.ts +202 -0
- package/src/metering/emitter.ts +227 -0
- package/src/metering/index.ts +21 -0
- package/src/metering/load-test.bench.ts +130 -0
- package/src/metering/meter-event-repository.ts +87 -0
- package/src/metering/meter-repositories.test.ts +491 -0
- package/src/metering/metering.test.ts +1317 -0
- package/src/metering/reconciliation-cron.test.ts +202 -0
- package/src/metering/reconciliation-cron.ts +134 -0
- package/src/metering/reconciliation-repository.test.ts +196 -0
- package/src/metering/reconciliation-repository.ts +83 -0
- package/src/metering/types.ts +93 -0
- package/src/metering/wal.test.ts +222 -0
- package/src/metering/wal.ts +139 -0
- package/src/middleware/csrf.test.ts +178 -0
- package/src/middleware/csrf.ts +101 -0
- package/src/middleware/drizzle-rate-limit-repository.test.ts +97 -0
- package/src/middleware/drizzle-rate-limit-repository.ts +57 -0
- package/src/middleware/get-client-ip.test.ts +49 -0
- package/src/middleware/get-client-ip.ts +62 -0
- package/src/middleware/index.ts +12 -0
- package/src/middleware/rate-limit-repository.ts +22 -0
- package/src/middleware/rate-limit.test.ts +338 -0
- package/src/middleware/rate-limit.ts +169 -0
- package/src/security/credential-vault/audit-repository.test.ts +91 -0
- package/src/security/credential-vault/audit-repository.ts +64 -0
- package/src/security/credential-vault/credential-repository.test.ts +264 -0
- package/src/security/credential-vault/credential-repository.ts +233 -0
- package/src/security/credential-vault/index.ts +26 -0
- package/src/security/credential-vault/key-rotation.test.ts +139 -0
- package/src/security/credential-vault/key-rotation.ts +70 -0
- package/src/security/credential-vault/migrate-plaintext.test.ts +138 -0
- package/src/security/credential-vault/migrate-plaintext.ts +101 -0
- package/src/security/credential-vault/migration-check.test.ts +533 -0
- package/src/security/credential-vault/migration-check.ts +88 -0
- package/src/security/credential-vault/store.test.ts +569 -0
- package/src/security/credential-vault/store.ts +284 -0
- package/src/security/encryption.test.ts +114 -0
- package/src/security/encryption.ts +65 -0
- package/src/security/host-validation.test.ts +136 -0
- package/src/security/host-validation.ts +116 -0
- package/src/security/index.ts +59 -0
- package/src/security/key-audit.test.ts +57 -0
- package/src/security/key-audit.ts +45 -0
- package/src/security/key-injection.test.ts +131 -0
- package/src/security/key-injection.ts +71 -0
- package/src/security/key-validation.test.ts +111 -0
- package/src/security/key-validation.ts +84 -0
- package/src/security/redirect-allowlist.test.ts +70 -0
- package/src/security/redirect-allowlist.ts +35 -0
- package/src/security/tenant-keys/capability-settings-store.test.ts +98 -0
- package/src/security/tenant-keys/capability-settings-store.ts +53 -0
- package/src/security/tenant-keys/index.ts +10 -0
- package/src/security/tenant-keys/key-resolution-repository.test.ts +95 -0
- package/src/security/tenant-keys/key-resolution-repository.ts +31 -0
- package/src/security/tenant-keys/key-resolution.test.ts +173 -0
- package/src/security/tenant-keys/key-resolution.ts +87 -0
- package/src/security/tenant-keys/org-key-resolution.test.ts +217 -0
- package/src/security/tenant-keys/org-key-resolution.ts +76 -0
- package/src/security/tenant-keys/tenant-key-repository.test.ts +143 -0
- package/src/security/tenant-keys/tenant-key-repository.ts +130 -0
- package/src/security/types.ts +43 -0
- package/src/tenancy/drizzle-org-repository.ts +169 -0
- package/src/tenancy/index.ts +6 -0
- package/src/tenancy/org-member-repository.ts +159 -0
- package/src/tenancy/org-repository.test.ts +172 -0
- package/src/tenancy/org-service.test.ts +634 -0
- package/src/tenancy/org-service.ts +290 -0
- package/src/test/db.ts +97 -0
- package/src/trpc/index.ts +11 -0
- package/src/trpc/init.test.ts +196 -0
- package/src/trpc/init.ts +138 -0
- package/tsconfig.json +20 -0
- package/vitest.config.ts +8 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { IAdapterUsageRepository, IUsageSummaryRepository } from "./reconciliation-repository.js";
|
|
2
|
+
export interface ReconciliationConfig {
|
|
3
|
+
usageSummaryRepo: IUsageSummaryRepository;
|
|
4
|
+
adapterUsageRepo: IAdapterUsageRepository;
|
|
5
|
+
/** The date to reconcile, as YYYY-MM-DD. Defaults to yesterday. */
|
|
6
|
+
targetDate?: string;
|
|
7
|
+
/** Raw nano-dollar threshold below which drift is ignored. Default: 1 cent in raw units. */
|
|
8
|
+
driftThresholdRaw?: number;
|
|
9
|
+
/** If drift exceeds this raw amount, flag the account for review. Default: $1.00 in raw units. */
|
|
10
|
+
flagThresholdRaw?: number;
|
|
11
|
+
/** Callback invoked for each tenant exceeding the flag threshold. */
|
|
12
|
+
onFlagForReview?: (tenantId: string, driftRaw: number) => void | Promise<void>;
|
|
13
|
+
}
|
|
14
|
+
export interface ReconciliationResult {
|
|
15
|
+
/** Date reconciled (YYYY-MM-DD). */
|
|
16
|
+
date: string;
|
|
17
|
+
/** Number of tenants checked. */
|
|
18
|
+
tenantsChecked: number;
|
|
19
|
+
/** Tenants with drift exceeding driftThresholdRaw. */
|
|
20
|
+
discrepancies: Array<{
|
|
21
|
+
tenantId: string;
|
|
22
|
+
/** Total charge from metering (raw nano-dollars). */
|
|
23
|
+
meteredChargeRaw: number;
|
|
24
|
+
/** Total debits from ledger for adapter_usage (raw nano-dollars, absolute value). */
|
|
25
|
+
ledgerDebitRaw: number;
|
|
26
|
+
/** meteredChargeRaw - ledgerDebitRaw (positive = under-billed, negative = over-billed). */
|
|
27
|
+
driftRaw: number;
|
|
28
|
+
}>;
|
|
29
|
+
/** Tenants flagged for review (drift > flagThresholdRaw). */
|
|
30
|
+
flagged: string[];
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Reconcile metered usage charges against ledger adapter_usage debits
|
|
34
|
+
* for a single day. Logs warnings for any per-tenant drift exceeding the
|
|
35
|
+
* threshold and optionally flags accounts for review.
|
|
36
|
+
*/
|
|
37
|
+
export declare function runReconciliation(cfg: ReconciliationConfig): Promise<ReconciliationResult>;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { logger } from "../config/logger.js";
|
|
2
|
+
import { Credit } from "../credits/credit.js";
|
|
3
|
+
/**
|
|
4
|
+
* Reconcile metered usage charges against ledger adapter_usage debits
|
|
5
|
+
* for a single day. Logs warnings for any per-tenant drift exceeding the
|
|
6
|
+
* threshold and optionally flags accounts for review.
|
|
7
|
+
*/
|
|
8
|
+
export async function runReconciliation(cfg) {
|
|
9
|
+
const yesterday = new Date();
|
|
10
|
+
yesterday.setDate(yesterday.getDate() - 1);
|
|
11
|
+
const targetDate = cfg.targetDate ?? yesterday.toISOString().slice(0, 10);
|
|
12
|
+
const driftThresholdRaw = cfg.driftThresholdRaw ?? Credit.fromCents(1).toRaw();
|
|
13
|
+
const flagThresholdRaw = cfg.flagThresholdRaw ?? Credit.fromCents(100).toRaw();
|
|
14
|
+
// Convert YYYY-MM-DD to epoch ms range [dayStart, dayEnd)
|
|
15
|
+
const dayStart = new Date(`${targetDate}T00:00:00Z`).getTime();
|
|
16
|
+
const dayEnd = dayStart + 24 * 60 * 60 * 1000;
|
|
17
|
+
const result = {
|
|
18
|
+
date: targetDate,
|
|
19
|
+
tenantsChecked: 0,
|
|
20
|
+
discrepancies: [],
|
|
21
|
+
flagged: [],
|
|
22
|
+
};
|
|
23
|
+
// 1. Sum metered charges per tenant from usage_summaries for the day window.
|
|
24
|
+
// Filter out __sentinel__ rows.
|
|
25
|
+
const meteredRows = await cfg.usageSummaryRepo.getAggregatedChargesByWindow(dayStart, dayEnd);
|
|
26
|
+
// 2. Sum ledger adapter_usage debits per tenant for the same day.
|
|
27
|
+
// credit_transactions.created_at is a text column storing Postgres now() values
|
|
28
|
+
// (e.g. "2026-02-28 17:00:00+00"). Cast to timestamptz for reliable range comparison.
|
|
29
|
+
const dayStartIso = new Date(dayStart).toISOString();
|
|
30
|
+
const dayEndIso = new Date(dayEnd).toISOString();
|
|
31
|
+
const ledgerRows = await cfg.adapterUsageRepo.getAggregatedAdapterUsageDebits(dayStartIso, dayEndIso);
|
|
32
|
+
// 3. Build union of tenant IDs from both sources so ledger-only tenants
|
|
33
|
+
// (e.g. debits with no metering summary — negative drift / over-billed) are also checked.
|
|
34
|
+
const meteredMap = new Map();
|
|
35
|
+
for (const row of meteredRows) {
|
|
36
|
+
meteredMap.set(row.tenant, row.totalChargeRaw);
|
|
37
|
+
}
|
|
38
|
+
const ledgerMap = new Map();
|
|
39
|
+
for (const row of ledgerRows) {
|
|
40
|
+
ledgerMap.set(row.tenantId, row.totalDebitRaw);
|
|
41
|
+
}
|
|
42
|
+
const allTenants = new Set([...meteredMap.keys(), ...ledgerMap.keys()]);
|
|
43
|
+
if (allTenants.size === 0) {
|
|
44
|
+
return result;
|
|
45
|
+
}
|
|
46
|
+
// 4. Compare per-tenant across the union
|
|
47
|
+
result.tenantsChecked = allTenants.size;
|
|
48
|
+
for (const tenantId of allTenants) {
|
|
49
|
+
const meteredChargeRaw = meteredMap.get(tenantId) ?? 0;
|
|
50
|
+
const ledgerDebitRaw = ledgerMap.get(tenantId) ?? 0;
|
|
51
|
+
const driftRaw = meteredChargeRaw - ledgerDebitRaw;
|
|
52
|
+
const absDrift = Math.abs(driftRaw);
|
|
53
|
+
if (absDrift > driftThresholdRaw) {
|
|
54
|
+
result.discrepancies.push({
|
|
55
|
+
tenantId,
|
|
56
|
+
meteredChargeRaw,
|
|
57
|
+
ledgerDebitRaw,
|
|
58
|
+
driftRaw,
|
|
59
|
+
});
|
|
60
|
+
logger.warn("Metering/ledger drift detected", {
|
|
61
|
+
tenantId,
|
|
62
|
+
meteredCharge: Credit.fromRaw(meteredChargeRaw).toDisplayString(),
|
|
63
|
+
ledgerDebit: Credit.fromRaw(ledgerDebitRaw).toDisplayString(),
|
|
64
|
+
drift: Credit.fromRaw(absDrift).toDisplayString(),
|
|
65
|
+
direction: driftRaw > 0 ? "under-billed" : "over-billed",
|
|
66
|
+
date: targetDate,
|
|
67
|
+
});
|
|
68
|
+
if (absDrift > flagThresholdRaw) {
|
|
69
|
+
result.flagged.push(tenantId);
|
|
70
|
+
if (cfg.onFlagForReview) {
|
|
71
|
+
try {
|
|
72
|
+
await cfg.onFlagForReview(tenantId, driftRaw);
|
|
73
|
+
}
|
|
74
|
+
catch (err) {
|
|
75
|
+
logger.error("onFlagForReview callback failed for tenant", {
|
|
76
|
+
tenantId,
|
|
77
|
+
error: err instanceof Error ? err.message : String(err),
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return result;
|
|
85
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
|
3
|
+
import { usageSummaries } from "../db/schema/meter-events.js";
|
|
4
|
+
import { createTestDb, truncateAllTables } from "../test/db.js";
|
|
5
|
+
import { Credit } from "../credits/credit.js";
|
|
6
|
+
import { CreditLedger } from "../credits/credit-ledger.js";
|
|
7
|
+
import { runReconciliation } from "./reconciliation-cron.js";
|
|
8
|
+
import { DrizzleAdapterUsageRepository, DrizzleUsageSummaryRepository } from "./reconciliation-repository.js";
|
|
9
|
+
/** Today's date as YYYY-MM-DD (UTC). We use "today" as targetDate since the
|
|
10
|
+
* credit ledger inserts use `now()` for createdAt, so debits land in today's window. */
|
|
11
|
+
const TODAY = new Date().toISOString().slice(0, 10);
|
|
12
|
+
const DAY_START = new Date(`${TODAY}T00:00:00Z`).getTime();
|
|
13
|
+
const DAY_END = DAY_START + 24 * 60 * 60 * 1000;
|
|
14
|
+
describe("runReconciliation", () => {
|
|
15
|
+
let pool;
|
|
16
|
+
let db;
|
|
17
|
+
let ledger;
|
|
18
|
+
let usageSummaryRepo;
|
|
19
|
+
let adapterUsageRepo;
|
|
20
|
+
beforeAll(async () => {
|
|
21
|
+
const t = await createTestDb();
|
|
22
|
+
pool = t.pool;
|
|
23
|
+
db = t.db;
|
|
24
|
+
ledger = new CreditLedger(db);
|
|
25
|
+
usageSummaryRepo = new DrizzleUsageSummaryRepository(db);
|
|
26
|
+
adapterUsageRepo = new DrizzleAdapterUsageRepository(db);
|
|
27
|
+
});
|
|
28
|
+
afterAll(async () => {
|
|
29
|
+
await pool.close();
|
|
30
|
+
});
|
|
31
|
+
beforeEach(async () => {
|
|
32
|
+
await truncateAllTables(pool);
|
|
33
|
+
});
|
|
34
|
+
/** Insert a usage_summaries row directly. */
|
|
35
|
+
async function insertSummary(opts) {
|
|
36
|
+
await db.insert(usageSummaries).values({
|
|
37
|
+
id: crypto.randomUUID(),
|
|
38
|
+
tenant: opts.tenant,
|
|
39
|
+
capability: opts.capability ?? "chat",
|
|
40
|
+
provider: opts.provider ?? "openai",
|
|
41
|
+
eventCount: 1,
|
|
42
|
+
totalCost: 0,
|
|
43
|
+
totalCharge: opts.totalCharge,
|
|
44
|
+
totalDuration: 0,
|
|
45
|
+
windowStart: opts.windowStart ?? DAY_START,
|
|
46
|
+
windowEnd: opts.windowEnd ?? DAY_END - 1,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
it("returns empty result when no metering or ledger data", async () => {
|
|
50
|
+
const result = await runReconciliation({ usageSummaryRepo, adapterUsageRepo, targetDate: TODAY });
|
|
51
|
+
expect(result.tenantsChecked).toBe(0);
|
|
52
|
+
expect(result.discrepancies).toEqual([]);
|
|
53
|
+
expect(result.flagged).toEqual([]);
|
|
54
|
+
expect(result.date).toBe(TODAY);
|
|
55
|
+
});
|
|
56
|
+
it("no discrepancy when metered charge matches ledger debit", async () => {
|
|
57
|
+
const charge = Credit.fromCents(50);
|
|
58
|
+
await insertSummary({ tenant: "t1", totalCharge: charge.toRaw() });
|
|
59
|
+
await ledger.credit("t1", Credit.fromCents(500), "purchase");
|
|
60
|
+
await ledger.debit("t1", charge, "adapter_usage", "chat usage");
|
|
61
|
+
const result = await runReconciliation({ usageSummaryRepo, adapterUsageRepo, targetDate: TODAY });
|
|
62
|
+
expect(result.tenantsChecked).toBe(1);
|
|
63
|
+
expect(result.discrepancies).toEqual([]);
|
|
64
|
+
});
|
|
65
|
+
it("detects drift when metered charge exceeds ledger debit", async () => {
|
|
66
|
+
await insertSummary({ tenant: "t1", totalCharge: Credit.fromCents(100).toRaw() });
|
|
67
|
+
await ledger.credit("t1", Credit.fromCents(500), "purchase");
|
|
68
|
+
await ledger.debit("t1", Credit.fromCents(80), "adapter_usage", "chat usage");
|
|
69
|
+
const result = await runReconciliation({ usageSummaryRepo, adapterUsageRepo, targetDate: TODAY });
|
|
70
|
+
expect(result.tenantsChecked).toBe(1);
|
|
71
|
+
expect(result.discrepancies).toHaveLength(1);
|
|
72
|
+
expect(result.discrepancies[0].tenantId).toBe("t1");
|
|
73
|
+
// drift = metered(100c) - ledger(80c) = 20c, in raw units
|
|
74
|
+
expect(result.discrepancies[0].driftRaw).toBe(Credit.fromCents(20).toRaw());
|
|
75
|
+
});
|
|
76
|
+
it("flags tenant when drift exceeds flag threshold", async () => {
|
|
77
|
+
await insertSummary({ tenant: "t1", totalCharge: Credit.fromCents(200).toRaw() });
|
|
78
|
+
// No ledger debit at all — simulating a missed deduction
|
|
79
|
+
const onFlagForReview = vi.fn();
|
|
80
|
+
const result = await runReconciliation({
|
|
81
|
+
usageSummaryRepo,
|
|
82
|
+
adapterUsageRepo,
|
|
83
|
+
targetDate: TODAY,
|
|
84
|
+
flagThresholdRaw: Credit.fromCents(100).toRaw(), // $1.00
|
|
85
|
+
onFlagForReview,
|
|
86
|
+
});
|
|
87
|
+
expect(result.flagged).toContain("t1");
|
|
88
|
+
expect(onFlagForReview).toHaveBeenCalledWith("t1", Credit.fromCents(200).toRaw());
|
|
89
|
+
});
|
|
90
|
+
it("ignores non-adapter_usage debits (bot_runtime, etc.)", async () => {
|
|
91
|
+
await insertSummary({ tenant: "t1", totalCharge: Credit.fromCents(20).toRaw() });
|
|
92
|
+
await ledger.credit("t1", Credit.fromCents(500), "purchase");
|
|
93
|
+
// Debit as bot_runtime — should NOT count toward reconciliation
|
|
94
|
+
await ledger.debit("t1", Credit.fromCents(20), "bot_runtime", "daily runtime");
|
|
95
|
+
const result = await runReconciliation({ usageSummaryRepo, adapterUsageRepo, targetDate: TODAY });
|
|
96
|
+
// Metered 20c, ledger adapter_usage = 0 => drift = 20c
|
|
97
|
+
expect(result.discrepancies).toHaveLength(1);
|
|
98
|
+
expect(result.discrepancies[0].driftRaw).toBe(Credit.fromCents(20).toRaw());
|
|
99
|
+
});
|
|
100
|
+
it("ignores __sentinel__ rows from usage_summaries", async () => {
|
|
101
|
+
// Insert a sentinel row (should be ignored)
|
|
102
|
+
await db.insert(usageSummaries).values({
|
|
103
|
+
id: crypto.randomUUID(),
|
|
104
|
+
tenant: "__sentinel__",
|
|
105
|
+
capability: "__none__",
|
|
106
|
+
provider: "__none__",
|
|
107
|
+
eventCount: 0,
|
|
108
|
+
totalCost: 0,
|
|
109
|
+
totalCharge: 0,
|
|
110
|
+
totalDuration: 0,
|
|
111
|
+
windowStart: DAY_START,
|
|
112
|
+
windowEnd: DAY_END - 1,
|
|
113
|
+
});
|
|
114
|
+
const result = await runReconciliation({ usageSummaryRepo, adapterUsageRepo, targetDate: TODAY });
|
|
115
|
+
expect(result.tenantsChecked).toBe(0);
|
|
116
|
+
expect(result.discrepancies).toEqual([]);
|
|
117
|
+
});
|
|
118
|
+
it("handles multiple tenants independently", async () => {
|
|
119
|
+
// t1: balanced
|
|
120
|
+
await insertSummary({ tenant: "t1", totalCharge: Credit.fromCents(50).toRaw() });
|
|
121
|
+
await ledger.credit("t1", Credit.fromCents(500), "purchase");
|
|
122
|
+
await ledger.debit("t1", Credit.fromCents(50), "adapter_usage", "chat");
|
|
123
|
+
// t2: drifted
|
|
124
|
+
await insertSummary({ tenant: "t2", totalCharge: Credit.fromCents(100).toRaw() });
|
|
125
|
+
await ledger.credit("t2", Credit.fromCents(500), "purchase");
|
|
126
|
+
await ledger.debit("t2", Credit.fromCents(60), "adapter_usage", "chat");
|
|
127
|
+
const result = await runReconciliation({ usageSummaryRepo, adapterUsageRepo, targetDate: TODAY });
|
|
128
|
+
expect(result.tenantsChecked).toBe(2);
|
|
129
|
+
expect(result.discrepancies).toHaveLength(1);
|
|
130
|
+
expect(result.discrepancies[0].tenantId).toBe("t2");
|
|
131
|
+
expect(result.discrepancies[0].driftRaw).toBe(Credit.fromCents(40).toRaw());
|
|
132
|
+
});
|
|
133
|
+
it("uses yesterday as default targetDate", async () => {
|
|
134
|
+
const yesterday = new Date();
|
|
135
|
+
yesterday.setDate(yesterday.getDate() - 1);
|
|
136
|
+
const expectedDate = yesterday.toISOString().slice(0, 10);
|
|
137
|
+
const result = await runReconciliation({ usageSummaryRepo, adapterUsageRepo });
|
|
138
|
+
expect(result.date).toBe(expectedDate);
|
|
139
|
+
expect(result.tenantsChecked).toBe(0);
|
|
140
|
+
});
|
|
141
|
+
it("ignores metering data outside the target date window", async () => {
|
|
142
|
+
// Summary from yesterday — should not appear in today's reconciliation
|
|
143
|
+
const yesterday = new Date(`${TODAY}T00:00:00Z`).getTime() - 24 * 60 * 60 * 1000;
|
|
144
|
+
await insertSummary({
|
|
145
|
+
tenant: "t1",
|
|
146
|
+
totalCharge: Credit.fromCents(100).toRaw(),
|
|
147
|
+
windowStart: yesterday,
|
|
148
|
+
windowEnd: yesterday + 24 * 60 * 60 * 1000 - 1,
|
|
149
|
+
});
|
|
150
|
+
const result = await runReconciliation({ usageSummaryRepo, adapterUsageRepo, targetDate: TODAY });
|
|
151
|
+
expect(result.tenantsChecked).toBe(0);
|
|
152
|
+
});
|
|
153
|
+
it("handles over-billed drift (ledger > metered)", async () => {
|
|
154
|
+
// Metered 50c but debited 80c (over-billed)
|
|
155
|
+
await insertSummary({ tenant: "t1", totalCharge: Credit.fromCents(50).toRaw() });
|
|
156
|
+
await ledger.credit("t1", Credit.fromCents(500), "purchase");
|
|
157
|
+
await ledger.debit("t1", Credit.fromCents(80), "adapter_usage", "chat usage");
|
|
158
|
+
const result = await runReconciliation({ usageSummaryRepo, adapterUsageRepo, targetDate: TODAY });
|
|
159
|
+
expect(result.discrepancies).toHaveLength(1);
|
|
160
|
+
expect(result.discrepancies[0].driftRaw).toBe(Credit.fromCents(-30).toRaw());
|
|
161
|
+
});
|
|
162
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { PlatformDb } from "../db/index.js";
|
|
2
|
+
export interface AggregatedCharge {
|
|
3
|
+
tenant: string;
|
|
4
|
+
totalChargeRaw: number;
|
|
5
|
+
}
|
|
6
|
+
export interface IUsageSummaryRepository {
|
|
7
|
+
/** Sum metered charges per tenant for windows overlapping [windowStart, windowEnd). */
|
|
8
|
+
getAggregatedChargesByWindow(windowStart: number, windowEnd: number): Promise<AggregatedCharge[]>;
|
|
9
|
+
}
|
|
10
|
+
export declare class DrizzleUsageSummaryRepository implements IUsageSummaryRepository {
|
|
11
|
+
private readonly db;
|
|
12
|
+
constructor(db: PlatformDb);
|
|
13
|
+
getAggregatedChargesByWindow(windowStart: number, windowEnd: number): Promise<AggregatedCharge[]>;
|
|
14
|
+
}
|
|
15
|
+
export interface AggregatedDebit {
|
|
16
|
+
tenantId: string;
|
|
17
|
+
totalDebitRaw: number;
|
|
18
|
+
}
|
|
19
|
+
export interface IAdapterUsageRepository {
|
|
20
|
+
/** Sum adapter_usage debits per tenant within [startIso, endIso). */
|
|
21
|
+
getAggregatedAdapterUsageDebits(startIso: string, endIso: string): Promise<AggregatedDebit[]>;
|
|
22
|
+
}
|
|
23
|
+
export declare class DrizzleAdapterUsageRepository implements IAdapterUsageRepository {
|
|
24
|
+
private readonly db;
|
|
25
|
+
constructor(db: PlatformDb);
|
|
26
|
+
getAggregatedAdapterUsageDebits(startIso: string, endIso: string): Promise<AggregatedDebit[]>;
|
|
27
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { and, eq, gte, lt, ne, sql } from "drizzle-orm";
|
|
2
|
+
import { creditTransactions } from "../db/schema/credits.js";
|
|
3
|
+
import { usageSummaries } from "../db/schema/meter-events.js";
|
|
4
|
+
export class DrizzleUsageSummaryRepository {
|
|
5
|
+
db;
|
|
6
|
+
constructor(db) {
|
|
7
|
+
this.db = db;
|
|
8
|
+
}
|
|
9
|
+
async getAggregatedChargesByWindow(windowStart, windowEnd) {
|
|
10
|
+
const rows = await this.db
|
|
11
|
+
.select({
|
|
12
|
+
tenant: usageSummaries.tenant,
|
|
13
|
+
// raw SQL: Drizzle cannot express COALESCE with SUM aggregation
|
|
14
|
+
totalCharge: sql `COALESCE(SUM(${usageSummaries.totalCharge}), 0)`,
|
|
15
|
+
})
|
|
16
|
+
.from(usageSummaries)
|
|
17
|
+
.where(and(gte(usageSummaries.windowStart, windowStart), lt(usageSummaries.windowEnd, windowEnd), ne(usageSummaries.tenant, "__sentinel__")))
|
|
18
|
+
.groupBy(usageSummaries.tenant);
|
|
19
|
+
return rows.map((r) => ({ tenant: r.tenant, totalChargeRaw: Number(r.totalCharge) }));
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export class DrizzleAdapterUsageRepository {
|
|
23
|
+
db;
|
|
24
|
+
constructor(db) {
|
|
25
|
+
this.db = db;
|
|
26
|
+
}
|
|
27
|
+
async getAggregatedAdapterUsageDebits(startIso, endIso) {
|
|
28
|
+
const rows = await this.db
|
|
29
|
+
.select({
|
|
30
|
+
tenantId: creditTransactions.tenantId,
|
|
31
|
+
// amount_credits stores negative values for debits; ABS gives the raw positive debit amount.
|
|
32
|
+
// Use the raw column name in sql to bypass the custom creditColumn type serializer.
|
|
33
|
+
// raw SQL: Drizzle cannot express ABS with COALESCE and SUM
|
|
34
|
+
totalDebitRaw: sql `COALESCE(SUM(ABS(amount_credits)), 0)`,
|
|
35
|
+
})
|
|
36
|
+
.from(creditTransactions)
|
|
37
|
+
.where(and(eq(creditTransactions.type, "adapter_usage"),
|
|
38
|
+
// raw SQL: Drizzle cannot express timestamptz cast for text column date comparison
|
|
39
|
+
sql `${creditTransactions.createdAt}::timestamptz >= ${startIso}::timestamptz`, sql `${creditTransactions.createdAt}::timestamptz < ${endIso}::timestamptz`))
|
|
40
|
+
.groupBy(creditTransactions.tenantId);
|
|
41
|
+
return rows.map((r) => ({ tenantId: r.tenantId, totalDebitRaw: Number(r.totalDebitRaw) }));
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import { afterAll, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
|
3
|
+
import { createTestDb, seedUsageSummary, truncateAllTables } from "../test/db.js";
|
|
4
|
+
import { Credit } from "../credits/credit.js";
|
|
5
|
+
import { CreditLedger } from "../credits/credit-ledger.js";
|
|
6
|
+
import { DrizzleAdapterUsageRepository, DrizzleUsageSummaryRepository } from "./reconciliation-repository.js";
|
|
7
|
+
let pool;
|
|
8
|
+
let db;
|
|
9
|
+
beforeAll(async () => {
|
|
10
|
+
({ db, pool } = await createTestDb());
|
|
11
|
+
});
|
|
12
|
+
afterAll(async () => {
|
|
13
|
+
await pool.close();
|
|
14
|
+
});
|
|
15
|
+
/** Epoch ms for a given date string. */
|
|
16
|
+
function epochMs(dateStr) {
|
|
17
|
+
return new Date(dateStr).getTime();
|
|
18
|
+
}
|
|
19
|
+
describe("DrizzleUsageSummaryRepository", () => {
|
|
20
|
+
let repo;
|
|
21
|
+
beforeEach(async () => {
|
|
22
|
+
await truncateAllTables(pool);
|
|
23
|
+
repo = new DrizzleUsageSummaryRepository(db);
|
|
24
|
+
});
|
|
25
|
+
it("returns empty array when no usage summaries exist", async () => {
|
|
26
|
+
const result = await repo.getAggregatedChargesByWindow(0, Date.now());
|
|
27
|
+
expect(result).toEqual([]);
|
|
28
|
+
});
|
|
29
|
+
it("aggregates charges per tenant within [windowStart, windowEnd)", async () => {
|
|
30
|
+
const dayStart = epochMs("2026-02-15T00:00:00Z");
|
|
31
|
+
const dayEnd = epochMs("2026-02-16T00:00:00Z");
|
|
32
|
+
// Two summaries for t1 within window
|
|
33
|
+
await seedUsageSummary(db, {
|
|
34
|
+
id: crypto.randomUUID(),
|
|
35
|
+
tenant: "t1",
|
|
36
|
+
totalCharge: 5000,
|
|
37
|
+
windowStart: dayStart,
|
|
38
|
+
windowEnd: dayStart + 3600000,
|
|
39
|
+
});
|
|
40
|
+
await seedUsageSummary(db, {
|
|
41
|
+
id: crypto.randomUUID(),
|
|
42
|
+
tenant: "t1",
|
|
43
|
+
totalCharge: 3000,
|
|
44
|
+
windowStart: dayStart + 3600000,
|
|
45
|
+
windowEnd: dayStart + 7200000,
|
|
46
|
+
});
|
|
47
|
+
// One summary for t2 within window
|
|
48
|
+
await seedUsageSummary(db, {
|
|
49
|
+
id: crypto.randomUUID(),
|
|
50
|
+
tenant: "t2",
|
|
51
|
+
totalCharge: 1000,
|
|
52
|
+
windowStart: dayStart,
|
|
53
|
+
windowEnd: dayStart + 3600000,
|
|
54
|
+
});
|
|
55
|
+
const result = await repo.getAggregatedChargesByWindow(dayStart, dayEnd);
|
|
56
|
+
expect(result).toHaveLength(2);
|
|
57
|
+
const t1 = result.find((r) => r.tenant === "t1");
|
|
58
|
+
const t2 = result.find((r) => r.tenant === "t2");
|
|
59
|
+
expect(t1?.totalChargeRaw).toBe(8000); // 5000 + 3000
|
|
60
|
+
expect(t2?.totalChargeRaw).toBe(1000);
|
|
61
|
+
});
|
|
62
|
+
it("excludes __sentinel__ rows", async () => {
|
|
63
|
+
const dayStart = epochMs("2026-02-15T00:00:00Z");
|
|
64
|
+
await seedUsageSummary(db, {
|
|
65
|
+
id: crypto.randomUUID(),
|
|
66
|
+
tenant: "__sentinel__",
|
|
67
|
+
totalCharge: 999999,
|
|
68
|
+
windowStart: dayStart,
|
|
69
|
+
windowEnd: dayStart + 3600000,
|
|
70
|
+
});
|
|
71
|
+
await seedUsageSummary(db, {
|
|
72
|
+
id: crypto.randomUUID(),
|
|
73
|
+
tenant: "t1",
|
|
74
|
+
totalCharge: 100,
|
|
75
|
+
windowStart: dayStart,
|
|
76
|
+
windowEnd: dayStart + 3600000,
|
|
77
|
+
});
|
|
78
|
+
const result = await repo.getAggregatedChargesByWindow(dayStart, dayStart + 86400000);
|
|
79
|
+
expect(result).toHaveLength(1);
|
|
80
|
+
expect(result[0].tenant).toBe("t1");
|
|
81
|
+
});
|
|
82
|
+
it("excludes summaries outside the window", async () => {
|
|
83
|
+
const dayStart = epochMs("2026-02-15T00:00:00Z");
|
|
84
|
+
const dayEnd = epochMs("2026-02-16T00:00:00Z");
|
|
85
|
+
// Before window
|
|
86
|
+
await seedUsageSummary(db, {
|
|
87
|
+
id: crypto.randomUUID(),
|
|
88
|
+
tenant: "t1",
|
|
89
|
+
totalCharge: 100,
|
|
90
|
+
windowStart: epochMs("2026-02-14T00:00:00Z"),
|
|
91
|
+
windowEnd: epochMs("2026-02-14T01:00:00Z"),
|
|
92
|
+
});
|
|
93
|
+
// After window
|
|
94
|
+
await seedUsageSummary(db, {
|
|
95
|
+
id: crypto.randomUUID(),
|
|
96
|
+
tenant: "t1",
|
|
97
|
+
totalCharge: 200,
|
|
98
|
+
windowStart: dayEnd,
|
|
99
|
+
windowEnd: dayEnd + 3600000,
|
|
100
|
+
});
|
|
101
|
+
const result = await repo.getAggregatedChargesByWindow(dayStart, dayEnd);
|
|
102
|
+
expect(result).toEqual([]);
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
describe("DrizzleAdapterUsageRepository", () => {
|
|
106
|
+
let repo;
|
|
107
|
+
let ledger;
|
|
108
|
+
beforeEach(async () => {
|
|
109
|
+
await truncateAllTables(pool);
|
|
110
|
+
repo = new DrizzleAdapterUsageRepository(db);
|
|
111
|
+
ledger = new CreditLedger(db);
|
|
112
|
+
});
|
|
113
|
+
it("returns empty array when no adapter_usage debits exist", async () => {
|
|
114
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
115
|
+
const startIso = `${today}T00:00:00Z`;
|
|
116
|
+
const endIso = new Date(new Date(startIso).getTime() + 86400000).toISOString();
|
|
117
|
+
const result = await repo.getAggregatedAdapterUsageDebits(startIso, endIso);
|
|
118
|
+
expect(result).toEqual([]);
|
|
119
|
+
});
|
|
120
|
+
it("aggregates adapter_usage debits per tenant within [startIso, endIso)", async () => {
|
|
121
|
+
// Fund tenants
|
|
122
|
+
await ledger.credit("t1", Credit.fromCents(1000), "purchase");
|
|
123
|
+
await ledger.credit("t2", Credit.fromCents(1000), "purchase");
|
|
124
|
+
await ledger.debit("t1", Credit.fromCents(30), "adapter_usage", "t1-debit-1");
|
|
125
|
+
await ledger.debit("t1", Credit.fromCents(20), "adapter_usage", "t1-debit-2");
|
|
126
|
+
await ledger.debit("t2", Credit.fromCents(50), "adapter_usage", "t2-debit-1");
|
|
127
|
+
// Query window covering today
|
|
128
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
129
|
+
const startIso = `${today}T00:00:00Z`;
|
|
130
|
+
const endIso = new Date(new Date(startIso).getTime() + 86400000).toISOString();
|
|
131
|
+
const result = await repo.getAggregatedAdapterUsageDebits(startIso, endIso);
|
|
132
|
+
expect(result).toHaveLength(2);
|
|
133
|
+
const t1 = result.find((r) => r.tenantId === "t1");
|
|
134
|
+
const t2 = result.find((r) => r.tenantId === "t2");
|
|
135
|
+
expect(t1?.totalDebitRaw).toBe(Credit.fromCents(50).toRaw()); // 30 + 20
|
|
136
|
+
expect(t2?.totalDebitRaw).toBe(Credit.fromCents(50).toRaw());
|
|
137
|
+
});
|
|
138
|
+
it("excludes non-adapter_usage debit types", async () => {
|
|
139
|
+
await ledger.credit("t1", Credit.fromCents(1000), "purchase");
|
|
140
|
+
await ledger.debit("t1", Credit.fromCents(30), "adapter_usage", "adapter debit");
|
|
141
|
+
await ledger.debit("t1", Credit.fromCents(20), "bot_runtime", "runtime debit");
|
|
142
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
143
|
+
const startIso = `${today}T00:00:00Z`;
|
|
144
|
+
const endIso = new Date(new Date(startIso).getTime() + 86400000).toISOString();
|
|
145
|
+
const result = await repo.getAggregatedAdapterUsageDebits(startIso, endIso);
|
|
146
|
+
expect(result).toHaveLength(1);
|
|
147
|
+
expect(result[0].totalDebitRaw).toBe(Credit.fromCents(30).toRaw());
|
|
148
|
+
});
|
|
149
|
+
it("excludes credit transactions (positive amounts are not debits)", async () => {
|
|
150
|
+
await ledger.credit("t1", Credit.fromCents(1000), "purchase");
|
|
151
|
+
await ledger.debit("t1", Credit.fromCents(10), "adapter_usage", "real debit");
|
|
152
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
153
|
+
const startIso = `${today}T00:00:00Z`;
|
|
154
|
+
const endIso = new Date(new Date(startIso).getTime() + 86400000).toISOString();
|
|
155
|
+
const result = await repo.getAggregatedAdapterUsageDebits(startIso, endIso);
|
|
156
|
+
expect(result).toHaveLength(1);
|
|
157
|
+
// Only the 10-cent adapter_usage debit
|
|
158
|
+
expect(result[0].totalDebitRaw).toBe(Credit.fromCents(10).toRaw());
|
|
159
|
+
});
|
|
160
|
+
});
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type { Credit } from "../credits/credit.js";
|
|
2
|
+
/** A single metering event emitted by the socket after observing an adapter call. */
|
|
3
|
+
export interface MeterEvent {
|
|
4
|
+
/** Tenant identifier (who). */
|
|
5
|
+
tenant: string;
|
|
6
|
+
/** Upstream cost from the provider (as Credit value object). */
|
|
7
|
+
cost: Credit;
|
|
8
|
+
/** What we charge the tenant (cost x multiplier, as Credit value object). */
|
|
9
|
+
charge: Credit;
|
|
10
|
+
/** Capability used (embeddings, voice, search, chat, etc.). */
|
|
11
|
+
capability: string;
|
|
12
|
+
/** Which adapter fulfilled the request (replicate, deepgram, elevenlabs, etc.). */
|
|
13
|
+
provider: string;
|
|
14
|
+
/** Unix epoch ms when the event occurred. */
|
|
15
|
+
timestamp: number;
|
|
16
|
+
/** Groups events from one continuous session (voice calls, etc.). */
|
|
17
|
+
sessionId?: string;
|
|
18
|
+
/** Session duration in ms (for usage dashboard display). */
|
|
19
|
+
duration?: number;
|
|
20
|
+
/** Generic usage measurement — units + unitType (WOP-512). */
|
|
21
|
+
usage?: {
|
|
22
|
+
/** Quantity consumed (tokens, seconds, characters, pixels, requests, etc.). */
|
|
23
|
+
units: number;
|
|
24
|
+
/** What the units represent. */
|
|
25
|
+
unitType: string;
|
|
26
|
+
};
|
|
27
|
+
/** Pricing tier for billing multiplier lookup (WOP-512). */
|
|
28
|
+
tier?: "wopr" | "branded" | "byok";
|
|
29
|
+
/** Provider-specific details (model name, voice ID, resolution, etc.) (WOP-512). */
|
|
30
|
+
metadata?: Record<string, unknown>;
|
|
31
|
+
}
|
|
32
|
+
/** A meter event row as stored in SQLite. */
|
|
33
|
+
export interface MeterEventRow {
|
|
34
|
+
id: string;
|
|
35
|
+
tenant: string;
|
|
36
|
+
cost: number;
|
|
37
|
+
charge: number;
|
|
38
|
+
capability: string;
|
|
39
|
+
provider: string;
|
|
40
|
+
timestamp: number;
|
|
41
|
+
session_id: string | null;
|
|
42
|
+
duration: number | null;
|
|
43
|
+
/** WOP-512: Generic usage fields */
|
|
44
|
+
usage_units: number | null;
|
|
45
|
+
usage_unit_type: string | null;
|
|
46
|
+
tier: string | null;
|
|
47
|
+
metadata: string | null;
|
|
48
|
+
}
|
|
49
|
+
/** Per-tenant usage summary for a given time window. */
|
|
50
|
+
export interface UsageSummary {
|
|
51
|
+
tenant: string;
|
|
52
|
+
capability: string;
|
|
53
|
+
provider: string;
|
|
54
|
+
/** Number of events aggregated. */
|
|
55
|
+
event_count: number;
|
|
56
|
+
/** Sum of upstream costs. */
|
|
57
|
+
total_cost: number;
|
|
58
|
+
/** Sum of charges to tenant. */
|
|
59
|
+
total_charge: number;
|
|
60
|
+
/** Sum of durations in ms (for session-based capabilities). */
|
|
61
|
+
total_duration: number;
|
|
62
|
+
/** Start of the aggregation window (unix epoch ms). */
|
|
63
|
+
window_start: number;
|
|
64
|
+
/** End of the aggregation window (unix epoch ms). */
|
|
65
|
+
window_end: number;
|
|
66
|
+
}
|
|
67
|
+
/** A billing period boundary (e.g., hourly). */
|
|
68
|
+
export interface BillingPeriod {
|
|
69
|
+
/** Start of the billing period (unix epoch ms, inclusive). */
|
|
70
|
+
start: number;
|
|
71
|
+
/** End of the billing period (unix epoch ms, exclusive). */
|
|
72
|
+
end: number;
|
|
73
|
+
}
|
|
74
|
+
/** A per-tenant, per-billing-period rolled-up summary row as stored in SQLite. */
|
|
75
|
+
export interface BillingPeriodSummary {
|
|
76
|
+
id: string;
|
|
77
|
+
tenant: string;
|
|
78
|
+
capability: string;
|
|
79
|
+
provider: string;
|
|
80
|
+
event_count: number;
|
|
81
|
+
total_cost: number;
|
|
82
|
+
total_charge: number;
|
|
83
|
+
total_duration: number;
|
|
84
|
+
period_start: number;
|
|
85
|
+
period_end: number;
|
|
86
|
+
/** Unix epoch ms when this row was last (re-)computed. */
|
|
87
|
+
updated_at: number;
|
|
88
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { MeterEvent } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Write-Ahead Log for meter events.
|
|
4
|
+
*
|
|
5
|
+
* Provides durable, fail-closed event persistence. Events are written
|
|
6
|
+
* to disk BEFORE being buffered, ensuring no event is lost even if
|
|
7
|
+
* the process crashes before flush completes.
|
|
8
|
+
*/
|
|
9
|
+
export declare class MeterWAL {
|
|
10
|
+
private readonly walPath;
|
|
11
|
+
private lock;
|
|
12
|
+
private withLock;
|
|
13
|
+
constructor(walPath: string);
|
|
14
|
+
private ensureDir;
|
|
15
|
+
/**
|
|
16
|
+
* Append an event to the WAL. appendFileSync is atomic on POSIX (O_APPEND),
|
|
17
|
+
* so no mutex is needed here. Returns the event with a generated ID.
|
|
18
|
+
*/
|
|
19
|
+
append(event: MeterEvent & {
|
|
20
|
+
id?: string;
|
|
21
|
+
}): MeterEvent & {
|
|
22
|
+
id: string;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Read all events from the WAL. Returns events in the order they were written.
|
|
26
|
+
* Skips malformed lines (defensive against incomplete writes).
|
|
27
|
+
*/
|
|
28
|
+
readAll(): Array<MeterEvent & {
|
|
29
|
+
id: string;
|
|
30
|
+
}>;
|
|
31
|
+
/**
|
|
32
|
+
* Remove specific event IDs from the WAL. This is done by rewriting
|
|
33
|
+
* the entire file without the specified events. Mutex-guarded.
|
|
34
|
+
*/
|
|
35
|
+
remove(eventIds: Set<string>): Promise<void>;
|
|
36
|
+
private _clear;
|
|
37
|
+
/**
|
|
38
|
+
* Clear the entire WAL (typically after successful flush). Mutex-guarded.
|
|
39
|
+
*/
|
|
40
|
+
clear(): Promise<void>;
|
|
41
|
+
/**
|
|
42
|
+
* Check if the WAL is empty.
|
|
43
|
+
*/
|
|
44
|
+
isEmpty(): boolean;
|
|
45
|
+
/**
|
|
46
|
+
* Get the number of events in the WAL.
|
|
47
|
+
*/
|
|
48
|
+
count(): number;
|
|
49
|
+
}
|