alepha 0.19.2 → 0.19.4
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/assets/swagger-ui/swagger-ui-bundle.js +1 -1
- package/dist/api/audits/index.d.ts +8 -8
- package/dist/api/invitations/index.d.ts +790 -0
- package/dist/api/invitations/index.d.ts.map +1 -0
- package/dist/api/invitations/index.js +665 -0
- package/dist/api/invitations/index.js.map +1 -0
- package/dist/api/jobs/index.browser.js +8 -9
- package/dist/api/jobs/index.browser.js.map +1 -1
- package/dist/api/jobs/index.d.ts +90 -34
- package/dist/api/jobs/index.d.ts.map +1 -1
- package/dist/api/jobs/index.js +267 -44
- package/dist/api/jobs/index.js.map +1 -1
- package/dist/api/notifications/index.browser.js +0 -1
- package/dist/api/notifications/index.browser.js.map +1 -1
- package/dist/api/notifications/index.d.ts +3 -3
- package/dist/api/notifications/index.d.ts.map +1 -1
- package/dist/api/notifications/index.js +0 -1
- package/dist/api/notifications/index.js.map +1 -1
- package/dist/api/parameters/index.browser.js +112 -1
- package/dist/api/parameters/index.browser.js.map +1 -1
- package/dist/api/parameters/index.d.ts +90 -3
- package/dist/api/parameters/index.d.ts.map +1 -1
- package/dist/api/parameters/index.js +79 -12
- package/dist/api/parameters/index.js.map +1 -1
- package/dist/{billing → api/payments}/index.d.ts +67 -49
- package/dist/api/payments/index.d.ts.map +1 -0
- package/dist/{billing → api/payments}/index.js +108 -74
- package/dist/api/payments/index.js.map +1 -0
- package/dist/api/subscriptions/index.d.ts +1692 -0
- package/dist/api/subscriptions/index.d.ts.map +1 -0
- package/dist/api/subscriptions/index.js +1870 -0
- package/dist/api/subscriptions/index.js.map +1 -0
- package/dist/api/users/index.d.ts +27 -21
- package/dist/api/users/index.d.ts.map +1 -1
- package/dist/api/users/index.js +167 -34
- package/dist/api/users/index.js.map +1 -1
- package/dist/api/workflows/index.browser.js +246 -0
- package/dist/api/workflows/index.browser.js.map +1 -0
- package/dist/api/workflows/index.d.ts +1618 -0
- package/dist/api/workflows/index.d.ts.map +1 -0
- package/dist/api/workflows/index.js +1504 -0
- package/dist/api/workflows/index.js.map +1 -0
- package/dist/cli/config/index.d.ts +6 -28
- package/dist/cli/config/index.d.ts.map +1 -1
- package/dist/cli/config/index.js +5 -10
- package/dist/cli/config/index.js.map +1 -1
- package/dist/cli/core/index.d.ts +11669 -208
- package/dist/cli/core/index.d.ts.map +1 -1
- package/dist/cli/core/index.js +60 -69
- package/dist/cli/core/index.js.map +1 -1
- package/dist/cli/devtools/index.d.ts +5 -0
- package/dist/cli/devtools/index.d.ts.map +1 -1
- package/dist/cli/devtools/index.js +4 -0
- package/dist/cli/devtools/index.js.map +1 -1
- package/dist/cli/platform/index.d.ts +69 -64
- package/dist/cli/platform/index.d.ts.map +1 -1
- package/dist/cli/platform/index.js +6 -2
- package/dist/cli/platform/index.js.map +1 -1
- package/dist/cli/vendor/index.d.ts +38 -10
- package/dist/cli/vendor/index.d.ts.map +1 -1
- package/dist/cli/vendor/index.js +85 -26
- package/dist/cli/vendor/index.js.map +1 -1
- package/dist/core/index.browser.js +21 -2
- package/dist/core/index.browser.js.map +1 -1
- package/dist/core/index.d.ts +33 -2
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +25 -2
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.native.js +25 -2
- package/dist/core/index.native.js.map +1 -1
- package/dist/core/index.workerd.js +25 -2
- package/dist/core/index.workerd.js.map +1 -1
- package/dist/email/smtp/index.js +24 -8
- package/dist/email/smtp/index.js.map +1 -1
- package/dist/logger/index.d.ts.map +1 -1
- package/dist/logger/index.js +1 -1
- package/dist/logger/index.js.map +1 -1
- package/dist/orm/core/index.browser.js +0 -18
- package/dist/orm/core/index.browser.js.map +1 -1
- package/dist/orm/core/index.bun.js +25 -73
- package/dist/orm/core/index.bun.js.map +1 -1
- package/dist/orm/core/index.d.ts +10 -32
- package/dist/orm/core/index.d.ts.map +1 -1
- package/dist/orm/core/index.js +25 -73
- package/dist/orm/core/index.js.map +1 -1
- package/dist/orm/postgres/index.bun.js +3 -3
- package/dist/orm/postgres/index.bun.js.map +1 -1
- package/dist/orm/postgres/index.d.ts +2 -1
- package/dist/orm/postgres/index.d.ts.map +1 -1
- package/dist/orm/postgres/index.js +3 -3
- package/dist/orm/postgres/index.js.map +1 -1
- package/dist/react/router/index.browser.js +25 -3
- package/dist/react/router/index.browser.js.map +1 -1
- package/dist/react/router/index.d.ts +16 -1
- package/dist/react/router/index.d.ts.map +1 -1
- package/dist/react/router/index.js +25 -3
- package/dist/react/router/index.js.map +1 -1
- package/dist/security/index.d.ts +28 -0
- package/dist/security/index.d.ts.map +1 -1
- package/dist/security/index.js +28 -0
- package/dist/security/index.js.map +1 -1
- package/package.json +37 -20
- package/src/api/invitations/__tests__/InvitationService.spec.ts +439 -0
- package/src/api/invitations/controllers/AdminInvitationController.ts +86 -0
- package/src/api/invitations/controllers/InvitationController.ts +84 -0
- package/src/api/invitations/entities/invitations.ts +33 -0
- package/src/api/invitations/index.ts +65 -0
- package/src/api/invitations/jobs/InvitationJobs.ts +37 -0
- package/src/api/invitations/providers/InvitationProvider.ts +45 -0
- package/src/api/invitations/schemas/createInvitationSchema.ts +12 -0
- package/src/api/invitations/schemas/invitationConfigAtom.ts +20 -0
- package/src/api/invitations/schemas/invitationQuerySchema.ts +15 -0
- package/src/api/invitations/schemas/invitationResourceSchema.ts +6 -0
- package/src/api/invitations/schemas/invitationWithResourceInfoSchema.ts +22 -0
- package/src/api/invitations/schemas/myInvitationsQuerySchema.ts +10 -0
- package/src/api/invitations/services/InvitationService.ts +556 -0
- package/src/api/jobs/__tests__/$job.spec.ts +876 -0
- package/src/api/jobs/controllers/AdminJobController.ts +44 -0
- package/src/api/jobs/entities/jobExecutionEntity.ts +0 -2
- package/src/api/jobs/index.ts +0 -3
- package/src/api/jobs/primitives/$job.ts +22 -11
- package/src/api/jobs/providers/JobProvider.ts +239 -25
- package/src/api/jobs/schemas/jobConfigAtom.ts +4 -0
- package/src/api/jobs/schemas/jobCronInfoSchema.ts +1 -0
- package/src/api/jobs/schemas/jobExecutionQuerySchema.ts +0 -1
- package/src/api/jobs/schemas/jobQueueDepthSchema.ts +1 -0
- package/src/api/jobs/schemas/jobRegistrationSchema.ts +1 -6
- package/src/api/jobs/services/JobService.ts +51 -12
- package/src/api/notifications/schemas/notificationQuerySchema.ts +0 -1
- package/src/api/parameters/__tests__/$parameter.spec.ts +327 -0
- package/src/api/parameters/controllers/AdminParameterController.ts +29 -3
- package/src/api/parameters/index.browser.ts +12 -0
- package/src/api/parameters/primitives/$parameter.ts +20 -3
- package/src/api/parameters/services/ParameterProvider.ts +48 -7
- package/src/{billing → api/payments}/__tests__/PaymentMethodService.spec.ts +32 -6
- package/src/api/payments/__tests__/PaymentService.spec.ts +279 -0
- package/src/{billing/controllers/AdminBillingController.ts → api/payments/controllers/AdminPaymentController.ts} +26 -21
- package/src/{billing/controllers/BillingController.ts → api/payments/controllers/PaymentController.ts} +23 -11
- package/src/{billing → api/payments}/entities/paymentIntents.ts +1 -0
- package/src/{billing/errors/BillingError.ts → api/payments/errors/PaymentError.ts} +1 -1
- package/src/{billing → api/payments}/index.ts +31 -25
- package/src/{billing/providers/MemoryBillingProvider.ts → api/payments/providers/MemoryPaymentProvider.ts} +4 -4
- package/src/{billing/providers/BillingProvider.ts → api/payments/providers/PaymentProvider.ts} +9 -2
- package/src/{billing → api/payments}/services/PaymentMethodService.ts +5 -5
- package/src/{billing/services/BillingService.ts → api/payments/services/PaymentService.ts} +94 -18
- package/src/api/subscriptions/__tests__/BillingService.spec.ts +218 -0
- package/src/api/subscriptions/__tests__/SubscriptionService.spec.ts +278 -0
- package/src/api/subscriptions/controllers/AdminSubscriptionController.ts +212 -0
- package/src/api/subscriptions/controllers/SubscriptionController.ts +189 -0
- package/src/api/subscriptions/entities/subscriptionEvents.ts +54 -0
- package/src/api/subscriptions/entities/subscriptions.ts +68 -0
- package/src/api/subscriptions/index.ts +144 -0
- package/src/api/subscriptions/jobs/SubscriptionJobs.ts +382 -0
- package/src/api/subscriptions/middleware/$requireLimit.ts +50 -0
- package/src/api/subscriptions/middleware/$requirePlan.ts +49 -0
- package/src/api/subscriptions/notifications/SubscriptionNotifications.ts +110 -0
- package/src/api/subscriptions/schemas/cancelSubscriptionSchema.ts +8 -0
- package/src/api/subscriptions/schemas/changePlanSchema.ts +9 -0
- package/src/api/subscriptions/schemas/createSubscriptionSchema.ts +11 -0
- package/src/api/subscriptions/schemas/entitlementsSchema.ts +21 -0
- package/src/api/subscriptions/schemas/mrrSchema.ts +13 -0
- package/src/api/subscriptions/schemas/planDefinitionSchema.ts +71 -0
- package/src/api/subscriptions/schemas/planResourceSchema.ts +25 -0
- package/src/api/subscriptions/schemas/subscriptionEventResourceSchema.ts +8 -0
- package/src/api/subscriptions/schemas/subscriptionQuerySchema.ts +19 -0
- package/src/api/subscriptions/schemas/subscriptionResourceSchema.ts +6 -0
- package/src/api/subscriptions/schemas/subscriptionSettingsSchema.ts +32 -0
- package/src/api/subscriptions/schemas/subscriptionStatsSchema.ts +23 -0
- package/src/api/subscriptions/services/BillingService.ts +437 -0
- package/src/api/subscriptions/services/SubscriptionConfig.ts +56 -0
- package/src/api/subscriptions/services/SubscriptionService.ts +867 -0
- package/src/api/subscriptions/services/UsageService.ts +118 -0
- package/src/api/users/__tests__/AdminUserController.spec.ts +80 -1
- package/src/api/users/__tests__/CredentialService.spec.ts +177 -0
- package/src/api/users/__tests__/EmailVerification.spec.ts +29 -18
- package/src/api/users/__tests__/PasswordReset.spec.ts +3 -0
- package/src/api/users/__tests__/RegistrationService.spec.ts +148 -1
- package/src/api/users/__tests__/SessionService.spec.ts +142 -1
- package/src/api/users/atoms/realmAuthSettingsAtom.ts +10 -1
- package/src/api/users/controllers/UserController.ts +3 -8
- package/src/api/users/notifications/UserNotifications.ts +23 -0
- package/src/api/users/schemas/loginSchema.ts +1 -1
- package/src/api/users/services/CredentialService.ts +51 -4
- package/src/api/users/services/RegistrationService.ts +38 -9
- package/src/api/users/services/SessionService.ts +62 -9
- package/src/api/users/services/UserService.ts +21 -12
- package/src/api/workflows/__tests__/$workflow.spec.ts +616 -0
- package/src/api/workflows/controllers/AdminWorkflowController.ts +191 -0
- package/src/api/workflows/entities/workflowExecutions.ts +74 -0
- package/src/api/workflows/entities/workflowStepExecutions.ts +74 -0
- package/src/api/workflows/entities/workflowStepLogs.ts +13 -0
- package/src/api/workflows/index.browser.ts +22 -0
- package/src/api/workflows/index.ts +124 -0
- package/src/api/workflows/jobs/WorkflowJobs.ts +77 -0
- package/src/api/workflows/primitives/$workflow.ts +202 -0
- package/src/api/workflows/providers/WorkflowProvider.ts +1284 -0
- package/src/api/workflows/schemas/workflowActivitySchema.ts +15 -0
- package/src/api/workflows/schemas/workflowConfigAtom.ts +51 -0
- package/src/api/workflows/schemas/workflowExecutionDetailSchema.ts +18 -0
- package/src/api/workflows/schemas/workflowExecutionQuerySchema.ts +26 -0
- package/src/api/workflows/schemas/workflowExecutionResourceSchema.ts +30 -0
- package/src/api/workflows/schemas/workflowRegistrationSchema.ts +26 -0
- package/src/api/workflows/schemas/workflowStatsSchema.ts +16 -0
- package/src/api/workflows/schemas/workflowStepExecutionResourceSchema.ts +15 -0
- package/src/api/workflows/services/WorkflowService.ts +382 -0
- package/src/cli/config/defineConfig.ts +17 -46
- package/src/cli/core/providers/ViteDevServerProvider.ts +45 -3
- package/src/cli/core/services/PackageManagerUtils.ts +3 -1
- package/src/cli/core/services/ProjectScaffolder.ts +5 -5
- package/src/cli/core/templates/agentMd.ts +14 -5
- package/src/cli/core/templates/webAppRouterTs.ts +5 -58
- package/src/cli/devtools/index.ts +21 -1
- package/src/cli/platform/index.ts +23 -2
- package/src/cli/vendor/__tests__/VendorService.spec.ts +283 -178
- package/src/cli/vendor/index.ts +20 -3
- package/src/cli/vendor/services/VendorService.ts +126 -27
- package/src/core/Alepha.ts +10 -0
- package/src/core/__tests__/TypeProvider.spec.ts +4 -2
- package/src/core/providers/SchemaValidator.ts +1 -1
- package/src/core/providers/TypeProvider.ts +46 -3
- package/src/logger/index.ts +6 -1
- package/src/orm/__tests__/enums.spec.ts +22 -29
- package/src/orm/__tests__/orm-showcase-tests.ts +430 -0
- package/src/orm/__tests__/orm-showcase.spec.ts +167 -0
- package/src/orm/core/providers/DatabaseTypeProvider.ts +0 -29
- package/src/orm/core/providers/DrizzleKitProvider.ts +56 -105
- package/src/orm/postgres/services/PostgresModelBuilder.ts +3 -6
- package/src/react/router/__tests__/$page.browser.spec.tsx +157 -0
- package/src/react/router/providers/ReactBrowserProvider.ts +39 -0
- package/src/react/router/providers/ReactBrowserRouterProvider.ts +22 -0
- package/src/security/__tests__/$secure-combinations.spec.ts +945 -0
- package/src/security/primitives/$secure.ts +28 -0
- package/tsconfig.base.json +0 -1
- package/dist/billing/index.d.ts.map +0 -1
- package/dist/billing/index.js.map +0 -1
- package/src/billing/__tests__/BillingService.spec.ts +0 -136
- /package/src/{billing → api/payments}/entities/paymentMethods.ts +0 -0
- /package/src/{billing → api/payments}/entities/refunds.ts +0 -0
- /package/src/{billing → api/payments}/schemas/intentSchemas.ts +0 -0
- /package/src/{billing → api/payments}/schemas/paymentMethodSchemas.ts +0 -0
- /package/src/{billing → api/payments}/schemas/refundSchemas.ts +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../../src/api/subscriptions/schemas/cancelSubscriptionSchema.ts","../../../src/api/subscriptions/schemas/changePlanSchema.ts","../../../src/api/subscriptions/schemas/mrrSchema.ts","../../../src/api/subscriptions/schemas/subscriptionQuerySchema.ts","../../../src/api/subscriptions/entities/subscriptions.ts","../../../src/api/subscriptions/schemas/subscriptionResourceSchema.ts","../../../src/api/subscriptions/schemas/subscriptionStatsSchema.ts","../../../src/api/subscriptions/schemas/planDefinitionSchema.ts","../../../src/api/subscriptions/schemas/subscriptionSettingsSchema.ts","../../../src/api/subscriptions/services/SubscriptionConfig.ts","../../../src/api/subscriptions/entities/subscriptionEvents.ts","../../../src/api/subscriptions/services/SubscriptionService.ts","../../../src/api/subscriptions/controllers/AdminSubscriptionController.ts","../../../src/api/subscriptions/schemas/createSubscriptionSchema.ts","../../../src/api/subscriptions/schemas/entitlementsSchema.ts","../../../src/api/subscriptions/schemas/planResourceSchema.ts","../../../src/api/subscriptions/schemas/subscriptionEventResourceSchema.ts","../../../src/api/subscriptions/controllers/SubscriptionController.ts","../../../src/api/subscriptions/jobs/SubscriptionJobs.ts","../../../src/api/subscriptions/notifications/SubscriptionNotifications.ts","../../../src/api/subscriptions/services/BillingService.ts","../../../src/api/subscriptions/services/UsageService.ts","../../../src/api/subscriptions/middleware/$requireLimit.ts","../../../src/api/subscriptions/middleware/$requirePlan.ts","../../../src/api/subscriptions/index.ts"],"sourcesContent":["import { type Static, t } from \"alepha\";\n\nexport const cancelSubscriptionSchema = t.object({\n reason: t.optional(t.string()),\n immediate: t.optional(t.boolean()),\n});\n\nexport type CancelSubscription = Static<typeof cancelSubscriptionSchema>;\n","import { type Static, t } from \"alepha\";\n\nexport const changePlanSchema = t.object({\n planId: t.string(),\n interval: t.optional(t.enum([\"monthly\", \"yearly\"])),\n immediate: t.optional(t.boolean()),\n});\n\nexport type ChangePlan = Static<typeof changePlanSchema>;\n","import { type Static, t } from \"alepha\";\n\nexport const mrrSchema = t.object({\n total: t.integer(),\n byPlan: t.record(t.text(), t.integer()),\n growth: t.integer(),\n newMrr: t.integer(),\n expansionMrr: t.integer(),\n contractionMrr: t.integer(),\n churnMrr: t.integer(),\n});\n\nexport type MrrData = Static<typeof mrrSchema>;\n","import { type Static, t } from \"alepha\";\nimport { pageQuerySchema } from \"alepha/orm\";\n\nexport const subscriptionQuerySchema = t.extend(pageQuerySchema, {\n status: t.optional(\n t.enum([\n \"trialing\",\n \"active\",\n \"past_due\",\n \"suspended\",\n \"cancelled\",\n \"expired\",\n ]),\n ),\n planId: t.optional(t.string()),\n organizationId: t.optional(t.uuid()),\n});\n\nexport type SubscriptionQuery = Static<typeof subscriptionQuerySchema>;\n","import { type Static, t } from \"alepha\";\nimport { $entity, db } from \"alepha/orm\";\n\nexport const subscriptions = $entity({\n name: \"subscriptions\",\n schema: t.object({\n id: db.primaryKey(t.uuid()),\n version: db.version(),\n createdAt: db.createdAt(),\n updatedAt: db.updatedAt(),\n organizationId: db.organization(),\n\n // Plan\n planId: t.string(),\n interval: t.enum([\"monthly\", \"yearly\"]),\n\n // Status\n status: t.enum([\n \"trialing\",\n \"active\",\n \"past_due\",\n \"suspended\",\n \"cancelled\",\n \"expired\",\n ]),\n\n // Billing cycle\n currentPeriodStart: t.datetime(),\n currentPeriodEnd: t.datetime(),\n\n // Trial\n trialStart: t.optional(t.datetime()),\n trialEnd: t.optional(t.datetime()),\n\n // Cancellation\n cancelledAt: t.optional(t.datetime()),\n cancelReason: t.optional(t.string()),\n cancelAtPeriodEnd: t.boolean({ default: false }),\n\n // Payment tracking\n lastPaymentIntentId: t.optional(t.uuid()),\n lastPaymentAt: t.optional(t.datetime()),\n nextBillingAt: t.optional(t.datetime()),\n\n // Dunning state\n dunningStartedAt: t.optional(t.datetime()),\n dunningAttempt: t.integer({ default: 0 }),\n dunningNextRetryAt: t.optional(t.datetime()),\n\n // Plan change (pending)\n pendingPlanId: t.optional(t.string()),\n pendingInterval: t.optional(t.enum([\"monthly\", \"yearly\"])),\n\n // Metadata\n metadata: t.optional(t.record(t.text(), t.any())),\n }),\n indexes: [\n { columns: [\"organizationId\"], unique: true },\n { columns: [\"status\"] },\n { columns: [\"planId\", \"status\"] },\n { columns: [\"nextBillingAt\"] },\n { columns: [\"trialEnd\"] },\n { columns: [\"dunningNextRetryAt\"] },\n { columns: [\"currentPeriodEnd\"] },\n ],\n});\n\nexport type SubscriptionEntity = Static<typeof subscriptions.schema>;\n","import type { Static } from \"alepha\";\nimport { subscriptions } from \"../entities/subscriptions.ts\";\n\nexport const subscriptionResourceSchema = subscriptions.schema;\n\nexport type SubscriptionResource = Static<typeof subscriptionResourceSchema>;\n","import { type Static, t } from \"alepha\";\n\nexport const subscriptionStatsSchema = t.object({\n total: t.integer(),\n trialing: t.integer(),\n active: t.integer(),\n pastDue: t.integer(),\n suspended: t.integer(),\n cancelled: t.integer(),\n expired: t.integer(),\n trialConversionRate: t.number(),\n churnRate: t.number(),\n byPlan: t.record(\n t.text(),\n t.object({\n active: t.integer(),\n trialing: t.integer(),\n total: t.integer(),\n }),\n ),\n});\n\nexport type SubscriptionStats = Static<typeof subscriptionStatsSchema>;\n","import { type Static, t } from \"alepha\";\n\nexport const planDefinitionSchema = t.object({\n /**\n * Unique plan identifier (e.g., \"free\", \"starter\", \"pro\", \"enterprise\").\n */\n id: t.string({ minLength: 1, maxLength: 50 }),\n\n /**\n * Display name (e.g., \"Pro Plan\").\n */\n name: t.string(),\n\n /**\n * Optional description.\n */\n description: t.optional(t.string()),\n\n /**\n * Whether this plan is available for new subscriptions.\n */\n available: t.boolean({ default: true }),\n\n /**\n * Pricing per billing interval.\n * Multiple entries for monthly/yearly.\n */\n pricing: t.array(\n t.object({\n interval: t.enum([\"monthly\", \"yearly\"]),\n amount: t.integer({ minimum: 0 }),\n currency: t.string({ minLength: 3, maxLength: 3 }),\n }),\n ),\n\n /**\n * Trial configuration for this plan.\n * Overrides global settings.trialDays if set.\n */\n trial: t.optional(\n t.object({\n days: t.integer({ minimum: 0, maximum: 365 }),\n requirePaymentMethod: t.boolean({ default: false }),\n }),\n ),\n\n /**\n * Feature entitlements. Boolean flags for feature access.\n * Checked via SubscriptionService.can(\"feature-name\").\n */\n features: t.array(t.string()),\n\n /**\n * Usage limits. Numeric caps on resources.\n * Checked via SubscriptionService.limit(\"resource-name\").\n * -1 = unlimited.\n */\n limits: t.record(t.text(), t.integer()),\n\n /**\n * Sort order for display (lower = first).\n */\n order: t.integer({ default: 0 }),\n\n /**\n * Metadata for app-specific plan data.\n */\n metadata: t.optional(t.record(t.text(), t.any())),\n});\n\nexport type PlanDefinition = Static<typeof planDefinitionSchema>;\n","import { type Static, t } from \"alepha\";\n\nexport const subscriptionSettingsSchema = t.object({\n /**\n * Default trial days (overridden per-plan if plan.trial.days is set).\n */\n trialDays: t.integer({ default: 14, minimum: 0, maximum: 365 }),\n\n /**\n * Days after payment failure before suspension.\n * During grace period, subscription remains active but flagged.\n */\n gracePeriodDays: t.integer({ default: 7, minimum: 0, maximum: 30 }),\n\n /**\n * Days after first payment failure to retry, relative to the failure date.\n * e.g., [1, 3, 5, 7] means retry on day 1, 3, 5, 7 after failure.\n */\n dunningSchedule: t.array(t.integer({ minimum: 1 })),\n\n /**\n * When user cancels, wait until period end (true) or cancel immediately (false).\n */\n cancelAtPeriodEnd: t.boolean({ default: true }),\n\n /**\n * Prorate charges when changing plans mid-cycle.\n */\n prorateOnChange: t.boolean({ default: true }),\n});\n\nexport type SubscriptionSettings = Static<typeof subscriptionSettingsSchema>;\n","import { t } from \"alepha\";\nimport { $parameter } from \"alepha/api/parameters\";\nimport { BadRequestError } from \"alepha/server\";\nimport {\n type PlanDefinition,\n planDefinitionSchema,\n} from \"../schemas/planDefinitionSchema.ts\";\nimport {\n type SubscriptionSettings,\n subscriptionSettingsSchema,\n} from \"../schemas/subscriptionSettingsSchema.ts\";\n\nexport class SubscriptionConfig {\n protected readonly plans = $parameter({\n name: \"subscriptions.plans\",\n description: \"Subscription plan definitions\",\n schema: t.object({ plans: t.array(planDefinitionSchema) }),\n default: { plans: [] },\n });\n\n protected readonly settings = $parameter({\n name: \"subscriptions.settings\",\n description: \"Global subscription settings\",\n schema: subscriptionSettingsSchema,\n default: {\n trialDays: 14,\n gracePeriodDays: 7,\n dunningSchedule: [1, 3, 5, 7],\n cancelAtPeriodEnd: true,\n prorateOnChange: true,\n },\n });\n\n public async getPlans(): Promise<PlanDefinition[]> {\n return (await this.plans.get()).plans;\n }\n\n public async getSettings(): Promise<SubscriptionSettings> {\n return this.settings.get();\n }\n\n public async getPlan(planId: string): Promise<PlanDefinition> {\n const plans = await this.getPlans();\n const plan = plans.find((p) => p.id === planId);\n if (!plan) throw new BadRequestError(`Plan '${planId}' not found`);\n return plan;\n }\n\n public async getPlanPricing(planId: string, interval: \"monthly\" | \"yearly\") {\n const plan = await this.getPlan(planId);\n const pricing = plan.pricing.find((p) => p.interval === interval);\n if (!pricing)\n throw new BadRequestError(`No ${interval} pricing for plan '${planId}'`);\n return pricing;\n }\n}\n","import { type Static, t } from \"alepha\";\nimport { $entity, db } from \"alepha/orm\";\nimport { subscriptions } from \"./subscriptions.ts\";\n\nexport const subscriptionEvents = $entity({\n name: \"subscription_events\",\n schema: t.object({\n id: db.primaryKey(t.uuid()),\n createdAt: db.createdAt(),\n subscriptionId: db.ref(t.uuid(), () => subscriptions.cols.id, {\n onDelete: \"cascade\",\n }),\n organizationId: db.organization(),\n\n type: t.enum([\n \"created\",\n \"trial_started\",\n \"trial_ended\",\n \"activated\",\n \"renewed\",\n \"payment_failed\",\n \"payment_retried\",\n \"past_due\",\n \"suspended\",\n \"reactivated\",\n \"plan_changed\",\n \"plan_change_scheduled\",\n \"cancelled\",\n \"expired\",\n \"resumed\",\n ]),\n\n // Context\n previousStatus: t.optional(t.string()),\n newStatus: t.optional(t.string()),\n previousPlanId: t.optional(t.string()),\n newPlanId: t.optional(t.string()),\n paymentIntentId: t.optional(t.uuid()),\n amount: t.optional(t.integer()),\n currency: t.optional(t.string()),\n\n // Who / why\n triggeredBy: t.optional(t.string()),\n userId: t.optional(t.uuid()),\n note: t.optional(t.string()),\n }),\n indexes: [\n { columns: [\"subscriptionId\", \"createdAt\"] },\n { columns: [\"organizationId\", \"createdAt\"] },\n { columns: [\"type\"] },\n ],\n});\n\nexport type SubscriptionEventEntity = Static<typeof subscriptionEvents.schema>;\n","import { $inject, Alepha } from \"alepha\";\nimport { DateTimeProvider } from \"alepha/datetime\";\nimport { $logger } from \"alepha/logger\";\nimport { $repository, type Page } from \"alepha/orm\";\nimport { BadRequestError, NotFoundError } from \"alepha/server\";\nimport {\n type SubscriptionEventEntity,\n subscriptionEvents,\n} from \"../entities/subscriptionEvents.ts\";\nimport {\n type SubscriptionEntity,\n subscriptions,\n} from \"../entities/subscriptions.ts\";\nimport type { Entitlements } from \"../schemas/entitlementsSchema.ts\";\nimport type { SubscriptionQuery } from \"../schemas/subscriptionQuerySchema.ts\";\nimport type { SubscriptionStats } from \"../schemas/subscriptionStatsSchema.ts\";\nimport { SubscriptionConfig } from \"./SubscriptionConfig.ts\";\n\n// -----------------------------------------------------------------------------------------------------------------\n\ninterface SubscribeOptions {\n /**\n * Override plan/global trial days.\n */\n trialDays?: number;\n\n /**\n * Go straight to active (requires payment).\n */\n skipTrial?: boolean;\n\n /**\n * Metadata to attach to the subscription.\n */\n metadata?: Record<string, unknown>;\n}\n\n// -----------------------------------------------------------------------------------------------------------------\n\ninterface CancelOptions {\n /**\n * Cancellation reason.\n */\n reason?: string;\n\n /**\n * Cancel immediately instead of at period end.\n */\n immediate?: boolean;\n\n /**\n * User who initiated the cancellation.\n */\n cancelledBy?: string;\n}\n\n// -----------------------------------------------------------------------------------------------------------------\n\ninterface ChangePlanOptions {\n /**\n * Apply now (with proration) or at period end.\n */\n immediate?: boolean;\n\n /**\n * Override settings.prorateOnChange.\n */\n prorate?: boolean;\n}\n\n// -----------------------------------------------------------------------------------------------------------------\n\ninterface EventContext {\n previousStatus?: string;\n newStatus?: string;\n previousPlanId?: string;\n newPlanId?: string;\n paymentIntentId?: string;\n amount?: number;\n currency?: string;\n triggeredBy?: string;\n userId?: string;\n note?: string;\n}\n\n// -----------------------------------------------------------------------------------------------------------------\n\nexport class SubscriptionService {\n protected readonly alepha = $inject(Alepha);\n protected readonly log = $logger();\n protected readonly dateTime = $inject(DateTimeProvider);\n protected readonly subscriptionRepo = $repository(subscriptions);\n protected readonly eventRepo = $repository(subscriptionEvents);\n protected readonly config = $inject(SubscriptionConfig);\n\n // ---------------------------------------------------------------------------------------------------------------\n // Helpers\n // ---------------------------------------------------------------------------------------------------------------\n\n /**\n * Find a subscription by organization ID.\n * Returns null if no subscription exists.\n */\n public async getByOrganization(\n organizationId: string,\n ): Promise<SubscriptionEntity | null> {\n const result = await this.subscriptionRepo.findOne({\n where: { organizationId: { eq: organizationId } },\n });\n return result ?? null;\n }\n\n /**\n * Get a subscription by ID. Throws NotFoundError if not found.\n */\n public async getSubscription(id: string): Promise<SubscriptionEntity> {\n return this.subscriptionRepo.getById(id);\n }\n\n /**\n * Returns true if the subscription currently grants access.\n * Accessible statuses: trialing, active, past_due (grace period),\n * or cancelled with cancelAtPeriodEnd before period end.\n */\n public isAccessible(sub: SubscriptionEntity): boolean {\n if (\n sub.status === \"trialing\" ||\n sub.status === \"active\" ||\n sub.status === \"past_due\"\n ) {\n return true;\n }\n\n if (\n sub.status === \"cancelled\" &&\n sub.cancelAtPeriodEnd &&\n this.dateTime.now().isBefore(sub.currentPeriodEnd)\n ) {\n return true;\n }\n\n return false;\n }\n\n /**\n * Record a subscription event in the event log.\n */\n public async recordEvent(\n subscriptionId: string,\n organizationId: string,\n type: SubscriptionEventEntity[\"type\"],\n context?: EventContext,\n ): Promise<void> {\n await this.eventRepo.create({\n subscriptionId,\n organizationId,\n type,\n previousStatus: context?.previousStatus,\n newStatus: context?.newStatus,\n previousPlanId: context?.previousPlanId,\n newPlanId: context?.newPlanId,\n paymentIntentId: context?.paymentIntentId,\n amount: context?.amount,\n currency: context?.currency,\n triggeredBy: context?.triggeredBy,\n userId: context?.userId,\n note: context?.note,\n });\n }\n\n /**\n * Compute the end of a billing interval from a start date.\n */\n public computeIntervalEnd(\n start: string,\n interval: \"monthly\" | \"yearly\",\n ): string {\n const startDate = this.dateTime.of(start);\n const unit = interval === \"monthly\" ? \"months\" : \"years\";\n return startDate.add(1, unit).toISOString();\n }\n\n // ---------------------------------------------------------------------------------------------------------------\n // Lifecycle\n // ---------------------------------------------------------------------------------------------------------------\n\n /**\n * Create a new subscription for an organization.\n */\n public async subscribe(\n organizationId: string,\n planId: string,\n interval: \"monthly\" | \"yearly\",\n options?: SubscribeOptions,\n ): Promise<SubscriptionEntity> {\n const plan = await this.config.getPlan(planId);\n\n if (!plan.available) {\n throw new BadRequestError(\n `Plan '${planId}' is not available for new subscriptions`,\n );\n }\n\n await this.config.getPlanPricing(planId, interval);\n\n const existing = await this.subscriptionRepo.findOne({\n where: {\n organizationId: { eq: organizationId },\n status: { inArray: [\"trialing\", \"active\", \"past_due\"] },\n },\n });\n\n if (existing) {\n throw new BadRequestError(\n \"Organization already has an active subscription\",\n );\n }\n\n const settings = await this.config.getSettings();\n const trialDays =\n options?.trialDays ?? plan.trial?.days ?? settings.trialDays;\n const skipTrial = options?.skipTrial ?? false;\n const now = this.dateTime.now();\n const nowISO = now.toISOString();\n\n if (trialDays > 0 && !skipTrial) {\n const trialEnd = now.add(trialDays, \"days\").toISOString();\n\n const entity = await this.subscriptionRepo.create({\n organizationId,\n planId,\n interval,\n status: \"trialing\",\n currentPeriodStart: nowISO,\n currentPeriodEnd: trialEnd,\n trialStart: nowISO,\n trialEnd,\n nextBillingAt: trialEnd,\n cancelAtPeriodEnd: false,\n dunningAttempt: 0,\n metadata: options?.metadata as any,\n });\n\n await this.recordEvent(entity.id, organizationId, \"created\", {\n newStatus: \"trialing\",\n });\n\n await this.recordEvent(entity.id, organizationId, \"trial_started\", {\n newStatus: \"trialing\",\n });\n\n this.log.info(\"Subscription created with trial\", {\n id: entity.id,\n organizationId,\n planId,\n trialDays,\n });\n\n await this.alepha.events.emit(\"subscription:created\" as any, {\n subscription: entity,\n });\n\n return entity;\n }\n\n const periodEnd = this.computeIntervalEnd(nowISO, interval);\n\n const entity = await this.subscriptionRepo.create({\n organizationId,\n planId,\n interval,\n status: \"active\",\n currentPeriodStart: nowISO,\n currentPeriodEnd: periodEnd,\n nextBillingAt: periodEnd,\n cancelAtPeriodEnd: false,\n dunningAttempt: 0,\n metadata: options?.metadata as any,\n });\n\n await this.recordEvent(entity.id, organizationId, \"created\", {\n newStatus: \"active\",\n });\n\n this.log.info(\"Subscription created\", {\n id: entity.id,\n organizationId,\n planId,\n });\n\n await this.alepha.events.emit(\"subscription:created\" as any, {\n subscription: entity,\n });\n\n return entity;\n }\n\n // ---------------------------------------------------------------------------------------------------------------\n\n /**\n * Cancel a subscription.\n * If immediate, the subscription expires right away.\n * If at period end, the subscription remains accessible until the period ends.\n */\n public async cancel(\n subscriptionId: string,\n options?: CancelOptions,\n ): Promise<void> {\n const sub = await this.subscriptionRepo.getById(subscriptionId);\n const orgId = sub.organizationId as string;\n\n if (\n sub.status !== \"trialing\" &&\n sub.status !== \"active\" &&\n sub.status !== \"past_due\"\n ) {\n throw new BadRequestError(\n `Cannot cancel subscription with status '${sub.status}'`,\n );\n }\n\n const settings = await this.config.getSettings();\n const immediate = options?.immediate ?? !settings.cancelAtPeriodEnd;\n const now = this.dateTime.now();\n const nowISO = now.toISOString();\n const previousStatus = sub.status;\n\n if (immediate) {\n await this.subscriptionRepo.updateById(subscriptionId, {\n status: \"expired\",\n cancelledAt: nowISO,\n cancelReason: options?.reason,\n cancelAtPeriodEnd: false,\n });\n\n await this.recordEvent(subscriptionId, orgId, \"cancelled\", {\n previousStatus,\n newStatus: \"expired\",\n triggeredBy: options?.cancelledBy ? \"user\" : \"system\",\n userId: options?.cancelledBy,\n note: options?.reason,\n });\n\n this.log.info(\"Subscription cancelled immediately\", {\n id: subscriptionId,\n organizationId: orgId,\n });\n } else {\n await this.subscriptionRepo.updateById(subscriptionId, {\n status: \"cancelled\",\n cancelledAt: nowISO,\n cancelReason: options?.reason,\n cancelAtPeriodEnd: true,\n });\n\n await this.recordEvent(subscriptionId, orgId, \"cancelled\", {\n previousStatus,\n newStatus: \"cancelled\",\n triggeredBy: options?.cancelledBy ? \"user\" : \"system\",\n userId: options?.cancelledBy,\n note: options?.reason,\n });\n\n this.log.info(\"Subscription cancelled at period end\", {\n id: subscriptionId,\n organizationId: orgId,\n periodEnd: sub.currentPeriodEnd,\n });\n }\n\n await this.alepha.events.emit(\"subscription:cancelled\" as any, {\n subscription: sub,\n immediate,\n reason: options?.reason,\n });\n }\n\n // ---------------------------------------------------------------------------------------------------------------\n\n /**\n * Resume a cancelled subscription before its period ends.\n * Only valid for subscriptions cancelled with cancelAtPeriodEnd.\n */\n public async resume(subscriptionId: string): Promise<void> {\n const sub = await this.subscriptionRepo.getById(subscriptionId);\n const orgId = sub.organizationId as string;\n\n if (sub.status !== \"cancelled\") {\n throw new BadRequestError(\n `Cannot resume subscription with status '${sub.status}', must be 'cancelled'`,\n );\n }\n\n if (!sub.cancelAtPeriodEnd) {\n throw new BadRequestError(\n \"Cannot resume a subscription that was not cancelled at period end\",\n );\n }\n\n if (!this.dateTime.now().isBefore(sub.currentPeriodEnd)) {\n throw new BadRequestError(\n \"Cannot resume subscription, period has already ended\",\n );\n }\n\n await this.subscriptionRepo.updateById(subscriptionId, {\n status: \"active\",\n cancelledAt: undefined,\n cancelReason: undefined,\n cancelAtPeriodEnd: false,\n });\n\n await this.recordEvent(subscriptionId, orgId, \"resumed\", {\n previousStatus: \"cancelled\",\n newStatus: \"active\",\n });\n\n this.log.info(\"Subscription resumed\", {\n id: subscriptionId,\n organizationId: orgId,\n });\n\n await this.alepha.events.emit(\"subscription:resumed\" as any, {\n subscription: sub,\n });\n }\n\n // ---------------------------------------------------------------------------------------------------------------\n\n /**\n * Change the plan of a subscription.\n * If immediate, proration is calculated and the plan changes now.\n * If at period end, the change is scheduled for the next renewal.\n * Returns the net proration amount (positive = charge, negative = credit).\n */\n public async changePlan(\n subscriptionId: string,\n newPlanId: string,\n newInterval?: \"monthly\" | \"yearly\",\n options?: ChangePlanOptions,\n ): Promise<number> {\n const sub = await this.subscriptionRepo.getById(subscriptionId);\n const orgId = sub.organizationId as string;\n\n if (sub.status !== \"active\" && sub.status !== \"trialing\") {\n throw new BadRequestError(\n `Cannot change plan for subscription with status '${sub.status}'`,\n );\n }\n\n const newPlan = await this.config.getPlan(newPlanId);\n\n if (!newPlan.available) {\n throw new BadRequestError(\n `Plan '${newPlanId}' is not available for new subscriptions`,\n );\n }\n\n const effectiveInterval = newInterval ?? sub.interval;\n await this.config.getPlanPricing(newPlanId, effectiveInterval);\n\n const settings = await this.config.getSettings();\n const immediate = options?.immediate ?? true;\n\n if (!immediate) {\n await this.subscriptionRepo.updateById(subscriptionId, {\n pendingPlanId: newPlanId,\n pendingInterval: effectiveInterval,\n });\n\n await this.recordEvent(subscriptionId, orgId, \"plan_change_scheduled\", {\n previousPlanId: sub.planId,\n newPlanId,\n note: `Scheduled change to '${newPlanId}' (${effectiveInterval}) at period end`,\n });\n\n this.log.info(\"Plan change scheduled for period end\", {\n id: subscriptionId,\n organizationId: orgId,\n newPlanId,\n newInterval: effectiveInterval,\n });\n\n await this.alepha.events.emit(\"subscription:plan_changed\" as any, {\n subscription: sub,\n previousPlanId: sub.planId,\n newPlanId,\n immediate: false,\n });\n\n return 0;\n }\n\n const shouldProrate = options?.prorate ?? settings.prorateOnChange;\n let netAmount = 0;\n\n if (shouldProrate && sub.status === \"active\") {\n netAmount = await this.calculateProration(\n sub,\n newPlanId,\n effectiveInterval,\n );\n }\n\n const previousPlanId = sub.planId;\n\n await this.subscriptionRepo.updateById(subscriptionId, {\n planId: newPlanId,\n interval: effectiveInterval,\n pendingPlanId: undefined,\n pendingInterval: undefined,\n metadata:\n netAmount < 0\n ? { ...sub.metadata, credit: Math.abs(netAmount) }\n : sub.metadata,\n });\n\n await this.recordEvent(subscriptionId, orgId, \"plan_changed\", {\n previousPlanId,\n newPlanId,\n amount: netAmount !== 0 ? Math.abs(netAmount) : undefined,\n note:\n netAmount > 0\n ? `Proration charge: ${netAmount}`\n : netAmount < 0\n ? `Proration credit: ${Math.abs(netAmount)}`\n : undefined,\n });\n\n this.log.info(\"Plan changed immediately\", {\n id: subscriptionId,\n organizationId: orgId,\n previousPlanId,\n newPlanId,\n netAmount,\n });\n\n await this.alepha.events.emit(\"subscription:plan_changed\" as any, {\n subscription: sub,\n previousPlanId,\n newPlanId,\n immediate: true,\n netAmount,\n });\n\n return netAmount;\n }\n\n // ---------------------------------------------------------------------------------------------------------------\n\n /**\n * Reactivate a suspended subscription (admin action).\n * Resets dunning state and starts a new billing period.\n */\n public async reactivate(subscriptionId: string): Promise<void> {\n const sub = await this.subscriptionRepo.getById(subscriptionId);\n const orgId = sub.organizationId as string;\n\n if (sub.status !== \"suspended\") {\n throw new BadRequestError(\n `Cannot reactivate subscription with status '${sub.status}', must be 'suspended'`,\n );\n }\n\n const now = this.dateTime.now();\n const nowISO = now.toISOString();\n const periodEnd = this.computeIntervalEnd(nowISO, sub.interval);\n\n await this.subscriptionRepo.updateById(subscriptionId, {\n status: \"active\",\n currentPeriodStart: nowISO,\n currentPeriodEnd: periodEnd,\n nextBillingAt: periodEnd,\n dunningStartedAt: undefined,\n dunningAttempt: 0,\n dunningNextRetryAt: undefined,\n });\n\n await this.recordEvent(subscriptionId, orgId, \"reactivated\", {\n previousStatus: \"suspended\",\n newStatus: \"active\",\n });\n\n this.log.info(\"Subscription reactivated\", {\n id: subscriptionId,\n organizationId: orgId,\n });\n\n await this.alepha.events.emit(\"subscription:reactivated\" as any, {\n subscription: sub,\n });\n }\n\n // ---------------------------------------------------------------------------------------------------------------\n\n /**\n * Extend the trial period of a trialing subscription.\n */\n public async extendTrial(\n subscriptionId: string,\n days: number,\n ): Promise<void> {\n const sub = await this.subscriptionRepo.getById(subscriptionId);\n\n if (sub.status !== \"trialing\") {\n throw new BadRequestError(\n `Cannot extend trial for subscription with status '${sub.status}', must be 'trialing'`,\n );\n }\n\n if (!sub.trialEnd) {\n throw new BadRequestError(\"Subscription has no trial end date set\");\n }\n\n const currentTrialEnd = this.dateTime.of(sub.trialEnd);\n const newTrialEnd = currentTrialEnd.add(days, \"days\").toISOString();\n\n await this.subscriptionRepo.updateById(subscriptionId, {\n trialEnd: newTrialEnd,\n currentPeriodEnd: newTrialEnd,\n nextBillingAt: newTrialEnd,\n });\n\n this.log.info(\"Trial extended\", {\n id: subscriptionId,\n organizationId: sub.organizationId as string,\n days,\n newTrialEnd,\n });\n }\n\n // ---------------------------------------------------------------------------------------------------------------\n // Entitlements\n // ---------------------------------------------------------------------------------------------------------------\n\n /**\n * Check if an organization has access to a specific feature.\n */\n public async can(organizationId: string, feature: string): Promise<boolean> {\n const sub = await this.getByOrganization(organizationId);\n if (!sub || !this.isAccessible(sub)) return false;\n const plan = await this.config.getPlan(sub.planId);\n return plan.features.includes(feature);\n }\n\n /**\n * Get the usage limit for a resource.\n * Returns -1 for unlimited, 0 for no access.\n */\n public async limit(\n organizationId: string,\n resource: string,\n ): Promise<number> {\n const sub = await this.getByOrganization(organizationId);\n if (!sub || !this.isAccessible(sub)) return 0;\n const plan = await this.config.getPlan(sub.planId);\n return plan.limits[resource] ?? 0;\n }\n\n /**\n * Get the full entitlements snapshot for an organization.\n */\n public async getEntitlements(organizationId: string): Promise<Entitlements> {\n const sub = await this.getByOrganization(organizationId);\n\n if (!sub) {\n throw new NotFoundError(\n `No subscription found for organization '${organizationId}'`,\n );\n }\n\n const plan = await this.config.getPlan(sub.planId);\n\n return {\n planId: plan.id,\n planName: plan.name,\n status: sub.status,\n features: plan.features,\n limits: plan.limits,\n trialEndsAt: sub.trialEnd,\n periodEndsAt: sub.currentPeriodEnd,\n cancelledAt: sub.cancelledAt,\n };\n }\n\n // ---------------------------------------------------------------------------------------------------------------\n // Queries\n // ---------------------------------------------------------------------------------------------------------------\n\n /**\n * Find subscriptions with pagination and filtering.\n */\n public async findSubscriptions(\n query: SubscriptionQuery = {},\n ): Promise<Page<SubscriptionEntity>> {\n query.sort ??= \"-createdAt\";\n\n const where = this.subscriptionRepo.createQueryWhere();\n\n if (query.status) {\n where.status = { eq: query.status };\n }\n\n if (query.planId) {\n where.planId = { eq: query.planId };\n }\n\n if (query.organizationId) {\n where.organizationId = { eq: query.organizationId };\n }\n\n return this.subscriptionRepo.paginate(query, { where }, { count: true });\n }\n\n // ---------------------------------------------------------------------------------------------------------------\n\n /**\n * Get the event history for a subscription, ordered by most recent first.\n */\n public async getHistory(\n subscriptionId: string,\n ): Promise<SubscriptionEventEntity[]> {\n return this.eventRepo.findMany({\n where: { subscriptionId: { eq: subscriptionId } },\n orderBy: { column: \"createdAt\", direction: \"desc\" },\n });\n }\n\n // ---------------------------------------------------------------------------------------------------------------\n\n /**\n * Get aggregated subscription statistics.\n */\n public async getStats(): Promise<SubscriptionStats> {\n const [trialing, active, pastDue, suspended, cancelled, expired] =\n await Promise.all([\n this.subscriptionRepo.count({ status: { eq: \"trialing\" } }),\n this.subscriptionRepo.count({ status: { eq: \"active\" } }),\n this.subscriptionRepo.count({ status: { eq: \"past_due\" } }),\n this.subscriptionRepo.count({ status: { eq: \"suspended\" } }),\n this.subscriptionRepo.count({ status: { eq: \"cancelled\" } }),\n this.subscriptionRepo.count({ status: { eq: \"expired\" } }),\n ]);\n\n const total = trialing + active + pastDue + suspended + cancelled + expired;\n\n const trialEndedEvents = await this.eventRepo.count({\n type: { eq: \"trial_ended\" },\n });\n const activatedEvents = await this.eventRepo.count({\n type: { eq: \"activated\" },\n });\n const trialConversionRate =\n trialEndedEvents > 0 ? activatedEvents / trialEndedEvents : 0;\n\n const cancelledEvents = await this.eventRepo.count({\n type: { eq: \"cancelled\" },\n });\n const totalSubscribed = active + trialing + pastDue;\n const churnRate =\n totalSubscribed + cancelledEvents > 0\n ? cancelledEvents / (totalSubscribed + cancelledEvents)\n : 0;\n\n const plans = await this.config.getPlans();\n const byPlan: Record<\n string,\n { active: number; trialing: number; total: number }\n > = {};\n\n for (const plan of plans) {\n const [planActive, planTrialing] = await Promise.all([\n this.subscriptionRepo.count({\n planId: { eq: plan.id },\n status: { eq: \"active\" },\n }),\n this.subscriptionRepo.count({\n planId: { eq: plan.id },\n status: { eq: \"trialing\" },\n }),\n ]);\n\n byPlan[plan.id] = {\n active: planActive,\n trialing: planTrialing,\n total: planActive + planTrialing,\n };\n }\n\n return {\n total,\n trialing,\n active,\n pastDue,\n suspended,\n cancelled,\n expired,\n trialConversionRate,\n churnRate,\n byPlan,\n };\n }\n\n // ---------------------------------------------------------------------------------------------------------------\n\n /**\n * Get revenue data from recent subscription events.\n * Sums amounts from renewed and activated events within the specified window.\n */\n public async getRevenue(\n days = 30,\n ): Promise<{ total: number; count: number }> {\n const cutoff = this.dateTime.now().subtract(days, \"days\").toISOString();\n\n const events = await this.eventRepo.findMany({\n where: {\n type: { inArray: [\"renewed\", \"activated\"] },\n createdAt: { gt: cutoff },\n },\n });\n\n let total = 0;\n for (const event of events) {\n total += event.amount ?? 0;\n }\n\n return { total, count: events.length };\n }\n\n // ---------------------------------------------------------------------------------------------------------------\n // Protected helpers\n // ---------------------------------------------------------------------------------------------------------------\n\n /**\n * Calculate proration for a mid-cycle plan change.\n * Returns the net amount: positive = charge, negative = credit.\n */\n protected async calculateProration(\n sub: SubscriptionEntity,\n newPlanId: string,\n newInterval: \"monthly\" | \"yearly\",\n ): Promise<number> {\n const oldPricing = await this.config.getPlanPricing(\n sub.planId,\n sub.interval,\n );\n const newPricing = await this.config.getPlanPricing(newPlanId, newInterval);\n\n const now = this.dateTime.now();\n const periodStart = this.dateTime.of(sub.currentPeriodStart);\n const periodEnd = this.dateTime.of(sub.currentPeriodEnd);\n\n const daysInPeriod = periodEnd.diff(periodStart, \"days\");\n if (daysInPeriod <= 0) return 0;\n\n const daysUsed = now.diff(periodStart, \"days\");\n const daysRemaining = daysInPeriod - daysUsed;\n\n const oldDailyRate = oldPricing.amount / daysInPeriod;\n const newDailyRate = newPricing.amount / daysInPeriod;\n\n const credit = Math.round(daysRemaining * oldDailyRate);\n const charge = Math.round(daysRemaining * newDailyRate);\n\n return charge - credit;\n }\n}\n","import { $inject, t } from \"alepha\";\nimport { $secure } from \"alepha/security\";\nimport { $action, okSchema } from \"alepha/server\";\nimport { cancelSubscriptionSchema } from \"../schemas/cancelSubscriptionSchema.ts\";\nimport { changePlanSchema } from \"../schemas/changePlanSchema.ts\";\nimport { mrrSchema } from \"../schemas/mrrSchema.ts\";\nimport { subscriptionQuerySchema } from \"../schemas/subscriptionQuerySchema.ts\";\nimport { subscriptionResourceSchema } from \"../schemas/subscriptionResourceSchema.ts\";\nimport { subscriptionStatsSchema } from \"../schemas/subscriptionStatsSchema.ts\";\nimport { SubscriptionConfig } from \"../services/SubscriptionConfig.ts\";\nimport { SubscriptionService } from \"../services/SubscriptionService.ts\";\n\nexport class AdminSubscriptionController {\n protected readonly url = \"/subscriptions\";\n protected readonly group = \"admin:subscriptions\";\n protected readonly service = $inject(SubscriptionService);\n protected readonly config = $inject(SubscriptionConfig);\n\n /**\n * Find subscriptions with pagination and filtering.\n */\n public readonly findSubscriptions = $action({\n path: this.url,\n group: this.group,\n use: [$secure({ permissions: [\"admin:subscription:read\"] })],\n description: \"Find subscriptions with pagination and filtering\",\n schema: {\n query: subscriptionQuerySchema,\n response: t.page(subscriptionResourceSchema),\n },\n handler: ({ query }) => this.service.findSubscriptions(query),\n });\n\n /**\n * Get a subscription by ID.\n */\n public readonly getSubscription = $action({\n path: `${this.url}/:id`,\n group: this.group,\n use: [$secure({ permissions: [\"admin:subscription:read\"] })],\n description: \"Get a subscription by ID\",\n schema: {\n params: t.object({ id: t.uuid() }),\n response: subscriptionResourceSchema,\n },\n handler: ({ params }) => this.service.getSubscription(params.id),\n });\n\n /**\n * Get aggregated subscription statistics.\n */\n public readonly getStats = $action({\n path: `${this.url}/stats`,\n group: this.group,\n use: [$secure({ permissions: [\"admin:subscription:read\"] })],\n description: \"Get aggregated subscription statistics\",\n schema: {\n response: subscriptionStatsSchema,\n },\n handler: () => this.service.getStats(),\n });\n\n /**\n * Get revenue data from recent subscription events.\n */\n public readonly getRevenue = $action({\n path: `${this.url}/revenue`,\n group: this.group,\n use: [$secure({ permissions: [\"admin:subscription:read\"] })],\n description: \"Get revenue data from recent subscription events\",\n schema: {\n query: t.object({\n days: t.optional(t.integer({ minimum: 1, maximum: 365 })),\n }),\n response: t.object({ total: t.integer(), count: t.integer() }),\n },\n handler: ({ query }) => this.service.getRevenue(query.days),\n });\n\n /**\n * Get Monthly Recurring Revenue breakdown.\n */\n public readonly getMrr = $action({\n path: `${this.url}/mrr`,\n group: this.group,\n use: [$secure({ permissions: [\"admin:subscription:read\"] })],\n description: \"Get Monthly Recurring Revenue breakdown\",\n schema: {\n response: mrrSchema,\n },\n handler: async () => {\n const activeSubs = await this.service.findSubscriptions({\n status: \"active\",\n size: 1000,\n });\n\n const plans = await this.config.getPlans();\n const byPlan: Record<string, number> = {};\n let total = 0;\n\n for (const sub of activeSubs.content) {\n const plan = plans.find((p) => p.id === sub.planId);\n if (!plan) continue;\n\n const pricing = plan.pricing.find((p) => p.interval === sub.interval);\n if (!pricing) continue;\n\n const monthlyAmount =\n sub.interval === \"yearly\"\n ? Math.round(pricing.amount / 12)\n : pricing.amount;\n\n byPlan[sub.planId] = (byPlan[sub.planId] ?? 0) + monthlyAmount;\n total += monthlyAmount;\n }\n\n return {\n total,\n byPlan,\n growth: 0,\n newMrr: 0,\n expansionMrr: 0,\n contractionMrr: 0,\n churnMrr: 0,\n };\n },\n });\n\n /**\n * Force a plan change for a subscription (admin action).\n */\n public readonly adminChangePlan = $action({\n method: \"POST\",\n path: `${this.url}/:id/change-plan`,\n group: this.group,\n use: [$secure({ permissions: [\"admin:subscription:update\"] })],\n description: \"Force a plan change for a subscription\",\n schema: {\n params: t.object({ id: t.uuid() }),\n body: changePlanSchema,\n response: subscriptionResourceSchema,\n },\n handler: async ({ params, body }) => {\n await this.service.changePlan(params.id, body.planId, body.interval, {\n immediate: body.immediate,\n });\n return this.service.getSubscription(params.id);\n },\n });\n\n /**\n * Force cancel a subscription (admin action).\n */\n public readonly adminCancel = $action({\n method: \"POST\",\n path: `${this.url}/:id/cancel`,\n group: this.group,\n use: [$secure({ permissions: [\"admin:subscription:update\"] })],\n description: \"Force cancel a subscription\",\n schema: {\n params: t.object({ id: t.uuid() }),\n body: cancelSubscriptionSchema,\n response: okSchema,\n },\n handler: async ({ params, body }) => {\n await this.service.cancel(params.id, {\n reason: body.reason,\n immediate: body.immediate,\n });\n return { ok: true };\n },\n });\n\n /**\n * Reactivate a suspended subscription (admin action).\n */\n public readonly adminReactivate = $action({\n method: \"POST\",\n path: `${this.url}/:id/reactivate`,\n group: this.group,\n use: [$secure({ permissions: [\"admin:subscription:update\"] })],\n description: \"Reactivate a suspended subscription\",\n schema: {\n params: t.object({ id: t.uuid() }),\n response: okSchema,\n },\n handler: async ({ params }) => {\n await this.service.reactivate(params.id);\n return { ok: true };\n },\n });\n\n /**\n * Extend the trial period for a trialing subscription (admin action).\n */\n public readonly adminExtendTrial = $action({\n method: \"POST\",\n path: `${this.url}/:id/extend-trial`,\n group: this.group,\n use: [$secure({ permissions: [\"admin:subscription:update\"] })],\n description: \"Extend the trial period for a subscription\",\n schema: {\n params: t.object({ id: t.uuid() }),\n body: t.object({ days: t.integer({ minimum: 1, maximum: 365 }) }),\n response: okSchema,\n },\n handler: async ({ params, body }) => {\n await this.service.extendTrial(params.id, body.days);\n return { ok: true };\n },\n });\n}\n","import { type Static, t } from \"alepha\";\n\nexport const createSubscriptionSchema = t.object({\n planId: t.string(),\n interval: t.enum([\"monthly\", \"yearly\"]),\n paymentMethodId: t.optional(t.uuid()),\n skipTrial: t.optional(t.boolean()),\n metadata: t.optional(t.record(t.text(), t.any())),\n});\n\nexport type CreateSubscription = Static<typeof createSubscriptionSchema>;\n","import { type Static, t } from \"alepha\";\n\nexport const entitlementsSchema = t.object({\n planId: t.string(),\n planName: t.string(),\n status: t.enum([\n \"trialing\",\n \"active\",\n \"past_due\",\n \"suspended\",\n \"cancelled\",\n \"expired\",\n ]),\n features: t.array(t.string()),\n limits: t.record(t.text(), t.integer()),\n trialEndsAt: t.optional(t.datetime()),\n periodEndsAt: t.datetime(),\n cancelledAt: t.optional(t.datetime()),\n});\n\nexport type Entitlements = Static<typeof entitlementsSchema>;\n","import { type Static, t } from \"alepha\";\n\nexport const planResourceSchema = t.object({\n id: t.string(),\n name: t.string(),\n description: t.optional(t.string()),\n pricing: t.array(\n t.object({\n interval: t.enum([\"monthly\", \"yearly\"]),\n amount: t.integer(),\n currency: t.string(),\n }),\n ),\n features: t.array(t.string()),\n limits: t.record(t.text(), t.integer()),\n trial: t.optional(\n t.object({\n days: t.integer(),\n requirePaymentMethod: t.boolean(),\n }),\n ),\n order: t.integer(),\n});\n\nexport type PlanResource = Static<typeof planResourceSchema>;\n","import type { Static } from \"alepha\";\nimport { subscriptionEvents } from \"../entities/subscriptionEvents.ts\";\n\nexport const subscriptionEventResourceSchema = subscriptionEvents.schema;\n\nexport type SubscriptionEventResource = Static<\n typeof subscriptionEventResourceSchema\n>;\n","import { $inject, t } from \"alepha\";\nimport { $secure } from \"alepha/security\";\nimport { $action, NotFoundError, okSchema } from \"alepha/server\";\nimport { cancelSubscriptionSchema } from \"../schemas/cancelSubscriptionSchema.ts\";\nimport { changePlanSchema } from \"../schemas/changePlanSchema.ts\";\nimport { createSubscriptionSchema } from \"../schemas/createSubscriptionSchema.ts\";\nimport { entitlementsSchema } from \"../schemas/entitlementsSchema.ts\";\nimport { planResourceSchema } from \"../schemas/planResourceSchema.ts\";\nimport { subscriptionEventResourceSchema } from \"../schemas/subscriptionEventResourceSchema.ts\";\nimport { subscriptionResourceSchema } from \"../schemas/subscriptionResourceSchema.ts\";\nimport { SubscriptionConfig } from \"../services/SubscriptionConfig.ts\";\nimport { SubscriptionService } from \"../services/SubscriptionService.ts\";\n\nexport class SubscriptionController {\n protected readonly url = \"/subscriptions\";\n protected readonly group = \"subscriptions\";\n protected readonly service = $inject(SubscriptionService);\n protected readonly config = $inject(SubscriptionConfig);\n\n /**\n * List available subscription plans with pricing.\n */\n public readonly getPlans = $action({\n path: `${this.url}/plans`,\n group: this.group,\n description: \"List available subscription plans\",\n schema: {\n response: t.array(planResourceSchema),\n },\n handler: async () => {\n const plans = await this.config.getPlans();\n return plans\n .filter((p) => p.available)\n .map((p) => ({\n id: p.id,\n name: p.name,\n description: p.description,\n pricing: p.pricing,\n features: p.features,\n limits: p.limits,\n trial: p.trial,\n order: p.order,\n }));\n },\n });\n\n /**\n * Get the current organization's subscription.\n */\n public readonly getMySubscription = $action({\n path: `${this.url}/mine`,\n group: this.group,\n use: [$secure()],\n description: \"Get the current organization subscription\",\n schema: {\n response: subscriptionResourceSchema,\n },\n handler: async ({ user }) => {\n const sub = await this.service.getByOrganization(user.organization!);\n if (!sub)\n throw new NotFoundError(\"No subscription found for your organization\");\n return sub;\n },\n });\n\n /**\n * Create a new subscription for the current organization.\n */\n public readonly subscribe = $action({\n method: \"POST\",\n path: this.url,\n group: this.group,\n use: [$secure({ permissions: [\"subscription:create\"] })],\n description: \"Create a new subscription\",\n schema: {\n body: createSubscriptionSchema,\n response: subscriptionResourceSchema,\n },\n handler: ({ body, user }) =>\n this.service.subscribe(user.organization!, body.planId, body.interval, {\n skipTrial: body.skipTrial,\n metadata: body.metadata,\n }),\n });\n\n /**\n * Change the plan for the current organization's subscription.\n */\n public readonly changePlan = $action({\n method: \"POST\",\n path: `${this.url}/mine/change-plan`,\n group: this.group,\n use: [$secure({ permissions: [\"subscription:update\"] })],\n description: \"Upgrade or downgrade the subscription plan\",\n schema: {\n body: changePlanSchema,\n response: subscriptionResourceSchema,\n },\n handler: async ({ body, user }) => {\n const sub = await this.service.getByOrganization(user.organization!);\n if (!sub)\n throw new NotFoundError(\"No subscription found for your organization\");\n await this.service.changePlan(sub.id, body.planId, body.interval, {\n immediate: body.immediate,\n });\n return this.service.getSubscription(sub.id);\n },\n });\n\n /**\n * Cancel the current organization's subscription.\n */\n public readonly cancel = $action({\n method: \"POST\",\n path: `${this.url}/mine/cancel`,\n group: this.group,\n use: [$secure({ permissions: [\"subscription:update\"] })],\n description: \"Cancel the current subscription\",\n schema: {\n body: cancelSubscriptionSchema,\n response: okSchema,\n },\n handler: async ({ body, user }) => {\n const sub = await this.service.getByOrganization(user.organization!);\n if (!sub)\n throw new NotFoundError(\"No subscription found for your organization\");\n await this.service.cancel(sub.id, {\n reason: body.reason,\n immediate: body.immediate,\n });\n return { ok: true };\n },\n });\n\n /**\n * Resume a cancelled subscription before the period ends.\n */\n public readonly resume = $action({\n method: \"POST\",\n path: `${this.url}/mine/resume`,\n group: this.group,\n use: [$secure({ permissions: [\"subscription:update\"] })],\n description: \"Resume a cancelled subscription\",\n schema: {\n response: okSchema,\n },\n handler: async ({ user }) => {\n const sub = await this.service.getByOrganization(user.organization!);\n if (!sub)\n throw new NotFoundError(\"No subscription found for your organization\");\n await this.service.resume(sub.id);\n return { ok: true };\n },\n });\n\n /**\n * Get the billing event history for the current organization's subscription.\n */\n public readonly getSubscriptionHistory = $action({\n path: `${this.url}/mine/history`,\n group: this.group,\n use: [$secure()],\n description: \"Get the subscription billing event history\",\n schema: {\n response: t.array(subscriptionEventResourceSchema),\n },\n handler: async ({ user }) => {\n const sub = await this.service.getByOrganization(user.organization!);\n if (!sub)\n throw new NotFoundError(\"No subscription found for your organization\");\n return this.service.getHistory(sub.id);\n },\n });\n\n /**\n * Get the feature and usage limit entitlements for the current organization.\n */\n public readonly getEntitlements = $action({\n path: `${this.url}/mine/entitlements`,\n group: this.group,\n use: [$secure()],\n description:\n \"Get the feature and limit entitlements for the current organization\",\n schema: {\n response: entitlementsSchema,\n },\n handler: ({ user }) => this.service.getEntitlements(user.organization!),\n });\n}\n","import { $inject } from \"alepha\";\nimport { $job } from \"alepha/api/jobs\";\nimport { PaymentService } from \"alepha/api/payments\";\nimport { DateTimeProvider } from \"alepha/datetime\";\nimport { $logger } from \"alepha/logger\";\nimport { $repository } from \"alepha/orm\";\nimport type { SubscriptionEventEntity } from \"../entities/subscriptionEvents.ts\";\nimport { subscriptionEvents } from \"../entities/subscriptionEvents.ts\";\nimport { subscriptions } from \"../entities/subscriptions.ts\";\nimport { SubscriptionConfig } from \"../services/SubscriptionConfig.ts\";\n\n// -----------------------------------------------------------------------------------------------------------------\n\ninterface EventContext {\n previousStatus?: string;\n newStatus?: string;\n paymentIntentId?: string;\n amount?: number;\n currency?: string;\n triggeredBy?: string;\n note?: string;\n}\n\n// -----------------------------------------------------------------------------------------------------------------\n\nexport class SubscriptionJobs {\n protected readonly log = $logger();\n protected readonly dateTime = $inject(DateTimeProvider);\n protected readonly paymentService = $inject(PaymentService);\n protected readonly config = $inject(SubscriptionConfig);\n protected readonly subscriptionRepo = $repository(subscriptions);\n protected readonly eventRepo = $repository(subscriptionEvents);\n\n // ---------------------------------------------------------------------------------------------------------------\n // Helpers\n // ---------------------------------------------------------------------------------------------------------------\n\n /**\n * Record a subscription event in the event log.\n */\n protected async recordEvent(\n subscriptionId: string,\n organizationId: string,\n type: SubscriptionEventEntity[\"type\"],\n context?: EventContext,\n ): Promise<void> {\n await this.eventRepo.create({\n subscriptionId,\n organizationId,\n type,\n previousStatus: context?.previousStatus,\n newStatus: context?.newStatus,\n paymentIntentId: context?.paymentIntentId,\n amount: context?.amount,\n currency: context?.currency,\n triggeredBy: context?.triggeredBy,\n note: context?.note,\n });\n }\n\n // ---------------------------------------------------------------------------------------------------------------\n // Jobs\n // ---------------------------------------------------------------------------------------------------------------\n\n /**\n * Creates payment intents for subscriptions due for renewal.\n * Runs hourly.\n */\n public readonly billingCycle = $job({\n cron: \"0 * * * *\",\n lock: true,\n timeout: [10, \"minute\"],\n handler: async ({ now }) => {\n const nowISO = now.toISOString();\n\n const due = await this.subscriptionRepo.findMany({\n where: {\n nextBillingAt: { lte: nowISO },\n status: { inArray: [\"active\", \"trialing\"] },\n },\n });\n\n this.log.info(`Billing cycle: processing ${due.length} subscription(s)`);\n\n for (const sub of due) {\n try {\n const pricing = await this.config.getPlanPricing(\n sub.planId,\n sub.interval,\n );\n\n const intent = await this.paymentService.createIntent(\n pricing.amount,\n pricing.currency,\n { subscriptionId: sub.id },\n );\n\n await this.subscriptionRepo.updateById(sub.id, {\n lastPaymentIntentId: intent.id,\n });\n\n this.log.debug(\"Created payment intent for subscription\", {\n subscriptionId: sub.id,\n intentId: intent.id,\n });\n } catch (err) {\n this.log.error(\"Failed to create payment intent for subscription\", {\n subscriptionId: sub.id,\n error: err,\n });\n }\n }\n },\n });\n\n // ---------------------------------------------------------------------------------------------------------------\n\n /**\n * Retries failed payments on the dunning schedule.\n * Runs hourly.\n */\n public readonly dunningRetry = $job({\n cron: \"0 * * * *\",\n lock: true,\n timeout: [10, \"minute\"],\n handler: async ({ now }) => {\n const nowISO = now.toISOString();\n\n const pastDue = await this.subscriptionRepo.findMany({\n where: {\n dunningNextRetryAt: { lte: nowISO },\n status: { eq: \"past_due\" },\n },\n });\n\n this.log.info(\n `Dunning retry: processing ${pastDue.length} subscription(s)`,\n );\n\n const settings = await this.config.getSettings();\n\n for (const sub of pastDue) {\n try {\n const pricing = await this.config.getPlanPricing(\n sub.planId,\n sub.interval,\n );\n\n const intent = await this.paymentService.createIntent(\n pricing.amount,\n pricing.currency,\n { subscriptionId: sub.id },\n );\n\n const newAttempt = sub.dunningAttempt + 1;\n const scheduleDays = settings.dunningSchedule[newAttempt - 1];\n const nextRetry =\n scheduleDays !== undefined\n ? now.add(scheduleDays, \"days\").toISOString()\n : undefined;\n\n await this.subscriptionRepo.updateById(sub.id, {\n lastPaymentIntentId: intent.id,\n dunningAttempt: newAttempt,\n dunningNextRetryAt: nextRetry,\n });\n\n await this.recordEvent(\n sub.id,\n sub.organizationId as string,\n \"payment_retried\",\n {\n paymentIntentId: intent.id,\n note: `Dunning retry attempt ${newAttempt}`,\n },\n );\n\n this.log.debug(\"Dunning retry payment intent created\", {\n subscriptionId: sub.id,\n attempt: newAttempt,\n });\n } catch (err) {\n this.log.error(\"Failed to create dunning retry intent\", {\n subscriptionId: sub.id,\n error: err,\n });\n }\n }\n },\n });\n\n // ---------------------------------------------------------------------------------------------------------------\n\n /**\n * Handles trial expirations.\n * Runs hourly.\n */\n public readonly trialExpiry = $job({\n cron: \"0 * * * *\",\n lock: true,\n handler: async ({ now }) => {\n const nowISO = now.toISOString();\n\n const expired = await this.subscriptionRepo.findMany({\n where: {\n trialEnd: { lte: nowISO },\n status: { eq: \"trialing\" },\n },\n });\n\n this.log.info(\n `Trial expiry: processing ${expired.length} subscription(s)`,\n );\n\n for (const sub of expired) {\n try {\n const pricing = await this.config.getPlanPricing(\n sub.planId,\n sub.interval,\n );\n\n const intent = await this.paymentService.createIntent(\n pricing.amount,\n pricing.currency,\n { subscriptionId: sub.id },\n );\n\n await this.subscriptionRepo.updateById(sub.id, {\n lastPaymentIntentId: intent.id,\n });\n\n this.log.debug(\"Created payment intent for trial expiry\", {\n subscriptionId: sub.id,\n intentId: intent.id,\n });\n } catch (err) {\n this.log.error(\"Failed to process trial expiry\", {\n subscriptionId: sub.id,\n error: err,\n });\n }\n }\n },\n });\n\n // ---------------------------------------------------------------------------------------------------------------\n\n /**\n * Expires cancelled subscriptions that reached period end.\n * Runs hourly.\n */\n public readonly expirationSweep = $job({\n cron: \"0 * * * *\",\n lock: true,\n handler: async ({ now }) => {\n const nowISO = now.toISOString();\n\n const toExpire = await this.subscriptionRepo.findMany({\n where: {\n currentPeriodEnd: { lte: nowISO },\n status: { eq: \"cancelled\" },\n cancelAtPeriodEnd: { eq: true },\n },\n });\n\n this.log.info(\n `Expiration sweep: expiring ${toExpire.length} subscription(s)`,\n );\n\n for (const sub of toExpire) {\n try {\n await this.subscriptionRepo.updateById(sub.id, {\n status: \"expired\",\n });\n\n await this.recordEvent(\n sub.id,\n sub.organizationId as string,\n \"expired\",\n {\n previousStatus: \"cancelled\",\n newStatus: \"expired\",\n },\n );\n\n this.log.debug(\"Subscription expired\", { subscriptionId: sub.id });\n } catch (err) {\n this.log.error(\"Failed to expire subscription\", {\n subscriptionId: sub.id,\n error: err,\n });\n }\n }\n },\n });\n\n // ---------------------------------------------------------------------------------------------------------------\n\n /**\n * Suspends past_due subscriptions where grace period has elapsed.\n * Runs daily at 2 AM.\n */\n public readonly gracePeriodSweep = $job({\n cron: \"0 2 * * *\",\n lock: true,\n handler: async ({ now }) => {\n const settings = await this.config.getSettings();\n const gracePeriodDays = settings.gracePeriodDays;\n\n const pastDueSubs = await this.subscriptionRepo.findMany({\n where: {\n status: { eq: \"past_due\" },\n },\n });\n\n const toSuspend = pastDueSubs.filter((sub) => {\n if (!sub.dunningStartedAt) return false;\n const graceEnd = this.dateTime\n .of(sub.dunningStartedAt)\n .add(gracePeriodDays, \"days\");\n return !now.isBefore(graceEnd.toISOString());\n });\n\n this.log.info(\n `Grace period sweep: suspending ${toSuspend.length} subscription(s)`,\n );\n\n for (const sub of toSuspend) {\n try {\n await this.subscriptionRepo.updateById(sub.id, {\n status: \"suspended\",\n });\n\n await this.recordEvent(\n sub.id,\n sub.organizationId as string,\n \"suspended\",\n {\n previousStatus: \"past_due\",\n newStatus: \"suspended\",\n },\n );\n\n this.log.debug(\"Subscription suspended after grace period\", {\n subscriptionId: sub.id,\n });\n } catch (err) {\n this.log.error(\"Failed to suspend subscription\", {\n subscriptionId: sub.id,\n error: err,\n });\n }\n }\n },\n });\n\n // ---------------------------------------------------------------------------------------------------------------\n\n /**\n * Purges old subscription events older than 365 days.\n * Runs daily at 3 AM.\n */\n public readonly purgeEvents = $job({\n cron: \"0 3 * * *\",\n lock: true,\n handler: async ({ now }) => {\n const cutoff = now.subtract(365, \"days\").toISOString();\n\n const old = await this.eventRepo.findMany({\n where: {\n createdAt: { lt: cutoff },\n },\n });\n\n this.log.info(`Purge events: removing ${old.length} old event(s)`);\n\n for (const event of old) {\n await this.eventRepo.deleteById(event.id);\n }\n },\n });\n}\n","import { t } from \"alepha\";\nimport { $notification } from \"alepha/api/notifications\";\n\nexport class SubscriptionNotifications {\n /**\n * Sent when a trial is ending soon.\n */\n protected readonly trialEnding = $notification({\n name: \"subscription-trial-ending\",\n category: \"subscriptions\",\n schema: t.object({\n planName: t.text(),\n trialEndDate: t.text(),\n amount: t.text(),\n interval: t.text(),\n }),\n email: {\n subject: \"Your trial is ending soon\",\n body: (v) =>\n `Your ${v.planName} trial is ending on ${v.trialEndDate}. You'll be charged ${v.amount}/${v.interval}.`,\n },\n });\n\n /**\n * Sent when a payment fails. Critical notification.\n */\n protected readonly paymentFailed = $notification({\n name: \"subscription-payment-failed\",\n category: \"subscriptions\",\n critical: true,\n schema: t.object({\n planName: t.text(),\n amount: t.text(),\n retryDate: t.optional(t.text()),\n }),\n email: {\n subject: \"Payment failed for your subscription\",\n body: (v) =>\n `We couldn't charge your card for ${v.planName} (${v.amount}). ${v.retryDate ? `We'll retry on ${v.retryDate}.` : \"Please update your payment method.\"}`,\n },\n });\n\n /**\n * Sent when a subscription is suspended due to failed payments. Critical notification.\n */\n protected readonly subscriptionSuspended = $notification({\n name: \"subscription-suspended\",\n category: \"subscriptions\",\n critical: true,\n schema: t.object({ planName: t.text() }),\n email: {\n subject: \"Your subscription has been suspended\",\n body: (v) =>\n `Your ${v.planName} subscription has been suspended due to failed payments. Update your payment method to reactivate.`,\n },\n });\n\n /**\n * Sent when a subscription is successfully renewed.\n */\n protected readonly subscriptionRenewed = $notification({\n name: \"subscription-renewed\",\n category: \"subscriptions\",\n schema: t.object({\n planName: t.text(),\n amount: t.text(),\n nextBillingDate: t.text(),\n }),\n email: {\n subject: \"Payment received — subscription renewed\",\n body: (v) =>\n `Your ${v.planName} subscription has been renewed. Amount: ${v.amount}. Next billing: ${v.nextBillingDate}.`,\n },\n });\n\n /**\n * Sent when a subscription plan is changed.\n */\n protected readonly planChanged = $notification({\n name: \"subscription-plan-changed\",\n category: \"subscriptions\",\n schema: t.object({\n oldPlanName: t.text(),\n newPlanName: t.text(),\n effectiveDate: t.text(),\n }),\n email: {\n subject: \"Your subscription plan has been changed\",\n body: (v) =>\n `Your plan has been changed from ${v.oldPlanName} to ${v.newPlanName}, effective ${v.effectiveDate}.`,\n },\n });\n\n /**\n * Sent when a subscription is cancelled.\n */\n protected readonly cancellationConfirmed = $notification({\n name: \"subscription-cancelled\",\n category: \"subscriptions\",\n schema: t.object({\n planName: t.text(),\n accessUntil: t.optional(t.text()),\n }),\n email: {\n subject: \"Your subscription has been cancelled\",\n body: (v) =>\n `Your ${v.planName} subscription has been cancelled.${v.accessUntil ? ` You'll have access until ${v.accessUntil}.` : \"\"}`,\n },\n });\n}\n","import { $hook, $inject, Alepha } from \"alepha\";\nimport { PaymentService } from \"alepha/api/payments\";\nimport { DateTimeProvider } from \"alepha/datetime\";\nimport { $logger } from \"alepha/logger\";\nimport { $repository } from \"alepha/orm\";\nimport type { SubscriptionEventEntity } from \"../entities/subscriptionEvents.ts\";\nimport { subscriptionEvents } from \"../entities/subscriptionEvents.ts\";\nimport {\n type SubscriptionEntity,\n subscriptions,\n} from \"../entities/subscriptions.ts\";\nimport { SubscriptionConfig } from \"./SubscriptionConfig.ts\";\n\n// -----------------------------------------------------------------------------------------------------------------\n\ninterface PaymentEvent {\n intentId: string;\n amount: number;\n currency: string;\n metadata?: unknown;\n}\n\n// -----------------------------------------------------------------------------------------------------------------\n\ninterface EventContext {\n previousStatus?: string;\n newStatus?: string;\n previousPlanId?: string;\n newPlanId?: string;\n paymentIntentId?: string;\n amount?: number;\n currency?: string;\n triggeredBy?: string;\n userId?: string;\n note?: string;\n}\n\n// -----------------------------------------------------------------------------------------------------------------\n\nexport class BillingService {\n protected readonly alepha = $inject(Alepha);\n protected readonly log = $logger();\n protected readonly dateTime = $inject(DateTimeProvider);\n protected readonly subscriptionRepo = $repository(subscriptions);\n protected readonly eventRepo = $repository(subscriptionEvents);\n protected readonly paymentService = $inject(PaymentService);\n protected readonly config = $inject(SubscriptionConfig);\n\n // ---------------------------------------------------------------------------------------------------------------\n // Payment hook listeners\n // ---------------------------------------------------------------------------------------------------------------\n\n /**\n * React to successful payment capture.\n * Routes to the appropriate handler based on subscription status.\n */\n protected readonly onPaymentCaptured = $hook({\n on: \"payments:captured\",\n handler: async (event) => {\n const sub = await this.findByPaymentIntent(event.intentId);\n if (!sub) return;\n\n if (sub.status === \"trialing\") {\n await this.activate(sub, event);\n } else if (sub.status === \"active\") {\n await this.renew(sub, event);\n } else if (sub.status === \"past_due\") {\n await this.recoverFromDunning(sub, event);\n } else if (sub.status === \"suspended\") {\n await this.reactivateFromPayment(sub, event);\n }\n },\n });\n\n /**\n * React to failed payment.\n * Starts or advances the dunning flow.\n */\n protected readonly onPaymentFailed = $hook({\n on: \"payments:failed\",\n handler: async (event) => {\n const sub = await this.findByPaymentIntent(event.intentId);\n if (!sub) return;\n\n await this.handlePaymentFailure(sub, event);\n },\n });\n\n // ---------------------------------------------------------------------------------------------------------------\n // Lookup\n // ---------------------------------------------------------------------------------------------------------------\n\n /**\n * Find a subscription by its last payment intent ID.\n * Returns null if no subscription matches.\n */\n protected async findByPaymentIntent(\n intentId: string,\n ): Promise<SubscriptionEntity | null> {\n const sub = await this.subscriptionRepo.findOne({\n where: { lastPaymentIntentId: { eq: intentId } },\n });\n return sub ?? null;\n }\n\n // ---------------------------------------------------------------------------------------------------------------\n // Lifecycle transitions\n // ---------------------------------------------------------------------------------------------------------------\n\n /**\n * Trial to active transition.\n * Sets the first paid billing period and records activation events.\n */\n protected async activate(\n sub: SubscriptionEntity,\n event: PaymentEvent,\n ): Promise<void> {\n const orgId = sub.organizationId as string;\n const now = this.dateTime.now();\n const nowISO = now.toISOString();\n const periodEnd = this.computeIntervalEnd(nowISO, sub.interval);\n\n await this.subscriptionRepo.updateById(sub.id, {\n status: \"active\",\n lastPaymentAt: nowISO,\n lastPaymentIntentId: event.intentId,\n currentPeriodStart: nowISO,\n currentPeriodEnd: periodEnd,\n nextBillingAt: periodEnd,\n });\n\n await this.recordEvent(sub.id, orgId, \"trial_ended\", {\n previousStatus: \"trialing\",\n newStatus: \"active\",\n paymentIntentId: event.intentId,\n amount: event.amount,\n currency: event.currency,\n });\n\n await this.recordEvent(sub.id, orgId, \"activated\", {\n previousStatus: \"trialing\",\n newStatus: \"active\",\n paymentIntentId: event.intentId,\n amount: event.amount,\n currency: event.currency,\n });\n\n this.log.info(\"Subscription activated from trial\", {\n id: sub.id,\n organizationId: orgId,\n planId: sub.planId,\n });\n\n await this.alepha.events.emit(\"subscription:activated\" as any, {\n subscriptionId: sub.id,\n organizationId: orgId,\n planId: sub.planId,\n });\n }\n\n // ---------------------------------------------------------------------------------------------------------------\n\n /**\n * Active to active cycle renewal.\n * Applies any pending plan change, then advances the billing period.\n */\n protected async renew(\n sub: SubscriptionEntity,\n event: PaymentEvent,\n ): Promise<void> {\n const orgId = sub.organizationId as string;\n let effectivePlanId = sub.planId;\n let effectiveInterval = sub.interval;\n\n if (sub.pendingPlanId) {\n effectivePlanId = sub.pendingPlanId;\n effectiveInterval = sub.pendingInterval ?? sub.interval;\n\n await this.subscriptionRepo.updateById(sub.id, {\n planId: effectivePlanId,\n interval: effectiveInterval,\n pendingPlanId: undefined,\n pendingInterval: undefined,\n });\n\n await this.recordEvent(sub.id, orgId, \"plan_changed\", {\n previousPlanId: sub.planId,\n newPlanId: effectivePlanId,\n note: `Plan changed on renewal from '${sub.planId}' to '${effectivePlanId}'`,\n });\n }\n\n const newPeriodStart = sub.currentPeriodEnd;\n const newPeriodEnd = this.computeIntervalEnd(\n newPeriodStart,\n effectiveInterval,\n );\n\n await this.subscriptionRepo.updateById(sub.id, {\n currentPeriodStart: newPeriodStart,\n currentPeriodEnd: newPeriodEnd,\n lastPaymentAt: this.dateTime.now().toISOString(),\n lastPaymentIntentId: event.intentId,\n nextBillingAt: newPeriodEnd,\n });\n\n await this.recordEvent(sub.id, orgId, \"renewed\", {\n paymentIntentId: event.intentId,\n amount: event.amount,\n currency: event.currency,\n });\n\n this.log.info(\"Subscription renewed\", {\n id: sub.id,\n organizationId: orgId,\n planId: effectivePlanId,\n periodEnd: newPeriodEnd,\n });\n\n await this.alepha.events.emit(\"subscription:renewed\" as any, {\n subscriptionId: sub.id,\n organizationId: orgId,\n planId: effectivePlanId,\n });\n }\n\n // ---------------------------------------------------------------------------------------------------------------\n\n /**\n * Recover from dunning: past_due to active.\n * Resets all dunning state and records reactivation.\n */\n protected async recoverFromDunning(\n sub: SubscriptionEntity,\n event: PaymentEvent,\n ): Promise<void> {\n const orgId = sub.organizationId as string;\n const nowISO = this.dateTime.now().toISOString();\n\n await this.subscriptionRepo.updateById(sub.id, {\n status: \"active\",\n lastPaymentAt: nowISO,\n lastPaymentIntentId: event.intentId,\n dunningStartedAt: undefined,\n dunningAttempt: 0,\n dunningNextRetryAt: undefined,\n });\n\n await this.recordEvent(sub.id, orgId, \"reactivated\", {\n previousStatus: \"past_due\",\n newStatus: \"active\",\n paymentIntentId: event.intentId,\n amount: event.amount,\n currency: event.currency,\n });\n\n this.log.info(\"Subscription recovered from dunning\", {\n id: sub.id,\n organizationId: orgId,\n });\n\n await this.alepha.events.emit(\"subscription:reactivated\" as any, {\n subscriptionId: sub.id,\n organizationId: orgId,\n planId: sub.planId,\n });\n }\n\n // ---------------------------------------------------------------------------------------------------------------\n\n /**\n * Reactivate from suspended state after a successful payment.\n * Resets dunning, sets a fresh billing period, and records reactivation.\n */\n protected async reactivateFromPayment(\n sub: SubscriptionEntity,\n event: PaymentEvent,\n ): Promise<void> {\n const orgId = sub.organizationId as string;\n const now = this.dateTime.now();\n const nowISO = now.toISOString();\n const periodEnd = this.computeIntervalEnd(nowISO, sub.interval);\n\n await this.subscriptionRepo.updateById(sub.id, {\n status: \"active\",\n currentPeriodStart: nowISO,\n currentPeriodEnd: periodEnd,\n nextBillingAt: periodEnd,\n lastPaymentAt: nowISO,\n lastPaymentIntentId: event.intentId,\n dunningStartedAt: undefined,\n dunningAttempt: 0,\n dunningNextRetryAt: undefined,\n });\n\n await this.recordEvent(sub.id, orgId, \"reactivated\", {\n previousStatus: \"suspended\",\n newStatus: \"active\",\n paymentIntentId: event.intentId,\n amount: event.amount,\n currency: event.currency,\n });\n\n this.log.info(\"Subscription reactivated from suspended\", {\n id: sub.id,\n organizationId: orgId,\n planId: sub.planId,\n });\n\n await this.alepha.events.emit(\"subscription:reactivated\" as any, {\n subscriptionId: sub.id,\n organizationId: orgId,\n planId: sub.planId,\n });\n }\n\n // ---------------------------------------------------------------------------------------------------------------\n\n /**\n * Handle a failed payment: start or advance dunning.\n * Updates dunning state and transitions to past_due if needed.\n */\n protected async handlePaymentFailure(\n sub: SubscriptionEntity,\n event: PaymentEvent,\n ): Promise<void> {\n const orgId = sub.organizationId as string;\n const now = this.dateTime.now();\n const nowISO = now.toISOString();\n const settings = await this.config.getSettings();\n const schedule = settings.dunningSchedule;\n\n let attempt: number;\n\n if (sub.dunningAttempt === 0) {\n attempt = 1;\n await this.subscriptionRepo.updateById(sub.id, {\n dunningStartedAt: nowISO,\n dunningAttempt: attempt,\n });\n } else {\n attempt = sub.dunningAttempt + 1;\n await this.subscriptionRepo.updateById(sub.id, {\n dunningAttempt: attempt,\n });\n }\n\n if (sub.status !== \"past_due\") {\n await this.subscriptionRepo.updateById(sub.id, {\n status: \"past_due\",\n });\n\n await this.recordEvent(sub.id, orgId, \"past_due\", {\n previousStatus: sub.status,\n newStatus: \"past_due\",\n paymentIntentId: event.intentId,\n });\n }\n\n const scheduleDays = schedule[attempt - 1];\n\n if (scheduleDays !== undefined) {\n const nextRetry = now.add(scheduleDays, \"days\").toISOString();\n\n await this.subscriptionRepo.updateById(sub.id, {\n dunningNextRetryAt: nextRetry,\n });\n } else {\n await this.subscriptionRepo.updateById(sub.id, {\n dunningNextRetryAt: undefined,\n });\n }\n\n await this.recordEvent(sub.id, orgId, \"payment_failed\", {\n paymentIntentId: event.intentId,\n amount: event.amount,\n currency: event.currency,\n note: `Dunning attempt ${attempt}/${schedule.length}`,\n });\n\n this.log.warn(\"Subscription payment failed\", {\n id: sub.id,\n organizationId: orgId,\n attempt,\n maxAttempts: schedule.length,\n });\n\n await this.alepha.events.emit(\"subscription:payment_failed\" as any, {\n subscriptionId: sub.id,\n organizationId: orgId,\n planId: sub.planId,\n attempt,\n });\n }\n\n // ---------------------------------------------------------------------------------------------------------------\n // Helpers\n // ---------------------------------------------------------------------------------------------------------------\n\n /**\n * Compute the end of a billing interval from a start date.\n */\n protected computeIntervalEnd(\n start: string,\n interval: \"monthly\" | \"yearly\",\n ): string {\n const startDate = this.dateTime.of(start);\n const unit = interval === \"monthly\" ? \"months\" : \"years\";\n return startDate.add(1, unit).toISOString();\n }\n\n /**\n * Record a subscription event in the event log.\n */\n protected async recordEvent(\n subscriptionId: string,\n organizationId: string,\n type: SubscriptionEventEntity[\"type\"],\n context?: EventContext,\n ): Promise<void> {\n await this.eventRepo.create({\n subscriptionId,\n organizationId,\n type,\n previousStatus: context?.previousStatus,\n newStatus: context?.newStatus,\n previousPlanId: context?.previousPlanId,\n newPlanId: context?.newPlanId,\n paymentIntentId: context?.paymentIntentId,\n amount: context?.amount,\n currency: context?.currency,\n triggeredBy: context?.triggeredBy,\n userId: context?.userId,\n note: context?.note,\n });\n }\n}\n","import { $inject } from \"alepha\";\nimport { CacheProvider } from \"alepha/cache\";\nimport { DateTimeProvider } from \"alepha/datetime\";\nimport { SubscriptionService } from \"./SubscriptionService.ts\";\n\n// -----------------------------------------------------------------------------------------------------------------\n\n/**\n * The result of a usage check or increment operation.\n */\nexport interface UsageResult {\n /**\n * Whether the operation is allowed given the current usage and limit.\n */\n allowed: boolean;\n\n /**\n * Current usage count for the period.\n */\n current: number;\n\n /**\n * The plan limit for the resource. -1 means unlimited.\n */\n limit: number;\n\n /**\n * Remaining capacity. -1 means unlimited.\n */\n remaining: number;\n}\n\n// -----------------------------------------------------------------------------------------------------------------\n\n/**\n * Tracks and enforces per-organization resource usage limits.\n *\n * Usage counters are keyed by `organizationId:resource:YYYY-MM` and stored in the cache.\n * Limits are resolved from the organization's current subscription plan.\n */\nexport class UsageService {\n protected readonly cache = $inject(CacheProvider);\n protected readonly dateTime = $inject(DateTimeProvider);\n protected readonly subscriptionService = $inject(SubscriptionService);\n\n /**\n * Increment a resource counter for the current period and return the usage result.\n *\n * @param organizationId The organization to track usage for.\n * @param resource The resource identifier (e.g., \"api_calls\", \"seats\").\n * @param amount Amount to increment by (default: 1).\n */\n public async increment(\n organizationId: string,\n resource: string,\n amount = 1,\n ): Promise<UsageResult> {\n const limit = await this.subscriptionService.limit(\n organizationId,\n resource,\n );\n const key = this.buildKey(organizationId, resource);\n const current = await this.cache.incr(\"subscriptions:usage\", key, amount);\n const allowed = limit === -1 || current <= limit;\n return {\n allowed,\n current,\n limit,\n remaining: limit === -1 ? -1 : Math.max(0, limit - current),\n };\n }\n\n /**\n * Get the current usage for a resource without incrementing.\n *\n * @param organizationId The organization to query usage for.\n * @param resource The resource identifier.\n */\n public async getUsage(\n organizationId: string,\n resource: string,\n ): Promise<UsageResult> {\n const limit = await this.subscriptionService.limit(\n organizationId,\n resource,\n );\n const key = this.buildKey(organizationId, resource);\n const current = await this.cache.incr(\"subscriptions:usage\", key, 0);\n return {\n allowed: limit === -1 || current <= limit,\n current,\n limit,\n remaining: limit === -1 ? -1 : Math.max(0, limit - current),\n };\n }\n\n /**\n * Reset all usage counters for an organization.\n *\n * Used at the start of a new billing period.\n *\n * @param organizationId The organization whose counters to reset.\n */\n public async resetForPeriod(organizationId: string): Promise<void> {\n const pattern = this.buildKey(organizationId, \"*\");\n await this.cache.invalidateKeys(\"subscriptions:usage\", [pattern]);\n }\n\n /**\n * Build the cache key for a usage counter.\n *\n * Format: `organizationId:resource:YYYY-MM`\n */\n protected buildKey(organizationId: string, resource: string): string {\n const period = this.dateTime.now().format(\"YYYY-MM\");\n return `${organizationId}:${resource}:${period}`;\n }\n}\n","import { $context, createMiddleware, type Middleware } from \"alepha\";\nimport { ForbiddenError } from \"alepha/server\";\nimport { UsageService } from \"../services/UsageService.ts\";\n\n/**\n * Middleware that enforces a per-organization usage limit for a resource.\n *\n * Resolves the organization from `args[0].user.organization`, increments the\n * usage counter for the given resource, and throws `ForbiddenError` if the\n * plan limit has been reached.\n * Throws `ForbiddenError` if no organization is present or the limit is exceeded.\n *\n * ```typescript\n * class ApiController {\n * search = $action({\n * use: [$requireLimit(\"api_calls\")],\n * handler: async ({ query }) => { ... },\n * });\n * }\n * ```\n *\n * @param resource The resource identifier to track (e.g., \"api_calls\", \"exports\").\n */\nexport const $requireLimit = (resource: string): Middleware => {\n const { alepha } = $context();\n const usageService = alepha.inject(UsageService);\n\n return createMiddleware({\n name: \"$requireLimit\",\n options: { resource } as Record<string, unknown>,\n handler: ({ next }) => {\n return async (...args: any[]) => {\n const user = args[0]?.user;\n if (!user?.organization) {\n throw new ForbiddenError(\"Organization required\");\n }\n const result = await usageService.increment(\n user.organization,\n resource,\n );\n if (!result.allowed) {\n throw new ForbiddenError(\n `Usage limit for '${resource}' has been reached (${result.current}/${result.limit})`,\n );\n }\n return next(...args);\n };\n },\n });\n};\n","import { $context, createMiddleware, type Middleware } from \"alepha\";\nimport { ForbiddenError } from \"alepha/server\";\nimport { SubscriptionService } from \"../services/SubscriptionService.ts\";\n\n/**\n * Middleware that gates access to a handler behind a subscription feature flag.\n *\n * Resolves the organization from `args[0].user.organization` and checks whether\n * the organization's current plan includes the given feature.\n * Throws `ForbiddenError` if no organization is present or the feature is not available.\n *\n * ```typescript\n * class ReportController {\n * generate = $action({\n * use: [$requirePlan(\"advanced_reports\")],\n * handler: async ({ user }) => { ... },\n * });\n * }\n * ```\n *\n * @param feature The feature identifier to check against the plan's feature list.\n */\nexport const $requirePlan = (feature: string): Middleware => {\n const { alepha } = $context();\n const subscriptionService = alepha.inject(SubscriptionService);\n\n return createMiddleware({\n name: \"$requirePlan\",\n options: { feature } as Record<string, unknown>,\n handler: ({ next }) => {\n return async (...args: any[]) => {\n const user = args[0]?.user;\n if (!user?.organization) {\n throw new ForbiddenError(\"Organization required\");\n }\n const allowed = await subscriptionService.can(\n user.organization,\n feature,\n );\n if (!allowed) {\n throw new ForbiddenError(\n `Feature '${feature}' not available on your plan`,\n );\n }\n return next(...args);\n };\n },\n });\n};\n","import { $module } from \"alepha\";\nimport { AdminSubscriptionController } from \"./controllers/AdminSubscriptionController.ts\";\nimport { SubscriptionController } from \"./controllers/SubscriptionController.ts\";\nimport { SubscriptionJobs } from \"./jobs/SubscriptionJobs.ts\";\nimport { SubscriptionNotifications } from \"./notifications/SubscriptionNotifications.ts\";\nimport { BillingService } from \"./services/BillingService.ts\";\nimport { SubscriptionConfig } from \"./services/SubscriptionConfig.ts\";\nimport { SubscriptionService } from \"./services/SubscriptionService.ts\";\nimport { UsageService } from \"./services/UsageService.ts\";\n\n// Controllers\nexport * from \"./controllers/AdminSubscriptionController.ts\";\nexport * from \"./controllers/SubscriptionController.ts\";\n// Entities\nexport * from \"./entities/subscriptionEvents.ts\";\nexport * from \"./entities/subscriptions.ts\";\n// Jobs\nexport * from \"./jobs/SubscriptionJobs.ts\";\n// Middleware\nexport * from \"./middleware/$requireLimit.ts\";\nexport * from \"./middleware/$requirePlan.ts\";\n// Notifications\nexport * from \"./notifications/SubscriptionNotifications.ts\";\n// Schemas\nexport * from \"./schemas/cancelSubscriptionSchema.ts\";\nexport * from \"./schemas/changePlanSchema.ts\";\nexport * from \"./schemas/createSubscriptionSchema.ts\";\nexport * from \"./schemas/entitlementsSchema.ts\";\nexport * from \"./schemas/mrrSchema.ts\";\nexport * from \"./schemas/planDefinitionSchema.ts\";\nexport * from \"./schemas/planResourceSchema.ts\";\nexport * from \"./schemas/subscriptionEventResourceSchema.ts\";\nexport * from \"./schemas/subscriptionQuerySchema.ts\";\nexport * from \"./schemas/subscriptionResourceSchema.ts\";\nexport * from \"./schemas/subscriptionSettingsSchema.ts\";\nexport * from \"./schemas/subscriptionStatsSchema.ts\";\n// Services\nexport * from \"./services/BillingService.ts\";\nexport * from \"./services/SubscriptionConfig.ts\";\nexport * from \"./services/SubscriptionService.ts\";\nexport * from \"./services/UsageService.ts\";\n\ndeclare module \"alepha\" {\n interface Hooks {\n \"subscription:created\": {\n subscriptionId: string;\n organizationId: string;\n planId: string;\n trial: boolean;\n };\n \"subscription:activated\": {\n subscriptionId: string;\n organizationId: string;\n planId: string;\n };\n \"subscription:renewed\": {\n subscriptionId: string;\n organizationId: string;\n planId: string;\n amount: number;\n currency: string;\n };\n \"subscription:cancelled\": {\n subscriptionId: string;\n organizationId: string;\n planId: string;\n reason?: string;\n immediate: boolean;\n };\n \"subscription:expired\": {\n subscriptionId: string;\n organizationId: string;\n planId: string;\n };\n \"subscription:resumed\": {\n subscriptionId: string;\n organizationId: string;\n planId: string;\n };\n \"subscription:plan_changed\": {\n subscriptionId: string;\n organizationId: string;\n oldPlanId: string;\n newPlanId: string;\n immediate: boolean;\n };\n \"subscription:payment_failed\": {\n subscriptionId: string;\n organizationId: string;\n planId: string;\n attempt: number;\n };\n \"subscription:suspended\": {\n subscriptionId: string;\n organizationId: string;\n planId: string;\n };\n \"subscription:reactivated\": {\n subscriptionId: string;\n organizationId: string;\n planId: string;\n };\n \"subscription:trial_ending\": {\n subscriptionId: string;\n organizationId: string;\n planId: string;\n endsAt: string;\n };\n }\n}\n\n/**\n * Subscription management module — plan-based access control, billing integration,\n * usage limits, and lifecycle events (trial, renewal, cancellation, suspension).\n *\n * Depends on `AlephaPayments` for payment processing — register it in your app\n * alongside this module. Use `SubscriptionConfig` to declare your plans and limits.\n *\n * @module alepha.api.subscriptions\n */\nexport const AlephaApiSubscriptions = $module({\n name: \"alepha.api.subscriptions\",\n services: [\n SubscriptionConfig,\n SubscriptionService,\n BillingService,\n UsageService,\n SubscriptionJobs,\n SubscriptionNotifications,\n SubscriptionController,\n AdminSubscriptionController,\n ],\n register: (alepha) => {\n alepha\n .with(SubscriptionConfig)\n .with(SubscriptionService)\n .with(BillingService)\n .with(UsageService)\n .with(SubscriptionJobs)\n .with(SubscriptionNotifications)\n .with(SubscriptionController)\n .with(AdminSubscriptionController);\n },\n});\n"],"mappings":"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"}
|
|
@@ -55,6 +55,8 @@ declare const realmAuthSettingsAtom: _$alepha.Atom<_$alepha.TObject<{
|
|
|
55
55
|
resetPasswordAllowed: _$alepha.TBoolean;
|
|
56
56
|
adminEmails: _$alepha.TArray<_$alepha.TString>;
|
|
57
57
|
adminUsernames: _$alepha.TArray<_$alepha.TString>;
|
|
58
|
+
defaultRoles: _$alepha.TArray<_$alepha.TString>;
|
|
59
|
+
verifyEmailUrl: _$alepha.TOptional<_$alepha.TString>;
|
|
58
60
|
passwordPolicy: _$alepha.TObject<{
|
|
59
61
|
minLength: _$alepha.TInteger;
|
|
60
62
|
requireUppercase: _$alepha.TBoolean;
|
|
@@ -1123,7 +1125,7 @@ declare abstract class ModelBuilder {
|
|
|
1123
1125
|
protected buildTableConfig<TConfig, TSelf>(entity: EntityPrimitive, builders: TableConfigBuilders<TConfig>, tableResolver?: (entityName: string) => any, customConfigHandler?: (config: any, self: TSelf) => TConfig[]): ((self: TSelf) => TConfig[]) | undefined;
|
|
1124
1126
|
}
|
|
1125
1127
|
//#endregion
|
|
1126
|
-
//#region ../../../../node_modules/@electric-sql/pglite/dist/pglite-
|
|
1128
|
+
//#region ../../../../node_modules/@electric-sql/pglite/dist/pglite-BZlQ7pL-.d.ts
|
|
1127
1129
|
type MessageName = 'parseComplete' | 'bindComplete' | 'closeComplete' | 'noData' | 'portalSuspended' | 'replicationStart' | 'emptyQuery' | 'copyDone' | 'copyData' | 'rowDescription' | 'parameterDescription' | 'parameterStatus' | 'backendKeyData' | 'notification' | 'readyForQuery' | 'commandComplete' | 'dataRow' | 'copyInResponse' | 'copyOutResponse' | 'authenticationOk' | 'authenticationMD5Password' | 'authenticationCleartextPassword' | 'authenticationSASL' | 'authenticationSASLContinue' | 'authenticationSASLFinal' | 'error' | 'notice';
|
|
1128
1130
|
type BackendMessage = {
|
|
1129
1131
|
name: MessageName;
|
|
@@ -1195,6 +1197,7 @@ interface PostgresMod extends Omit<EmscriptenModule, 'preInit' | 'preRun' | 'pos
|
|
|
1195
1197
|
thisProgram: string;
|
|
1196
1198
|
stdin: (() => number | null) | null;
|
|
1197
1199
|
FS: FS;
|
|
1200
|
+
wasmMemory: WebAssembly.Memory;
|
|
1198
1201
|
PROXYFS: Emscripten.FileSystemType;
|
|
1199
1202
|
WASM_PREFIX: string;
|
|
1200
1203
|
pg_extensions: Record<string, Promise<Blob | null>>;
|
|
@@ -6050,18 +6053,16 @@ declare class DrizzleKitProvider {
|
|
|
6050
6053
|
*/
|
|
6051
6054
|
protected pushPostgres(kit: typeof api_d_exports, models: Record<string, unknown>, provider: DatabaseProvider): Promise<void>;
|
|
6052
6055
|
/**
|
|
6053
|
-
*
|
|
6056
|
+
* Execute a list of SQL statements against the provider.
|
|
6054
6057
|
*/
|
|
6055
|
-
protected
|
|
6056
|
-
statementsToExecute: string[];
|
|
6057
|
-
warnings: string[];
|
|
6058
|
-
hasDataLoss: boolean;
|
|
6059
|
-
}, provider: DatabaseProvider): Promise<void>;
|
|
6058
|
+
protected executeStatements(statements: string[], provider: DatabaseProvider): Promise<void>;
|
|
6060
6059
|
/**
|
|
6061
|
-
* Execute
|
|
6062
|
-
*
|
|
6060
|
+
* Execute SQL statements, ignoring "already exists" errors.
|
|
6061
|
+
*
|
|
6062
|
+
* Used by the fallback migration path where push may have partially
|
|
6063
|
+
* applied changes before erroring, leaving some objects already created.
|
|
6063
6064
|
*/
|
|
6064
|
-
protected
|
|
6065
|
+
protected executeStatementsLenient(statements: string[], provider: DatabaseProvider): Promise<void>;
|
|
6065
6066
|
protected createSchemaIfNotExists(provider: DatabaseProvider, schemaName: string): Promise<void>;
|
|
6066
6067
|
/**
|
|
6067
6068
|
* Wrap a Drizzle PgDatabase instance for compatibility with Drizzle Kit.
|
|
@@ -6071,15 +6072,6 @@ declare class DrizzleKitProvider {
|
|
|
6071
6072
|
* extends Array directly — no .rows property.
|
|
6072
6073
|
*/
|
|
6073
6074
|
protected wrapDbForDrizzleKit(db: any): any;
|
|
6074
|
-
/**
|
|
6075
|
-
* Suppress Drizzle Kit's spinner output during a callback.
|
|
6076
|
-
*
|
|
6077
|
-
* Drizzle Kit uses hanji's renderWithTask with a setInterval-based spinner.
|
|
6078
|
-
* If the wrapped task throws, the interval is never cleared and leaks
|
|
6079
|
-
* spinner frames to stdout. We keep the filter active until the next
|
|
6080
|
-
* tick after the promise settles to catch any straggling writes.
|
|
6081
|
-
*/
|
|
6082
|
-
protected muteSpinner<T>(fn: () => Promise<T>): Promise<T>;
|
|
6083
6075
|
/**
|
|
6084
6076
|
* Try to load the official Drizzle Kit API.
|
|
6085
6077
|
*/
|
|
@@ -7329,6 +7321,10 @@ declare class UserNotifications {
|
|
|
7329
7321
|
resetUrl: _$alepha.TString;
|
|
7330
7322
|
expiresInMinutes: _$alepha.TNumber;
|
|
7331
7323
|
}>>;
|
|
7324
|
+
readonly accountLockout: _$alepha_api_notifications0.NotificationPrimitive<_$alepha.TObject<{
|
|
7325
|
+
email: _$alepha.TString;
|
|
7326
|
+
lockoutMinutes: _$alepha.TNumber;
|
|
7327
|
+
}>>;
|
|
7332
7328
|
readonly emailVerificationLink: _$alepha_api_notifications0.NotificationPrimitive<_$alepha.TObject<{
|
|
7333
7329
|
email: _$alepha.TString;
|
|
7334
7330
|
verifyUrl: _$alepha.TString;
|
|
@@ -7414,7 +7410,7 @@ declare class UserService {
|
|
|
7414
7410
|
* @param method - The verification method: "code" (default) or "link".
|
|
7415
7411
|
* @param verifyUrl - Base URL for verification link (required when method is "link").
|
|
7416
7412
|
*/
|
|
7417
|
-
requestEmailVerification(email: string, userRealmName?: string, method?: "code" | "link"
|
|
7413
|
+
requestEmailVerification(email: string, userRealmName?: string, method?: "code" | "link"): Promise<boolean>;
|
|
7418
7414
|
/**
|
|
7419
7415
|
* Verify a user's email using a valid verification token.
|
|
7420
7416
|
* Supports both code (6-digit) and link (UUID) verification tokens.
|
|
@@ -7644,6 +7640,8 @@ declare class RealmController {
|
|
|
7644
7640
|
resetPasswordAllowed: _$alepha.TBoolean;
|
|
7645
7641
|
adminEmails: _$alepha.TArray<_$alepha.TString>;
|
|
7646
7642
|
adminUsernames: _$alepha.TArray<_$alepha.TString>;
|
|
7643
|
+
defaultRoles: _$alepha.TArray<_$alepha.TString>;
|
|
7644
|
+
verifyEmailUrl: _$alepha.TOptional<_$alepha.TString>;
|
|
7647
7645
|
passwordPolicy: _$alepha.TObject<{
|
|
7648
7646
|
minLength: _$alepha.TInteger;
|
|
7649
7647
|
requireUppercase: _$alepha.TBoolean;
|
|
@@ -7768,6 +7766,10 @@ declare class CredentialService {
|
|
|
7768
7766
|
providerUserId: _$alepha.TOptional<_$alepha.TString>;
|
|
7769
7767
|
providerData: _$alepha.TOptional<_$alepha.TRecord<string, _$alepha.TAny>>;
|
|
7770
7768
|
}>>;
|
|
7769
|
+
/**
|
|
7770
|
+
* Validate a password against the realm's password policy.
|
|
7771
|
+
*/
|
|
7772
|
+
validatePasswordPolicy(password: string, policy: RealmAuthSettings["passwordPolicy"]): void;
|
|
7771
7773
|
/**
|
|
7772
7774
|
* Phase 1: Create a password reset intent.
|
|
7773
7775
|
*
|
|
@@ -7866,7 +7868,9 @@ declare class RegistrationService {
|
|
|
7866
7868
|
protected readonly cryptoProvider: CryptoProvider;
|
|
7867
7869
|
protected readonly verificationController: _$alepha_server_links0.HttpVirtualClient<VerificationController>;
|
|
7868
7870
|
protected readonly realmProvider: RealmProvider;
|
|
7871
|
+
protected readonly credentialService: CredentialService;
|
|
7869
7872
|
protected readonly intentCache: _$alepha_cache0.CacheMiddlewareFn<RegistrationIntent>;
|
|
7873
|
+
protected readonly rateLimitCache: _$alepha_cache0.CacheMiddlewareFn<number>;
|
|
7870
7874
|
protected userAudits(realmName?: string): UserAudits | undefined;
|
|
7871
7875
|
protected userNotifications(realmName?: string): UserNotifications;
|
|
7872
7876
|
/**
|
|
@@ -8054,7 +8058,6 @@ declare class UserController {
|
|
|
8054
8058
|
query: _$alepha.TObject<{
|
|
8055
8059
|
userRealmName: _$alepha.TOptional<_$alepha.TString>;
|
|
8056
8060
|
method: _$alepha.TOptional<_$alepha.TUnsafe<"link" | "code">>;
|
|
8057
|
-
verifyUrl: _$alepha.TOptional<_$alepha.TString>;
|
|
8058
8061
|
}>;
|
|
8059
8062
|
body: _$alepha.TObject<{
|
|
8060
8063
|
email: _$alepha.TString;
|
|
@@ -8170,6 +8173,8 @@ declare const realmConfigSchema: _$alepha.TObject<{
|
|
|
8170
8173
|
resetPasswordAllowed: _$alepha.TBoolean;
|
|
8171
8174
|
adminEmails: _$alepha.TArray<_$alepha.TString>;
|
|
8172
8175
|
adminUsernames: _$alepha.TArray<_$alepha.TString>;
|
|
8176
|
+
defaultRoles: _$alepha.TArray<_$alepha.TString>;
|
|
8177
|
+
verifyEmailUrl: _$alepha.TOptional<_$alepha.TString>;
|
|
8173
8178
|
passwordPolicy: _$alepha.TObject<{
|
|
8174
8179
|
minLength: _$alepha.TInteger;
|
|
8175
8180
|
requireUppercase: _$alepha.TBoolean;
|
|
@@ -8263,6 +8268,7 @@ declare class SessionService {
|
|
|
8263
8268
|
protected readonly fileController: _$alepha_server_links0.HttpVirtualClient<FileController>;
|
|
8264
8269
|
protected readonly cacheProvider: CacheProvider;
|
|
8265
8270
|
protected userAudits(realmName?: string): UserAudits | undefined;
|
|
8271
|
+
protected userNotifications(realmName?: string): UserNotifications | undefined;
|
|
8266
8272
|
users(userRealmName?: string): Repository$1<_$alepha.TObject<{
|
|
8267
8273
|
id: PgAttr<PgAttr<_$alepha.TString, typeof PG_PRIMARY_KEY>, typeof PG_DEFAULT>;
|
|
8268
8274
|
version: PgAttr<PgAttr<_$alepha.TInteger, typeof PG_VERSION>, typeof PG_DEFAULT>;
|