@fonderie/billing 1.0.0 → 1.1.1
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/brain/outcomes.md +91 -0
- package/brain/signatures.md +268 -0
- package/dist/index.cjs +41 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +48 -1
- package/dist/index.d.ts +48 -1
- package/dist/index.js +47 -5
- package/dist/index.js.map +1 -1
- package/dist/migrations/index.d.ts +3 -0
- package/package.json +11 -7
- package/dist/migrations/sql/001_billing.sql +0 -38
- package/dist/migrations/sql/002_billing_plan_fields.sql +0 -5
- package/dist/migrations/sql/003_drop_limits.sql +0 -1
- package/dist/migrations/sql/004_polymorphic_subscribers.sql +0 -49
- package/dist/migrations/sql/005_billing_notifications.sql +0 -13
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/routes.ts","../src/controllers/plan.controller.ts","../src/services/plans.ts","../src/models/plan.model.ts","../src/dtos/billing.ts","../src/controllers/subscription.controller.ts","../src/services/subscriptions.ts","../src/models/subscription.model.ts","../src/utils.ts","../src/controllers/checkout.controller.ts","../src/controllers/usage.controller.ts","../src/services/usage.ts","../src/models/usage.model.ts","../src/controllers/webhook.controller.ts","../src/middlewares/billing.ts","../src/config.ts","../src/services/policy.ts","../src/backends/memory.ts","../src/backends/db.ts","../src/backends/index.ts","../src/module.ts","../src/providers/stripe.ts","../src/middlewares/require-plan.ts","../src/helpers.ts"],"sourcesContent":["import type { IStoreAdapter } from '@fonderie/store';\nimport type { Middleware } from '@fonderie/core';\nimport { requireAuth } from '@fonderie/core/middlewares';\n\nimport type { IBillingConfig } from './config';\nimport { planController } from './controllers/plan.controller';\nimport { subscriptionController } from './controllers/subscription.controller';\nimport { checkoutController } from './controllers/checkout.controller';\nimport { usageController } from './controllers/usage.controller';\nimport { webhookController } from './controllers/webhook.controller';\n\ntype RouteDefinition = [string, string, ...Middleware[]];\n\nexport function buildBillingRoutes(\n\tstore: IStoreAdapter,\n\tconfig: IBillingConfig,\n): RouteDefinition[] {\n\tconst plan = planController(store);\n\tconst subscription = subscriptionController(store);\n\tconst checkout = checkoutController(store, config);\n\tconst usage = usageController(store);\n\tconst webhook = webhookController(store, config);\n\n\treturn [\n\t\t// Plans — public read-only\n\t\t['GET', '/plans', plan.list],\n\t\t['GET', '/plans/:planId', plan.get],\n\n\t\t// Plans — admin write (caller is responsible for authorization)\n\t\t['POST', '/plans', plan.create],\n\t\t['PUT', '/plans/:planId', plan.update],\n\t\t['DELETE', '/plans/:planId', plan.delete],\n\n\t\t// Billing — subscriber resolved from X-Workspace-ID header (workspace) or session (user)\n\t\t// Workspace membership is verified automatically by the withBilling global middleware\n\t\t['GET', '/billing/subscription', requireAuth, subscription.get],\n\t\t['POST', '/billing/checkout', requireAuth, checkout.createSession],\n\t\t['POST', '/billing/portal', requireAuth, checkout.createPortal],\n\t\t['POST', '/billing/usage', requireAuth, usage.record],\n\t\t['GET', '/billing/usage/:metric', requireAuth, usage.get],\n\n\t\t// Webhook — signature verified inside the handler\n\t\t['POST', '/billing/webhook', webhook.handle],\n\t];\n}\n","import { setApiResponse, HTTP, stringOrEmpty, numberOrZero } from '@fonderie/core';\nimport type { IFonderieContext } from '@fonderie/core';\nimport type { IStoreAdapter } from '@fonderie/store';\n\nimport { PlanModel } from '../models/plan.model';\nimport { toPlanDTO } from '../dtos/billing';\n\nexport function planController(store: IStoreAdapter) {\n\tconst plans = new PlanModel(store);\n\n\treturn {\n\t\tasync list(_ctx: IFonderieContext): Promise<Response> {\n\t\t\tconst list = await plans.list();\n\t\t\tconst dtos = list.map(toPlanDTO);\n\t\t\treturn setApiResponse(HTTP.OK, 'PLAN_LIST', `Retrieved ${list.length} workspace plans`, {\n\t\t\t\tplans: dtos,\n\t\t\t});\n\t\t},\n\n\t\tasync get(ctx: IFonderieContext): Promise<Response> {\n\t\t\tconst params = ctx.meta['params'] as Record<string, string> | undefined;\n\t\t\tconst id = params?.['planId'];\n\t\t\tif (!id) return setApiResponse(HTTP.BAD_REQUEST, 'INVALID_PARAMETER', 'Plan ID required');\n\n\t\t\tconst plan = await plans.findById(id);\n\t\t\tif (!plan) return setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Plan not found');\n\n\t\t\treturn setApiResponse(HTTP.OK, 'PLAN_FETCHED', 'Plan retrieved successfully.', {\n\t\t\t\tplan: toPlanDTO(plan),\n\t\t\t});\n\t\t},\n\n\t\tasync create(ctx: IFonderieContext): Promise<Response> {\n\t\t\tconst body = ctx.meta['body'] as Record<string, unknown> | undefined;\n\t\t\tconst name = stringOrEmpty(body?.['name']);\n\t\t\tif (!name) {\n\t\t\t\treturn setApiResponse(HTTP.UNPROCESSABLE, 'VALIDATION_ERROR', 'name is required');\n\t\t\t}\n\n\t\t\tconst plan = await plans.create({\n\t\t\t\tname,\n\t\t\t\tdescription: body?.['description'] != null ? String(body['description']) : null,\n\t\t\t\ttier: body?.['tier'] != null ? numberOrZero(body['tier']) : 0,\n\t\t\t\tseats: body?.['seats'] != null ? numberOrZero(body['seats']) : null,\n\t\t\t\ttrialDays: body?.['trialDays'] != null ? numberOrZero(body['trialDays']) : 0,\n\t\t\t\tmonthlyAmount: body?.['monthlyAmount'] != null ? numberOrZero(body['monthlyAmount']) : null,\n\t\t\t\tmonthlyPriceId: body?.['monthlyPriceId'] != null ? String(body['monthlyPriceId']) : null,\n\t\t\t\tyearlyAmount: body?.['yearlyAmount'] != null ? numberOrZero(body['yearlyAmount']) : null,\n\t\t\t\tyearlyPriceId: body?.['yearlyPriceId'] != null ? String(body['yearlyPriceId']) : null,\n\t\t\t\tfeatures: body?.['features'],\n\t\t\t\tmetadata: body?.['metadata'],\n\t\t\t});\n\n\t\t\treturn setApiResponse(HTTP.CREATED, 'PLAN_CREATED', 'Plan created successfully.', {\n\t\t\t\tplan: toPlanDTO(plan),\n\t\t\t});\n\t\t},\n\n\t\tasync update(ctx: IFonderieContext): Promise<Response> {\n\t\t\tconst params = ctx.meta['params'] as Record<string, string> | undefined;\n\t\t\tconst id = params?.['planId'];\n\t\t\tif (!id) {\n\t\t\t\treturn setApiResponse(HTTP.BAD_REQUEST, 'INVALID_PARAMETER', 'Plan ID required');\n\t\t\t}\n\n\t\t\tconst body = ctx.meta['body'] as Record<string, unknown> | undefined;\n\t\t\tif (!body || Object.keys(body).length === 0) {\n\t\t\t\treturn setApiResponse(HTTP.UNPROCESSABLE, 'VALIDATION_ERROR', 'Request body is empty');\n\t\t\t}\n\n\t\t\tconst patch: Record<string, unknown> = {};\n\t\t\tconst allowed = ['name', 'description', 'tier', 'seats', 'trialDays',\n\t\t\t\t'monthlyAmount', 'monthlyPriceId', 'yearlyAmount', 'yearlyPriceId',\n\t\t\t\t'features', 'metadata'];\n\n\t\t\tfor (const key of allowed) {\n\t\t\t\tif (key in body) patch[key] = body[key];\n\t\t\t}\n\n\t\t\tconst plan = await plans.update(id, patch);\n\t\t\tif (!plan) {\n\t\t\t\treturn setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Plan not found');\n\t\t\t}\n\n\t\t\treturn setApiResponse(HTTP.OK, 'PLAN_UPDATED', 'Plan updated successfully.', {\n\t\t\t\tplan: toPlanDTO(plan),\n\t\t\t});\n\t\t},\n\n\t\tasync delete(ctx: IFonderieContext): Promise<Response> {\n\t\t\tconst params = ctx.meta['params'] as Record<string, string> | undefined;\n\t\t\tconst id = params?.['planId'];\n\t\t\tif (!id) {\n\t\t\t\treturn setApiResponse(HTTP.BAD_REQUEST, 'INVALID_PARAMETER', 'Plan ID required');\n\t\t\t}\n\n\t\t\tconst deleted = await plans.delete(id);\n\t\t\tif (!deleted) {\n\t\t\t\treturn setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Plan not found');\n\t\t\t}\n\n\t\t\treturn setApiResponse(HTTP.OK, 'PLAN_DELETED', 'Plan deleted successfully.');\n\t\t},\n\t};\n}\n","import type { IStoreAdapter } from '@fonderie/store';\n\nimport type { IPlan } from '../types';\nimport type { IBillingConfig, IBillingPlan } from '../config';\n\nexport function getPlans(config: IBillingConfig): IBillingPlan[] {\n\treturn config.plans;\n}\n\nexport function getPlanByName(name: string, config: IBillingConfig): IBillingPlan | null {\n\treturn config.plans.find((p) => p.name.toLowerCase() === name.toLowerCase()) ?? null;\n}\n\nexport async function syncPlansToDB(config: IBillingConfig, store: IStoreAdapter): Promise<void> {\n\tconst plans = config.plans;\n\tif (plans.length === 0) return;\n\n\tconst values = plans.map((_, i) => {\n\t\tconst b = i * 9;\n\t\treturn `($${b + 1}, $${b + 2}, $${b + 3}, $${b + 4}, $${b + 5}, $${b + 6}, $${b + 7}, $${b + 8}, $${b + 9}::jsonb)`;\n\t});\n\n\tconst params = plans.flatMap((plan) => [\n\t\tplan.name,\n\t\tplan.trialDays ?? 0,\n\t\tplan.monthly?.amount ?? null,\n\t\tplan.monthly?.priceId ?? null,\n\t\tplan.yearly?.amount ?? null,\n\t\tplan.yearly?.priceId ?? null,\n\t\tplan.description ?? null,\n\t\tplan.tier ?? 0,\n\t\tJSON.stringify(plan.metadata ?? {}),\n\t]);\n\n\tawait store.query(\n\t\t`INSERT INTO fonderie_plans\n\t\t\t(name, trial_days,\n\t\t\t monthly_amount, monthly_price_id,\n\t\t\t yearly_amount, yearly_price_id,\n\t\t\t description, tier, metadata)\n\t\tVALUES ${values.join(', ')}\n\t\tON CONFLICT (name) DO UPDATE SET\n\t\t\ttrial_days = EXCLUDED.trial_days,\n\t\t\tmonthly_amount = EXCLUDED.monthly_amount,\n\t\t\tmonthly_price_id = EXCLUDED.monthly_price_id,\n\t\t\tyearly_amount = EXCLUDED.yearly_amount,\n\t\t\tyearly_price_id = EXCLUDED.yearly_price_id,\n\t\t\tdescription = EXCLUDED.description,\n\t\t\ttier = EXCLUDED.tier,\n\t\t\tmetadata = EXCLUDED.metadata`,\n\t\tparams,\n\t);\n}\n\nconst SELECT_PLAN = `\n\tSELECT\n\t\tid,\n\t\tname,\n\t\tseats,\n\t\ttrial_days AS \"trialDays\",\n\t\tmonthly_amount AS \"monthlyAmount\",\n\t\tmonthly_price_id AS \"monthlyPriceId\",\n\t\tyearly_amount AS \"yearlyAmount\",\n\t\tyearly_price_id AS \"yearlyPriceId\",\n\t\tdescription,\n\t\ttier,\n\t\tfeatures,\n\t\tmetadata\n\tFROM fonderie_plans`;\n\nexport async function getDBPlans(store: IStoreAdapter): Promise<IPlan[]> {\n\treturn store.query<IPlan>(\n\t\t`${SELECT_PLAN} WHERE active = true ORDER BY tier ASC, monthly_amount ASC NULLS LAST`,\n\t);\n}\n\nexport async function getPlanById(id: string, store: IStoreAdapter): Promise<IPlan | null> {\n\tconst [row] = await store.query<IPlan>(`${SELECT_PLAN} WHERE id = $1`, [id]);\n\treturn row ?? null;\n}\n\nexport async function createPlan(\n\tdata: {\n\t\tname: string;\n\t\tdescription?: string | null;\n\t\ttier?: number;\n\t\tseats?: number | null;\n\t\ttrialDays?: number;\n\t\tfeatures?: unknown;\n\t\tmetadata?: unknown;\n\t\tmonthlyAmount?: number | null;\n\t\tmonthlyPriceId?: string | null;\n\t\tyearlyAmount?: number | null;\n\t\tyearlyPriceId?: string | null;\n\t},\n\tstore: IStoreAdapter,\n): Promise<IPlan> {\n\tconst [row] = await store.query<IPlan>(\n\t\t`INSERT INTO fonderie_plans\n\t\t\t(name, seats, trial_days, monthly_amount, monthly_price_id,\n\t\t\t yearly_amount, yearly_price_id, description, tier, features, metadata)\n\t\tVALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10::jsonb, $11::jsonb)\n\t\tRETURNING\n\t\t\tid, name, seats,\n\t\t\ttrial_days AS \"trialDays\",\n\t\t\tmonthly_amount AS \"monthlyAmount\",\n\t\t\tmonthly_price_id AS \"monthlyPriceId\",\n\t\t\tyearly_amount AS \"yearlyAmount\",\n\t\t\tyearly_price_id AS \"yearlyPriceId\",\n\t\t\tdescription, tier, features, metadata`,\n\t\t[\n\t\t\tdata.name,\n\t\t\tdata.seats ?? null,\n\t\t\tdata.trialDays ?? 0,\n\t\t\tdata.monthlyAmount ?? null,\n\t\t\tdata.monthlyPriceId ?? null,\n\t\t\tdata.yearlyAmount ?? null,\n\t\t\tdata.yearlyPriceId ?? null,\n\t\t\tdata.description ?? null,\n\t\t\tdata.tier ?? 0,\n\t\t\tJSON.stringify(data.features ?? []),\n\t\t\tJSON.stringify(data.metadata ?? {}),\n\t\t],\n\t);\n\tif (!row) throw new Error('Failed to create plan');\n\treturn row;\n}\n\nexport async function updatePlan(\n\tid: string,\n\tdata: Partial<Omit<IPlan, 'id'>>,\n\tstore: IStoreAdapter,\n): Promise<IPlan | null> {\n\tconst fieldMap: Record<string, string> = {\n\t\tname: 'name',\n\t\tseats: 'seats',\n\t\ttrialDays: 'trial_days',\n\t\tmonthlyAmount: 'monthly_amount',\n\t\tmonthlyPriceId: 'monthly_price_id',\n\t\tyearlyAmount: 'yearly_amount',\n\t\tyearlyPriceId: 'yearly_price_id',\n\t\tdescription: 'description',\n\t\ttier: 'tier',\n\t};\n\n\tconst jsonbFields = new Set(['features', 'metadata']);\n\tconst setClauses: string[] = [];\n\tconst params: unknown[] = [id];\n\n\tfor (const [key, col] of Object.entries(fieldMap)) {\n\t\tif (key in data) {\n\t\t\tparams.push((data as Record<string, unknown>)[key]);\n\t\t\tsetClauses.push(`${col} = $${params.length}`);\n\t\t}\n\t}\n\n\tfor (const key of jsonbFields) {\n\t\tif (key in data) {\n\t\t\tparams.push(JSON.stringify((data as Record<string, unknown>)[key]));\n\t\t\tsetClauses.push(`${key} = $${params.length}::jsonb`);\n\t\t}\n\t}\n\n\tif (setClauses.length === 0) return getPlanById(id, store);\n\n\tconst [row] = await store.query<IPlan>(\n\t\t`UPDATE fonderie_plans SET ${setClauses.join(', ')}\n\t\tWHERE id = $1\n\t\tRETURNING\n\t\t\tid, name, seats,\n\t\t\ttrial_days AS \"trialDays\",\n\t\t\tmonthly_amount AS \"monthlyAmount\",\n\t\t\tmonthly_price_id AS \"monthlyPriceId\",\n\t\t\tyearly_amount AS \"yearlyAmount\",\n\t\t\tyearly_price_id AS \"yearlyPriceId\",\n\t\t\tdescription, tier, features, metadata`,\n\t\tparams,\n\t);\n\treturn row ?? null;\n}\n\nexport async function deletePlan(id: string, store: IStoreAdapter): Promise<boolean> {\n\tconst rows = await store.query<{ id: string }>(\n\t\t`DELETE FROM fonderie_plans WHERE id = $1 RETURNING id`,\n\t\t[id],\n\t);\n\treturn rows.length > 0;\n}\n","import type { IStoreAdapter } from '@fonderie/store';\n\nimport type { IPlan } from '../types';\nimport type { IBillingConfig } from '../config';\nimport {\n\tgetDBPlans,\n\tgetPlanById,\n\tcreatePlan,\n\tupdatePlan,\n\tdeletePlan,\n\tgetPlans,\n\tgetPlanByName,\n} from '../services/plans';\n\nexport class PlanModel {\n\tconstructor(private readonly store: IStoreAdapter) {}\n\n\tlistFromConfig(config: IBillingConfig) {\n\t\treturn getPlans(config);\n\t}\n\n\tfindByNameInConfig(name: string, config: IBillingConfig) {\n\t\treturn getPlanByName(name, config);\n\t}\n\n\tlist(): Promise<IPlan[]> {\n\t\treturn getDBPlans(this.store);\n\t}\n\n\tfindById(id: string): Promise<IPlan | null> {\n\t\treturn getPlanById(id, this.store);\n\t}\n\n\tcreate(data: Parameters<typeof createPlan>[0]): Promise<IPlan> {\n\t\treturn createPlan(data, this.store);\n\t}\n\n\tupdate(id: string, data: Parameters<typeof updatePlan>[1]): Promise<IPlan | null> {\n\t\treturn updatePlan(id, data, this.store);\n\t}\n\n\tdelete(id: string): Promise<boolean> {\n\t\treturn deletePlan(id, this.store);\n\t}\n}\n","import type { IPlan, IPlanFeature, ISubscription, IUsageRecord, SubscriberType } from '../types';\n\nexport interface IPlanDTO {\n\tid: string;\n\tplanId: string;\n\tname: string;\n\tdescription: string;\n\ttier: number;\n\tseats: number | null;\n\ttrialDays: number;\n\tpricing: {\n\t\tmonthly: number; // in cents, e.g. 1999 = $19.99\n\t\tyearly: number; // in cents\n\t\tcurrency: string; // ISO 4217, e.g. 'USD'\n\t};\n\tfeatures: IPlanFeature[];\n\tmetadata: Record<string, unknown>;\n}\n\nexport interface ISubscriptionDTO {\n\tid: string;\n\tsubscriberType: SubscriberType;\n\tsubscriberId: string;\n\tplan: string;\n\tinterval: string;\n\tstatus: string;\n\tcancelAtPeriodEnd: boolean;\n\tcurrentPeriodStart: string | null;\n\tcurrentPeriodEnd: string | null;\n\ttrialEndsAt: string | null;\n\tcreatedAt: string;\n}\n\nexport interface IUsageRecordDTO {\n\tid: string;\n\tsubscriberType: SubscriberType;\n\tsubscriberId: string;\n\tmetric: string;\n\tquantity: number;\n\trecordedAt: string;\n}\n\nexport function toPlanDTO(plan: IPlan): IPlanDTO {\n\treturn {\n\t\tid: plan.id,\n\t\tplanId: plan.name.toUpperCase(),\n\t\tname: plan.name,\n\t\tdescription: plan.description ?? '',\n\t\ttier: plan.tier,\n\t\tseats: plan.seats,\n\t\ttrialDays: plan.trialDays,\n\t\tpricing: {\n\t\t\tmonthly: plan.monthlyAmount ?? 0,\n\t\t\tyearly: plan.yearlyAmount ?? 0,\n\t\t\tcurrency: 'USD',\n\t\t},\n\t\tfeatures: Array.isArray(plan.features) ? plan.features : [],\n\t\tmetadata:\n\t\t\tplan.metadata && typeof plan.metadata === 'object'\n\t\t\t\t? (plan.metadata as Record<string, unknown>)\n\t\t\t\t: {},\n\t};\n}\n\nexport function toSubscriptionDTO(sub: ISubscription): ISubscriptionDTO {\n\treturn {\n\t\tid: sub.id,\n\t\tsubscriberType: sub.subscriberType,\n\t\tsubscriberId: sub.subscriberId,\n\t\tplan: sub.plan,\n\t\tinterval: sub.interval,\n\t\tstatus: sub.status,\n\t\tcancelAtPeriodEnd: sub.cancelAtPeriodEnd,\n\t\tcurrentPeriodStart: sub.currentPeriodStart,\n\t\tcurrentPeriodEnd: sub.currentPeriodEnd,\n\t\ttrialEndsAt: sub.trialEndsAt,\n\t\tcreatedAt: sub.createdAt,\n\t};\n}\n\nexport function toUsageRecordDTO(record: IUsageRecord): IUsageRecordDTO {\n\treturn {\n\t\tid: record.id,\n\t\tsubscriberType: record.subscriberType,\n\t\tsubscriberId: record.subscriberId,\n\t\tmetric: record.metric,\n\t\tquantity: record.quantity,\n\t\trecordedAt: record.recordedAt,\n\t};\n}\n","import { setApiResponse, HTTP } from '@fonderie/core';\nimport type { IFonderieContext } from '@fonderie/core';\nimport type { IStoreAdapter } from '@fonderie/store';\n\nimport { SubscriptionModel } from '../models/subscription.model';\nimport { toSubscriptionDTO } from '../dtos/billing';\nimport { resolveSubscriber } from '../utils';\n\nexport function subscriptionController(store: IStoreAdapter) {\n\tconst subscriptions = new SubscriptionModel(store);\n\n\treturn {\n\t\tasync get(ctx: IFonderieContext): Promise<Response> {\n\t\t\tconst subscriber = resolveSubscriber(ctx);\n\t\t\tif (!subscriber) {\n\t\t\t\treturn setApiResponse(\n\t\t\t\t\tHTTP.BAD_REQUEST,\n\t\t\t\t\t'SUBSCRIBER_REQUIRED',\n\t\t\t\t\t'Subscriber context required',\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst subscription = await subscriptions.get(subscriber.type, subscriber.id);\n\t\t\tif (!subscription)\n\t\t\t\treturn setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'No active subscription');\n\n\t\t\treturn setApiResponse(\n\t\t\t\tHTTP.OK,\n\t\t\t\t'SUBSCRIPTION_FETCHED',\n\t\t\t\t'Subscription retrieved successfully.',\n\t\t\t\t{\n\t\t\t\t\tsubscription: toSubscriptionDTO(subscription),\n\t\t\t\t},\n\t\t\t);\n\t\t},\n\t};\n}\n","import type { IStoreAdapter } from '@fonderie/store';\n\nimport type { ISubscription, SubscriberType } from '../types';\n\nconst SELECT_SUBSCRIPTION = `\n\tSELECT\n\t\tid,\n\t\tsubscriber_type AS \"subscriberType\",\n\t\tsubscriber_id AS \"subscriberId\",\n\t\tplan,\n\t\tinterval,\n\t\tstatus,\n\t\tprovider_customer_id AS \"providerCustomerId\",\n\t\tprovider_subscription_id AS \"providerSubscriptionId\",\n\t\tcurrent_period_start AS \"currentPeriodStart\",\n\t\tcurrent_period_end AS \"currentPeriodEnd\",\n\t\tcancel_at_period_end AS \"cancelAtPeriodEnd\",\n\t\ttrial_ends_at AS \"trialEndsAt\",\n\t\tcreated_at AS \"createdAt\"\n\tFROM fonderie_subscriptions`;\n\nexport async function getSubscription(\n\tsubscriberType: SubscriberType,\n\tsubscriberId: string,\n\tstore: IStoreAdapter,\n): Promise<ISubscription | null> {\n\tconst [row] = await store.query<ISubscription>(\n\t\t`${SELECT_SUBSCRIPTION} WHERE subscriber_type = $1 AND subscriber_id = $2`,\n\t\t[subscriberType, subscriberId],\n\t);\n\treturn row ?? null;\n}\n\nexport async function upsertSubscription(\n\tdata: {\n\t\tsubscriberType: SubscriberType;\n\t\tsubscriberId: string;\n\t\tplan: string;\n\t\tinterval?: 'month' | 'year';\n\t\tstatus: string;\n\t\tproviderCustomerId?: string;\n\t\tproviderSubscriptionId?: string;\n\t\tcurrentPeriodStart?: Date;\n\t\tcurrentPeriodEnd?: Date;\n\t\tcancelAtPeriodEnd?: boolean;\n\t\ttrialEndsAt?: Date | null;\n\t},\n\tstore: IStoreAdapter,\n): Promise<void> {\n\tawait store.query(\n\t\t`INSERT INTO fonderie_subscriptions\n\t\t\t(subscriber_type, subscriber_id, plan, interval, status,\n\t\t\t provider_customer_id, provider_subscription_id,\n\t\t\t current_period_start, current_period_end,\n\t\t\t cancel_at_period_end, trial_ends_at)\n\t\t VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)\n\t\t ON CONFLICT (subscriber_type, subscriber_id) DO UPDATE SET\n\t\t\t plan = $3,\n\t\t\t interval = $4,\n\t\t\t status = $5,\n\t\t\t provider_customer_id = COALESCE($6, fonderie_subscriptions.provider_customer_id),\n\t\t\t provider_subscription_id = COALESCE($7, fonderie_subscriptions.provider_subscription_id),\n\t\t\t current_period_start = $8,\n\t\t\t current_period_end = $9,\n\t\t\t cancel_at_period_end = $10,\n\t\t\t trial_ends_at = $11`,\n\t\t[\n\t\t\tdata.subscriberType,\n\t\t\tdata.subscriberId,\n\t\t\tdata.plan,\n\t\t\tdata.interval ?? 'month',\n\t\t\tdata.status,\n\t\t\tdata.providerCustomerId ?? null,\n\t\t\tdata.providerSubscriptionId ?? null,\n\t\t\tdata.currentPeriodStart ?? null,\n\t\t\tdata.currentPeriodEnd ?? null,\n\t\t\tdata.cancelAtPeriodEnd ?? false,\n\t\t\tdata.trialEndsAt ?? null,\n\t\t],\n\t);\n}\n","import type { IStoreAdapter } from '@fonderie/store';\n\nimport type { ISubscription, SubscriberType } from '../types';\nimport { getSubscription, upsertSubscription } from '../services/subscriptions';\n\nexport class SubscriptionModel {\n\tconstructor(private readonly store: IStoreAdapter) {}\n\n\tget(subscriberType: SubscriberType, subscriberId: string): Promise<ISubscription | null> {\n\t\treturn getSubscription(subscriberType, subscriberId, this.store);\n\t}\n\n\tupsert(data: Parameters<typeof upsertSubscription>[0]): Promise<void> {\n\t\treturn upsertSubscription(data, this.store);\n\t}\n}\n","import type { IFonderieContext } from '@fonderie/core';\n\nimport type { SubscriberType } from './types';\n\nexport interface ISubscriber {\n\ttype: SubscriberType;\n\tid: string;\n}\n\n// Converts window strings like '1d', '30d', '1h' to milliseconds.\nexport function parseWindowMs(window: string): number {\n\tconst n = parseInt(window, 10);\n\tconst unit = window.slice(String(n).length);\n\tswitch (unit) {\n\t\tcase 'h':\n\t\t\treturn n * 3_600_000;\n\t\tcase 'd':\n\t\t\treturn n * 86_400_000;\n\t\tcase 'm':\n\t\t\treturn n * 60_000;\n\t\tdefault:\n\t\t\tthrow new Error(`Unknown window unit: '${unit}' in '${window}'`);\n\t}\n}\n\n// Resolves billing subscriber from request context.\n// Precedence: X-Workspace-ID header → ctx.workspace (set by withWorkspace) → ctx.user\nexport function resolveSubscriber(ctx: IFonderieContext): ISubscriber | null {\n\tconst wsFromHeader = ctx.request.headers.get('x-workspace-id');\n\n\tif (wsFromHeader) {\n\t\treturn {\n\t\t\ttype: 'workspace',\n\t\t\tid: wsFromHeader,\n\t\t};\n\t}\n\n\tif (ctx.workspace?.id) {\n\t\treturn {\n\t\t\ttype: 'workspace',\n\t\t\tid: ctx.workspace.id,\n\t\t};\n\t}\n\n\tif (ctx.user?.id) {\n\t\treturn {\n\t\t\ttype: 'user',\n\t\t\tid: ctx.user.id,\n\t\t};\n\t}\n\n\treturn null;\n}\n","import { setApiResponse, HTTP } from '@fonderie/core';\nimport type { IFonderieContext } from '@fonderie/core';\nimport type { IStoreAdapter } from '@fonderie/store';\n\nimport type { IBillingConfig } from '../config';\nimport { PlanModel } from '../models/plan.model';\nimport { SubscriptionModel } from '../models/subscription.model';\nimport { resolveSubscriber } from '../utils';\n\nexport function checkoutController(store: IStoreAdapter, config: IBillingConfig) {\n\tconst plans = new PlanModel(store);\n\tconst subscriptions = new SubscriptionModel(store);\n\n\treturn {\n\t\tasync createSession(ctx: IFonderieContext): Promise<Response> {\n\t\t\tconst body = ctx.meta['body'] as Record<string, unknown> | undefined;\n\t\t\tconst planName = body?.['plan'];\n\t\t\tconst interval = (body?.['interval'] ?? 'month') as 'month' | 'year';\n\t\t\tconst subscriber = resolveSubscriber(ctx);\n\n\t\t\tif (typeof planName !== 'string') {\n\t\t\t\treturn setApiResponse(HTTP.UNPROCESSABLE, 'INVALID_PARAMETER', 'plan is required');\n\t\t\t}\n\t\t\tif (interval !== 'month' && interval !== 'year') {\n\t\t\t\treturn setApiResponse(\n\t\t\t\t\tHTTP.UNPROCESSABLE,\n\t\t\t\t\t'INVALID_PARAMETER',\n\t\t\t\t\t'interval must be month or year',\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (!subscriber) {\n\t\t\t\treturn setApiResponse(\n\t\t\t\t\tHTTP.BAD_REQUEST,\n\t\t\t\t\t'SUBSCRIBER_REQUIRED',\n\t\t\t\t\t'Subscriber context required',\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst plan = plans.findByNameInConfig(planName, config);\n\t\t\tif (!plan) {\n\t\t\t\treturn setApiResponse(HTTP.UNPROCESSABLE, 'INVALID_PARAMETER', `Unknown plan: ${planName}`);\n\t\t\t}\n\n\t\t\tconst pricing = interval === 'year' ? plan.yearly : plan.monthly;\n\t\t\tif (!pricing?.priceId) {\n\t\t\t\treturn setApiResponse(\n\t\t\t\t\tHTTP.UNPROCESSABLE,\n\t\t\t\t\t'INVALID_PARAMETER',\n\t\t\t\t\t`Plan ${planName} does not support ${interval} billing`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst { customerId } = await config.provider.createCustomer({\n\t\t\t\temail: ctx.user!.email ?? '',\n\t\t\t\tsubscriberType: subscriber.type,\n\t\t\t\tsubscriberId: subscriber.id,\n\t\t\t\tuserId: ctx.user!.id,\n\t\t\t});\n\n\t\t\tconst sessionOpts: Parameters<typeof config.provider.createCheckoutSession>[0] = {\n\t\t\t\tcustomerId,\n\t\t\t\tpriceId: pricing.priceId,\n\t\t\t\tsubscriberType: subscriber.type,\n\t\t\t\tsubscriberId: subscriber.id,\n\t\t\t\tsuccessUrl: config.successUrl,\n\t\t\t\tcancelUrl: config.cancelUrl,\n\t\t\t};\n\t\t\tif (plan.trialDays !== undefined) sessionOpts.trialDays = plan.trialDays;\n\n\t\t\tconst { url } = await config.provider.createCheckoutSession(sessionOpts);\n\n\t\t\tawait subscriptions.upsert({\n\t\t\t\tsubscriberType: subscriber.type,\n\t\t\t\tsubscriberId: subscriber.id,\n\t\t\t\tplan: planName,\n\t\t\t\tinterval,\n\t\t\t\tstatus: 'incomplete',\n\t\t\t\tproviderCustomerId: customerId,\n\t\t\t});\n\n\t\t\treturn setApiResponse(HTTP.OK, 'CHECKOUT_URL', 'Checkout session created.', { url });\n\t\t},\n\n\t\tasync createPortal(ctx: IFonderieContext): Promise<Response> {\n\t\t\tconst subscriber = resolveSubscriber(ctx);\n\t\t\tif (!subscriber) {\n\t\t\t\treturn setApiResponse(\n\t\t\t\t\tHTTP.BAD_REQUEST,\n\t\t\t\t\t'SUBSCRIBER_REQUIRED',\n\t\t\t\t\t'Subscriber context required',\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst subscription = await subscriptions.get(subscriber.type, subscriber.id);\n\t\t\tif (!subscription?.providerCustomerId) {\n\t\t\t\treturn setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'No active subscription');\n\t\t\t}\n\n\t\t\tconst { url } = await config.provider.createPortalSession({\n\t\t\t\tcustomerId: subscription.providerCustomerId,\n\t\t\t\treturnUrl: config.successUrl,\n\t\t\t});\n\n\t\t\treturn setApiResponse(HTTP.OK, 'PORTAL_URL', 'Portal session created.', { url });\n\t\t},\n\t};\n}\n","import { setApiResponse, HTTP } from '@fonderie/core';\nimport type { IFonderieContext } from '@fonderie/core';\nimport type { IStoreAdapter } from '@fonderie/store';\n\nimport { UsageModel } from '../models/usage.model';\nimport { resolveSubscriber } from '../utils';\n\nexport function usageController(store: IStoreAdapter) {\n\tconst usage = new UsageModel(store);\n\n\treturn {\n\t\tasync record(ctx: IFonderieContext): Promise<Response> {\n\t\t\tconst body = ctx.meta['body'] as Record<string, unknown> | undefined;\n\t\t\tconst metric = body?.['metric'];\n\t\t\tconst quantity = body?.['quantity'];\n\t\t\tconst subscriber = resolveSubscriber(ctx);\n\n\t\t\tif (!subscriber) {\n\t\t\t\treturn setApiResponse(\n\t\t\t\t\tHTTP.BAD_REQUEST,\n\t\t\t\t\t'SUBSCRIBER_REQUIRED',\n\t\t\t\t\t'Subscriber context required',\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (typeof metric !== 'string') {\n\t\t\t\treturn setApiResponse(HTTP.UNPROCESSABLE, 'INVALID_PARAMETER', 'metric is required');\n\t\t\t}\n\n\t\t\tawait usage.record({\n\t\t\t\tsubscriberType: subscriber.type,\n\t\t\t\tsubscriberId: subscriber.id,\n\t\t\t\tmetric,\n\t\t\t\tquantity: typeof quantity === 'number' ? quantity : 1,\n\t\t\t});\n\t\t\treturn setApiResponse(HTTP.OK, 'USAGE_RECORDED', 'Usage recorded successfully.');\n\t\t},\n\n\t\tasync get(ctx: IFonderieContext): Promise<Response> {\n\t\t\tconst params = ctx.meta['params'] as Record<string, string> | undefined;\n\t\t\tconst metric = params?.['metric'];\n\t\t\tconst subscriber = resolveSubscriber(ctx);\n\n\t\t\tif (!subscriber || !metric) {\n\t\t\t\treturn setApiResponse(\n\t\t\t\t\tHTTP.UNPROCESSABLE,\n\t\t\t\t\t'INVALID_PARAMETER',\n\t\t\t\t\t'subscriber and metric are required',\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst since = new Date();\n\t\t\tsince.setDate(1);\n\t\t\tsince.setHours(0, 0, 0, 0);\n\n\t\t\tconst total = await usage.get(subscriber.type, subscriber.id, metric, since);\n\t\t\treturn setApiResponse(HTTP.OK, 'USAGE_FETCHED', 'Usage retrieved successfully.', {\n\t\t\t\tmetric,\n\t\t\t\ttotal,\n\t\t\t\tsince,\n\t\t\t});\n\t\t},\n\t};\n}\n","import type { IStoreAdapter } from '@fonderie/store';\nimport type { SubscriberType } from '../types';\n\nexport async function recordUsage(\n\topts: { subscriberType: SubscriberType; subscriberId: string; metric: string; quantity: number },\n\tstore: IStoreAdapter,\n): Promise<void> {\n\tawait store.query(\n\t\t`INSERT INTO fonderie_usage_records (subscriber_type, subscriber_id, metric, quantity)\n\t\tVALUES ($1, $2, $3, $4)`,\n\t\t[opts.subscriberType, opts.subscriberId, opts.metric, opts.quantity],\n\t);\n}\n\nexport async function getUsage(\n\tsubscriberType: SubscriberType,\n\tsubscriberId: string,\n\tmetric: string,\n\tsince: Date,\n\tstore: IStoreAdapter,\n): Promise<number> {\n\tconst rows = await store.query<{ total: string }>(\n\t\t`SELECT COALESCE(SUM(quantity), 0) AS total\n\t\tFROM fonderie_usage_records\n\t\tWHERE subscriber_type = $1\n\t\t\tAND subscriber_id = $2\n\t\t\tAND metric = $3\n\t\t\tAND recorded_at >= $4`,\n\t\t[subscriberType, subscriberId, metric, since],\n\t);\n\treturn parseInt(rows[0]?.total ?? '0', 10);\n}\n","import type { IStoreAdapter } from '@fonderie/store';\nimport type { SubscriberType } from '../types';\n\nimport { recordUsage, getUsage } from '../services/usage';\n\nexport class UsageModel {\n\tconstructor(private readonly store: IStoreAdapter) {}\n\n\trecord(opts: Parameters<typeof recordUsage>[0]): Promise<void> {\n\t\treturn recordUsage(opts, this.store);\n\t}\n\n\tget(\n\t\tsubscriberType: SubscriberType,\n\t\tsubscriberId: string,\n\t\tmetric: string,\n\t\tsince: Date,\n\t): Promise<number> {\n\t\treturn getUsage(subscriberType, subscriberId, metric, since, this.store);\n\t}\n}\n","import { setApiResponse, HTTP } from '@fonderie/core';\nimport type { IFonderieContext } from '@fonderie/core';\nimport type { IStoreAdapter } from '@fonderie/store';\n\nimport type { IBillingConfig } from '../config';\nimport { SubscriptionModel } from '../models/subscription.model';\n\nexport function webhookController(store: IStoreAdapter, config: IBillingConfig) {\n\tconst subscriptions = new SubscriptionModel(store);\n\n\treturn {\n\t\tasync handle(ctx: IFonderieContext): Promise<Response> {\n\t\t\tif (!config.webhookSecret) {\n\t\t\t\treturn setApiResponse(HTTP.SERVER_ERROR, 'SERVER_ERROR', 'Webhook secret not configured');\n\t\t\t}\n\n\t\t\tconst signature =\n\t\t\t\tctx.request.headers.get('stripe-signature') ??\n\t\t\t\tctx.request.headers.get('paypal-auth-algo') ??\n\t\t\t\t'';\n\n\t\t\tif (!signature) {\n\t\t\t\treturn setApiResponse(HTTP.BAD_REQUEST, 'INVALID_REQUEST', 'Missing webhook signature');\n\t\t\t}\n\n\t\t\tconst payload = await ctx.request.text();\n\n\t\t\tlet event: Awaited<ReturnType<typeof config.provider.constructEvent>>;\n\t\t\ttry {\n\t\t\t\tevent = await config.provider.constructEvent({\n\t\t\t\t\tpayload,\n\t\t\t\t\tsignature,\n\t\t\t\t\tsecret: config.webhookSecret,\n\t\t\t\t});\n\t\t\t} catch {\n\t\t\t\treturn setApiResponse(HTTP.BAD_REQUEST, 'INVALID_REQUEST', 'Invalid webhook signature');\n\t\t\t}\n\n\t\t\tif (event.subscription) {\n\t\t\t\tawait subscriptions.upsert({\n\t\t\t\t\tsubscriberType: event.subscription.subscriberType,\n\t\t\t\t\tsubscriberId: event.subscription.subscriberId,\n\t\t\t\t\tplan: event.subscription.plan,\n\t\t\t\t\tstatus: event.subscription.status,\n\t\t\t\t\tproviderCustomerId: event.subscription.providerCustomerId,\n\t\t\t\t\tproviderSubscriptionId: event.subscription.providerSubscriptionId,\n\t\t\t\t\tcurrentPeriodStart: event.subscription.currentPeriodStart,\n\t\t\t\t\tcurrentPeriodEnd: event.subscription.currentPeriodEnd,\n\t\t\t\t\tcancelAtPeriodEnd: event.subscription.cancelAtPeriodEnd,\n\t\t\t\t\ttrialEndsAt: event.subscription.trialEndsAt,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\treturn Response.json({ received: true });\n\t\t},\n\t};\n}\n","import type { Middleware, ICourierMessage } from '@fonderie/core';\nimport { setApiResponse, HTTP } from '@fonderie/core';\nimport type { IStoreAdapter } from '@fonderie/store';\n\nimport type { IBillingConfig } from '../config';\nimport type { ICounterBackend } from '../backends/types';\nimport { MESSAGE_KEYS } from '../config';\nimport { getSubscription } from '../services/subscriptions';\nimport { buildBillingContext } from '../services/policy';\nimport { resolveSubscriber, parseWindowMs } from '../utils';\n\n// In-process de-dup: tracks which threshold notifications have fired this session.\n// Acceptable to lose on restart (may send one duplicate after a redeploy).\nconst notified = new Set<string>();\n\nexport function withBilling(\n\tstore: IStoreAdapter,\n\tconfig: IBillingConfig,\n\tbackend: ICounterBackend,\n): Middleware {\n\treturn async (ctx, next) => {\n\t\tconst subscriber = resolveSubscriber(ctx);\n\n\t\t// No subscriber (unauthenticated / public route) — skip entirely\n\t\tif (!subscriber) return next();\n\n\t\t// Resolve subscription → plan name (fall back to first plan = free)\n\t\tconst subscription = await getSubscription(subscriber.type, subscriber.id, store);\n\t\tconst planName = subscription?.plan ?? config.plans[0]?.name ?? 'free';\n\t\tconst active =\n\t\t\t!subscription || subscription.status === 'active' || subscription.status === 'trialing';\n\n\t\tconst plan = config.plans.find((p) => p.name === planName) ?? config.plans[0];\n\t\tif (!plan) return next();\n\n\t\t// Increment windowed (rate-limit) counters and read their current totals\n\t\tconst counters: Record<string, number> = {};\n\n\t\tfor (const [key, entry] of Object.entries(plan.policy ?? {})) {\n\t\t\tif ('enabled' in entry || !entry.window) continue;\n\n\t\t\tconst windowMs = parseWindowMs(entry.window);\n\t\t\tconst counterKey = `${subscriber.type}:${subscriber.id}:${key}`;\n\t\t\tcounters[key] = await backend.increment(counterKey, windowMs);\n\t\t}\n\n\t\t// Build and cache billing context on ctx\n\t\tconst billingCtx = buildBillingContext({ subscriber, plan, active, counters });\n\t\tctx.meta['billing'] = billingCtx;\n\n\t\t// Block requests that have hit a hard limit\n\t\tfor (const [key, status] of Object.entries(billingCtx.statuses)) {\n\t\t\tif (status.type === 'counter' && status.status === 'blocked') {\n\t\t\t\treturn setApiResponse(\n\t\t\t\t\tHTTP.TOO_MANY_REQUESTS,\n\t\t\t\t\t'RATE_LIMIT_EXCEEDED',\n\t\t\t\t\t`Limit exceeded for: ${key}`,\n\t\t\t\t\t{ key, limit: status.limit, used: status.used, resetsAt: status.resetsAt },\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\t// Fire threshold notifications (once per subscriber per key per session)\n\t\tif (config.notifications) {\n\t\t\tconst toNotify: ICourierMessage[] = [];\n\t\t\tconst recipient = {\n\t\t\t\temail: ctx.user?.email ?? null,\n\t\t\t\tphone: null,\n\t\t\t\tdeviceToken: null,\n\t\t\t};\n\n\t\t\tfor (const [key, status] of Object.entries(billingCtx.statuses)) {\n\t\t\t\tif (status.type !== 'counter' || status.limit === null) continue;\n\n\t\t\t\tconst base = `${subscriber.type}:${subscriber.id}:${key}`;\n\n\t\t\t\tif (config.notifications.softHit && status.status === 'over_limit') {\n\t\t\t\t\tconst nk = `${base}:reached`;\n\t\t\t\t\tif (!notified.has(nk)) {\n\t\t\t\t\t\tnotified.add(nk);\n\t\t\t\t\t\ttoNotify.push({\n\t\t\t\t\t\t\ttype: MESSAGE_KEYS.limitReached,\n\t\t\t\t\t\t\trecipient,\n\t\t\t\t\t\t\tdata: {\n\t\t\t\t\t\t\t\tkey,\n\t\t\t\t\t\t\t\tplan: plan.name,\n\t\t\t\t\t\t\t\tlimit: status.limit,\n\t\t\t\t\t\t\t\tused: status.used,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t} else if (config.notifications.warnAt && status.status === 'warning') {\n\t\t\t\t\tconst nk = `${base}:warning`;\n\t\t\t\t\tif (!notified.has(nk)) {\n\t\t\t\t\t\tnotified.add(nk);\n\t\t\t\t\t\ttoNotify.push({\n\t\t\t\t\t\t\ttype: MESSAGE_KEYS.limitWarning,\n\t\t\t\t\t\t\trecipient,\n\t\t\t\t\t\t\tdata: {\n\t\t\t\t\t\t\t\tkey,\n\t\t\t\t\t\t\t\tplan: plan.name,\n\t\t\t\t\t\t\t\tlimit: status.limit,\n\t\t\t\t\t\t\t\tused: status.used,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (toNotify.length > 0) {\n\t\t\t\tconst existing = ctx.meta['messages'] as ICourierMessage[] | undefined;\n\t\t\t\tctx.meta['messages'] = [...(existing ?? []), ...toNotify];\n\t\t\t}\n\t\t}\n\n\t\treturn next();\n\t};\n}\n","import type { IBillingProvider } from './providers/types';\nimport type { PolicyEntry } from './types';\nimport type { ICounterBackend } from './backends/types';\n\nexport interface IBillingPlanPrice {\n\tamount: number;\n\tpriceId?: string;\n}\n\nexport interface IBillingPlanDefaults {\n\twarnAt?: number; // default warnAt fraction (0–1) for counter policies\n\tbuffer?: number; // default buffer for counter policies\n}\n\nexport interface IBillingPlan {\n\tname: string;\n\tdescription?: string;\n\ttier?: number;\n\ttrialDays?: number;\n\tmonthly?: IBillingPlanPrice;\n\tyearly?: IBillingPlanPrice;\n\tdefaults?: IBillingPlanDefaults;\n\tpolicy?: Record<string, PolicyEntry>;\n\tmetadata?: Record<string, unknown>;\n}\n\nexport type RateLimitBackendConfig = 'memory' | 'db' | ICounterBackend;\n\nexport interface IBillingNotificationsConfig {\n\twarnAt?: boolean; // fire courier message when warnAt threshold crossed\n\tsoftHit?: boolean; // fire when soft limit crossed\n}\n\nexport interface IBillingConfig {\n\tprovider: IBillingProvider;\n\tplans: IBillingPlan[];\n\tsuccessUrl: string;\n\tcancelUrl: string;\n\twebhookSecret?: string;\n\trateLimit?: { backend?: RateLimitBackendConfig };\n\tnotifications?: IBillingNotificationsConfig;\n}\n\nexport const MESSAGE_KEYS = {\n\tlimitWarning: 'billing.limit-warning',\n\tlimitReached: 'billing.limit-reached',\n\tlimitBlocked: 'billing.limit-blocked',\n} as const;\n\nexport type BillingMessageKey = (typeof MESSAGE_KEYS)[keyof typeof MESSAGE_KEYS];\n","import type { IBillingPlan } from '../config';\nimport type { LimitStatus, IPolicyStatus, IBillingContext, SubscriberType } from '../types';\nimport { parseWindowMs } from '../utils';\n\nexport function buildBillingContext(opts: {\n\tsubscriber: { type: SubscriberType; id: string };\n\tplan: IBillingPlan;\n\tactive: boolean;\n\t// Pre-fetched windowed counter values keyed by policy key.\n\t// Non-windowed counter keys are absent (their used count is 0 — app manages those).\n\tcounters: Record<string, number>;\n}): IBillingContext {\n\tconst { subscriber, plan, active, counters } = opts;\n\tconst defaults = plan.defaults ?? {};\n\tconst statuses: Record<string, IPolicyStatus> = {};\n\n\tfor (const [key, entry] of Object.entries(plan.policy ?? {})) {\n\t\tif ('enabled' in entry) {\n\t\t\tstatuses[key] = { type: 'feature', enabled: entry.enabled };\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst { limit, buffer = defaults.buffer ?? 0, warnAt = defaults.warnAt ?? 0.8, window } = entry;\n\n\t\tconst used = counters[key] ?? 0;\n\t\tconst hardLimit = limit !== null ? limit + buffer : null;\n\n\t\tlet status: LimitStatus = 'ok';\n\t\tif (hardLimit !== null && used >= hardLimit) status = 'blocked';\n\t\telse if (limit !== null && used >= limit) status = 'over_limit';\n\t\telse if (limit !== null && used >= limit * warnAt) status = 'warning';\n\n\t\tlet resetsAt: string | null = null;\n\t\tif (window) {\n\t\t\tconst windowMs = parseWindowMs(window);\n\t\t\tconst windowStart = Math.floor(Date.now() / windowMs) * windowMs;\n\t\t\tresetsAt = new Date(windowStart + windowMs).toISOString();\n\t\t}\n\n\t\tstatuses[key] = { type: 'counter', limit, used, status, resetsAt };\n\t}\n\n\treturn { subscriber, plan: plan.name, active, statuses };\n}\n","import type { ICounterBackend } from './types';\n\ninterface Entry {\n\tcount: number;\n\twindowStart: number; // epoch ms — used for windowed expiry\n}\n\nexport class MemoryCounterBackend implements ICounterBackend {\n\tprivate readonly counters = new Map<string, Entry>();\n\n\tasync increment(key: string, windowMs: number | null, quantity = 1): Promise<number> {\n\t\tconst now = Date.now();\n\t\tconst existing = this.counters.get(key);\n\n\t\tif (!existing || (windowMs !== null && now - existing.windowStart >= windowMs)) {\n\t\t\tthis.counters.set(key, { count: quantity, windowStart: now });\n\t\t\treturn quantity;\n\t\t}\n\n\t\texisting.count += quantity;\n\t\treturn existing.count;\n\t}\n\n\tasync get(key: string, windowMs: number | null): Promise<number> {\n\t\tconst now = Date.now();\n\t\tconst existing = this.counters.get(key);\n\t\tif (!existing) return 0;\n\t\tif (windowMs !== null && now - existing.windowStart >= windowMs) return 0;\n\t\treturn existing.count;\n\t}\n}\n","import type { IStoreAdapter } from '@fonderie/store';\nimport type { ICounterBackend } from './types';\n\nexport class DBCounterBackend implements ICounterBackend {\n\tconstructor(private readonly store: IStoreAdapter) {}\n\n\tasync increment(key: string, windowMs: number | null, quantity = 1): Promise<number> {\n\t\tconst [subscriberType, subscriberId, ...rest] = key.split(':');\n\t\tconst metric = rest.join(':');\n\n\t\tawait this.store.query(\n\t\t\t`INSERT INTO fonderie_usage_records (subscriber_type, subscriber_id, metric, quantity)\n\t\t\t VALUES ($1, $2, $3, $4)`,\n\t\t\t[subscriberType, subscriberId, metric, quantity],\n\t\t);\n\n\t\treturn this.get(key, windowMs);\n\t}\n\n\tasync get(key: string, windowMs: number | null): Promise<number> {\n\t\tconst [subscriberType, subscriberId, ...rest] = key.split(':');\n\t\tconst metric = rest.join(':');\n\t\tconst since = windowMs !== null ? new Date(Date.now() - windowMs) : new Date(0);\n\n\t\tconst rows = await this.store.query<{ total: string }>(\n\t\t\t`SELECT COALESCE(SUM(quantity), 0) AS total\n\t\t\t FROM fonderie_usage_records\n\t\t\t WHERE subscriber_type = $1\n\t\t\t AND subscriber_id = $2\n\t\t\t AND metric = $3\n\t\t\t AND recorded_at >= $4`,\n\t\t\t[subscriberType, subscriberId, metric, since],\n\t\t);\n\n\t\treturn parseInt(rows[0]?.total ?? '0', 10);\n\t}\n}\n","import type { IStoreAdapter } from '@fonderie/store';\nimport type { RateLimitBackendConfig } from '../config';\nimport { MemoryCounterBackend } from './memory';\nimport { DBCounterBackend } from './db';\n\nexport function createBackend(config: RateLimitBackendConfig | undefined, store: IStoreAdapter) {\n\tif (!config || config === 'memory') return new MemoryCounterBackend();\n\tif (config === 'db') return new DBCounterBackend(store);\n\treturn config;\n}\n\nexport type { ICounterBackend } from './types';\nexport { MemoryCounterBackend } from './memory';\nexport { DBCounterBackend } from './db';\n","import type { IFonderieModule, IFonderieApp } from '@fonderie/core';\nimport type { IStoreAdapter } from '@fonderie/store';\n\nimport type { IBillingConfig } from './config';\nimport { buildBillingRoutes } from './routes';\nimport { syncPlansToDB } from './services/plans';\nimport { withBilling } from './middlewares/billing';\nimport { createBackend } from './backends';\n\nexport class BillingModule implements IFonderieModule {\n\treadonly name = '@fonderie/billing';\n\treadonly deps = ['@fonderie/auth'];\n\n\tconstructor(\n\t\tprivate store: IStoreAdapter,\n\t\tprivate config: IBillingConfig,\n\t) {}\n\n\tasync install(app: IFonderieApp): Promise<void> {\n\t\tawait syncPlansToDB(this.config, this.store);\n\n\t\tconst backend = createBackend(this.config.rateLimit?.backend, this.store);\n\n\t\t// Global middleware — resolves subscriber + plan, enforces rate limits,\n\t\t// caches IBillingContext on ctx.meta['billing'] for every request.\n\t\t// Runs after auth (ctx.user available), before route handlers.\n\t\tapp.use(withBilling(this.store, this.config, backend));\n\n\t\tconst routes = buildBillingRoutes(this.store, this.config);\n\t\tfor (const [method, path, ...handlers] of routes) {\n\t\t\tapp.addRoute(method, path, ...handlers);\n\t\t}\n\t}\n}\n","import type { IBillingProvider, IBillingEvent, INormalizedSubscription } from './types';\nimport type { SubscriberType } from '../types';\n\ninterface IStripeSubscriptionRaw {\n\tid: string;\n\tstatus: string;\n\tcustomer: string;\n\tmetadata?: Record<string, string>;\n\titems: { data: Array<{ price: { id: string; nickname: string | null } }> };\n\tcurrent_period_start: number;\n\tcurrent_period_end: number;\n\tcancel_at_period_end: boolean;\n\ttrial_end: number | null;\n}\n\ninterface IStripeEventRaw {\n\ttype: string;\n\tdata: { object: unknown };\n}\n\n// Lazy singleton — Stripe SDK is optional\nlet _client: unknown = null;\n\nasync function getClient(secretKey: string): Promise<unknown> {\n\tif (_client) return _client;\n\n\tconst pkg = 'stripe';\n\n\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n\tconst mod: any = await import(pkg).catch(() => {\n\t\tthrow new Error('[billing:stripe] stripe is required: npm install stripe');\n\t});\n\n\tconst Stripe = mod.default ?? mod;\n\t_client = new Stripe(secretKey, { apiVersion: '2024-11-20.acacia' });\n\treturn _client;\n}\n\nfunction normalizeSubscription(sub: IStripeSubscriptionRaw): INormalizedSubscription {\n\treturn {\n\t\tsubscriberType: (sub.metadata?.['subscriberType'] ?? 'workspace') as SubscriberType,\n\t\tsubscriberId: sub.metadata?.['subscriberId'] ?? '',\n\t\tplan: sub.items.data[0]?.price.nickname ?? 'unknown',\n\t\tstatus: sub.status,\n\t\tproviderCustomerId: sub.customer,\n\t\tproviderSubscriptionId: sub.id,\n\t\tcurrentPeriodStart: new Date(sub.current_period_start * 1000),\n\t\tcurrentPeriodEnd: new Date(sub.current_period_end * 1000),\n\t\tcancelAtPeriodEnd: sub.cancel_at_period_end,\n\t\ttrialEndsAt: sub.trial_end ? new Date(sub.trial_end * 1000) : null,\n\t};\n}\n\nexport class StripeProvider implements IBillingProvider {\n\treadonly name = 'stripe';\n\n\tconstructor(\n\t\tprivate secretKey: string,\n\t\tprivate webhookSecret?: string,\n\t) {}\n\n\tprivate async client(): Promise<any> {\n\t\treturn getClient(this.secretKey);\n\t}\n\n\tasync createCustomer(opts: {\n\t\temail: string;\n\t\tsubscriberType: SubscriberType;\n\t\tsubscriberId: string;\n\t\tuserId: string;\n\t}): Promise<{ customerId: string }> {\n\t\tconst stripe = await this.client();\n\t\tconst customer = await stripe.customers.create({\n\t\t\temail: opts.email,\n\t\t\tmetadata: {\n\t\t\t\tsubscriberType: opts.subscriberType,\n\t\t\t\tsubscriberId: opts.subscriberId,\n\t\t\t\tuserId: opts.userId,\n\t\t\t},\n\t\t});\n\t\treturn { customerId: customer.id };\n\t}\n\n\tasync createCheckoutSession(opts: {\n\t\tcustomerId: string;\n\t\tpriceId: string;\n\t\tsubscriberType: SubscriberType;\n\t\tsubscriberId: string;\n\t\ttrialDays?: number;\n\t\tsuccessUrl: string;\n\t\tcancelUrl: string;\n\t}): Promise<{ url: string }> {\n\t\tconst stripe = await this.client();\n\t\tconst session = await stripe.checkout.sessions.create({\n\t\t\tcustomer: opts.customerId,\n\t\t\tmode: 'subscription',\n\t\t\tline_items: [{ price: opts.priceId, quantity: 1 }],\n\t\t\tsuccess_url: opts.successUrl,\n\t\t\tcancel_url: opts.cancelUrl,\n\t\t\tsubscription_data: {\n\t\t\t\tmetadata: {\n\t\t\t\t\tsubscriberType: opts.subscriberType,\n\t\t\t\t\tsubscriberId: opts.subscriberId,\n\t\t\t\t},\n\t\t\t\t...(opts.trialDays && opts.trialDays > 0 ? { trial_period_days: opts.trialDays } : {}),\n\t\t\t},\n\t\t});\n\t\treturn { url: session.url ?? '' };\n\t}\n\n\tasync createPortalSession(opts: {\n\t\tcustomerId: string;\n\t\treturnUrl: string;\n\t}): Promise<{ url: string }> {\n\t\tconst stripe = await this.client();\n\t\tconst session = await stripe.billingPortal.sessions.create({\n\t\t\tcustomer: opts.customerId,\n\t\t\treturn_url: opts.returnUrl,\n\t\t});\n\t\treturn { url: session.url };\n\t}\n\n\tasync constructEvent(opts: {\n\t\tpayload: string;\n\t\tsignature: string;\n\t\tsecret: string;\n\t}): Promise<IBillingEvent> {\n\t\tconst stripe = await this.client();\n\n\t\tlet raw: IStripeEventRaw;\n\t\ttry {\n\t\t\traw = stripe.webhooks.constructEvent(opts.payload, opts.signature, opts.secret);\n\t\t} catch {\n\t\t\tthrow new Error('[billing:stripe] Invalid webhook signature');\n\t\t}\n\n\t\tconst isSubscriptionEvent = [\n\t\t\t'customer.subscription.created',\n\t\t\t'customer.subscription.updated',\n\t\t\t'customer.subscription.deleted',\n\t\t].includes(raw.type);\n\n\t\tif (!isSubscriptionEvent) {\n\t\t\treturn { type: raw.type, subscription: null };\n\t\t}\n\n\t\tconst sub = raw.data.object as IStripeSubscriptionRaw;\n\n\t\tif (raw.type === 'customer.subscription.deleted') {\n\t\t\treturn {\n\t\t\t\ttype: raw.type,\n\t\t\t\tsubscription: { ...normalizeSubscription(sub), plan: 'free', status: 'canceled' },\n\t\t\t};\n\t\t}\n\n\t\treturn { type: raw.type, subscription: normalizeSubscription(sub) };\n\t}\n}\n","import { setApiResponse, HTTP } from '@fonderie/core';\nimport type { Middleware } from '@fonderie/core';\nimport type { IFonderieContext } from '@fonderie/core';\nimport type { IStoreAdapter } from '@fonderie/store';\n\nimport { getSubscription } from '../services/subscriptions';\nimport { resolveSubscriber } from '../utils';\n\n// Gates a route behind a minimum plan.\n// Works for both user-level and workspace-level subscriptions.\n// Usage: requirePlan(['pro', 'enterprise'], store)\n\nfunction makeHandler(plans: string | string[], store: IStoreAdapter): Middleware {\n\tconst allowed = Array.isArray(plans) ? plans : [plans];\n\n\treturn async (ctx, next) => {\n\t\tif (!ctx.user) {\n\t\t\treturn setApiResponse(HTTP.UNAUTHORIZED, 'UNAUTHORIZED', 'Unauthorized');\n\t\t}\n\n\t\tconst subscriber = resolveSubscriber(ctx);\n\t\tif (!subscriber) {\n\t\t\treturn setApiResponse(HTTP.BAD_REQUEST, 'SUBSCRIBER_REQUIRED', 'Subscriber context required');\n\t\t}\n\n\t\tconst subscription = await getSubscription(subscriber.type, subscriber.id, store);\n\n\t\tif (!subscription || !allowed.includes(subscription.plan)) {\n\t\t\treturn setApiResponse(\n\t\t\t\tHTTP.PAYMENT_REQUIRED,\n\t\t\t\t'PLAN_UPGRADE_REQUIRED',\n\t\t\t\t'Plan upgrade required',\n\t\t\t\t{ required: allowed, current: subscription?.plan ?? 'none' },\n\t\t\t);\n\t\t}\n\n\t\tif (subscription.status !== 'active' && subscription.status !== 'trialing') {\n\t\t\treturn setApiResponse(\n\t\t\t\tHTTP.PAYMENT_REQUIRED,\n\t\t\t\t'SUBSCRIPTION_INACTIVE',\n\t\t\t\t'Subscription is not active',\n\t\t\t\t{ status: subscription.status },\n\t\t\t);\n\t\t}\n\n\t\treturn next();\n\t};\n}\n\nexport function requirePlan(plans: string | string[], store: IStoreAdapter): Middleware;\nexport function requirePlan(\n\tplans: string | string[],\n\tstore: IStoreAdapter,\n\tctx: IFonderieContext,\n\tnext: () => Promise<Response>,\n): Promise<Response>;\nexport function requirePlan(\n\tplans: string | string[],\n\tstore: IStoreAdapter,\n\tctx?: IFonderieContext,\n\tnext?: () => Promise<Response>,\n): Middleware | Promise<Response> {\n\tconst handler = makeHandler(plans, store);\n\tif (ctx !== undefined && next !== undefined) return handler(ctx, next);\n\treturn handler;\n}\n","import { setApiResponse, HTTP } from '@fonderie/core';\nimport type { IFonderieContext } from '@fonderie/core';\nimport type { Middleware } from '@fonderie/core';\nimport type { IBillingContext, IPolicyStatus } from './types';\n\nfunction getBillingContext(ctx: IFonderieContext): IBillingContext | null {\n\treturn (ctx.meta['billing'] as IBillingContext | undefined) ?? null;\n}\n\n// Returns true if the feature flag is enabled on the subscriber's plan.\n// Returns true when no billing context is present (fail-open when billing not configured).\nexport function hasFeature(ctx: IFonderieContext, key: string): boolean {\n\tconst billing = getBillingContext(ctx);\n\tif (!billing) return true;\n\n\tconst status = billing.statuses[key];\n\tif (!status) return true; // key not declared in policy → allow\n\tif (status.type === 'feature') return status.enabled;\n\treturn true; // counter entry = feature present\n}\n\n// Returns the advertised limit for a counter policy key, or null if unlimited / not configured.\nexport function getPlanLimit(ctx: IFonderieContext, key: string): number | null {\n\tconst billing = getBillingContext(ctx);\n\tif (!billing) return null;\n\n\tconst status = billing.statuses[key];\n\tif (!status || status.type === 'feature') return null;\n\treturn status.limit;\n}\n\n// Returns the full policy status for a key, or null if not configured.\nexport function getLimitStatus(ctx: IFonderieContext, key: string): IPolicyStatus | null {\n\tconst billing = getBillingContext(ctx);\n\tif (!billing) return null;\n\treturn billing.statuses[key] ?? null;\n}\n\n// Middleware — gates a route behind a feature flag.\n// Reads from cached ctx.meta['billing']; no store arg, no async DB call.\n// Fails open if billing context is absent (billing module not registered).\nexport function requireFeature(key: string): Middleware {\n\treturn (ctx, next) => {\n\t\tif (!hasFeature(ctx, key)) {\n\t\t\treturn Promise.resolve(\n\t\t\t\tsetApiResponse(\n\t\t\t\t\tHTTP.PAYMENT_REQUIRED,\n\t\t\t\t\t'FEATURE_UNAVAILABLE',\n\t\t\t\t\t`Feature '${key}' is not available on your current plan`,\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\t\treturn next();\n\t};\n}\n"],"mappings":"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","names":["setApiResponse","HTTP","setApiResponse","HTTP","setApiResponse","HTTP","setApiResponse","HTTP","setApiResponse","HTTP","setApiResponse","HTTP","setApiResponse","HTTP","setApiResponse","HTTP","setApiResponse","HTTP","setApiResponse","HTTP","setApiResponse","HTTP","setApiResponse","HTTP","setApiResponse","HTTP"]}
|
|
1
|
+
{"version":3,"sources":["../src/routes.ts","../src/schemas.ts","../src/controllers/plan.controller.ts","../src/services/plans.ts","../src/models/plan.model.ts","../src/dtos/billing.ts","../src/controllers/subscription.controller.ts","../src/services/subscriptions.ts","../src/models/subscription.model.ts","../src/utils.ts","../src/controllers/checkout.controller.ts","../src/controllers/usage.controller.ts","../src/services/usage.ts","../src/models/usage.model.ts","../src/controllers/webhook.controller.ts","../src/middlewares/billing.ts","../src/config.ts","../src/services/policy.ts","../src/backends/memory.ts","../src/backends/db.ts","../src/backends/index.ts","../src/module.ts","../src/providers/stripe.ts","../src/middlewares/require-plan.ts","../src/helpers.ts"],"sourcesContent":["import type { IStoreAdapter } from '@fonderie/store';\nimport type { Middleware } from '@fonderie/core';\nimport { requireAuth, validate } from '@fonderie/core/middlewares';\n\nimport { checkoutSchema, createPlanSchema, recordUsageSchema, updatePlanSchema } from './schemas';\n\nimport type { IBillingConfig } from './config';\nimport { planController } from './controllers/plan.controller';\nimport { subscriptionController } from './controllers/subscription.controller';\nimport { checkoutController } from './controllers/checkout.controller';\nimport { usageController } from './controllers/usage.controller';\nimport { webhookController } from './controllers/webhook.controller';\n\ntype RouteDefinition = [string, string, ...Middleware[]];\n\nexport function buildBillingRoutes(\n\tstore: IStoreAdapter,\n\tconfig: IBillingConfig,\n): RouteDefinition[] {\n\tconst plan = planController(store);\n\tconst subscription = subscriptionController(store);\n\tconst checkout = checkoutController(store, config);\n\tconst usage = usageController(store);\n\tconst webhook = webhookController(store, config);\n\n\treturn [\n\t\t// Plans — public read-only\n\t\t['GET', '/plans', plan.list],\n\t\t['GET', '/plans/:planId', plan.get],\n\n\t\t// Plans — admin write (caller is responsible for authorization)\n\t\t['POST', '/plans', validate(createPlanSchema), plan.create],\n\t\t['PUT', '/plans/:planId', validate(updatePlanSchema), plan.update],\n\t\t['DELETE', '/plans/:planId', plan.delete],\n\n\t\t// Billing — subscriber resolved from X-Workspace-ID header (workspace) or session (user)\n\t\t// Workspace membership is verified automatically by the withBilling global middleware\n\t\t['GET', '/billing/subscription', requireAuth, subscription.get],\n\t\t['POST', '/billing/checkout', requireAuth, validate(checkoutSchema), checkout.createSession],\n\t\t['POST', '/billing/portal', requireAuth, checkout.createPortal],\n\t\t['POST', '/billing/usage', requireAuth, validate(recordUsageSchema), usage.record],\n\t\t['GET', '/billing/usage/:metric', requireAuth, usage.get],\n\n\t\t// Webhook — signature verified inside the handler\n\t\t['POST', '/billing/webhook', webhook.handle],\n\t];\n}\n","import { z } from 'zod';\n\n// Request schemas — the validation contract for billing's body-taking routes\n// (webhook excluded: provider-shaped, signature-verified in the handler).\n// Wired via @fonderie/core's validate(); same pattern as @fonderie/auth.\n\nconst planFields = {\n\tdescription: z.string().max(2000).nullable().optional(),\n\ttier: z.number().int().min(0).optional(),\n\tseats: z.number().int().min(0).nullable().optional(),\n\ttrialDays: z.number().int().min(0).optional(),\n\tmonthlyAmount: z.number().min(0).nullable().optional(),\n\tmonthlyPriceId: z.string().max(200).nullable().optional(),\n\tyearlyAmount: z.number().min(0).nullable().optional(),\n\tyearlyPriceId: z.string().max(200).nullable().optional(),\n\tfeatures: z.unknown().optional(),\n\tmetadata: z.unknown().optional(),\n};\n\nexport const createPlanSchema = z.object({\n\tname: z.string().trim().min(1, 'name is required').max(200),\n\t...planFields,\n});\n\nexport const updatePlanSchema = z\n\t.object({ name: z.string().trim().min(1).max(200).optional(), ...planFields })\n\t.refine((o) => Object.values(o).some((v) => v !== undefined), 'Provide at least one field');\n\nexport const checkoutSchema = z.object({\n\tplan: z.string().min(1, 'plan is required'),\n\tinterval: z.enum(['month', 'year']).optional(),\n});\n\nexport const recordUsageSchema = z.object({\n\tmetric: z.string().min(1, 'metric is required').max(100),\n\tquantity: z.number().min(0).optional(),\n});\n","import { setApiResponse, HTTP, stringOrEmpty, numberOrZero } from '@fonderie/core';\nimport type { IFonderieContext } from '@fonderie/core';\nimport type { IStoreAdapter } from '@fonderie/store';\n\nimport { PlanModel } from '../models/plan.model';\nimport { toPlanDTO } from '../dtos/billing';\n\nexport function planController(store: IStoreAdapter) {\n\tconst plans = new PlanModel(store);\n\n\treturn {\n\t\tasync list(_ctx: IFonderieContext): Promise<Response> {\n\t\t\tconst list = await plans.list();\n\t\t\tconst dtos = list.map(toPlanDTO);\n\t\t\treturn setApiResponse(HTTP.OK, 'PLAN_LIST', `Retrieved ${list.length} workspace plans`, {\n\t\t\t\tplans: dtos,\n\t\t\t});\n\t\t},\n\n\t\tasync get(ctx: IFonderieContext): Promise<Response> {\n\t\t\tconst params = ctx.meta['params'] as Record<string, string> | undefined;\n\t\t\tconst id = params?.['planId'];\n\t\t\tif (!id) return setApiResponse(HTTP.BAD_REQUEST, 'INVALID_PARAMETER', 'Plan ID required');\n\n\t\t\tconst plan = await plans.findById(id);\n\t\t\tif (!plan) return setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Plan not found');\n\n\t\t\treturn setApiResponse(HTTP.OK, 'PLAN_FETCHED', 'Plan retrieved successfully.', {\n\t\t\t\tplan: toPlanDTO(plan),\n\t\t\t});\n\t\t},\n\n\t\tasync create(ctx: IFonderieContext): Promise<Response> {\n\t\t\tconst body = ctx.meta['body'] as Record<string, unknown> | undefined;\n\t\t\tconst name = stringOrEmpty(body?.['name']);\n\t\t\tif (!name) {\n\t\t\t\treturn setApiResponse(HTTP.UNPROCESSABLE, 'VALIDATION_ERROR', 'name is required');\n\t\t\t}\n\n\t\t\tconst plan = await plans.create({\n\t\t\t\tname,\n\t\t\t\tdescription: body?.['description'] != null ? String(body['description']) : null,\n\t\t\t\ttier: body?.['tier'] != null ? numberOrZero(body['tier']) : 0,\n\t\t\t\tseats: body?.['seats'] != null ? numberOrZero(body['seats']) : null,\n\t\t\t\ttrialDays: body?.['trialDays'] != null ? numberOrZero(body['trialDays']) : 0,\n\t\t\t\tmonthlyAmount: body?.['monthlyAmount'] != null ? numberOrZero(body['monthlyAmount']) : null,\n\t\t\t\tmonthlyPriceId: body?.['monthlyPriceId'] != null ? String(body['monthlyPriceId']) : null,\n\t\t\t\tyearlyAmount: body?.['yearlyAmount'] != null ? numberOrZero(body['yearlyAmount']) : null,\n\t\t\t\tyearlyPriceId: body?.['yearlyPriceId'] != null ? String(body['yearlyPriceId']) : null,\n\t\t\t\tfeatures: body?.['features'],\n\t\t\t\tmetadata: body?.['metadata'],\n\t\t\t});\n\n\t\t\treturn setApiResponse(HTTP.CREATED, 'PLAN_CREATED', 'Plan created successfully.', {\n\t\t\t\tplan: toPlanDTO(plan),\n\t\t\t});\n\t\t},\n\n\t\tasync update(ctx: IFonderieContext): Promise<Response> {\n\t\t\tconst params = ctx.meta['params'] as Record<string, string> | undefined;\n\t\t\tconst id = params?.['planId'];\n\t\t\tif (!id) {\n\t\t\t\treturn setApiResponse(HTTP.BAD_REQUEST, 'INVALID_PARAMETER', 'Plan ID required');\n\t\t\t}\n\n\t\t\tconst body = ctx.meta['body'] as Record<string, unknown> | undefined;\n\t\t\tif (!body || Object.keys(body).length === 0) {\n\t\t\t\treturn setApiResponse(HTTP.UNPROCESSABLE, 'VALIDATION_ERROR', 'Request body is empty');\n\t\t\t}\n\n\t\t\tconst patch: Record<string, unknown> = {};\n\t\t\tconst allowed = ['name', 'description', 'tier', 'seats', 'trialDays',\n\t\t\t\t'monthlyAmount', 'monthlyPriceId', 'yearlyAmount', 'yearlyPriceId',\n\t\t\t\t'features', 'metadata'];\n\n\t\t\tfor (const key of allowed) {\n\t\t\t\tif (key in body) patch[key] = body[key];\n\t\t\t}\n\n\t\t\tconst plan = await plans.update(id, patch);\n\t\t\tif (!plan) {\n\t\t\t\treturn setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Plan not found');\n\t\t\t}\n\n\t\t\treturn setApiResponse(HTTP.OK, 'PLAN_UPDATED', 'Plan updated successfully.', {\n\t\t\t\tplan: toPlanDTO(plan),\n\t\t\t});\n\t\t},\n\n\t\tasync delete(ctx: IFonderieContext): Promise<Response> {\n\t\t\tconst params = ctx.meta['params'] as Record<string, string> | undefined;\n\t\t\tconst id = params?.['planId'];\n\t\t\tif (!id) {\n\t\t\t\treturn setApiResponse(HTTP.BAD_REQUEST, 'INVALID_PARAMETER', 'Plan ID required');\n\t\t\t}\n\n\t\t\tconst deleted = await plans.delete(id);\n\t\t\tif (!deleted) {\n\t\t\t\treturn setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Plan not found');\n\t\t\t}\n\n\t\t\treturn setApiResponse(HTTP.OK, 'PLAN_DELETED', 'Plan deleted successfully.');\n\t\t},\n\t};\n}\n","import type { IStoreAdapter } from '@fonderie/store';\n\nimport type { IPlan } from '../types';\nimport type { IBillingConfig, IBillingPlan } from '../config';\n\nexport function getPlans(config: IBillingConfig): IBillingPlan[] {\n\treturn config.plans;\n}\n\nexport function getPlanByName(name: string, config: IBillingConfig): IBillingPlan | null {\n\treturn config.plans.find((p) => p.name.toLowerCase() === name.toLowerCase()) ?? null;\n}\n\nexport async function syncPlansToDB(config: IBillingConfig, store: IStoreAdapter): Promise<void> {\n\tconst plans = config.plans;\n\tif (plans.length === 0) return;\n\n\tconst values = plans.map((_, i) => {\n\t\tconst b = i * 9;\n\t\treturn `($${b + 1}, $${b + 2}, $${b + 3}, $${b + 4}, $${b + 5}, $${b + 6}, $${b + 7}, $${b + 8}, $${b + 9}::jsonb)`;\n\t});\n\n\tconst params = plans.flatMap((plan) => [\n\t\tplan.name,\n\t\tplan.trialDays ?? 0,\n\t\tplan.monthly?.amount ?? null,\n\t\tplan.monthly?.priceId ?? null,\n\t\tplan.yearly?.amount ?? null,\n\t\tplan.yearly?.priceId ?? null,\n\t\tplan.description ?? null,\n\t\tplan.tier ?? 0,\n\t\tJSON.stringify(plan.metadata ?? {}),\n\t]);\n\n\tawait store.query(\n\t\t`INSERT INTO fonderie_plans\n\t\t\t(name, trial_days,\n\t\t\t monthly_amount, monthly_price_id,\n\t\t\t yearly_amount, yearly_price_id,\n\t\t\t description, tier, metadata)\n\t\tVALUES ${values.join(', ')}\n\t\tON CONFLICT (name) DO UPDATE SET\n\t\t\ttrial_days = EXCLUDED.trial_days,\n\t\t\tmonthly_amount = EXCLUDED.monthly_amount,\n\t\t\tmonthly_price_id = EXCLUDED.monthly_price_id,\n\t\t\tyearly_amount = EXCLUDED.yearly_amount,\n\t\t\tyearly_price_id = EXCLUDED.yearly_price_id,\n\t\t\tdescription = EXCLUDED.description,\n\t\t\ttier = EXCLUDED.tier,\n\t\t\tmetadata = EXCLUDED.metadata`,\n\t\tparams,\n\t);\n}\n\nconst SELECT_PLAN = `\n\tSELECT\n\t\tid,\n\t\tname,\n\t\tseats,\n\t\ttrial_days AS \"trialDays\",\n\t\tmonthly_amount AS \"monthlyAmount\",\n\t\tmonthly_price_id AS \"monthlyPriceId\",\n\t\tyearly_amount AS \"yearlyAmount\",\n\t\tyearly_price_id AS \"yearlyPriceId\",\n\t\tdescription,\n\t\ttier,\n\t\tfeatures,\n\t\tmetadata\n\tFROM fonderie_plans`;\n\nexport async function getDBPlans(store: IStoreAdapter): Promise<IPlan[]> {\n\treturn store.query<IPlan>(\n\t\t`${SELECT_PLAN} WHERE active = true ORDER BY tier ASC, monthly_amount ASC NULLS LAST`,\n\t);\n}\n\nexport async function getPlanById(id: string, store: IStoreAdapter): Promise<IPlan | null> {\n\tconst [row] = await store.query<IPlan>(`${SELECT_PLAN} WHERE id = $1`, [id]);\n\treturn row ?? null;\n}\n\nexport async function createPlan(\n\tdata: {\n\t\tname: string;\n\t\tdescription?: string | null;\n\t\ttier?: number;\n\t\tseats?: number | null;\n\t\ttrialDays?: number;\n\t\tfeatures?: unknown;\n\t\tmetadata?: unknown;\n\t\tmonthlyAmount?: number | null;\n\t\tmonthlyPriceId?: string | null;\n\t\tyearlyAmount?: number | null;\n\t\tyearlyPriceId?: string | null;\n\t},\n\tstore: IStoreAdapter,\n): Promise<IPlan> {\n\tconst [row] = await store.query<IPlan>(\n\t\t`INSERT INTO fonderie_plans\n\t\t\t(name, seats, trial_days, monthly_amount, monthly_price_id,\n\t\t\t yearly_amount, yearly_price_id, description, tier, features, metadata)\n\t\tVALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10::jsonb, $11::jsonb)\n\t\tRETURNING\n\t\t\tid, name, seats,\n\t\t\ttrial_days AS \"trialDays\",\n\t\t\tmonthly_amount AS \"monthlyAmount\",\n\t\t\tmonthly_price_id AS \"monthlyPriceId\",\n\t\t\tyearly_amount AS \"yearlyAmount\",\n\t\t\tyearly_price_id AS \"yearlyPriceId\",\n\t\t\tdescription, tier, features, metadata`,\n\t\t[\n\t\t\tdata.name,\n\t\t\tdata.seats ?? null,\n\t\t\tdata.trialDays ?? 0,\n\t\t\tdata.monthlyAmount ?? null,\n\t\t\tdata.monthlyPriceId ?? null,\n\t\t\tdata.yearlyAmount ?? null,\n\t\t\tdata.yearlyPriceId ?? null,\n\t\t\tdata.description ?? null,\n\t\t\tdata.tier ?? 0,\n\t\t\tJSON.stringify(data.features ?? []),\n\t\t\tJSON.stringify(data.metadata ?? {}),\n\t\t],\n\t);\n\tif (!row) throw new Error('Failed to create plan');\n\treturn row;\n}\n\nexport async function updatePlan(\n\tid: string,\n\tdata: Partial<Omit<IPlan, 'id'>>,\n\tstore: IStoreAdapter,\n): Promise<IPlan | null> {\n\tconst fieldMap: Record<string, string> = {\n\t\tname: 'name',\n\t\tseats: 'seats',\n\t\ttrialDays: 'trial_days',\n\t\tmonthlyAmount: 'monthly_amount',\n\t\tmonthlyPriceId: 'monthly_price_id',\n\t\tyearlyAmount: 'yearly_amount',\n\t\tyearlyPriceId: 'yearly_price_id',\n\t\tdescription: 'description',\n\t\ttier: 'tier',\n\t};\n\n\tconst jsonbFields = new Set(['features', 'metadata']);\n\tconst setClauses: string[] = [];\n\tconst params: unknown[] = [id];\n\n\tfor (const [key, col] of Object.entries(fieldMap)) {\n\t\tif (key in data) {\n\t\t\tparams.push((data as Record<string, unknown>)[key]);\n\t\t\tsetClauses.push(`${col} = $${params.length}`);\n\t\t}\n\t}\n\n\tfor (const key of jsonbFields) {\n\t\tif (key in data) {\n\t\t\tparams.push(JSON.stringify((data as Record<string, unknown>)[key]));\n\t\t\tsetClauses.push(`${key} = $${params.length}::jsonb`);\n\t\t}\n\t}\n\n\tif (setClauses.length === 0) return getPlanById(id, store);\n\n\tconst [row] = await store.query<IPlan>(\n\t\t`UPDATE fonderie_plans SET ${setClauses.join(', ')}\n\t\tWHERE id = $1\n\t\tRETURNING\n\t\t\tid, name, seats,\n\t\t\ttrial_days AS \"trialDays\",\n\t\t\tmonthly_amount AS \"monthlyAmount\",\n\t\t\tmonthly_price_id AS \"monthlyPriceId\",\n\t\t\tyearly_amount AS \"yearlyAmount\",\n\t\t\tyearly_price_id AS \"yearlyPriceId\",\n\t\t\tdescription, tier, features, metadata`,\n\t\tparams,\n\t);\n\treturn row ?? null;\n}\n\nexport async function deletePlan(id: string, store: IStoreAdapter): Promise<boolean> {\n\tconst rows = await store.query<{ id: string }>(\n\t\t`DELETE FROM fonderie_plans WHERE id = $1 RETURNING id`,\n\t\t[id],\n\t);\n\treturn rows.length > 0;\n}\n","import type { IStoreAdapter } from '@fonderie/store';\n\nimport type { IPlan } from '../types';\nimport type { IBillingConfig } from '../config';\nimport {\n\tgetDBPlans,\n\tgetPlanById,\n\tcreatePlan,\n\tupdatePlan,\n\tdeletePlan,\n\tgetPlans,\n\tgetPlanByName,\n} from '../services/plans';\n\nexport class PlanModel {\n\tconstructor(private readonly store: IStoreAdapter) {}\n\n\tlistFromConfig(config: IBillingConfig) {\n\t\treturn getPlans(config);\n\t}\n\n\tfindByNameInConfig(name: string, config: IBillingConfig) {\n\t\treturn getPlanByName(name, config);\n\t}\n\n\tlist(): Promise<IPlan[]> {\n\t\treturn getDBPlans(this.store);\n\t}\n\n\tfindById(id: string): Promise<IPlan | null> {\n\t\treturn getPlanById(id, this.store);\n\t}\n\n\tcreate(data: Parameters<typeof createPlan>[0]): Promise<IPlan> {\n\t\treturn createPlan(data, this.store);\n\t}\n\n\tupdate(id: string, data: Parameters<typeof updatePlan>[1]): Promise<IPlan | null> {\n\t\treturn updatePlan(id, data, this.store);\n\t}\n\n\tdelete(id: string): Promise<boolean> {\n\t\treturn deletePlan(id, this.store);\n\t}\n}\n","import type { IPlan, IPlanFeature, ISubscription, IUsageRecord, SubscriberType } from '../types';\n\nexport interface IPlanDTO {\n\tid: string;\n\tplanId: string;\n\tname: string;\n\tdescription: string;\n\ttier: number;\n\tseats: number | null;\n\ttrialDays: number;\n\tpricing: {\n\t\tmonthly: number; // in cents, e.g. 1999 = $19.99\n\t\tyearly: number; // in cents\n\t\tcurrency: string; // ISO 4217, e.g. 'USD'\n\t};\n\tfeatures: IPlanFeature[];\n\tmetadata: Record<string, unknown>;\n}\n\nexport interface ISubscriptionDTO {\n\tid: string;\n\tsubscriberType: SubscriberType;\n\tsubscriberId: string;\n\tplan: string;\n\tinterval: string;\n\tstatus: string;\n\tcancelAtPeriodEnd: boolean;\n\tcurrentPeriodStart: string | null;\n\tcurrentPeriodEnd: string | null;\n\ttrialEndsAt: string | null;\n\tcreatedAt: string;\n}\n\nexport interface IUsageRecordDTO {\n\tid: string;\n\tsubscriberType: SubscriberType;\n\tsubscriberId: string;\n\tmetric: string;\n\tquantity: number;\n\trecordedAt: string;\n}\n\nexport function toPlanDTO(plan: IPlan): IPlanDTO {\n\treturn {\n\t\tid: plan.id,\n\t\tplanId: plan.name.toUpperCase(),\n\t\tname: plan.name,\n\t\tdescription: plan.description ?? '',\n\t\ttier: plan.tier,\n\t\tseats: plan.seats,\n\t\ttrialDays: plan.trialDays,\n\t\tpricing: {\n\t\t\tmonthly: plan.monthlyAmount ?? 0,\n\t\t\tyearly: plan.yearlyAmount ?? 0,\n\t\t\tcurrency: 'USD',\n\t\t},\n\t\tfeatures: Array.isArray(plan.features) ? plan.features : [],\n\t\tmetadata:\n\t\t\tplan.metadata && typeof plan.metadata === 'object'\n\t\t\t\t? (plan.metadata as Record<string, unknown>)\n\t\t\t\t: {},\n\t};\n}\n\nexport function toSubscriptionDTO(sub: ISubscription): ISubscriptionDTO {\n\treturn {\n\t\tid: sub.id,\n\t\tsubscriberType: sub.subscriberType,\n\t\tsubscriberId: sub.subscriberId,\n\t\tplan: sub.plan,\n\t\tinterval: sub.interval,\n\t\tstatus: sub.status,\n\t\tcancelAtPeriodEnd: sub.cancelAtPeriodEnd,\n\t\tcurrentPeriodStart: sub.currentPeriodStart,\n\t\tcurrentPeriodEnd: sub.currentPeriodEnd,\n\t\ttrialEndsAt: sub.trialEndsAt,\n\t\tcreatedAt: sub.createdAt,\n\t};\n}\n\nexport function toUsageRecordDTO(record: IUsageRecord): IUsageRecordDTO {\n\treturn {\n\t\tid: record.id,\n\t\tsubscriberType: record.subscriberType,\n\t\tsubscriberId: record.subscriberId,\n\t\tmetric: record.metric,\n\t\tquantity: record.quantity,\n\t\trecordedAt: record.recordedAt,\n\t};\n}\n","import { setApiResponse, HTTP } from '@fonderie/core';\nimport type { IFonderieContext } from '@fonderie/core';\nimport type { IStoreAdapter } from '@fonderie/store';\n\nimport { SubscriptionModel } from '../models/subscription.model';\nimport { toSubscriptionDTO } from '../dtos/billing';\nimport { resolveSubscriber } from '../utils';\n\nexport function subscriptionController(store: IStoreAdapter) {\n\tconst subscriptions = new SubscriptionModel(store);\n\n\treturn {\n\t\tasync get(ctx: IFonderieContext): Promise<Response> {\n\t\t\tconst subscriber = resolveSubscriber(ctx);\n\t\t\tif (!subscriber) {\n\t\t\t\treturn setApiResponse(\n\t\t\t\t\tHTTP.BAD_REQUEST,\n\t\t\t\t\t'SUBSCRIBER_REQUIRED',\n\t\t\t\t\t'Subscriber context required',\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst subscription = await subscriptions.get(subscriber.type, subscriber.id);\n\t\t\tif (!subscription)\n\t\t\t\treturn setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'No active subscription');\n\n\t\t\treturn setApiResponse(\n\t\t\t\tHTTP.OK,\n\t\t\t\t'SUBSCRIPTION_FETCHED',\n\t\t\t\t'Subscription retrieved successfully.',\n\t\t\t\t{\n\t\t\t\t\tsubscription: toSubscriptionDTO(subscription),\n\t\t\t\t},\n\t\t\t);\n\t\t},\n\t};\n}\n","import type { IStoreAdapter } from '@fonderie/store';\n\nimport type { ISubscription, SubscriberType } from '../types';\n\nconst SELECT_SUBSCRIPTION = `\n\tSELECT\n\t\tid,\n\t\tsubscriber_type AS \"subscriberType\",\n\t\tsubscriber_id AS \"subscriberId\",\n\t\tplan,\n\t\tinterval,\n\t\tstatus,\n\t\tprovider_customer_id AS \"providerCustomerId\",\n\t\tprovider_subscription_id AS \"providerSubscriptionId\",\n\t\tcurrent_period_start AS \"currentPeriodStart\",\n\t\tcurrent_period_end AS \"currentPeriodEnd\",\n\t\tcancel_at_period_end AS \"cancelAtPeriodEnd\",\n\t\ttrial_ends_at AS \"trialEndsAt\",\n\t\tcreated_at AS \"createdAt\"\n\tFROM fonderie_subscriptions`;\n\nexport async function getSubscription(\n\tsubscriberType: SubscriberType,\n\tsubscriberId: string,\n\tstore: IStoreAdapter,\n): Promise<ISubscription | null> {\n\tconst [row] = await store.query<ISubscription>(\n\t\t`${SELECT_SUBSCRIPTION} WHERE subscriber_type = $1 AND subscriber_id = $2`,\n\t\t[subscriberType, subscriberId],\n\t);\n\treturn row ?? null;\n}\n\nexport async function upsertSubscription(\n\tdata: {\n\t\tsubscriberType: SubscriberType;\n\t\tsubscriberId: string;\n\t\tplan: string;\n\t\tinterval?: 'month' | 'year';\n\t\tstatus: string;\n\t\tproviderCustomerId?: string;\n\t\tproviderSubscriptionId?: string;\n\t\tcurrentPeriodStart?: Date;\n\t\tcurrentPeriodEnd?: Date;\n\t\tcancelAtPeriodEnd?: boolean;\n\t\ttrialEndsAt?: Date | null;\n\t},\n\tstore: IStoreAdapter,\n): Promise<void> {\n\tawait store.query(\n\t\t`INSERT INTO fonderie_subscriptions\n\t\t\t(subscriber_type, subscriber_id, plan, interval, status,\n\t\t\t provider_customer_id, provider_subscription_id,\n\t\t\t current_period_start, current_period_end,\n\t\t\t cancel_at_period_end, trial_ends_at)\n\t\t VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)\n\t\t ON CONFLICT (subscriber_type, subscriber_id) DO UPDATE SET\n\t\t\t plan = $3,\n\t\t\t interval = $4,\n\t\t\t status = $5,\n\t\t\t provider_customer_id = COALESCE($6, fonderie_subscriptions.provider_customer_id),\n\t\t\t provider_subscription_id = COALESCE($7, fonderie_subscriptions.provider_subscription_id),\n\t\t\t current_period_start = $8,\n\t\t\t current_period_end = $9,\n\t\t\t cancel_at_period_end = $10,\n\t\t\t trial_ends_at = $11`,\n\t\t[\n\t\t\tdata.subscriberType,\n\t\t\tdata.subscriberId,\n\t\t\tdata.plan,\n\t\t\tdata.interval ?? 'month',\n\t\t\tdata.status,\n\t\t\tdata.providerCustomerId ?? null,\n\t\t\tdata.providerSubscriptionId ?? null,\n\t\t\tdata.currentPeriodStart ?? null,\n\t\t\tdata.currentPeriodEnd ?? null,\n\t\t\tdata.cancelAtPeriodEnd ?? false,\n\t\t\tdata.trialEndsAt ?? null,\n\t\t],\n\t);\n}\n","import type { IStoreAdapter } from '@fonderie/store';\n\nimport type { ISubscription, SubscriberType } from '../types';\nimport { getSubscription, upsertSubscription } from '../services/subscriptions';\n\nexport class SubscriptionModel {\n\tconstructor(private readonly store: IStoreAdapter) {}\n\n\tget(subscriberType: SubscriberType, subscriberId: string): Promise<ISubscription | null> {\n\t\treturn getSubscription(subscriberType, subscriberId, this.store);\n\t}\n\n\tupsert(data: Parameters<typeof upsertSubscription>[0]): Promise<void> {\n\t\treturn upsertSubscription(data, this.store);\n\t}\n}\n","import type { IFonderieContext } from '@fonderie/core';\n\nimport type { SubscriberType } from './types';\n\nexport interface ISubscriber {\n\ttype: SubscriberType;\n\tid: string;\n}\n\n// Converts window strings like '1d', '30d', '1h' to milliseconds.\nexport function parseWindowMs(window: string): number {\n\tconst n = parseInt(window, 10);\n\tconst unit = window.slice(String(n).length);\n\tswitch (unit) {\n\t\tcase 'h':\n\t\t\treturn n * 3_600_000;\n\t\tcase 'd':\n\t\t\treturn n * 86_400_000;\n\t\tcase 'm':\n\t\t\treturn n * 60_000;\n\t\tdefault:\n\t\t\tthrow new Error(`Unknown window unit: '${unit}' in '${window}'`);\n\t}\n}\n\n// Resolves billing subscriber from request context.\n// Precedence: X-Workspace-ID header → ctx.workspace (set by withWorkspace) → ctx.user\nexport function resolveSubscriber(ctx: IFonderieContext): ISubscriber | null {\n\tconst wsFromHeader = ctx.request.headers.get('x-workspace-id');\n\n\tif (wsFromHeader) {\n\t\treturn {\n\t\t\ttype: 'workspace',\n\t\t\tid: wsFromHeader,\n\t\t};\n\t}\n\n\tif (ctx.workspace?.id) {\n\t\treturn {\n\t\t\ttype: 'workspace',\n\t\t\tid: ctx.workspace.id,\n\t\t};\n\t}\n\n\tif (ctx.user?.id) {\n\t\treturn {\n\t\t\ttype: 'user',\n\t\t\tid: ctx.user.id,\n\t\t};\n\t}\n\n\treturn null;\n}\n","import { setApiResponse, HTTP } from '@fonderie/core';\nimport type { IFonderieContext } from '@fonderie/core';\nimport type { IStoreAdapter } from '@fonderie/store';\n\nimport type { IBillingConfig } from '../config';\nimport { PlanModel } from '../models/plan.model';\nimport { SubscriptionModel } from '../models/subscription.model';\nimport { resolveSubscriber } from '../utils';\n\nexport function checkoutController(store: IStoreAdapter, config: IBillingConfig) {\n\tconst plans = new PlanModel(store);\n\tconst subscriptions = new SubscriptionModel(store);\n\n\treturn {\n\t\tasync createSession(ctx: IFonderieContext): Promise<Response> {\n\t\t\tconst body = ctx.meta['body'] as Record<string, unknown> | undefined;\n\t\t\tconst planName = body?.['plan'];\n\t\t\tconst interval = (body?.['interval'] ?? 'month') as 'month' | 'year';\n\t\t\tconst subscriber = resolveSubscriber(ctx);\n\n\t\t\tif (typeof planName !== 'string') {\n\t\t\t\treturn setApiResponse(HTTP.UNPROCESSABLE, 'INVALID_PARAMETER', 'plan is required');\n\t\t\t}\n\t\t\tif (interval !== 'month' && interval !== 'year') {\n\t\t\t\treturn setApiResponse(\n\t\t\t\t\tHTTP.UNPROCESSABLE,\n\t\t\t\t\t'INVALID_PARAMETER',\n\t\t\t\t\t'interval must be month or year',\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (!subscriber) {\n\t\t\t\treturn setApiResponse(\n\t\t\t\t\tHTTP.BAD_REQUEST,\n\t\t\t\t\t'SUBSCRIBER_REQUIRED',\n\t\t\t\t\t'Subscriber context required',\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst plan = plans.findByNameInConfig(planName, config);\n\t\t\tif (!plan) {\n\t\t\t\treturn setApiResponse(HTTP.UNPROCESSABLE, 'INVALID_PARAMETER', `Unknown plan: ${planName}`);\n\t\t\t}\n\n\t\t\tconst pricing = interval === 'year' ? plan.yearly : plan.monthly;\n\t\t\tif (!pricing?.priceId) {\n\t\t\t\treturn setApiResponse(\n\t\t\t\t\tHTTP.UNPROCESSABLE,\n\t\t\t\t\t'INVALID_PARAMETER',\n\t\t\t\t\t`Plan ${planName} does not support ${interval} billing`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst { customerId } = await config.provider.createCustomer({\n\t\t\t\temail: ctx.user!.email ?? '',\n\t\t\t\tsubscriberType: subscriber.type,\n\t\t\t\tsubscriberId: subscriber.id,\n\t\t\t\tuserId: ctx.user!.id,\n\t\t\t});\n\n\t\t\tconst sessionOpts: Parameters<typeof config.provider.createCheckoutSession>[0] = {\n\t\t\t\tcustomerId,\n\t\t\t\tpriceId: pricing.priceId,\n\t\t\t\tsubscriberType: subscriber.type,\n\t\t\t\tsubscriberId: subscriber.id,\n\t\t\t\tsuccessUrl: config.successUrl,\n\t\t\t\tcancelUrl: config.cancelUrl,\n\t\t\t};\n\t\t\tif (plan.trialDays !== undefined) sessionOpts.trialDays = plan.trialDays;\n\n\t\t\tconst { url } = await config.provider.createCheckoutSession(sessionOpts);\n\n\t\t\tawait subscriptions.upsert({\n\t\t\t\tsubscriberType: subscriber.type,\n\t\t\t\tsubscriberId: subscriber.id,\n\t\t\t\tplan: planName,\n\t\t\t\tinterval,\n\t\t\t\tstatus: 'incomplete',\n\t\t\t\tproviderCustomerId: customerId,\n\t\t\t});\n\n\t\t\treturn setApiResponse(HTTP.OK, 'CHECKOUT_URL', 'Checkout session created.', { url });\n\t\t},\n\n\t\tasync createPortal(ctx: IFonderieContext): Promise<Response> {\n\t\t\tconst subscriber = resolveSubscriber(ctx);\n\t\t\tif (!subscriber) {\n\t\t\t\treturn setApiResponse(\n\t\t\t\t\tHTTP.BAD_REQUEST,\n\t\t\t\t\t'SUBSCRIBER_REQUIRED',\n\t\t\t\t\t'Subscriber context required',\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst subscription = await subscriptions.get(subscriber.type, subscriber.id);\n\t\t\tif (!subscription?.providerCustomerId) {\n\t\t\t\treturn setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'No active subscription');\n\t\t\t}\n\n\t\t\tconst { url } = await config.provider.createPortalSession({\n\t\t\t\tcustomerId: subscription.providerCustomerId,\n\t\t\t\treturnUrl: config.successUrl,\n\t\t\t});\n\n\t\t\treturn setApiResponse(HTTP.OK, 'PORTAL_URL', 'Portal session created.', { url });\n\t\t},\n\t};\n}\n","import { setApiResponse, HTTP } from '@fonderie/core';\nimport type { IFonderieContext } from '@fonderie/core';\nimport type { IStoreAdapter } from '@fonderie/store';\n\nimport { UsageModel } from '../models/usage.model';\nimport { resolveSubscriber } from '../utils';\n\nexport function usageController(store: IStoreAdapter) {\n\tconst usage = new UsageModel(store);\n\n\treturn {\n\t\tasync record(ctx: IFonderieContext): Promise<Response> {\n\t\t\tconst body = ctx.meta['body'] as Record<string, unknown> | undefined;\n\t\t\tconst metric = body?.['metric'];\n\t\t\tconst quantity = body?.['quantity'];\n\t\t\tconst subscriber = resolveSubscriber(ctx);\n\n\t\t\tif (!subscriber) {\n\t\t\t\treturn setApiResponse(\n\t\t\t\t\tHTTP.BAD_REQUEST,\n\t\t\t\t\t'SUBSCRIBER_REQUIRED',\n\t\t\t\t\t'Subscriber context required',\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (typeof metric !== 'string') {\n\t\t\t\treturn setApiResponse(HTTP.UNPROCESSABLE, 'INVALID_PARAMETER', 'metric is required');\n\t\t\t}\n\n\t\t\tawait usage.record({\n\t\t\t\tsubscriberType: subscriber.type,\n\t\t\t\tsubscriberId: subscriber.id,\n\t\t\t\tmetric,\n\t\t\t\tquantity: typeof quantity === 'number' ? quantity : 1,\n\t\t\t});\n\t\t\treturn setApiResponse(HTTP.OK, 'USAGE_RECORDED', 'Usage recorded successfully.');\n\t\t},\n\n\t\tasync get(ctx: IFonderieContext): Promise<Response> {\n\t\t\tconst params = ctx.meta['params'] as Record<string, string> | undefined;\n\t\t\tconst metric = params?.['metric'];\n\t\t\tconst subscriber = resolveSubscriber(ctx);\n\n\t\t\tif (!subscriber || !metric) {\n\t\t\t\treturn setApiResponse(\n\t\t\t\t\tHTTP.UNPROCESSABLE,\n\t\t\t\t\t'INVALID_PARAMETER',\n\t\t\t\t\t'subscriber and metric are required',\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst since = new Date();\n\t\t\tsince.setDate(1);\n\t\t\tsince.setHours(0, 0, 0, 0);\n\n\t\t\tconst total = await usage.get(subscriber.type, subscriber.id, metric, since);\n\t\t\treturn setApiResponse(HTTP.OK, 'USAGE_FETCHED', 'Usage retrieved successfully.', {\n\t\t\t\tmetric,\n\t\t\t\ttotal,\n\t\t\t\tsince,\n\t\t\t});\n\t\t},\n\t};\n}\n","import type { IStoreAdapter } from '@fonderie/store';\nimport type { SubscriberType } from '../types';\n\nexport async function recordUsage(\n\topts: { subscriberType: SubscriberType; subscriberId: string; metric: string; quantity: number },\n\tstore: IStoreAdapter,\n): Promise<void> {\n\tawait store.query(\n\t\t`INSERT INTO fonderie_usage_records (subscriber_type, subscriber_id, metric, quantity)\n\t\tVALUES ($1, $2, $3, $4)`,\n\t\t[opts.subscriberType, opts.subscriberId, opts.metric, opts.quantity],\n\t);\n}\n\nexport async function getUsage(\n\tsubscriberType: SubscriberType,\n\tsubscriberId: string,\n\tmetric: string,\n\tsince: Date,\n\tstore: IStoreAdapter,\n): Promise<number> {\n\tconst rows = await store.query<{ total: string }>(\n\t\t`SELECT COALESCE(SUM(quantity), 0) AS total\n\t\tFROM fonderie_usage_records\n\t\tWHERE subscriber_type = $1\n\t\t\tAND subscriber_id = $2\n\t\t\tAND metric = $3\n\t\t\tAND recorded_at >= $4`,\n\t\t[subscriberType, subscriberId, metric, since],\n\t);\n\treturn parseInt(rows[0]?.total ?? '0', 10);\n}\n","import type { IStoreAdapter } from '@fonderie/store';\nimport type { SubscriberType } from '../types';\n\nimport { recordUsage, getUsage } from '../services/usage';\n\nexport class UsageModel {\n\tconstructor(private readonly store: IStoreAdapter) {}\n\n\trecord(opts: Parameters<typeof recordUsage>[0]): Promise<void> {\n\t\treturn recordUsage(opts, this.store);\n\t}\n\n\tget(\n\t\tsubscriberType: SubscriberType,\n\t\tsubscriberId: string,\n\t\tmetric: string,\n\t\tsince: Date,\n\t): Promise<number> {\n\t\treturn getUsage(subscriberType, subscriberId, metric, since, this.store);\n\t}\n}\n","import { setApiResponse, HTTP } from '@fonderie/core';\nimport type { IFonderieContext } from '@fonderie/core';\nimport type { IStoreAdapter } from '@fonderie/store';\n\nimport type { IBillingConfig } from '../config';\nimport { SubscriptionModel } from '../models/subscription.model';\n\nexport function webhookController(store: IStoreAdapter, config: IBillingConfig) {\n\tconst subscriptions = new SubscriptionModel(store);\n\n\treturn {\n\t\tasync handle(ctx: IFonderieContext): Promise<Response> {\n\t\t\tif (!config.webhookSecret) {\n\t\t\t\treturn setApiResponse(HTTP.SERVER_ERROR, 'SERVER_ERROR', 'Webhook secret not configured');\n\t\t\t}\n\n\t\t\tconst signature =\n\t\t\t\tctx.request.headers.get('stripe-signature') ??\n\t\t\t\tctx.request.headers.get('paypal-auth-algo') ??\n\t\t\t\t'';\n\n\t\t\tif (!signature) {\n\t\t\t\treturn setApiResponse(HTTP.BAD_REQUEST, 'INVALID_REQUEST', 'Missing webhook signature');\n\t\t\t}\n\n\t\t\tconst payload = await ctx.request.text();\n\n\t\t\tlet event: Awaited<ReturnType<typeof config.provider.constructEvent>>;\n\t\t\ttry {\n\t\t\t\tevent = await config.provider.constructEvent({\n\t\t\t\t\tpayload,\n\t\t\t\t\tsignature,\n\t\t\t\t\tsecret: config.webhookSecret,\n\t\t\t\t});\n\t\t\t} catch {\n\t\t\t\treturn setApiResponse(HTTP.BAD_REQUEST, 'INVALID_REQUEST', 'Invalid webhook signature');\n\t\t\t}\n\n\t\t\tif (event.subscription) {\n\t\t\t\tawait subscriptions.upsert({\n\t\t\t\t\tsubscriberType: event.subscription.subscriberType,\n\t\t\t\t\tsubscriberId: event.subscription.subscriberId,\n\t\t\t\t\tplan: event.subscription.plan,\n\t\t\t\t\tstatus: event.subscription.status,\n\t\t\t\t\tproviderCustomerId: event.subscription.providerCustomerId,\n\t\t\t\t\tproviderSubscriptionId: event.subscription.providerSubscriptionId,\n\t\t\t\t\tcurrentPeriodStart: event.subscription.currentPeriodStart,\n\t\t\t\t\tcurrentPeriodEnd: event.subscription.currentPeriodEnd,\n\t\t\t\t\tcancelAtPeriodEnd: event.subscription.cancelAtPeriodEnd,\n\t\t\t\t\ttrialEndsAt: event.subscription.trialEndsAt,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\treturn Response.json({ received: true });\n\t\t},\n\t};\n}\n","import type { Middleware, ICourierMessage } from '@fonderie/core';\nimport { setApiResponse, HTTP } from '@fonderie/core';\nimport type { IStoreAdapter } from '@fonderie/store';\n\nimport type { IBillingConfig } from '../config';\nimport type { ICounterBackend } from '../backends/types';\nimport { MESSAGE_KEYS } from '../config';\nimport { getSubscription } from '../services/subscriptions';\nimport { buildBillingContext } from '../services/policy';\nimport { resolveSubscriber, parseWindowMs } from '../utils';\n\n// In-process de-dup: tracks which threshold notifications have fired this session.\n// Acceptable to lose on restart (may send one duplicate after a redeploy).\nconst notified = new Set<string>();\n\nexport function withBilling(\n\tstore: IStoreAdapter,\n\tconfig: IBillingConfig,\n\tbackend: ICounterBackend,\n): Middleware {\n\treturn async (ctx, next) => {\n\t\tconst subscriber = resolveSubscriber(ctx);\n\n\t\t// No subscriber (unauthenticated / public route) — skip entirely\n\t\tif (!subscriber) return next();\n\n\t\t// Resolve subscription → plan name (fall back to first plan = free)\n\t\tconst subscription = await getSubscription(subscriber.type, subscriber.id, store);\n\t\tconst planName = subscription?.plan ?? config.plans[0]?.name ?? 'free';\n\t\tconst active =\n\t\t\t!subscription || subscription.status === 'active' || subscription.status === 'trialing';\n\n\t\tconst plan = config.plans.find((p) => p.name === planName) ?? config.plans[0];\n\t\tif (!plan) return next();\n\n\t\t// Increment windowed (rate-limit) counters and read their current totals\n\t\tconst counters: Record<string, number> = {};\n\n\t\tfor (const [key, entry] of Object.entries(plan.policy ?? {})) {\n\t\t\tif ('enabled' in entry || !entry.window) continue;\n\n\t\t\tconst windowMs = parseWindowMs(entry.window);\n\t\t\tconst counterKey = `${subscriber.type}:${subscriber.id}:${key}`;\n\t\t\tcounters[key] = await backend.increment(counterKey, windowMs);\n\t\t}\n\n\t\t// Build and cache billing context on ctx\n\t\tconst billingCtx = buildBillingContext({ subscriber, plan, active, counters });\n\t\tctx.meta['billing'] = billingCtx;\n\n\t\t// Block requests that have hit a hard limit\n\t\tfor (const [key, status] of Object.entries(billingCtx.statuses)) {\n\t\t\tif (status.type === 'counter' && status.status === 'blocked') {\n\t\t\t\treturn setApiResponse(\n\t\t\t\t\tHTTP.TOO_MANY_REQUESTS,\n\t\t\t\t\t'RATE_LIMIT_EXCEEDED',\n\t\t\t\t\t`Limit exceeded for: ${key}`,\n\t\t\t\t\t{ key, limit: status.limit, used: status.used, resetsAt: status.resetsAt },\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\t// Fire threshold notifications (once per subscriber per key per session)\n\t\tif (config.notifications) {\n\t\t\tconst toNotify: ICourierMessage[] = [];\n\t\t\tconst recipient = {\n\t\t\t\temail: ctx.user?.email ?? null,\n\t\t\t\tphone: null,\n\t\t\t\tdeviceToken: null,\n\t\t\t};\n\n\t\t\tfor (const [key, status] of Object.entries(billingCtx.statuses)) {\n\t\t\t\tif (status.type !== 'counter' || status.limit === null) continue;\n\n\t\t\t\tconst base = `${subscriber.type}:${subscriber.id}:${key}`;\n\n\t\t\t\tif (config.notifications.softHit && status.status === 'over_limit') {\n\t\t\t\t\tconst nk = `${base}:reached`;\n\t\t\t\t\tif (!notified.has(nk)) {\n\t\t\t\t\t\tnotified.add(nk);\n\t\t\t\t\t\ttoNotify.push({\n\t\t\t\t\t\t\ttype: MESSAGE_KEYS.limitReached,\n\t\t\t\t\t\t\trecipient,\n\t\t\t\t\t\t\tdata: {\n\t\t\t\t\t\t\t\tkey,\n\t\t\t\t\t\t\t\tplan: plan.name,\n\t\t\t\t\t\t\t\tlimit: status.limit,\n\t\t\t\t\t\t\t\tused: status.used,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t} else if (config.notifications.warnAt && status.status === 'warning') {\n\t\t\t\t\tconst nk = `${base}:warning`;\n\t\t\t\t\tif (!notified.has(nk)) {\n\t\t\t\t\t\tnotified.add(nk);\n\t\t\t\t\t\ttoNotify.push({\n\t\t\t\t\t\t\ttype: MESSAGE_KEYS.limitWarning,\n\t\t\t\t\t\t\trecipient,\n\t\t\t\t\t\t\tdata: {\n\t\t\t\t\t\t\t\tkey,\n\t\t\t\t\t\t\t\tplan: plan.name,\n\t\t\t\t\t\t\t\tlimit: status.limit,\n\t\t\t\t\t\t\t\tused: status.used,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (toNotify.length > 0) {\n\t\t\t\tconst existing = ctx.meta['messages'] as ICourierMessage[] | undefined;\n\t\t\t\tctx.meta['messages'] = [...(existing ?? []), ...toNotify];\n\t\t\t}\n\t\t}\n\n\t\treturn next();\n\t};\n}\n","import type { IBillingProvider } from './providers/types';\nimport type { PolicyEntry } from './types';\nimport type { ICounterBackend } from './backends/types';\n\nexport interface IBillingPlanPrice {\n\tamount: number;\n\tpriceId?: string;\n}\n\nexport interface IBillingPlanDefaults {\n\twarnAt?: number; // default warnAt fraction (0–1) for counter policies\n\tbuffer?: number; // default buffer for counter policies\n}\n\nexport interface IBillingPlan {\n\tname: string;\n\tdescription?: string;\n\ttier?: number;\n\ttrialDays?: number;\n\tmonthly?: IBillingPlanPrice;\n\tyearly?: IBillingPlanPrice;\n\tdefaults?: IBillingPlanDefaults;\n\tpolicy?: Record<string, PolicyEntry>;\n\tmetadata?: Record<string, unknown>;\n}\n\nexport type RateLimitBackendConfig = 'memory' | 'db' | ICounterBackend;\n\nexport interface IBillingNotificationsConfig {\n\twarnAt?: boolean; // fire courier message when warnAt threshold crossed\n\tsoftHit?: boolean; // fire when soft limit crossed\n}\n\nexport interface IBillingConfig {\n\tprovider: IBillingProvider;\n\tplans: IBillingPlan[];\n\tsuccessUrl: string;\n\tcancelUrl: string;\n\twebhookSecret?: string;\n\trateLimit?: { backend?: RateLimitBackendConfig };\n\tnotifications?: IBillingNotificationsConfig;\n}\n\nexport const MESSAGE_KEYS = {\n\tlimitWarning: 'billing.limit-warning',\n\tlimitReached: 'billing.limit-reached',\n\tlimitBlocked: 'billing.limit-blocked',\n} as const;\n\nexport type BillingMessageKey = (typeof MESSAGE_KEYS)[keyof typeof MESSAGE_KEYS];\n","import type { IBillingPlan } from '../config';\nimport type { LimitStatus, IPolicyStatus, IBillingContext, SubscriberType } from '../types';\nimport { parseWindowMs } from '../utils';\n\nexport function buildBillingContext(opts: {\n\tsubscriber: { type: SubscriberType; id: string };\n\tplan: IBillingPlan;\n\tactive: boolean;\n\t// Pre-fetched windowed counter values keyed by policy key.\n\t// Non-windowed counter keys are absent (their used count is 0 — app manages those).\n\tcounters: Record<string, number>;\n}): IBillingContext {\n\tconst { subscriber, plan, active, counters } = opts;\n\tconst defaults = plan.defaults ?? {};\n\tconst statuses: Record<string, IPolicyStatus> = {};\n\n\tfor (const [key, entry] of Object.entries(plan.policy ?? {})) {\n\t\tif ('enabled' in entry) {\n\t\t\tstatuses[key] = { type: 'feature', enabled: entry.enabled };\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst { limit, buffer = defaults.buffer ?? 0, warnAt = defaults.warnAt ?? 0.8, window } = entry;\n\n\t\tconst used = counters[key] ?? 0;\n\t\tconst hardLimit = limit !== null ? limit + buffer : null;\n\n\t\tlet status: LimitStatus = 'ok';\n\t\tif (hardLimit !== null && used >= hardLimit) status = 'blocked';\n\t\telse if (limit !== null && used >= limit) status = 'over_limit';\n\t\telse if (limit !== null && used >= limit * warnAt) status = 'warning';\n\n\t\tlet resetsAt: string | null = null;\n\t\tif (window) {\n\t\t\tconst windowMs = parseWindowMs(window);\n\t\t\tconst windowStart = Math.floor(Date.now() / windowMs) * windowMs;\n\t\t\tresetsAt = new Date(windowStart + windowMs).toISOString();\n\t\t}\n\n\t\tstatuses[key] = { type: 'counter', limit, used, status, resetsAt };\n\t}\n\n\treturn { subscriber, plan: plan.name, active, statuses };\n}\n","import type { ICounterBackend } from './types';\n\ninterface Entry {\n\tcount: number;\n\twindowStart: number; // epoch ms — used for windowed expiry\n}\n\nexport class MemoryCounterBackend implements ICounterBackend {\n\tprivate readonly counters = new Map<string, Entry>();\n\n\tasync increment(key: string, windowMs: number | null, quantity = 1): Promise<number> {\n\t\tconst now = Date.now();\n\t\tconst existing = this.counters.get(key);\n\n\t\tif (!existing || (windowMs !== null && now - existing.windowStart >= windowMs)) {\n\t\t\tthis.counters.set(key, { count: quantity, windowStart: now });\n\t\t\treturn quantity;\n\t\t}\n\n\t\texisting.count += quantity;\n\t\treturn existing.count;\n\t}\n\n\tasync get(key: string, windowMs: number | null): Promise<number> {\n\t\tconst now = Date.now();\n\t\tconst existing = this.counters.get(key);\n\t\tif (!existing) return 0;\n\t\tif (windowMs !== null && now - existing.windowStart >= windowMs) return 0;\n\t\treturn existing.count;\n\t}\n}\n","import type { IStoreAdapter } from '@fonderie/store';\nimport type { ICounterBackend } from './types';\n\nexport class DBCounterBackend implements ICounterBackend {\n\tconstructor(private readonly store: IStoreAdapter) {}\n\n\tasync increment(key: string, windowMs: number | null, quantity = 1): Promise<number> {\n\t\tconst [subscriberType, subscriberId, ...rest] = key.split(':');\n\t\tconst metric = rest.join(':');\n\n\t\tawait this.store.query(\n\t\t\t`INSERT INTO fonderie_usage_records (subscriber_type, subscriber_id, metric, quantity)\n\t\t\t VALUES ($1, $2, $3, $4)`,\n\t\t\t[subscriberType, subscriberId, metric, quantity],\n\t\t);\n\n\t\treturn this.get(key, windowMs);\n\t}\n\n\tasync get(key: string, windowMs: number | null): Promise<number> {\n\t\tconst [subscriberType, subscriberId, ...rest] = key.split(':');\n\t\tconst metric = rest.join(':');\n\t\tconst since = windowMs !== null ? new Date(Date.now() - windowMs) : new Date(0);\n\n\t\tconst rows = await this.store.query<{ total: string }>(\n\t\t\t`SELECT COALESCE(SUM(quantity), 0) AS total\n\t\t\t FROM fonderie_usage_records\n\t\t\t WHERE subscriber_type = $1\n\t\t\t AND subscriber_id = $2\n\t\t\t AND metric = $3\n\t\t\t AND recorded_at >= $4`,\n\t\t\t[subscriberType, subscriberId, metric, since],\n\t\t);\n\n\t\treturn parseInt(rows[0]?.total ?? '0', 10);\n\t}\n}\n","import type { IStoreAdapter } from '@fonderie/store';\nimport type { RateLimitBackendConfig } from '../config';\nimport { MemoryCounterBackend } from './memory';\nimport { DBCounterBackend } from './db';\n\nexport function createBackend(config: RateLimitBackendConfig | undefined, store: IStoreAdapter) {\n\tif (!config || config === 'memory') return new MemoryCounterBackend();\n\tif (config === 'db') return new DBCounterBackend(store);\n\treturn config;\n}\n\nexport type { ICounterBackend } from './types';\nexport { MemoryCounterBackend } from './memory';\nexport { DBCounterBackend } from './db';\n","import type { IFonderieModule, IFonderieApp } from '@fonderie/core';\nimport type { IStoreAdapter } from '@fonderie/store';\n\nimport type { IBillingConfig } from './config';\nimport { buildBillingRoutes } from './routes';\nimport { syncPlansToDB } from './services/plans';\nimport { withBilling } from './middlewares/billing';\nimport { createBackend } from './backends';\n\nexport class BillingModule implements IFonderieModule {\n\treadonly name = '@fonderie/billing';\n\treadonly deps = ['@fonderie/auth'];\n\n\tconstructor(\n\t\tprivate store: IStoreAdapter,\n\t\tprivate config: IBillingConfig,\n\t) {}\n\n\tasync install(app: IFonderieApp): Promise<void> {\n\t\tawait syncPlansToDB(this.config, this.store);\n\n\t\tconst backend = createBackend(this.config.rateLimit?.backend, this.store);\n\n\t\t// Global middleware — resolves subscriber + plan, enforces rate limits,\n\t\t// caches IBillingContext on ctx.meta['billing'] for every request.\n\t\t// Runs after auth (ctx.user available), before route handlers.\n\t\tapp.use(withBilling(this.store, this.config, backend));\n\n\t\tconst routes = buildBillingRoutes(this.store, this.config);\n\t\tfor (const [method, path, ...handlers] of routes) {\n\t\t\tapp.addRoute(method, path, ...handlers);\n\t\t}\n\t}\n}\n","import type { IBillingProvider, IBillingEvent, INormalizedSubscription } from './types';\nimport type { SubscriberType } from '../types';\n\ninterface IStripeSubscriptionRaw {\n\tid: string;\n\tstatus: string;\n\tcustomer: string;\n\tmetadata?: Record<string, string>;\n\titems: { data: Array<{ price: { id: string; nickname: string | null } }> };\n\tcurrent_period_start: number;\n\tcurrent_period_end: number;\n\tcancel_at_period_end: boolean;\n\ttrial_end: number | null;\n}\n\ninterface IStripeEventRaw {\n\ttype: string;\n\tdata: { object: unknown };\n}\n\n// Lazy singleton — Stripe SDK is optional\nlet _client: unknown = null;\n\nasync function getClient(secretKey: string): Promise<unknown> {\n\tif (_client) return _client;\n\n\tconst pkg = 'stripe';\n\n\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n\tconst mod: any = await import(pkg).catch(() => {\n\t\tthrow new Error('[billing:stripe] stripe is required: npm install stripe');\n\t});\n\n\tconst Stripe = mod.default ?? mod;\n\t_client = new Stripe(secretKey, { apiVersion: '2024-11-20.acacia' });\n\treturn _client;\n}\n\nfunction normalizeSubscription(sub: IStripeSubscriptionRaw): INormalizedSubscription {\n\treturn {\n\t\tsubscriberType: (sub.metadata?.['subscriberType'] ?? 'workspace') as SubscriberType,\n\t\tsubscriberId: sub.metadata?.['subscriberId'] ?? '',\n\t\tplan: sub.items.data[0]?.price.nickname ?? 'unknown',\n\t\tstatus: sub.status,\n\t\tproviderCustomerId: sub.customer,\n\t\tproviderSubscriptionId: sub.id,\n\t\tcurrentPeriodStart: new Date(sub.current_period_start * 1000),\n\t\tcurrentPeriodEnd: new Date(sub.current_period_end * 1000),\n\t\tcancelAtPeriodEnd: sub.cancel_at_period_end,\n\t\ttrialEndsAt: sub.trial_end ? new Date(sub.trial_end * 1000) : null,\n\t};\n}\n\nexport class StripeProvider implements IBillingProvider {\n\treadonly name = 'stripe';\n\n\tconstructor(\n\t\tprivate secretKey: string,\n\t\tprivate webhookSecret?: string,\n\t) {}\n\n\tprivate async client(): Promise<any> {\n\t\treturn getClient(this.secretKey);\n\t}\n\n\tasync createCustomer(opts: {\n\t\temail: string;\n\t\tsubscriberType: SubscriberType;\n\t\tsubscriberId: string;\n\t\tuserId: string;\n\t}): Promise<{ customerId: string }> {\n\t\tconst stripe = await this.client();\n\t\tconst customer = await stripe.customers.create({\n\t\t\temail: opts.email,\n\t\t\tmetadata: {\n\t\t\t\tsubscriberType: opts.subscriberType,\n\t\t\t\tsubscriberId: opts.subscriberId,\n\t\t\t\tuserId: opts.userId,\n\t\t\t},\n\t\t});\n\t\treturn { customerId: customer.id };\n\t}\n\n\tasync createCheckoutSession(opts: {\n\t\tcustomerId: string;\n\t\tpriceId: string;\n\t\tsubscriberType: SubscriberType;\n\t\tsubscriberId: string;\n\t\ttrialDays?: number;\n\t\tsuccessUrl: string;\n\t\tcancelUrl: string;\n\t}): Promise<{ url: string }> {\n\t\tconst stripe = await this.client();\n\t\tconst session = await stripe.checkout.sessions.create({\n\t\t\tcustomer: opts.customerId,\n\t\t\tmode: 'subscription',\n\t\t\tline_items: [{ price: opts.priceId, quantity: 1 }],\n\t\t\tsuccess_url: opts.successUrl,\n\t\t\tcancel_url: opts.cancelUrl,\n\t\t\tsubscription_data: {\n\t\t\t\tmetadata: {\n\t\t\t\t\tsubscriberType: opts.subscriberType,\n\t\t\t\t\tsubscriberId: opts.subscriberId,\n\t\t\t\t},\n\t\t\t\t...(opts.trialDays && opts.trialDays > 0 ? { trial_period_days: opts.trialDays } : {}),\n\t\t\t},\n\t\t});\n\t\treturn { url: session.url ?? '' };\n\t}\n\n\tasync createPortalSession(opts: {\n\t\tcustomerId: string;\n\t\treturnUrl: string;\n\t}): Promise<{ url: string }> {\n\t\tconst stripe = await this.client();\n\t\tconst session = await stripe.billingPortal.sessions.create({\n\t\t\tcustomer: opts.customerId,\n\t\t\treturn_url: opts.returnUrl,\n\t\t});\n\t\treturn { url: session.url };\n\t}\n\n\tasync constructEvent(opts: {\n\t\tpayload: string;\n\t\tsignature: string;\n\t\tsecret: string;\n\t}): Promise<IBillingEvent> {\n\t\tconst stripe = await this.client();\n\n\t\tlet raw: IStripeEventRaw;\n\t\ttry {\n\t\t\traw = stripe.webhooks.constructEvent(opts.payload, opts.signature, opts.secret);\n\t\t} catch {\n\t\t\tthrow new Error('[billing:stripe] Invalid webhook signature');\n\t\t}\n\n\t\tconst isSubscriptionEvent = [\n\t\t\t'customer.subscription.created',\n\t\t\t'customer.subscription.updated',\n\t\t\t'customer.subscription.deleted',\n\t\t].includes(raw.type);\n\n\t\tif (!isSubscriptionEvent) {\n\t\t\treturn { type: raw.type, subscription: null };\n\t\t}\n\n\t\tconst sub = raw.data.object as IStripeSubscriptionRaw;\n\n\t\tif (raw.type === 'customer.subscription.deleted') {\n\t\t\treturn {\n\t\t\t\ttype: raw.type,\n\t\t\t\tsubscription: { ...normalizeSubscription(sub), plan: 'free', status: 'canceled' },\n\t\t\t};\n\t\t}\n\n\t\treturn { type: raw.type, subscription: normalizeSubscription(sub) };\n\t}\n}\n","import { setApiResponse, HTTP } from '@fonderie/core';\nimport type { Middleware } from '@fonderie/core';\nimport type { IFonderieContext } from '@fonderie/core';\nimport type { IStoreAdapter } from '@fonderie/store';\n\nimport { getSubscription } from '../services/subscriptions';\nimport { resolveSubscriber } from '../utils';\n\n// Gates a route behind a minimum plan.\n// Works for both user-level and workspace-level subscriptions.\n// Usage: requirePlan(['pro', 'enterprise'], store)\n\nfunction makeHandler(plans: string | string[], store: IStoreAdapter): Middleware {\n\tconst allowed = Array.isArray(plans) ? plans : [plans];\n\n\treturn async (ctx, next) => {\n\t\tif (!ctx.user) {\n\t\t\treturn setApiResponse(HTTP.UNAUTHORIZED, 'UNAUTHORIZED', 'Unauthorized');\n\t\t}\n\n\t\tconst subscriber = resolveSubscriber(ctx);\n\t\tif (!subscriber) {\n\t\t\treturn setApiResponse(HTTP.BAD_REQUEST, 'SUBSCRIBER_REQUIRED', 'Subscriber context required');\n\t\t}\n\n\t\tconst subscription = await getSubscription(subscriber.type, subscriber.id, store);\n\n\t\tif (!subscription || !allowed.includes(subscription.plan)) {\n\t\t\treturn setApiResponse(\n\t\t\t\tHTTP.PAYMENT_REQUIRED,\n\t\t\t\t'PLAN_UPGRADE_REQUIRED',\n\t\t\t\t'Plan upgrade required',\n\t\t\t\t{ required: allowed, current: subscription?.plan ?? 'none' },\n\t\t\t);\n\t\t}\n\n\t\tif (subscription.status !== 'active' && subscription.status !== 'trialing') {\n\t\t\treturn setApiResponse(\n\t\t\t\tHTTP.PAYMENT_REQUIRED,\n\t\t\t\t'SUBSCRIPTION_INACTIVE',\n\t\t\t\t'Subscription is not active',\n\t\t\t\t{ status: subscription.status },\n\t\t\t);\n\t\t}\n\n\t\treturn next();\n\t};\n}\n\nexport function requirePlan(plans: string | string[], store: IStoreAdapter): Middleware;\nexport function requirePlan(\n\tplans: string | string[],\n\tstore: IStoreAdapter,\n\tctx: IFonderieContext,\n\tnext: () => Promise<Response>,\n): Promise<Response>;\nexport function requirePlan(\n\tplans: string | string[],\n\tstore: IStoreAdapter,\n\tctx?: IFonderieContext,\n\tnext?: () => Promise<Response>,\n): Middleware | Promise<Response> {\n\tconst handler = makeHandler(plans, store);\n\tif (ctx !== undefined && next !== undefined) return handler(ctx, next);\n\treturn handler;\n}\n","import { setApiResponse, HTTP } from '@fonderie/core';\nimport type { IFonderieContext } from '@fonderie/core';\nimport type { Middleware } from '@fonderie/core';\nimport type { IBillingContext, IPolicyStatus } from './types';\n\nfunction getBillingContext(ctx: IFonderieContext): IBillingContext | null {\n\treturn (ctx.meta['billing'] as IBillingContext | undefined) ?? null;\n}\n\n// Returns true if the feature flag is enabled on the subscriber's plan.\n// Returns true when no billing context is present (fail-open when billing not configured).\nexport function hasFeature(ctx: IFonderieContext, key: string): boolean {\n\tconst billing = getBillingContext(ctx);\n\tif (!billing) return true;\n\n\tconst status = billing.statuses[key];\n\tif (!status) return true; // key not declared in policy → allow\n\tif (status.type === 'feature') return status.enabled;\n\treturn true; // counter entry = feature present\n}\n\n// Returns the advertised limit for a counter policy key, or null if unlimited / not configured.\nexport function getPlanLimit(ctx: IFonderieContext, key: string): number | null {\n\tconst billing = getBillingContext(ctx);\n\tif (!billing) return null;\n\n\tconst status = billing.statuses[key];\n\tif (!status || status.type === 'feature') return null;\n\treturn status.limit;\n}\n\n// Returns the full policy status for a key, or null if not configured.\nexport function getLimitStatus(ctx: IFonderieContext, key: string): IPolicyStatus | null {\n\tconst billing = getBillingContext(ctx);\n\tif (!billing) return null;\n\treturn billing.statuses[key] ?? null;\n}\n\n// Middleware — gates a route behind a feature flag.\n// Reads from cached ctx.meta['billing']; no store arg, no async DB call.\n// Fails open if billing context is absent (billing module not registered).\nexport function requireFeature(key: string): Middleware {\n\treturn (ctx, next) => {\n\t\tif (!hasFeature(ctx, key)) {\n\t\t\treturn Promise.resolve(\n\t\t\t\tsetApiResponse(\n\t\t\t\t\tHTTP.PAYMENT_REQUIRED,\n\t\t\t\t\t'FEATURE_UNAVAILABLE',\n\t\t\t\t\t`Feature '${key}' is not available on your current plan`,\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\t\treturn next();\n\t};\n}\n"],"mappings":";;;;;;;AAEA,SAAS,aAAa,gBAAgB;;;ACFtC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,SAAS,SAAS;AAMlB,IAAM,aAAa;AAAA,EAClB,aAAa,EAAE,OAAO,EAAE,IAAI,GAAI,EAAE,SAAS,EAAE,SAAS;AAAA,EACtD,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,SAAS;AAAA,EACvC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,SAAS,EAAE,SAAS;AAAA,EACnD,WAAW,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,SAAS;AAAA,EAC5C,eAAe,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,SAAS,EAAE,SAAS;AAAA,EACrD,gBAAgB,EAAE,OAAO,EAAE,IAAI,GAAG,EAAE,SAAS,EAAE,SAAS;AAAA,EACxD,cAAc,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,SAAS,EAAE,SAAS;AAAA,EACpD,eAAe,EAAE,OAAO,EAAE,IAAI,GAAG,EAAE,SAAS,EAAE,SAAS;AAAA,EACvD,UAAU,EAAE,QAAQ,EAAE,SAAS;AAAA,EAC/B,UAAU,EAAE,QAAQ,EAAE,SAAS;AAChC;AAEO,IAAM,mBAAmB,EAAE,OAAO;AAAA,EACxC,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,GAAG,kBAAkB,EAAE,IAAI,GAAG;AAAA,EAC1D,GAAG;AACJ,CAAC;AAEM,IAAM,mBAAmB,EAC9B,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,SAAS,GAAG,GAAG,WAAW,CAAC,EAC5E,OAAO,CAAC,MAAM,OAAO,OAAO,CAAC,EAAE,KAAK,CAAC,MAAM,MAAM,MAAS,GAAG,4BAA4B;AAEpF,IAAM,iBAAiB,EAAE,OAAO;AAAA,EACtC,MAAM,EAAE,OAAO,EAAE,IAAI,GAAG,kBAAkB;AAAA,EAC1C,UAAU,EAAE,KAAK,CAAC,SAAS,MAAM,CAAC,EAAE,SAAS;AAC9C,CAAC;AAEM,IAAM,oBAAoB,EAAE,OAAO;AAAA,EACzC,QAAQ,EAAE,OAAO,EAAE,IAAI,GAAG,oBAAoB,EAAE,IAAI,GAAG;AAAA,EACvD,UAAU,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,SAAS;AACtC,CAAC;;;ACpCD,SAAS,gBAAgB,MAAM,eAAe,oBAAoB;;;ACK3D,SAAS,SAAS,QAAwC;AAChE,SAAO,OAAO;AACf;AAEO,SAAS,cAAc,MAAc,QAA6C;AACxF,SAAO,OAAO,MAAM,KAAK,CAAC,MAAM,EAAE,KAAK,YAAY,MAAM,KAAK,YAAY,CAAC,KAAK;AACjF;AAEA,eAAsB,cAAc,QAAwB,OAAqC;AAChG,QAAM,QAAQ,OAAO;AACrB,MAAI,MAAM,WAAW,EAAG;AAExB,QAAM,SAAS,MAAM,IAAI,CAAC,GAAG,MAAM;AAClC,UAAM,IAAI,IAAI;AACd,WAAO,KAAK,IAAI,CAAC,MAAM,IAAI,CAAC,MAAM,IAAI,CAAC,MAAM,IAAI,CAAC,MAAM,IAAI,CAAC,MAAM,IAAI,CAAC,MAAM,IAAI,CAAC,MAAM,IAAI,CAAC,MAAM,IAAI,CAAC;AAAA,EAC1G,CAAC;AAED,QAAM,SAAS,MAAM,QAAQ,CAAC,SAAS;AAAA,IACtC,KAAK;AAAA,IACL,KAAK,aAAa;AAAA,IAClB,KAAK,SAAS,UAAU;AAAA,IACxB,KAAK,SAAS,WAAW;AAAA,IACzB,KAAK,QAAQ,UAAU;AAAA,IACvB,KAAK,QAAQ,WAAW;AAAA,IACxB,KAAK,eAAe;AAAA,IACpB,KAAK,QAAQ;AAAA,IACb,KAAK,UAAU,KAAK,YAAY,CAAC,CAAC;AAAA,EACnC,CAAC;AAED,QAAM,MAAM;AAAA,IACX;AAAA;AAAA;AAAA;AAAA;AAAA,WAKS,OAAO,KAAK,IAAI,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAU1B;AAAA,EACD;AACD;AAEA,IAAM,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAgBpB,eAAsB,WAAW,OAAwC;AACxE,SAAO,MAAM;AAAA,IACZ,GAAG,WAAW;AAAA,EACf;AACD;AAEA,eAAsB,YAAY,IAAY,OAA6C;AAC1F,QAAM,CAAC,GAAG,IAAI,MAAM,MAAM,MAAa,GAAG,WAAW,kBAAkB,CAAC,EAAE,CAAC;AAC3E,SAAO,OAAO;AACf;AAEA,eAAsB,WACrB,MAaA,OACiB;AACjB,QAAM,CAAC,GAAG,IAAI,MAAM,MAAM;AAAA,IACzB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAYA;AAAA,MACC,KAAK;AAAA,MACL,KAAK,SAAS;AAAA,MACd,KAAK,aAAa;AAAA,MAClB,KAAK,iBAAiB;AAAA,MACtB,KAAK,kBAAkB;AAAA,MACvB,KAAK,gBAAgB;AAAA,MACrB,KAAK,iBAAiB;AAAA,MACtB,KAAK,eAAe;AAAA,MACpB,KAAK,QAAQ;AAAA,MACb,KAAK,UAAU,KAAK,YAAY,CAAC,CAAC;AAAA,MAClC,KAAK,UAAU,KAAK,YAAY,CAAC,CAAC;AAAA,IACnC;AAAA,EACD;AACA,MAAI,CAAC,IAAK,OAAM,IAAI,MAAM,uBAAuB;AACjD,SAAO;AACR;AAEA,eAAsB,WACrB,IACA,MACA,OACwB;AACxB,QAAM,WAAmC;AAAA,IACxC,MAAM;AAAA,IACN,OAAO;AAAA,IACP,WAAW;AAAA,IACX,eAAe;AAAA,IACf,gBAAgB;AAAA,IAChB,cAAc;AAAA,IACd,eAAe;AAAA,IACf,aAAa;AAAA,IACb,MAAM;AAAA,EACP;AAEA,QAAM,cAAc,oBAAI,IAAI,CAAC,YAAY,UAAU,CAAC;AACpD,QAAM,aAAuB,CAAC;AAC9B,QAAM,SAAoB,CAAC,EAAE;AAE7B,aAAW,CAAC,KAAK,GAAG,KAAK,OAAO,QAAQ,QAAQ,GAAG;AAClD,QAAI,OAAO,MAAM;AAChB,aAAO,KAAM,KAAiC,GAAG,CAAC;AAClD,iBAAW,KAAK,GAAG,GAAG,OAAO,OAAO,MAAM,EAAE;AAAA,IAC7C;AAAA,EACD;AAEA,aAAW,OAAO,aAAa;AAC9B,QAAI,OAAO,MAAM;AAChB,aAAO,KAAK,KAAK,UAAW,KAAiC,GAAG,CAAC,CAAC;AAClE,iBAAW,KAAK,GAAG,GAAG,OAAO,OAAO,MAAM,SAAS;AAAA,IACpD;AAAA,EACD;AAEA,MAAI,WAAW,WAAW,EAAG,QAAO,YAAY,IAAI,KAAK;AAEzD,QAAM,CAAC,GAAG,IAAI,MAAM,MAAM;AAAA,IACzB,6BAA6B,WAAW,KAAK,IAAI,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAUlD;AAAA,EACD;AACA,SAAO,OAAO;AACf;AAEA,eAAsB,WAAW,IAAY,OAAwC;AACpF,QAAM,OAAO,MAAM,MAAM;AAAA,IACxB;AAAA,IACA,CAAC,EAAE;AAAA,EACJ;AACA,SAAO,KAAK,SAAS;AACtB;;;AC7KO,IAAM,YAAN,MAAgB;AAAA,EACtB,YAA6B,OAAsB;AAAtB;AAAA,EAAuB;AAAA,EAAvB;AAAA,EAE7B,eAAe,QAAwB;AACtC,WAAO,SAAS,MAAM;AAAA,EACvB;AAAA,EAEA,mBAAmB,MAAc,QAAwB;AACxD,WAAO,cAAc,MAAM,MAAM;AAAA,EAClC;AAAA,EAEA,OAAyB;AACxB,WAAO,WAAW,KAAK,KAAK;AAAA,EAC7B;AAAA,EAEA,SAAS,IAAmC;AAC3C,WAAO,YAAY,IAAI,KAAK,KAAK;AAAA,EAClC;AAAA,EAEA,OAAO,MAAwD;AAC9D,WAAO,WAAW,MAAM,KAAK,KAAK;AAAA,EACnC;AAAA,EAEA,OAAO,IAAY,MAA+D;AACjF,WAAO,WAAW,IAAI,MAAM,KAAK,KAAK;AAAA,EACvC;AAAA,EAEA,OAAO,IAA8B;AACpC,WAAO,WAAW,IAAI,KAAK,KAAK;AAAA,EACjC;AACD;;;ACFO,SAAS,UAAU,MAAuB;AAChD,SAAO;AAAA,IACN,IAAI,KAAK;AAAA,IACT,QAAQ,KAAK,KAAK,YAAY;AAAA,IAC9B,MAAM,KAAK;AAAA,IACX,aAAa,KAAK,eAAe;AAAA,IACjC,MAAM,KAAK;AAAA,IACX,OAAO,KAAK;AAAA,IACZ,WAAW,KAAK;AAAA,IAChB,SAAS;AAAA,MACR,SAAS,KAAK,iBAAiB;AAAA,MAC/B,QAAQ,KAAK,gBAAgB;AAAA,MAC7B,UAAU;AAAA,IACX;AAAA,IACA,UAAU,MAAM,QAAQ,KAAK,QAAQ,IAAI,KAAK,WAAW,CAAC;AAAA,IAC1D,UACC,KAAK,YAAY,OAAO,KAAK,aAAa,WACtC,KAAK,WACN,CAAC;AAAA,EACN;AACD;AAEO,SAAS,kBAAkB,KAAsC;AACvE,SAAO;AAAA,IACN,IAAI,IAAI;AAAA,IACR,gBAAgB,IAAI;AAAA,IACpB,cAAc,IAAI;AAAA,IAClB,MAAM,IAAI;AAAA,IACV,UAAU,IAAI;AAAA,IACd,QAAQ,IAAI;AAAA,IACZ,mBAAmB,IAAI;AAAA,IACvB,oBAAoB,IAAI;AAAA,IACxB,kBAAkB,IAAI;AAAA,IACtB,aAAa,IAAI;AAAA,IACjB,WAAW,IAAI;AAAA,EAChB;AACD;AAEO,SAAS,iBAAiB,QAAuC;AACvE,SAAO;AAAA,IACN,IAAI,OAAO;AAAA,IACX,gBAAgB,OAAO;AAAA,IACvB,cAAc,OAAO;AAAA,IACrB,QAAQ,OAAO;AAAA,IACf,UAAU,OAAO;AAAA,IACjB,YAAY,OAAO;AAAA,EACpB;AACD;;;AHlFO,SAAS,eAAe,OAAsB;AACpD,QAAM,QAAQ,IAAI,UAAU,KAAK;AAEjC,SAAO;AAAA,IACN,MAAM,KAAK,MAA2C;AACrD,YAAM,OAAO,MAAM,MAAM,KAAK;AAC9B,YAAM,OAAO,KAAK,IAAI,SAAS;AAC/B,aAAO,eAAe,KAAK,IAAI,aAAa,aAAa,KAAK,MAAM,oBAAoB;AAAA,QACvF,OAAO;AAAA,MACR,CAAC;AAAA,IACF;AAAA,IAEA,MAAM,IAAI,KAA0C;AACnD,YAAM,SAAS,IAAI,KAAK,QAAQ;AAChC,YAAM,KAAK,SAAS,QAAQ;AAC5B,UAAI,CAAC,GAAI,QAAO,eAAe,KAAK,aAAa,qBAAqB,kBAAkB;AAExF,YAAM,OAAO,MAAM,MAAM,SAAS,EAAE;AACpC,UAAI,CAAC,KAAM,QAAO,eAAe,KAAK,WAAW,aAAa,gBAAgB;AAE9E,aAAO,eAAe,KAAK,IAAI,gBAAgB,gCAAgC;AAAA,QAC9E,MAAM,UAAU,IAAI;AAAA,MACrB,CAAC;AAAA,IACF;AAAA,IAEA,MAAM,OAAO,KAA0C;AACtD,YAAM,OAAO,IAAI,KAAK,MAAM;AAC5B,YAAM,OAAO,cAAc,OAAO,MAAM,CAAC;AACzC,UAAI,CAAC,MAAM;AACV,eAAO,eAAe,KAAK,eAAe,oBAAoB,kBAAkB;AAAA,MACjF;AAEA,YAAM,OAAO,MAAM,MAAM,OAAO;AAAA,QAC/B;AAAA,QACA,aAAkB,OAAO,aAAa,KAAU,OAAO,OAAO,KAAK,aAAa,CAAC,IAAS;AAAA,QAC1F,MAAkB,OAAO,MAAM,KAAkB,OAAO,aAAa,KAAK,MAAM,CAAC,IAAW;AAAA,QAC5F,OAAkB,OAAO,OAAO,KAAiB,OAAO,aAAa,KAAK,OAAO,CAAC,IAAU;AAAA,QAC5F,WAAkB,OAAO,WAAW,KAAa,OAAO,aAAa,KAAK,WAAW,CAAC,IAAM;AAAA,QAC5F,eAAkB,OAAO,eAAe,KAAS,OAAO,aAAa,KAAK,eAAe,CAAC,IAAI;AAAA,QAC9F,gBAAkB,OAAO,gBAAgB,KAAO,OAAO,OAAO,KAAK,gBAAgB,CAAC,IAAM;AAAA,QAC1F,cAAkB,OAAO,cAAc,KAAU,OAAO,aAAa,KAAK,cAAc,CAAC,IAAK;AAAA,QAC9F,eAAkB,OAAO,eAAe,KAAQ,OAAO,OAAO,KAAK,eAAe,CAAC,IAAO;AAAA,QAC1F,UAAkB,OAAO,UAAU;AAAA,QACnC,UAAkB,OAAO,UAAU;AAAA,MACpC,CAAC;AAED,aAAO,eAAe,KAAK,SAAS,gBAAgB,8BAA8B;AAAA,QACjF,MAAM,UAAU,IAAI;AAAA,MACrB,CAAC;AAAA,IACF;AAAA,IAEA,MAAM,OAAO,KAA0C;AACtD,YAAM,SAAS,IAAI,KAAK,QAAQ;AAChC,YAAM,KAAK,SAAS,QAAQ;AAC5B,UAAI,CAAC,IAAI;AACR,eAAO,eAAe,KAAK,aAAa,qBAAqB,kBAAkB;AAAA,MAChF;AAEA,YAAM,OAAO,IAAI,KAAK,MAAM;AAC5B,UAAI,CAAC,QAAQ,OAAO,KAAK,IAAI,EAAE,WAAW,GAAG;AAC5C,eAAO,eAAe,KAAK,eAAe,oBAAoB,uBAAuB;AAAA,MACtF;AAEA,YAAM,QAAiC,CAAC;AACxC,YAAM,UAAU;AAAA,QAAC;AAAA,QAAQ;AAAA,QAAe;AAAA,QAAQ;AAAA,QAAS;AAAA,QACxD;AAAA,QAAiB;AAAA,QAAkB;AAAA,QAAgB;AAAA,QACnD;AAAA,QAAY;AAAA,MAAU;AAEvB,iBAAW,OAAO,SAAS;AAC1B,YAAI,OAAO,KAAM,OAAM,GAAG,IAAI,KAAK,GAAG;AAAA,MACvC;AAEA,YAAM,OAAO,MAAM,MAAM,OAAO,IAAI,KAAK;AACzC,UAAI,CAAC,MAAM;AACV,eAAO,eAAe,KAAK,WAAW,aAAa,gBAAgB;AAAA,MACpE;AAEA,aAAO,eAAe,KAAK,IAAI,gBAAgB,8BAA8B;AAAA,QAC5E,MAAM,UAAU,IAAI;AAAA,MACrB,CAAC;AAAA,IACF;AAAA,IAEA,MAAM,OAAO,KAA0C;AACtD,YAAM,SAAS,IAAI,KAAK,QAAQ;AAChC,YAAM,KAAK,SAAS,QAAQ;AAC5B,UAAI,CAAC,IAAI;AACR,eAAO,eAAe,KAAK,aAAa,qBAAqB,kBAAkB;AAAA,MAChF;AAEA,YAAM,UAAU,MAAM,MAAM,OAAO,EAAE;AACrC,UAAI,CAAC,SAAS;AACb,eAAO,eAAe,KAAK,WAAW,aAAa,gBAAgB;AAAA,MACpE;AAEA,aAAO,eAAe,KAAK,IAAI,gBAAgB,4BAA4B;AAAA,IAC5E;AAAA,EACD;AACD;;;AIxGA,SAAS,kBAAAA,iBAAgB,QAAAC,aAAY;;;ACIrC,IAAM,sBAAsB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAiB5B,eAAsB,gBACrB,gBACA,cACA,OACgC;AAChC,QAAM,CAAC,GAAG,IAAI,MAAM,MAAM;AAAA,IACzB,GAAG,mBAAmB;AAAA,IACtB,CAAC,gBAAgB,YAAY;AAAA,EAC9B;AACA,SAAO,OAAO;AACf;AAEA,eAAsB,mBACrB,MAaA,OACgB;AAChB,QAAM,MAAM;AAAA,IACX;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAgBA;AAAA,MACC,KAAK;AAAA,MACL,KAAK;AAAA,MACL,KAAK;AAAA,MACL,KAAK,YAAY;AAAA,MACjB,KAAK;AAAA,MACL,KAAK,sBAAsB;AAAA,MAC3B,KAAK,0BAA0B;AAAA,MAC/B,KAAK,sBAAsB;AAAA,MAC3B,KAAK,oBAAoB;AAAA,MACzB,KAAK,qBAAqB;AAAA,MAC1B,KAAK,eAAe;AAAA,IACrB;AAAA,EACD;AACD;;;AC3EO,IAAM,oBAAN,MAAwB;AAAA,EAC9B,YAA6B,OAAsB;AAAtB;AAAA,EAAuB;AAAA,EAAvB;AAAA,EAE7B,IAAI,gBAAgC,cAAqD;AACxF,WAAO,gBAAgB,gBAAgB,cAAc,KAAK,KAAK;AAAA,EAChE;AAAA,EAEA,OAAO,MAA+D;AACrE,WAAO,mBAAmB,MAAM,KAAK,KAAK;AAAA,EAC3C;AACD;;;ACLO,SAAS,cAAc,QAAwB;AACrD,QAAM,IAAI,SAAS,QAAQ,EAAE;AAC7B,QAAM,OAAO,OAAO,MAAM,OAAO,CAAC,EAAE,MAAM;AAC1C,UAAQ,MAAM;AAAA,IACb,KAAK;AACJ,aAAO,IAAI;AAAA,IACZ,KAAK;AACJ,aAAO,IAAI;AAAA,IACZ,KAAK;AACJ,aAAO,IAAI;AAAA,IACZ;AACC,YAAM,IAAI,MAAM,yBAAyB,IAAI,SAAS,MAAM,GAAG;AAAA,EACjE;AACD;AAIO,SAAS,kBAAkB,KAA2C;AAC5E,QAAM,eAAe,IAAI,QAAQ,QAAQ,IAAI,gBAAgB;AAE7D,MAAI,cAAc;AACjB,WAAO;AAAA,MACN,MAAM;AAAA,MACN,IAAI;AAAA,IACL;AAAA,EACD;AAEA,MAAI,IAAI,WAAW,IAAI;AACtB,WAAO;AAAA,MACN,MAAM;AAAA,MACN,IAAI,IAAI,UAAU;AAAA,IACnB;AAAA,EACD;AAEA,MAAI,IAAI,MAAM,IAAI;AACjB,WAAO;AAAA,MACN,MAAM;AAAA,MACN,IAAI,IAAI,KAAK;AAAA,IACd;AAAA,EACD;AAEA,SAAO;AACR;;;AH5CO,SAAS,uBAAuB,OAAsB;AAC5D,QAAM,gBAAgB,IAAI,kBAAkB,KAAK;AAEjD,SAAO;AAAA,IACN,MAAM,IAAI,KAA0C;AACnD,YAAM,aAAa,kBAAkB,GAAG;AACxC,UAAI,CAAC,YAAY;AAChB,eAAOC;AAAA,UACNC,MAAK;AAAA,UACL;AAAA,UACA;AAAA,QACD;AAAA,MACD;AAEA,YAAM,eAAe,MAAM,cAAc,IAAI,WAAW,MAAM,WAAW,EAAE;AAC3E,UAAI,CAAC;AACJ,eAAOD,gBAAeC,MAAK,WAAW,aAAa,wBAAwB;AAE5E,aAAOD;AAAA,QACNC,MAAK;AAAA,QACL;AAAA,QACA;AAAA,QACA;AAAA,UACC,cAAc,kBAAkB,YAAY;AAAA,QAC7C;AAAA,MACD;AAAA,IACD;AAAA,EACD;AACD;;;AIpCA,SAAS,kBAAAC,iBAAgB,QAAAC,aAAY;AAS9B,SAAS,mBAAmB,OAAsB,QAAwB;AAChF,QAAM,QAAQ,IAAI,UAAU,KAAK;AACjC,QAAM,gBAAgB,IAAI,kBAAkB,KAAK;AAEjD,SAAO;AAAA,IACN,MAAM,cAAc,KAA0C;AAC7D,YAAM,OAAO,IAAI,KAAK,MAAM;AAC5B,YAAM,WAAW,OAAO,MAAM;AAC9B,YAAM,WAAY,OAAO,UAAU,KAAK;AACxC,YAAM,aAAa,kBAAkB,GAAG;AAExC,UAAI,OAAO,aAAa,UAAU;AACjC,eAAOC,gBAAeC,MAAK,eAAe,qBAAqB,kBAAkB;AAAA,MAClF;AACA,UAAI,aAAa,WAAW,aAAa,QAAQ;AAChD,eAAOD;AAAA,UACNC,MAAK;AAAA,UACL;AAAA,UACA;AAAA,QACD;AAAA,MACD;AACA,UAAI,CAAC,YAAY;AAChB,eAAOD;AAAA,UACNC,MAAK;AAAA,UACL;AAAA,UACA;AAAA,QACD;AAAA,MACD;AAEA,YAAM,OAAO,MAAM,mBAAmB,UAAU,MAAM;AACtD,UAAI,CAAC,MAAM;AACV,eAAOD,gBAAeC,MAAK,eAAe,qBAAqB,iBAAiB,QAAQ,EAAE;AAAA,MAC3F;AAEA,YAAM,UAAU,aAAa,SAAS,KAAK,SAAS,KAAK;AACzD,UAAI,CAAC,SAAS,SAAS;AACtB,eAAOD;AAAA,UACNC,MAAK;AAAA,UACL;AAAA,UACA,QAAQ,QAAQ,qBAAqB,QAAQ;AAAA,QAC9C;AAAA,MACD;AAEA,YAAM,EAAE,WAAW,IAAI,MAAM,OAAO,SAAS,eAAe;AAAA,QAC3D,OAAO,IAAI,KAAM,SAAS;AAAA,QAC1B,gBAAgB,WAAW;AAAA,QAC3B,cAAc,WAAW;AAAA,QACzB,QAAQ,IAAI,KAAM;AAAA,MACnB,CAAC;AAED,YAAM,cAA2E;AAAA,QAChF;AAAA,QACA,SAAS,QAAQ;AAAA,QACjB,gBAAgB,WAAW;AAAA,QAC3B,cAAc,WAAW;AAAA,QACzB,YAAY,OAAO;AAAA,QACnB,WAAW,OAAO;AAAA,MACnB;AACA,UAAI,KAAK,cAAc,OAAW,aAAY,YAAY,KAAK;AAE/D,YAAM,EAAE,IAAI,IAAI,MAAM,OAAO,SAAS,sBAAsB,WAAW;AAEvE,YAAM,cAAc,OAAO;AAAA,QAC1B,gBAAgB,WAAW;AAAA,QAC3B,cAAc,WAAW;AAAA,QACzB,MAAM;AAAA,QACN;AAAA,QACA,QAAQ;AAAA,QACR,oBAAoB;AAAA,MACrB,CAAC;AAED,aAAOD,gBAAeC,MAAK,IAAI,gBAAgB,6BAA6B,EAAE,IAAI,CAAC;AAAA,IACpF;AAAA,IAEA,MAAM,aAAa,KAA0C;AAC5D,YAAM,aAAa,kBAAkB,GAAG;AACxC,UAAI,CAAC,YAAY;AAChB,eAAOD;AAAA,UACNC,MAAK;AAAA,UACL;AAAA,UACA;AAAA,QACD;AAAA,MACD;AAEA,YAAM,eAAe,MAAM,cAAc,IAAI,WAAW,MAAM,WAAW,EAAE;AAC3E,UAAI,CAAC,cAAc,oBAAoB;AACtC,eAAOD,gBAAeC,MAAK,WAAW,aAAa,wBAAwB;AAAA,MAC5E;AAEA,YAAM,EAAE,IAAI,IAAI,MAAM,OAAO,SAAS,oBAAoB;AAAA,QACzD,YAAY,aAAa;AAAA,QACzB,WAAW,OAAO;AAAA,MACnB,CAAC;AAED,aAAOD,gBAAeC,MAAK,IAAI,cAAc,2BAA2B,EAAE,IAAI,CAAC;AAAA,IAChF;AAAA,EACD;AACD;;;AC1GA,SAAS,kBAAAC,iBAAgB,QAAAC,aAAY;;;ACGrC,eAAsB,YACrB,MACA,OACgB;AAChB,QAAM,MAAM;AAAA,IACX;AAAA;AAAA,IAEA,CAAC,KAAK,gBAAgB,KAAK,cAAc,KAAK,QAAQ,KAAK,QAAQ;AAAA,EACpE;AACD;AAEA,eAAsB,SACrB,gBACA,cACA,QACA,OACA,OACkB;AAClB,QAAM,OAAO,MAAM,MAAM;AAAA,IACxB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAMA,CAAC,gBAAgB,cAAc,QAAQ,KAAK;AAAA,EAC7C;AACA,SAAO,SAAS,KAAK,CAAC,GAAG,SAAS,KAAK,EAAE;AAC1C;;;AC1BO,IAAM,aAAN,MAAiB;AAAA,EACvB,YAA6B,OAAsB;AAAtB;AAAA,EAAuB;AAAA,EAAvB;AAAA,EAE7B,OAAO,MAAwD;AAC9D,WAAO,YAAY,MAAM,KAAK,KAAK;AAAA,EACpC;AAAA,EAEA,IACC,gBACA,cACA,QACA,OACkB;AAClB,WAAO,SAAS,gBAAgB,cAAc,QAAQ,OAAO,KAAK,KAAK;AAAA,EACxE;AACD;;;AFbO,SAAS,gBAAgB,OAAsB;AACrD,QAAM,QAAQ,IAAI,WAAW,KAAK;AAElC,SAAO;AAAA,IACN,MAAM,OAAO,KAA0C;AACtD,YAAM,OAAO,IAAI,KAAK,MAAM;AAC5B,YAAM,SAAS,OAAO,QAAQ;AAC9B,YAAM,WAAW,OAAO,UAAU;AAClC,YAAM,aAAa,kBAAkB,GAAG;AAExC,UAAI,CAAC,YAAY;AAChB,eAAOC;AAAA,UACNC,MAAK;AAAA,UACL;AAAA,UACA;AAAA,QACD;AAAA,MACD;AACA,UAAI,OAAO,WAAW,UAAU;AAC/B,eAAOD,gBAAeC,MAAK,eAAe,qBAAqB,oBAAoB;AAAA,MACpF;AAEA,YAAM,MAAM,OAAO;AAAA,QAClB,gBAAgB,WAAW;AAAA,QAC3B,cAAc,WAAW;AAAA,QACzB;AAAA,QACA,UAAU,OAAO,aAAa,WAAW,WAAW;AAAA,MACrD,CAAC;AACD,aAAOD,gBAAeC,MAAK,IAAI,kBAAkB,8BAA8B;AAAA,IAChF;AAAA,IAEA,MAAM,IAAI,KAA0C;AACnD,YAAM,SAAS,IAAI,KAAK,QAAQ;AAChC,YAAM,SAAS,SAAS,QAAQ;AAChC,YAAM,aAAa,kBAAkB,GAAG;AAExC,UAAI,CAAC,cAAc,CAAC,QAAQ;AAC3B,eAAOD;AAAA,UACNC,MAAK;AAAA,UACL;AAAA,UACA;AAAA,QACD;AAAA,MACD;AAEA,YAAM,QAAQ,oBAAI,KAAK;AACvB,YAAM,QAAQ,CAAC;AACf,YAAM,SAAS,GAAG,GAAG,GAAG,CAAC;AAEzB,YAAM,QAAQ,MAAM,MAAM,IAAI,WAAW,MAAM,WAAW,IAAI,QAAQ,KAAK;AAC3E,aAAOD,gBAAeC,MAAK,IAAI,iBAAiB,iCAAiC;AAAA,QAChF;AAAA,QACA;AAAA,QACA;AAAA,MACD,CAAC;AAAA,IACF;AAAA,EACD;AACD;;;AG9DA,SAAS,kBAAAC,iBAAgB,QAAAC,aAAY;AAO9B,SAAS,kBAAkB,OAAsB,QAAwB;AAC/E,QAAM,gBAAgB,IAAI,kBAAkB,KAAK;AAEjD,SAAO;AAAA,IACN,MAAM,OAAO,KAA0C;AACtD,UAAI,CAAC,OAAO,eAAe;AAC1B,eAAOC,gBAAeC,MAAK,cAAc,gBAAgB,+BAA+B;AAAA,MACzF;AAEA,YAAM,YACL,IAAI,QAAQ,QAAQ,IAAI,kBAAkB,KAC1C,IAAI,QAAQ,QAAQ,IAAI,kBAAkB,KAC1C;AAED,UAAI,CAAC,WAAW;AACf,eAAOD,gBAAeC,MAAK,aAAa,mBAAmB,2BAA2B;AAAA,MACvF;AAEA,YAAM,UAAU,MAAM,IAAI,QAAQ,KAAK;AAEvC,UAAI;AACJ,UAAI;AACH,gBAAQ,MAAM,OAAO,SAAS,eAAe;AAAA,UAC5C;AAAA,UACA;AAAA,UACA,QAAQ,OAAO;AAAA,QAChB,CAAC;AAAA,MACF,QAAQ;AACP,eAAOD,gBAAeC,MAAK,aAAa,mBAAmB,2BAA2B;AAAA,MACvF;AAEA,UAAI,MAAM,cAAc;AACvB,cAAM,cAAc,OAAO;AAAA,UAC1B,gBAAgB,MAAM,aAAa;AAAA,UACnC,cAAc,MAAM,aAAa;AAAA,UACjC,MAAM,MAAM,aAAa;AAAA,UACzB,QAAQ,MAAM,aAAa;AAAA,UAC3B,oBAAoB,MAAM,aAAa;AAAA,UACvC,wBAAwB,MAAM,aAAa;AAAA,UAC3C,oBAAoB,MAAM,aAAa;AAAA,UACvC,kBAAkB,MAAM,aAAa;AAAA,UACrC,mBAAmB,MAAM,aAAa;AAAA,UACtC,aAAa,MAAM,aAAa;AAAA,QACjC,CAAC;AAAA,MACF;AAEA,aAAO,SAAS,KAAK,EAAE,UAAU,KAAK,CAAC;AAAA,IACxC;AAAA,EACD;AACD;;;AdzCO,SAAS,mBACf,OACA,QACoB;AACpB,QAAM,OAAO,eAAe,KAAK;AACjC,QAAM,eAAe,uBAAuB,KAAK;AACjD,QAAM,WAAW,mBAAmB,OAAO,MAAM;AACjD,QAAM,QAAQ,gBAAgB,KAAK;AACnC,QAAM,UAAU,kBAAkB,OAAO,MAAM;AAE/C,SAAO;AAAA;AAAA,IAEN,CAAC,OAAO,UAAU,KAAK,IAAI;AAAA,IAC3B,CAAC,OAAO,kBAAkB,KAAK,GAAG;AAAA;AAAA,IAGlC,CAAC,QAAQ,UAAU,SAAS,gBAAgB,GAAG,KAAK,MAAM;AAAA,IAC1D,CAAC,OAAO,kBAAkB,SAAS,gBAAgB,GAAG,KAAK,MAAM;AAAA,IACjE,CAAC,UAAU,kBAAkB,KAAK,MAAM;AAAA;AAAA;AAAA,IAIxC,CAAC,OAAO,yBAAyB,aAAa,aAAa,GAAG;AAAA,IAC9D,CAAC,QAAQ,qBAAqB,aAAa,SAAS,cAAc,GAAG,SAAS,aAAa;AAAA,IAC3F,CAAC,QAAQ,mBAAmB,aAAa,SAAS,YAAY;AAAA,IAC9D,CAAC,QAAQ,kBAAkB,aAAa,SAAS,iBAAiB,GAAG,MAAM,MAAM;AAAA,IACjF,CAAC,OAAO,0BAA0B,aAAa,MAAM,GAAG;AAAA;AAAA,IAGxD,CAAC,QAAQ,oBAAoB,QAAQ,MAAM;AAAA,EAC5C;AACD;;;Ae7CA,SAAS,kBAAAC,iBAAgB,QAAAC,aAAY;;;AC0C9B,IAAM,eAAe;AAAA,EAC3B,cAAc;AAAA,EACd,cAAc;AAAA,EACd,cAAc;AACf;;;AC3CO,SAAS,oBAAoB,MAOhB;AACnB,QAAM,EAAE,YAAY,MAAM,QAAQ,SAAS,IAAI;AAC/C,QAAM,WAAW,KAAK,YAAY,CAAC;AACnC,QAAM,WAA0C,CAAC;AAEjD,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,KAAK,UAAU,CAAC,CAAC,GAAG;AAC7D,QAAI,aAAa,OAAO;AACvB,eAAS,GAAG,IAAI,EAAE,MAAM,WAAW,SAAS,MAAM,QAAQ;AAC1D;AAAA,IACD;AAEA,UAAM,EAAE,OAAO,SAAS,SAAS,UAAU,GAAG,SAAS,SAAS,UAAU,KAAK,OAAO,IAAI;AAE1F,UAAM,OAAO,SAAS,GAAG,KAAK;AAC9B,UAAM,YAAY,UAAU,OAAO,QAAQ,SAAS;AAEpD,QAAI,SAAsB;AAC1B,QAAI,cAAc,QAAQ,QAAQ,UAAW,UAAS;AAAA,aAC7C,UAAU,QAAQ,QAAQ,MAAO,UAAS;AAAA,aAC1C,UAAU,QAAQ,QAAQ,QAAQ,OAAQ,UAAS;AAE5D,QAAI,WAA0B;AAC9B,QAAI,QAAQ;AACX,YAAM,WAAW,cAAc,MAAM;AACrC,YAAM,cAAc,KAAK,MAAM,KAAK,IAAI,IAAI,QAAQ,IAAI;AACxD,iBAAW,IAAI,KAAK,cAAc,QAAQ,EAAE,YAAY;AAAA,IACzD;AAEA,aAAS,GAAG,IAAI,EAAE,MAAM,WAAW,OAAO,MAAM,QAAQ,SAAS;AAAA,EAClE;AAEA,SAAO,EAAE,YAAY,MAAM,KAAK,MAAM,QAAQ,SAAS;AACxD;;;AF9BA,IAAM,WAAW,oBAAI,IAAY;AAE1B,SAAS,YACf,OACA,QACA,SACa;AACb,SAAO,OAAO,KAAK,SAAS;AAC3B,UAAM,aAAa,kBAAkB,GAAG;AAGxC,QAAI,CAAC,WAAY,QAAO,KAAK;AAG7B,UAAM,eAAe,MAAM,gBAAgB,WAAW,MAAM,WAAW,IAAI,KAAK;AAChF,UAAM,WAAW,cAAc,QAAQ,OAAO,MAAM,CAAC,GAAG,QAAQ;AAChE,UAAM,SACL,CAAC,gBAAgB,aAAa,WAAW,YAAY,aAAa,WAAW;AAE9E,UAAM,OAAO,OAAO,MAAM,KAAK,CAAC,MAAM,EAAE,SAAS,QAAQ,KAAK,OAAO,MAAM,CAAC;AAC5E,QAAI,CAAC,KAAM,QAAO,KAAK;AAGvB,UAAM,WAAmC,CAAC;AAE1C,eAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,KAAK,UAAU,CAAC,CAAC,GAAG;AAC7D,UAAI,aAAa,SAAS,CAAC,MAAM,OAAQ;AAEzC,YAAM,WAAW,cAAc,MAAM,MAAM;AAC3C,YAAM,aAAa,GAAG,WAAW,IAAI,IAAI,WAAW,EAAE,IAAI,GAAG;AAC7D,eAAS,GAAG,IAAI,MAAM,QAAQ,UAAU,YAAY,QAAQ;AAAA,IAC7D;AAGA,UAAM,aAAa,oBAAoB,EAAE,YAAY,MAAM,QAAQ,SAAS,CAAC;AAC7E,QAAI,KAAK,SAAS,IAAI;AAGtB,eAAW,CAAC,KAAK,MAAM,KAAK,OAAO,QAAQ,WAAW,QAAQ,GAAG;AAChE,UAAI,OAAO,SAAS,aAAa,OAAO,WAAW,WAAW;AAC7D,eAAOC;AAAA,UACNC,MAAK;AAAA,UACL;AAAA,UACA,uBAAuB,GAAG;AAAA,UAC1B,EAAE,KAAK,OAAO,OAAO,OAAO,MAAM,OAAO,MAAM,UAAU,OAAO,SAAS;AAAA,QAC1E;AAAA,MACD;AAAA,IACD;AAGA,QAAI,OAAO,eAAe;AACzB,YAAM,WAA8B,CAAC;AACrC,YAAM,YAAY;AAAA,QACjB,OAAO,IAAI,MAAM,SAAS;AAAA,QAC1B,OAAO;AAAA,QACP,aAAa;AAAA,MACd;AAEA,iBAAW,CAAC,KAAK,MAAM,KAAK,OAAO,QAAQ,WAAW,QAAQ,GAAG;AAChE,YAAI,OAAO,SAAS,aAAa,OAAO,UAAU,KAAM;AAExD,cAAM,OAAO,GAAG,WAAW,IAAI,IAAI,WAAW,EAAE,IAAI,GAAG;AAEvD,YAAI,OAAO,cAAc,WAAW,OAAO,WAAW,cAAc;AACnE,gBAAM,KAAK,GAAG,IAAI;AAClB,cAAI,CAAC,SAAS,IAAI,EAAE,GAAG;AACtB,qBAAS,IAAI,EAAE;AACf,qBAAS,KAAK;AAAA,cACb,MAAM,aAAa;AAAA,cACnB;AAAA,cACA,MAAM;AAAA,gBACL;AAAA,gBACA,MAAM,KAAK;AAAA,gBACX,OAAO,OAAO;AAAA,gBACd,MAAM,OAAO;AAAA,cACd;AAAA,YACD,CAAC;AAAA,UACF;AAAA,QACD,WAAW,OAAO,cAAc,UAAU,OAAO,WAAW,WAAW;AACtE,gBAAM,KAAK,GAAG,IAAI;AAClB,cAAI,CAAC,SAAS,IAAI,EAAE,GAAG;AACtB,qBAAS,IAAI,EAAE;AACf,qBAAS,KAAK;AAAA,cACb,MAAM,aAAa;AAAA,cACnB;AAAA,cACA,MAAM;AAAA,gBACL;AAAA,gBACA,MAAM,KAAK;AAAA,gBACX,OAAO,OAAO;AAAA,gBACd,MAAM,OAAO;AAAA,cACd;AAAA,YACD,CAAC;AAAA,UACF;AAAA,QACD;AAAA,MACD;AAEA,UAAI,SAAS,SAAS,GAAG;AACxB,cAAM,WAAW,IAAI,KAAK,UAAU;AACpC,YAAI,KAAK,UAAU,IAAI,CAAC,GAAI,YAAY,CAAC,GAAI,GAAG,QAAQ;AAAA,MACzD;AAAA,IACD;AAEA,WAAO,KAAK;AAAA,EACb;AACD;;;AG9GO,IAAM,uBAAN,MAAsD;AAAA,EAC3C,WAAW,oBAAI,IAAmB;AAAA,EAEnD,MAAM,UAAU,KAAa,UAAyB,WAAW,GAAoB;AACpF,UAAM,MAAM,KAAK,IAAI;AACrB,UAAM,WAAW,KAAK,SAAS,IAAI,GAAG;AAEtC,QAAI,CAAC,YAAa,aAAa,QAAQ,MAAM,SAAS,eAAe,UAAW;AAC/E,WAAK,SAAS,IAAI,KAAK,EAAE,OAAO,UAAU,aAAa,IAAI,CAAC;AAC5D,aAAO;AAAA,IACR;AAEA,aAAS,SAAS;AAClB,WAAO,SAAS;AAAA,EACjB;AAAA,EAEA,MAAM,IAAI,KAAa,UAA0C;AAChE,UAAM,MAAM,KAAK,IAAI;AACrB,UAAM,WAAW,KAAK,SAAS,IAAI,GAAG;AACtC,QAAI,CAAC,SAAU,QAAO;AACtB,QAAI,aAAa,QAAQ,MAAM,SAAS,eAAe,SAAU,QAAO;AACxE,WAAO,SAAS;AAAA,EACjB;AACD;;;AC3BO,IAAM,mBAAN,MAAkD;AAAA,EACxD,YAA6B,OAAsB;AAAtB;AAAA,EAAuB;AAAA,EAAvB;AAAA,EAE7B,MAAM,UAAU,KAAa,UAAyB,WAAW,GAAoB;AACpF,UAAM,CAAC,gBAAgB,cAAc,GAAG,IAAI,IAAI,IAAI,MAAM,GAAG;AAC7D,UAAM,SAAS,KAAK,KAAK,GAAG;AAE5B,UAAM,KAAK,MAAM;AAAA,MAChB;AAAA;AAAA,MAEA,CAAC,gBAAgB,cAAc,QAAQ,QAAQ;AAAA,IAChD;AAEA,WAAO,KAAK,IAAI,KAAK,QAAQ;AAAA,EAC9B;AAAA,EAEA,MAAM,IAAI,KAAa,UAA0C;AAChE,UAAM,CAAC,gBAAgB,cAAc,GAAG,IAAI,IAAI,IAAI,MAAM,GAAG;AAC7D,UAAM,SAAS,KAAK,KAAK,GAAG;AAC5B,UAAM,QAAQ,aAAa,OAAO,IAAI,KAAK,KAAK,IAAI,IAAI,QAAQ,IAAI,oBAAI,KAAK,CAAC;AAE9E,UAAM,OAAO,MAAM,KAAK,MAAM;AAAA,MAC7B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAMA,CAAC,gBAAgB,cAAc,QAAQ,KAAK;AAAA,IAC7C;AAEA,WAAO,SAAS,KAAK,CAAC,GAAG,SAAS,KAAK,EAAE;AAAA,EAC1C;AACD;;;AC/BO,SAAS,cAAc,QAA4C,OAAsB;AAC/F,MAAI,CAAC,UAAU,WAAW,SAAU,QAAO,IAAI,qBAAqB;AACpE,MAAI,WAAW,KAAM,QAAO,IAAI,iBAAiB,KAAK;AACtD,SAAO;AACR;;;ACAO,IAAM,gBAAN,MAA+C;AAAA,EAIrD,YACS,OACA,QACP;AAFO;AACA;AAAA,EACN;AAAA,EAFM;AAAA,EACA;AAAA,EALA,OAAO;AAAA,EACP,OAAO,CAAC,gBAAgB;AAAA,EAOjC,MAAM,QAAQ,KAAkC;AAC/C,UAAM,cAAc,KAAK,QAAQ,KAAK,KAAK;AAE3C,UAAM,UAAU,cAAc,KAAK,OAAO,WAAW,SAAS,KAAK,KAAK;AAKxE,QAAI,IAAI,YAAY,KAAK,OAAO,KAAK,QAAQ,OAAO,CAAC;AAErD,UAAM,SAAS,mBAAmB,KAAK,OAAO,KAAK,MAAM;AACzD,eAAW,CAAC,QAAQ,MAAM,GAAG,QAAQ,KAAK,QAAQ;AACjD,UAAI,SAAS,QAAQ,MAAM,GAAG,QAAQ;AAAA,IACvC;AAAA,EACD;AACD;;;ACZA,IAAI,UAAmB;AAEvB,eAAe,UAAU,WAAqC;AAC7D,MAAI,QAAS,QAAO;AAEpB,QAAM,MAAM;AAGZ,QAAM,MAAW,MAAM,OAAO,KAAK,MAAM,MAAM;AAC9C,UAAM,IAAI,MAAM,yDAAyD;AAAA,EAC1E,CAAC;AAED,QAAM,SAAS,IAAI,WAAW;AAC9B,YAAU,IAAI,OAAO,WAAW,EAAE,YAAY,oBAAoB,CAAC;AACnE,SAAO;AACR;AAEA,SAAS,sBAAsB,KAAsD;AACpF,SAAO;AAAA,IACN,gBAAiB,IAAI,WAAW,gBAAgB,KAAK;AAAA,IACrD,cAAc,IAAI,WAAW,cAAc,KAAK;AAAA,IAChD,MAAM,IAAI,MAAM,KAAK,CAAC,GAAG,MAAM,YAAY;AAAA,IAC3C,QAAQ,IAAI;AAAA,IACZ,oBAAoB,IAAI;AAAA,IACxB,wBAAwB,IAAI;AAAA,IAC5B,oBAAoB,IAAI,KAAK,IAAI,uBAAuB,GAAI;AAAA,IAC5D,kBAAkB,IAAI,KAAK,IAAI,qBAAqB,GAAI;AAAA,IACxD,mBAAmB,IAAI;AAAA,IACvB,aAAa,IAAI,YAAY,IAAI,KAAK,IAAI,YAAY,GAAI,IAAI;AAAA,EAC/D;AACD;AAEO,IAAM,iBAAN,MAAiD;AAAA,EAGvD,YACS,WACA,eACP;AAFO;AACA;AAAA,EACN;AAAA,EAFM;AAAA,EACA;AAAA,EAJA,OAAO;AAAA,EAOhB,MAAc,SAAuB;AACpC,WAAO,UAAU,KAAK,SAAS;AAAA,EAChC;AAAA,EAEA,MAAM,eAAe,MAKe;AACnC,UAAM,SAAS,MAAM,KAAK,OAAO;AACjC,UAAM,WAAW,MAAM,OAAO,UAAU,OAAO;AAAA,MAC9C,OAAO,KAAK;AAAA,MACZ,UAAU;AAAA,QACT,gBAAgB,KAAK;AAAA,QACrB,cAAc,KAAK;AAAA,QACnB,QAAQ,KAAK;AAAA,MACd;AAAA,IACD,CAAC;AACD,WAAO,EAAE,YAAY,SAAS,GAAG;AAAA,EAClC;AAAA,EAEA,MAAM,sBAAsB,MAQC;AAC5B,UAAM,SAAS,MAAM,KAAK,OAAO;AACjC,UAAM,UAAU,MAAM,OAAO,SAAS,SAAS,OAAO;AAAA,MACrD,UAAU,KAAK;AAAA,MACf,MAAM;AAAA,MACN,YAAY,CAAC,EAAE,OAAO,KAAK,SAAS,UAAU,EAAE,CAAC;AAAA,MACjD,aAAa,KAAK;AAAA,MAClB,YAAY,KAAK;AAAA,MACjB,mBAAmB;AAAA,QAClB,UAAU;AAAA,UACT,gBAAgB,KAAK;AAAA,UACrB,cAAc,KAAK;AAAA,QACpB;AAAA,QACA,GAAI,KAAK,aAAa,KAAK,YAAY,IAAI,EAAE,mBAAmB,KAAK,UAAU,IAAI,CAAC;AAAA,MACrF;AAAA,IACD,CAAC;AACD,WAAO,EAAE,KAAK,QAAQ,OAAO,GAAG;AAAA,EACjC;AAAA,EAEA,MAAM,oBAAoB,MAGG;AAC5B,UAAM,SAAS,MAAM,KAAK,OAAO;AACjC,UAAM,UAAU,MAAM,OAAO,cAAc,SAAS,OAAO;AAAA,MAC1D,UAAU,KAAK;AAAA,MACf,YAAY,KAAK;AAAA,IAClB,CAAC;AACD,WAAO,EAAE,KAAK,QAAQ,IAAI;AAAA,EAC3B;AAAA,EAEA,MAAM,eAAe,MAIM;AAC1B,UAAM,SAAS,MAAM,KAAK,OAAO;AAEjC,QAAI;AACJ,QAAI;AACH,YAAM,OAAO,SAAS,eAAe,KAAK,SAAS,KAAK,WAAW,KAAK,MAAM;AAAA,IAC/E,QAAQ;AACP,YAAM,IAAI,MAAM,4CAA4C;AAAA,IAC7D;AAEA,UAAM,sBAAsB;AAAA,MAC3B;AAAA,MACA;AAAA,MACA;AAAA,IACD,EAAE,SAAS,IAAI,IAAI;AAEnB,QAAI,CAAC,qBAAqB;AACzB,aAAO,EAAE,MAAM,IAAI,MAAM,cAAc,KAAK;AAAA,IAC7C;AAEA,UAAM,MAAM,IAAI,KAAK;AAErB,QAAI,IAAI,SAAS,iCAAiC;AACjD,aAAO;AAAA,QACN,MAAM,IAAI;AAAA,QACV,cAAc,EAAE,GAAG,sBAAsB,GAAG,GAAG,MAAM,QAAQ,QAAQ,WAAW;AAAA,MACjF;AAAA,IACD;AAEA,WAAO,EAAE,MAAM,IAAI,MAAM,cAAc,sBAAsB,GAAG,EAAE;AAAA,EACnE;AACD;;;AC7JA,SAAS,kBAAAC,iBAAgB,QAAAC,aAAY;AAYrC,SAAS,YAAY,OAA0B,OAAkC;AAChF,QAAM,UAAU,MAAM,QAAQ,KAAK,IAAI,QAAQ,CAAC,KAAK;AAErD,SAAO,OAAO,KAAK,SAAS;AAC3B,QAAI,CAAC,IAAI,MAAM;AACd,aAAOC,gBAAeC,MAAK,cAAc,gBAAgB,cAAc;AAAA,IACxE;AAEA,UAAM,aAAa,kBAAkB,GAAG;AACxC,QAAI,CAAC,YAAY;AAChB,aAAOD,gBAAeC,MAAK,aAAa,uBAAuB,6BAA6B;AAAA,IAC7F;AAEA,UAAM,eAAe,MAAM,gBAAgB,WAAW,MAAM,WAAW,IAAI,KAAK;AAEhF,QAAI,CAAC,gBAAgB,CAAC,QAAQ,SAAS,aAAa,IAAI,GAAG;AAC1D,aAAOD;AAAA,QACNC,MAAK;AAAA,QACL;AAAA,QACA;AAAA,QACA,EAAE,UAAU,SAAS,SAAS,cAAc,QAAQ,OAAO;AAAA,MAC5D;AAAA,IACD;AAEA,QAAI,aAAa,WAAW,YAAY,aAAa,WAAW,YAAY;AAC3E,aAAOD;AAAA,QACNC,MAAK;AAAA,QACL;AAAA,QACA;AAAA,QACA,EAAE,QAAQ,aAAa,OAAO;AAAA,MAC/B;AAAA,IACD;AAEA,WAAO,KAAK;AAAA,EACb;AACD;AASO,SAAS,YACf,OACA,OACA,KACA,MACiC;AACjC,QAAM,UAAU,YAAY,OAAO,KAAK;AACxC,MAAI,QAAQ,UAAa,SAAS,OAAW,QAAO,QAAQ,KAAK,IAAI;AACrE,SAAO;AACR;;;ACjEA,SAAS,kBAAAC,iBAAgB,QAAAC,aAAY;AAKrC,SAAS,kBAAkB,KAA+C;AACzE,SAAQ,IAAI,KAAK,SAAS,KAAqC;AAChE;AAIO,SAAS,WAAW,KAAuB,KAAsB;AACvE,QAAM,UAAU,kBAAkB,GAAG;AACrC,MAAI,CAAC,QAAS,QAAO;AAErB,QAAM,SAAS,QAAQ,SAAS,GAAG;AACnC,MAAI,CAAC,OAAQ,QAAO;AACpB,MAAI,OAAO,SAAS,UAAW,QAAO,OAAO;AAC7C,SAAO;AACR;AAGO,SAAS,aAAa,KAAuB,KAA4B;AAC/E,QAAM,UAAU,kBAAkB,GAAG;AACrC,MAAI,CAAC,QAAS,QAAO;AAErB,QAAM,SAAS,QAAQ,SAAS,GAAG;AACnC,MAAI,CAAC,UAAU,OAAO,SAAS,UAAW,QAAO;AACjD,SAAO,OAAO;AACf;AAGO,SAAS,eAAe,KAAuB,KAAmC;AACxF,QAAM,UAAU,kBAAkB,GAAG;AACrC,MAAI,CAAC,QAAS,QAAO;AACrB,SAAO,QAAQ,SAAS,GAAG,KAAK;AACjC;AAKO,SAAS,eAAe,KAAyB;AACvD,SAAO,CAAC,KAAK,SAAS;AACrB,QAAI,CAAC,WAAW,KAAK,GAAG,GAAG;AAC1B,aAAO,QAAQ;AAAA,QACdD;AAAA,UACCC,MAAK;AAAA,UACL;AAAA,UACA,YAAY,GAAG;AAAA,QAChB;AAAA,MACD;AAAA,IACD;AACA,WAAO,KAAK;AAAA,EACb;AACD;","names":["setApiResponse","HTTP","setApiResponse","HTTP","setApiResponse","HTTP","setApiResponse","HTTP","setApiResponse","HTTP","setApiResponse","HTTP","setApiResponse","HTTP","setApiResponse","HTTP","setApiResponse","HTTP","setApiResponse","HTTP","setApiResponse","HTTP","setApiResponse","HTTP","setApiResponse","HTTP"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fonderie/billing",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "SaaS billing in one module — config-driven plan catalogue, Stripe subscriptions, polymorphic user and workspace billing surfaces, usage metering, and webhook handling.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"fonderie-js",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"module": "./dist/index.js",
|
|
44
44
|
"types": "./dist/index.d.ts",
|
|
45
45
|
"scripts": {
|
|
46
|
-
"build": "tsup &&
|
|
46
|
+
"build": "tsup && tsup --config tsup.migrations.ts",
|
|
47
47
|
"dev": "tsup --watch",
|
|
48
48
|
"typecheck": "tsc --noEmit",
|
|
49
49
|
"test": "tsx --test src/__tests__/*.test.ts",
|
|
@@ -55,8 +55,8 @@
|
|
|
55
55
|
"stripe": "^22.1.1"
|
|
56
56
|
},
|
|
57
57
|
"peerDependencies": {
|
|
58
|
-
"@fonderie/core": "^0.1.
|
|
59
|
-
"@fonderie/store": "^0.1.
|
|
58
|
+
"@fonderie/core": "^0.1.1",
|
|
59
|
+
"@fonderie/store": "^0.1.1"
|
|
60
60
|
},
|
|
61
61
|
"devDependencies": {
|
|
62
62
|
"@fonderie/core": "../core",
|
|
@@ -71,16 +71,20 @@
|
|
|
71
71
|
},
|
|
72
72
|
"files": [
|
|
73
73
|
"dist",
|
|
74
|
+
"brain",
|
|
74
75
|
"LICENSE",
|
|
75
76
|
"README.md"
|
|
76
77
|
],
|
|
77
78
|
"repository": {
|
|
78
79
|
"type": "git",
|
|
79
|
-
"url": "git+https://github.com/
|
|
80
|
+
"url": "git+https://github.com/fonderiejs/sdk.git",
|
|
80
81
|
"directory": "packages/billing"
|
|
81
82
|
},
|
|
82
|
-
"homepage": "https://github.com/
|
|
83
|
+
"homepage": "https://github.com/fonderiejs/sdk/tree/main/packages/billing#readme",
|
|
83
84
|
"bugs": {
|
|
84
|
-
"url": "https://github.com/
|
|
85
|
+
"url": "https://github.com/fonderiejs/sdk/issues"
|
|
86
|
+
},
|
|
87
|
+
"dependencies": {
|
|
88
|
+
"zod": "^4.4.3"
|
|
85
89
|
}
|
|
86
90
|
}
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
CREATE TABLE IF NOT EXISTS fonderie_plans (
|
|
2
|
-
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
3
|
-
name TEXT NOT NULL UNIQUE,
|
|
4
|
-
seats INT,
|
|
5
|
-
trial_days INT NOT NULL DEFAULT 0,
|
|
6
|
-
monthly_amount INT,
|
|
7
|
-
monthly_price_id TEXT,
|
|
8
|
-
yearly_amount INT,
|
|
9
|
-
yearly_price_id TEXT,
|
|
10
|
-
active BOOLEAN NOT NULL DEFAULT true,
|
|
11
|
-
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
12
|
-
);
|
|
13
|
-
|
|
14
|
-
CREATE TABLE IF NOT EXISTS fonderie_subscriptions (
|
|
15
|
-
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
16
|
-
workspace_id UUID NOT NULL UNIQUE,
|
|
17
|
-
plan TEXT NOT NULL,
|
|
18
|
-
interval TEXT NOT NULL DEFAULT 'month',
|
|
19
|
-
status TEXT NOT NULL DEFAULT 'incomplete',
|
|
20
|
-
provider_customer_id TEXT,
|
|
21
|
-
provider_subscription_id TEXT,
|
|
22
|
-
current_period_start TIMESTAMPTZ,
|
|
23
|
-
current_period_end TIMESTAMPTZ,
|
|
24
|
-
cancel_at_period_end BOOLEAN NOT NULL DEFAULT false,
|
|
25
|
-
trial_ends_at TIMESTAMPTZ,
|
|
26
|
-
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
27
|
-
);
|
|
28
|
-
|
|
29
|
-
CREATE TABLE IF NOT EXISTS fonderie_usage_records (
|
|
30
|
-
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
31
|
-
workspace_id UUID NOT NULL,
|
|
32
|
-
metric TEXT NOT NULL,
|
|
33
|
-
quantity INT NOT NULL DEFAULT 1,
|
|
34
|
-
recorded_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
35
|
-
);
|
|
36
|
-
|
|
37
|
-
CREATE INDEX IF NOT EXISTS fonderie_usage_records_workspace_metric_idx
|
|
38
|
-
ON fonderie_usage_records (workspace_id, metric, recorded_at);
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
ALTER TABLE fonderie_plans DROP COLUMN IF EXISTS limits;
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
-- fonderie_subscriptions: replace workspace_id with polymorphic subscriber
|
|
2
|
-
ALTER TABLE fonderie_subscriptions
|
|
3
|
-
ADD COLUMN subscriber_type TEXT,
|
|
4
|
-
ADD COLUMN subscriber_id UUID;
|
|
5
|
-
|
|
6
|
-
UPDATE fonderie_subscriptions
|
|
7
|
-
SET subscriber_type = 'workspace',
|
|
8
|
-
subscriber_id = workspace_id;
|
|
9
|
-
|
|
10
|
-
ALTER TABLE fonderie_subscriptions
|
|
11
|
-
ALTER COLUMN subscriber_type SET NOT NULL,
|
|
12
|
-
ALTER COLUMN subscriber_id SET NOT NULL;
|
|
13
|
-
|
|
14
|
-
ALTER TABLE fonderie_subscriptions
|
|
15
|
-
DROP CONSTRAINT fonderie_subscriptions_workspace_id_key;
|
|
16
|
-
|
|
17
|
-
ALTER TABLE fonderie_subscriptions
|
|
18
|
-
DROP COLUMN workspace_id;
|
|
19
|
-
|
|
20
|
-
ALTER TABLE fonderie_subscriptions
|
|
21
|
-
ADD CONSTRAINT fonderie_subscriptions_subscriber_type_check
|
|
22
|
-
CHECK (subscriber_type IN ('user', 'workspace')),
|
|
23
|
-
ADD CONSTRAINT fonderie_subscriptions_subscriber_unique
|
|
24
|
-
UNIQUE (subscriber_type, subscriber_id);
|
|
25
|
-
|
|
26
|
-
-- fonderie_usage_records: replace workspace_id with polymorphic subscriber
|
|
27
|
-
ALTER TABLE fonderie_usage_records
|
|
28
|
-
ADD COLUMN subscriber_type TEXT,
|
|
29
|
-
ADD COLUMN subscriber_id UUID;
|
|
30
|
-
|
|
31
|
-
UPDATE fonderie_usage_records
|
|
32
|
-
SET subscriber_type = 'workspace',
|
|
33
|
-
subscriber_id = workspace_id;
|
|
34
|
-
|
|
35
|
-
ALTER TABLE fonderie_usage_records
|
|
36
|
-
ALTER COLUMN subscriber_type SET NOT NULL,
|
|
37
|
-
ALTER COLUMN subscriber_id SET NOT NULL;
|
|
38
|
-
|
|
39
|
-
ALTER TABLE fonderie_usage_records
|
|
40
|
-
DROP COLUMN workspace_id;
|
|
41
|
-
|
|
42
|
-
ALTER TABLE fonderie_usage_records
|
|
43
|
-
ADD CONSTRAINT fonderie_usage_records_subscriber_type_check
|
|
44
|
-
CHECK (subscriber_type IN ('user', 'workspace'));
|
|
45
|
-
|
|
46
|
-
DROP INDEX IF EXISTS fonderie_usage_records_workspace_metric_idx;
|
|
47
|
-
|
|
48
|
-
CREATE INDEX fonderie_usage_records_subscriber_metric_idx
|
|
49
|
-
ON fonderie_usage_records (subscriber_type, subscriber_id, metric, recorded_at);
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
-- Tracks which threshold notifications have been sent per subscriber/key/window.
|
|
2
|
-
-- Prevents duplicate emails when a subscriber hovers around a threshold.
|
|
3
|
-
CREATE TABLE IF NOT EXISTS fonderie_billing_notifications (
|
|
4
|
-
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
5
|
-
subscriber_type TEXT NOT NULL,
|
|
6
|
-
subscriber_id UUID NOT NULL,
|
|
7
|
-
policy_key TEXT NOT NULL,
|
|
8
|
-
notification TEXT NOT NULL, -- 'warning' | 'reached' | 'blocked'
|
|
9
|
-
window_key TEXT NOT NULL, -- e.g. '2026-05-13' for a 1-day window
|
|
10
|
-
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
11
|
-
CONSTRAINT fonderie_billing_notifications_unique
|
|
12
|
-
UNIQUE (subscriber_type, subscriber_id, policy_key, notification, window_key)
|
|
13
|
-
);
|