@secondlayer/shared 2.1.0 → 3.0.0-beta.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/README.md +2 -2
- package/dist/src/crypto/secrets.js +47 -3
- package/dist/src/crypto/secrets.js.map +5 -4
- package/dist/src/db/index.d.ts +112 -137
- package/dist/src/db/index.js.map +2 -2
- package/dist/src/db/jsonb.d.ts +5 -1
- package/dist/src/db/jsonb.js.map +2 -2
- package/dist/src/db/queries/account-spend-caps.d.ts +444 -0
- package/dist/src/db/queries/account-spend-caps.js +60 -0
- package/dist/src/db/queries/account-spend-caps.js.map +10 -0
- package/dist/src/db/queries/account-usage.d.ts +468 -0
- package/dist/src/db/queries/account-usage.js +222 -0
- package/dist/src/db/queries/account-usage.js.map +11 -0
- package/dist/src/db/queries/accounts.d.ts +100 -109
- package/dist/src/db/queries/accounts.js +15 -1
- package/dist/src/db/queries/accounts.js.map +3 -3
- package/dist/src/db/queries/integrity.d.ts +85 -107
- package/dist/src/db/queries/projects.d.ts +87 -109
- package/dist/src/db/queries/provisioning-audit.d.ts +85 -107
- package/dist/src/db/queries/subgraph-gaps.d.ts +85 -107
- package/dist/src/db/queries/subgraphs.d.ts +86 -109
- package/dist/src/db/queries/subgraphs.js +2 -3
- package/dist/src/db/queries/subgraphs.js.map +4 -4
- package/dist/src/db/queries/{workflows.d.ts → tenant-compute-addons.d.ts} +108 -142
- package/dist/src/db/queries/tenant-compute-addons.js +47 -0
- package/dist/src/db/queries/tenant-compute-addons.js.map +10 -0
- package/dist/src/db/queries/tenants.d.ts +98 -110
- package/dist/src/db/queries/tenants.js +55 -8
- package/dist/src/db/queries/tenants.js.map +6 -5
- package/dist/src/db/queries/usage.d.ts +86 -132
- package/dist/src/db/queries/usage.js +5 -64
- package/dist/src/db/queries/usage.js.map +4 -5
- package/dist/src/db/schema.d.ts +107 -136
- package/dist/src/errors.d.ts +8 -7
- package/dist/src/errors.js +11 -12
- package/dist/src/errors.js.map +3 -3
- package/dist/src/index.d.ts +119 -143
- package/dist/src/index.js +11 -12
- package/dist/src/index.js.map +4 -4
- package/dist/src/node/local-client.d.ts +85 -107
- package/dist/src/pricing.d.ts +20 -1
- package/dist/src/pricing.js +58 -1
- package/dist/src/pricing.js.map +3 -3
- package/migrations/0045_drop_marketplace_columns.ts +47 -0
- package/migrations/0046_tenant_activity_signal.ts +47 -0
- package/migrations/0047_usage_daily_tenant_id.ts +73 -0
- package/migrations/0048_tenant_compute_addons.ts +49 -0
- package/migrations/0049_accounts_stripe_customer_id.ts +30 -0
- package/migrations/0050_account_spend_caps.ts +45 -0
- package/migrations/0051_workflow_ai_usage_daily.ts +40 -0
- package/migrations/0052_sentries.ts +61 -0
- package/migrations/0053_workflow_runtime.ts +88 -0
- package/migrations/0054_accounts_plan_hobby.ts +32 -0
- package/migrations/0055_ai_usage_account_scope.ts +108 -0
- package/migrations/0056_drop_workflow_sentry_residuals.ts +23 -0
- package/migrations/0057_subscriptions.ts +137 -0
- package/package.json +26 -14
- package/dist/src/db/queries/workflows.js +0 -260
- package/dist/src/db/queries/workflows.js.map +0 -12
- package/dist/src/lib/plans.d.ts +0 -9
- package/dist/src/lib/plans.js +0 -37
- package/dist/src/lib/plans.js.map +0 -10
- package/dist/src/schemas/workflows.d.ts +0 -70
- package/dist/src/schemas/workflows.js +0 -43
- package/dist/src/schemas/workflows.js.map +0 -10
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/pricing.ts", "../src/db/queries/account-usage.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"/**\n * Token → USD pricing constants for internal observability.\n *\n * **Not customer billing.** Tier pricing covers compute; these constants\n * let the dashboard display \"~$X spent in AI today\" and let us track\n * gross-margin internally. Update manually when providers change prices\n * (roughly twice per year).\n *\n * Source: public provider pricing pages as of 2026-04-17.\n */\n\nexport interface ModelPricing {\n\t/** USD per 1M input tokens */\n\tinputPerMTokens: number;\n\t/** USD per 1M output tokens */\n\toutputPerMTokens: number;\n}\n\nexport const MODEL_PRICING: Record<string, Record<string, ModelPricing>> = {\n\tanthropic: {\n\t\t\"claude-haiku-4-5\": { inputPerMTokens: 1, outputPerMTokens: 5 },\n\t\t\"claude-haiku-4-5-20251001\": { inputPerMTokens: 1, outputPerMTokens: 5 },\n\t\t\"claude-sonnet-4-6\": { inputPerMTokens: 3, outputPerMTokens: 15 },\n\t\t\"claude-opus-4-7\": { inputPerMTokens: 15, outputPerMTokens: 75 },\n\t},\n\topenai: {\n\t\t\"gpt-4.1\": { inputPerMTokens: 2.5, outputPerMTokens: 10 },\n\t\t\"gpt-4o\": { inputPerMTokens: 2.5, outputPerMTokens: 10 },\n\t\t\"gpt-4o-mini\": { inputPerMTokens: 0.15, outputPerMTokens: 0.6 },\n\t},\n\tgoogle: {\n\t\t\"gemini-2.5-pro\": { inputPerMTokens: 1.25, outputPerMTokens: 10 },\n\t\t\"gemini-2.5-flash\": { inputPerMTokens: 0.3, outputPerMTokens: 2.5 },\n\t},\n};\n\nexport interface TokenUsage {\n\tinputTokens: number;\n\toutputTokens: number;\n}\n\n/**\n * Compute approximate USD cost for an AI step. Returns `null` if the\n * (provider, model) pair isn't in the pricing table — the caller should\n * display \"-\" rather than \"$0\".\n */\nexport function computeUsdCost(\n\tprovider: string,\n\tmodelId: string,\n\tusage: TokenUsage,\n): number | null {\n\tconst p = MODEL_PRICING[provider]?.[modelId];\n\tif (!p) return null;\n\treturn (\n\t\t(usage.inputTokens * p.inputPerMTokens) / 1_000_000 +\n\t\t(usage.outputTokens * p.outputPerMTokens) / 1_000_000\n\t);\n}\n\n// ── Per-tier AI eval caps (workflow-runner) ───────────────────────────\n//\n// Daily `step.ai` budget by tier. Hit the cap → runner throws\n// AI_CAP_REACHED and the step fails cleanly so condition-only paths\n// continue. Overage (past cap) bills to the `ai_evals` Stripe meter on\n// paid tiers.\n//\n// Caps set conservatively so Pro Micro stays margin-positive at\n// realistic AI utilization. Raise based on data, not aspiration.\n\nexport interface AiCap {\n\t/** Max step.ai / generateText / generateObject calls per UTC day. */\n\tevalsPerDay: number;\n\t/** Stripe meter events emitted for overage past this cap. */\n\toverageMeterEventName: \"ai_evals\";\n}\n\nconst AI_CAP_UNLIMITED: AiCap = {\n\tevalsPerDay: Number.POSITIVE_INFINITY,\n\toverageMeterEventName: \"ai_evals\",\n};\n\nconst AI_CAPS_BY_PLAN: Record<string, AiCap> = {\n\thobby: { evalsPerDay: 50, overageMeterEventName: \"ai_evals\" },\n\tlaunch: { evalsPerDay: 500, overageMeterEventName: \"ai_evals\" },\n\tgrow: { evalsPerDay: 1000, overageMeterEventName: \"ai_evals\" },\n\tscale: { evalsPerDay: 2500, overageMeterEventName: \"ai_evals\" },\n\tenterprise: AI_CAP_UNLIMITED,\n};\n\n/** Resolve the daily AI eval cap for a plan. Unknown plans get Hobby's\n * cap so a stray DB value can't accidentally become unlimited. */\nexport function getAiCapForPlan(plan: string): AiCap {\n\treturn AI_CAPS_BY_PLAN[plan] ?? AI_CAPS_BY_PLAN.hobby;\n}\n\n// ── Per-tier compute + storage allowances (usage page) ──────────────\n//\n// \"Included\" amounts per billing period. Actual billing comes from\n// Stripe compute-hours + storage-overage meters; these constants power\n// the dashboard display and the approximation used by `/api/accounts/usage`.\n//\n// Values picked to match `project_supabase_pricing_model.md`:\n// Hobby — 50 h / 5 GB (Nano — free tier)\n// Launch — 500 h / 50 GB ($149/mo)\n// Grow — 1,000 h / 200 GB ($349/mo)\n// Scale — 2,500 h / 1 TB ($799/mo)\n// Enterprise — ∞ / ∞\n\nconst BYTES_PER_GB = 1024 ** 3;\n\n// Hobby has no compute-hour cap — auto-pause after 7d idle is the cap.\n// Paid tiers bill Stripe-metered hours past the included credit; the\n// hour equivalents below are approximate display values. Real billing\n// uses the `compute_hours` meter in Stripe, not these numbers.\nconst COMPUTE_ALLOWANCE_BY_PLAN: Record<string, number> = {\n\thobby: Number.POSITIVE_INFINITY,\n\tlaunch: 500,\n\tgrow: 1000,\n\tscale: 2500,\n\tenterprise: Number.POSITIVE_INFINITY,\n};\n\nconst STORAGE_ALLOWANCE_BYTES_BY_PLAN: Record<string, number> = {\n\thobby: 5 * BYTES_PER_GB,\n\tlaunch: 50 * BYTES_PER_GB,\n\tgrow: 200 * BYTES_PER_GB,\n\tscale: 1000 * BYTES_PER_GB,\n\tenterprise: Number.POSITIVE_INFINITY,\n};\n\n/** Included compute hours per billing period. Unknown → hobby. */\nexport function getComputeAllowanceHours(plan: string): number {\n\treturn COMPUTE_ALLOWANCE_BY_PLAN[plan] ?? COMPUTE_ALLOWANCE_BY_PLAN.hobby;\n}\n\n/** Included storage bytes. Unknown → hobby. */\nexport function getStorageAllowanceBytes(plan: string): number {\n\treturn (\n\t\tSTORAGE_ALLOWANCE_BYTES_BY_PLAN[plan] ??\n\t\tSTORAGE_ALLOWANCE_BYTES_BY_PLAN.hobby\n\t);\n}\n\n/** Whether this plan bills storage overage. Hobby has a hard cap\n * (no overage billing); paid tiers bill $2/GB over allowance. */\nexport function hasStorageOverage(plan: string): boolean {\n\treturn plan !== \"hobby\";\n}\n\n/** Base monthly price for a plan, in cents. Enterprise is custom → 0. */\nconst BASE_PRICE_CENTS_BY_PLAN: Record<string, number> = {\n\thobby: 0,\n\tlaunch: 14900,\n\tgrow: 34900,\n\tscale: 79900,\n\tenterprise: 0,\n};\n\nexport function getBasePriceCents(plan: string): number {\n\treturn BASE_PRICE_CENTS_BY_PLAN[plan] ?? 0;\n}\n\n/** Capitalized display name for a plan tier. */\nexport function getPlanDisplayName(plan: string): string {\n\treturn plan.charAt(0).toUpperCase() + plan.slice(1);\n}\n",
|
|
6
|
+
"import { type Kysely, sql } from \"kysely\";\nimport {\n\tgetAiCapForPlan,\n\tgetComputeAllowanceHours,\n\tgetStorageAllowanceBytes,\n} from \"../../pricing.ts\";\nimport type { Database } from \"../types.ts\";\n\n/**\n * Rollup queries that power the `/platform/usage` page.\n *\n * Compute-hours approximation: each active tenant contributes\n * cpus × hours-in-period-while-active\n * where \"active\" is approximated from `last_active_at`. This undercounts\n * tenants that went idle between cron ticks and overcounts nothing.\n *\n * Actual Stripe billing happens in `packages/worker/src/jobs/compute-metering.ts`\n * — these numbers are for display only. Follow-up work: write-through\n * compute ledger so this query reads truth instead of estimating.\n */\n\nconst IDLE_GRACE_MS = 2 * 60 * 60 * 1000; // 2h\nconst BYTES_PER_MB = 1024 * 1024;\n\n// ── Types ────────────────────────────────────────────────────────────\n\nexport interface SparklinePoint {\n\tday: string; // YYYY-MM-DD\n\tvalue: number;\n}\n\nexport interface ComputeUsage {\n\tusedHours: number;\n\tallowanceHours: number;\n\tpct: number;\n\tsparkline: SparklinePoint[];\n}\n\nexport interface StorageUsage {\n\tusedBytes: number;\n\tallowanceBytes: number;\n\tpct: number;\n\tsparkline: SparklinePoint[];\n}\n\nexport interface AiUsage {\n\ttodayCount: number;\n\tperiodCount: number;\n\tdailyCap: number;\n\tpct: number;\n\tsparkline: SparklinePoint[];\n}\n\nexport interface ProjectRow {\n\tid: string;\n\tslug: string;\n\tname: string;\n\tstatus: string;\n\tsubgraphCount: number;\n\tcompute: { hours: number; pct: number };\n\tstorage: { bytes: number; pct: number };\n\taiEvals: { todayCount: number; pct: number };\n}\n\n// ── Helpers ──────────────────────────────────────────────────────────\n\nfunction toDayKey(d: Date): string {\n\treturn d.toISOString().slice(0, 10);\n}\n\nfunction* lastNDays(n: number, endInclusive: Date): Generator<string> {\n\tconst end = new Date(endInclusive);\n\tfor (let i = n - 1; i >= 0; i--) {\n\t\tconst d = new Date(end);\n\t\td.setUTCDate(d.getUTCDate() - i);\n\t\tyield toDayKey(d);\n\t}\n}\n\nfunction computeActiveHours(\n\tperiodStart: Date,\n\tnow: Date,\n\ttenant: {\n\t\tcreated_at: Date;\n\t\tlast_active_at: Date;\n\t\tstatus: string;\n\t},\n): number {\n\tif (tenant.status !== \"active\") return 0;\n\tconst rangeStart = Math.max(\n\t\tperiodStart.getTime(),\n\t\ttenant.created_at.getTime(),\n\t);\n\tconst rangeEnd = Math.min(\n\t\tnow.getTime(),\n\t\ttenant.last_active_at.getTime() + IDLE_GRACE_MS,\n\t);\n\tif (rangeEnd <= rangeStart) return 0;\n\treturn (rangeEnd - rangeStart) / (1000 * 60 * 60);\n}\n\nfunction pct(used: number, allowance: number): number {\n\tif (!Number.isFinite(allowance) || allowance <= 0) return 0;\n\treturn Math.min((used / allowance) * 100, 100);\n}\n\n// ── Queries ──────────────────────────────────────────────────────────\n\nexport async function getComputeUsage(\n\tdb: Kysely<Database>,\n\taccountId: string,\n\tplan: string,\n\tperiodStart: Date,\n\tnow: Date = new Date(),\n): Promise<ComputeUsage> {\n\tconst tenants = await db\n\t\t.selectFrom(\"tenants\")\n\t\t.select([\"id\", \"cpus\", \"status\", \"created_at\", \"last_active_at\"])\n\t\t.where(\"account_id\", \"=\", accountId)\n\t\t.where(\"status\", \"!=\", \"deleted\")\n\t\t.execute();\n\n\tlet totalHours = 0;\n\tfor (const t of tenants) {\n\t\tconst hours = computeActiveHours(periodStart, now, {\n\t\t\tcreated_at: t.created_at,\n\t\t\tlast_active_at: t.last_active_at,\n\t\t\tstatus: String(t.status),\n\t\t});\n\t\ttotalHours += hours * Number(t.cpus);\n\t}\n\n\tconst allowance = getComputeAllowanceHours(plan);\n\n\t// 14-day sparkline: bucket the same formula per-day.\n\tconst sparkline: SparklinePoint[] = [];\n\tfor (const day of lastNDays(14, now)) {\n\t\tconst dayStart = new Date(`${day}T00:00:00.000Z`);\n\t\tconst dayEnd = new Date(`${day}T23:59:59.999Z`);\n\t\tlet dayHours = 0;\n\t\tfor (const t of tenants) {\n\t\t\tconst hours = computeActiveHours(dayStart, dayEnd, {\n\t\t\t\tcreated_at: t.created_at,\n\t\t\t\tlast_active_at: t.last_active_at,\n\t\t\t\tstatus: String(t.status),\n\t\t\t});\n\t\t\tdayHours += Math.min(hours, 24) * Number(t.cpus);\n\t\t}\n\t\tsparkline.push({ day, value: dayHours });\n\t}\n\n\treturn {\n\t\tusedHours: totalHours,\n\t\tallowanceHours: allowance,\n\t\tpct: pct(totalHours, allowance),\n\t\tsparkline,\n\t};\n}\n\nexport async function getStorageUsage(\n\tdb: Kysely<Database>,\n\taccountId: string,\n\tplan: string,\n\tnow: Date = new Date(),\n): Promise<StorageUsage> {\n\t// Current usage: sum of tenants.storage_used_mb for this account.\n\tconst current = await db\n\t\t.selectFrom(\"tenants\")\n\t\t.select(sql<string>`COALESCE(SUM(storage_used_mb), 0)`.as(\"mb\"))\n\t\t.where(\"account_id\", \"=\", accountId)\n\t\t.where(\"status\", \"!=\", \"deleted\")\n\t\t.executeTakeFirst();\n\n\tconst usedBytes = Number(current?.mb ?? 0) * BYTES_PER_MB;\n\tconst allowance = getStorageAllowanceBytes(plan);\n\n\t// 14-day sparkline: per-month snapshots only — fall back to a flat\n\t// line at the current value. When per-day storage history lands, swap\n\t// this for a real bucket query.\n\tconst sparkline: SparklinePoint[] = [];\n\tfor (const day of lastNDays(14, now)) {\n\t\tsparkline.push({ day, value: Number(current?.mb ?? 0) });\n\t}\n\n\treturn {\n\t\tusedBytes,\n\t\tallowanceBytes: allowance,\n\t\tpct: pct(usedBytes, allowance),\n\t\tsparkline,\n\t};\n}\n\nexport async function getAiUsage(\n\t_db: Kysely<Database>,\n\t_accountId: string,\n\tplan: string,\n\t_periodStart: Date,\n\tnow: Date = new Date(),\n): Promise<AiUsage> {\n\tconst dailyCap = getAiCapForPlan(plan).evalsPerDay;\n\tconst sparkline: SparklinePoint[] = [];\n\tfor (const day of lastNDays(14, now)) sparkline.push({ day, value: 0 });\n\treturn { todayCount: 0, periodCount: 0, dailyCap, pct: 0, sparkline };\n}\n\nexport async function getProjectBreakdown(\n\tdb: Kysely<Database>,\n\taccountId: string,\n\tplan: string,\n\tperiodStart: Date,\n\tnow: Date = new Date(),\n): Promise<ProjectRow[]> {\n\tconst tenants = await db\n\t\t.selectFrom(\"tenants\")\n\t\t.select([\n\t\t\t\"id\",\n\t\t\t\"slug\",\n\t\t\t\"status\",\n\t\t\t\"cpus\",\n\t\t\t\"storage_used_mb\",\n\t\t\t\"created_at\",\n\t\t\t\"last_active_at\",\n\t\t])\n\t\t.where(\"account_id\", \"=\", accountId)\n\t\t.where(\"status\", \"!=\", \"deleted\")\n\t\t.orderBy(\"created_at\", \"desc\")\n\t\t.execute();\n\n\tif (tenants.length === 0) return [];\n\n\tconst aiByTenant = new Map<string, number>();\n\n\tconst computeAllowance = getComputeAllowanceHours(plan);\n\tconst storageAllowance = getStorageAllowanceBytes(plan);\n\tconst aiDailyCap = getAiCapForPlan(plan).evalsPerDay;\n\n\treturn tenants.map((t) => {\n\t\tconst hours =\n\t\t\tcomputeActiveHours(periodStart, now, {\n\t\t\t\tcreated_at: t.created_at,\n\t\t\t\tlast_active_at: t.last_active_at,\n\t\t\t\tstatus: String(t.status),\n\t\t\t}) * Number(t.cpus);\n\t\tconst bytes = Number(t.storage_used_mb ?? 0) * BYTES_PER_MB;\n\t\tconst todayAi = aiByTenant.get(t.id) ?? 0;\n\n\t\treturn {\n\t\t\tid: t.id,\n\t\t\tslug: t.slug,\n\t\t\tname: t.slug,\n\t\t\tstatus: String(t.status),\n\t\t\t// subgraphCount not tracked at account-DB level (subgraphs live on\n\t\t\t// per-tenant DBs). Left at 0; later pass can ping each tenant API.\n\t\t\tsubgraphCount: 0,\n\t\t\tcompute: { hours, pct: pct(hours, computeAllowance) },\n\t\t\tstorage: { bytes, pct: pct(bytes, storageAllowance) },\n\t\t\taiEvals: { todayCount: todayAi, pct: pct(todayAi, aiDailyCap) },\n\t\t};\n\t});\n}\n"
|
|
7
|
+
],
|
|
8
|
+
"mappings": "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",
|
|
9
|
+
"debugId": "D94D306CAF5E896064756E2164756E21",
|
|
10
|
+
"names": []
|
|
11
|
+
}
|
|
@@ -61,10 +61,6 @@ interface SubgraphsTable {
|
|
|
61
61
|
handler_code: string | null;
|
|
62
62
|
source_code: string | null;
|
|
63
63
|
project_id: string | null;
|
|
64
|
-
is_public: Generated<boolean>;
|
|
65
|
-
tags: Generated<string[]>;
|
|
66
|
-
description: string | null;
|
|
67
|
-
forked_from_id: string | null;
|
|
68
64
|
created_at: Generated<Date>;
|
|
69
65
|
updated_at: Generated<Date>;
|
|
70
66
|
}
|
|
@@ -99,6 +95,7 @@ interface AccountsTable {
|
|
|
99
95
|
bio: string | null;
|
|
100
96
|
avatar_url: string | null;
|
|
101
97
|
slug: string | null;
|
|
98
|
+
stripe_customer_id: string | null;
|
|
102
99
|
created_at: Generated<Date>;
|
|
103
100
|
}
|
|
104
101
|
interface SessionsTable {
|
|
@@ -124,6 +121,7 @@ interface MagicLinksTable {
|
|
|
124
121
|
}
|
|
125
122
|
interface UsageDailyTable {
|
|
126
123
|
account_id: string;
|
|
124
|
+
tenant_id: string | null;
|
|
127
125
|
date: string;
|
|
128
126
|
api_requests: Generated<number>;
|
|
129
127
|
deliveries: Generated<number>;
|
|
@@ -250,83 +248,6 @@ interface ChatMessagesTable {
|
|
|
250
248
|
metadata: unknown | null;
|
|
251
249
|
created_at: Generated<Date>;
|
|
252
250
|
}
|
|
253
|
-
interface WorkflowDefinitionsTable {
|
|
254
|
-
id: Generated<string>;
|
|
255
|
-
name: string;
|
|
256
|
-
version: Generated<string>;
|
|
257
|
-
status: Generated<string>;
|
|
258
|
-
trigger_type: string;
|
|
259
|
-
trigger_config: unknown;
|
|
260
|
-
handler_path: string;
|
|
261
|
-
source_code: string | null;
|
|
262
|
-
retries_config: unknown | null;
|
|
263
|
-
timeout_ms: number | null;
|
|
264
|
-
api_key_id: string;
|
|
265
|
-
project_id: string | null;
|
|
266
|
-
created_at: Generated<Date>;
|
|
267
|
-
updated_at: Generated<Date>;
|
|
268
|
-
}
|
|
269
|
-
interface WorkflowRunsTable {
|
|
270
|
-
id: Generated<string>;
|
|
271
|
-
definition_id: string;
|
|
272
|
-
status: Generated<string>;
|
|
273
|
-
trigger_type: string;
|
|
274
|
-
trigger_data: unknown | null;
|
|
275
|
-
dedup_key: string | null;
|
|
276
|
-
error: string | null;
|
|
277
|
-
started_at: Date | null;
|
|
278
|
-
completed_at: Date | null;
|
|
279
|
-
duration_ms: number | null;
|
|
280
|
-
total_ai_tokens: Generated<number>;
|
|
281
|
-
created_at: Generated<Date>;
|
|
282
|
-
}
|
|
283
|
-
interface WorkflowStepsTable {
|
|
284
|
-
id: Generated<string>;
|
|
285
|
-
run_id: string;
|
|
286
|
-
step_index: number;
|
|
287
|
-
step_id: string;
|
|
288
|
-
step_type: string;
|
|
289
|
-
status: Generated<string>;
|
|
290
|
-
input: unknown | null;
|
|
291
|
-
output: unknown | null;
|
|
292
|
-
error: string | null;
|
|
293
|
-
retry_count: Generated<number>;
|
|
294
|
-
ai_tokens_used: Generated<number>;
|
|
295
|
-
started_at: Date | null;
|
|
296
|
-
completed_at: Date | null;
|
|
297
|
-
duration_ms: number | null;
|
|
298
|
-
memo_key: string | null;
|
|
299
|
-
parent_step_id: string | null;
|
|
300
|
-
created_at: Generated<Date>;
|
|
301
|
-
}
|
|
302
|
-
interface WorkflowQueueTable {
|
|
303
|
-
id: Generated<string>;
|
|
304
|
-
run_id: string;
|
|
305
|
-
status: Generated<string>;
|
|
306
|
-
attempts: Generated<number>;
|
|
307
|
-
max_attempts: Generated<number>;
|
|
308
|
-
scheduled_for: Generated<Date>;
|
|
309
|
-
locked_at: Date | null;
|
|
310
|
-
locked_by: string | null;
|
|
311
|
-
error: string | null;
|
|
312
|
-
created_at: Generated<Date>;
|
|
313
|
-
completed_at: Date | null;
|
|
314
|
-
}
|
|
315
|
-
interface WorkflowSchedulesTable {
|
|
316
|
-
id: Generated<string>;
|
|
317
|
-
definition_id: string;
|
|
318
|
-
cron_expr: string;
|
|
319
|
-
timezone: Generated<string>;
|
|
320
|
-
next_run_at: Date;
|
|
321
|
-
last_run_at: Date | null;
|
|
322
|
-
enabled: Generated<boolean>;
|
|
323
|
-
created_at: Generated<Date>;
|
|
324
|
-
}
|
|
325
|
-
interface WorkflowCursorsTable {
|
|
326
|
-
name: string;
|
|
327
|
-
block_height: Generated<number>;
|
|
328
|
-
updated_at: Generated<Date>;
|
|
329
|
-
}
|
|
330
251
|
interface Database {
|
|
331
252
|
blocks: BlocksTable;
|
|
332
253
|
transactions: TransactionsTable;
|
|
@@ -352,17 +273,14 @@ interface Database {
|
|
|
352
273
|
team_invitations: TeamInvitationsTable;
|
|
353
274
|
chat_sessions: ChatSessionsTable;
|
|
354
275
|
chat_messages: ChatMessagesTable;
|
|
355
|
-
workflow_definitions: WorkflowDefinitionsTable;
|
|
356
|
-
workflow_runs: WorkflowRunsTable;
|
|
357
|
-
workflow_steps: WorkflowStepsTable;
|
|
358
|
-
workflow_queue: WorkflowQueueTable;
|
|
359
|
-
workflow_schedules: WorkflowSchedulesTable;
|
|
360
|
-
workflow_cursors: WorkflowCursorsTable;
|
|
361
|
-
workflow_signer_secrets: WorkflowSignerSecretsTable;
|
|
362
|
-
workflow_budgets: WorkflowBudgetsTable;
|
|
363
276
|
tenants: TenantsTable;
|
|
364
277
|
tenant_usage_monthly: TenantUsageMonthlyTable;
|
|
278
|
+
tenant_compute_addons: TenantComputeAddonsTable;
|
|
279
|
+
account_spend_caps: AccountSpendCapsTable;
|
|
365
280
|
provisioning_audit_log: ProvisioningAuditLogTable;
|
|
281
|
+
subscriptions: SubscriptionsTable;
|
|
282
|
+
subscription_outbox: SubscriptionOutboxTable;
|
|
283
|
+
subscription_deliveries: SubscriptionDeliveriesTable;
|
|
366
284
|
}
|
|
367
285
|
type TenantStatus = "provisioning" | "active" | "suspended" | "error" | "deleted";
|
|
368
286
|
interface TenantsTable {
|
|
@@ -384,9 +302,9 @@ interface TenantsTable {
|
|
|
384
302
|
service_key_enc: Buffer;
|
|
385
303
|
api_url_internal: string;
|
|
386
304
|
api_url_public: string;
|
|
387
|
-
trial_ends_at: Date;
|
|
388
305
|
suspended_at: Date | null;
|
|
389
306
|
last_health_check_at: Date | null;
|
|
307
|
+
last_active_at: Generated<Date>;
|
|
390
308
|
service_gen: Generated<number>;
|
|
391
309
|
anon_gen: Generated<number>;
|
|
392
310
|
project_id: string | null;
|
|
@@ -404,6 +322,28 @@ interface TenantUsageMonthlyTable {
|
|
|
404
322
|
first_at: Generated<Date>;
|
|
405
323
|
last_at: Generated<Date>;
|
|
406
324
|
}
|
|
325
|
+
interface TenantComputeAddonsTable {
|
|
326
|
+
id: Generated<string>;
|
|
327
|
+
tenant_id: string;
|
|
328
|
+
memory_mb_delta: Generated<number>;
|
|
329
|
+
cpu_delta: Generated<number | string>;
|
|
330
|
+
storage_mb_delta: Generated<number>;
|
|
331
|
+
effective_from: Generated<Date>;
|
|
332
|
+
effective_until: Date | null;
|
|
333
|
+
stripe_subscription_item_id: string | null;
|
|
334
|
+
created_at: Generated<Date>;
|
|
335
|
+
}
|
|
336
|
+
interface AccountSpendCapsTable {
|
|
337
|
+
account_id: string;
|
|
338
|
+
monthly_cap_cents: number | null;
|
|
339
|
+
compute_cap_cents: number | null;
|
|
340
|
+
storage_cap_cents: number | null;
|
|
341
|
+
ai_cap_cents: number | null;
|
|
342
|
+
alert_threshold_pct: Generated<number>;
|
|
343
|
+
alert_sent_at: Date | null;
|
|
344
|
+
frozen_at: Date | null;
|
|
345
|
+
updated_at: Generated<Date>;
|
|
346
|
+
}
|
|
407
347
|
type ProvisioningAuditEvent = "provision.start" | "provision.success" | "provision.failure" | "suspend" | "resume" | "resize" | "keys.rotate" | "bastion.key.upload" | "bastion.key.revoke" | "teardown";
|
|
408
348
|
type ProvisioningAuditStatus = "ok" | "error";
|
|
409
349
|
interface ProvisioningAuditLogTable {
|
|
@@ -418,31 +358,69 @@ interface ProvisioningAuditLogTable {
|
|
|
418
358
|
error: string | null;
|
|
419
359
|
created_at: Generated<Date>;
|
|
420
360
|
}
|
|
421
|
-
|
|
361
|
+
type Account = Selectable<AccountsTable>;
|
|
362
|
+
type SubscriptionStatus = "active" | "paused" | "error";
|
|
363
|
+
type SubscriptionFormat = "standard-webhooks" | "inngest" | "trigger" | "cloudflare" | "cloudevents" | "raw";
|
|
364
|
+
type SubscriptionRuntime = "inngest" | "trigger" | "cloudflare" | "node";
|
|
365
|
+
interface SubscriptionsTable {
|
|
422
366
|
id: Generated<string>;
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
367
|
+
account_id: string;
|
|
368
|
+
project_id: string | null;
|
|
369
|
+
name: string;
|
|
370
|
+
status: ColumnType<SubscriptionStatus, SubscriptionStatus | undefined, SubscriptionStatus>;
|
|
371
|
+
subgraph_name: string;
|
|
372
|
+
table_name: string;
|
|
373
|
+
filter: Generated<unknown>;
|
|
374
|
+
format: ColumnType<SubscriptionFormat, SubscriptionFormat | undefined, SubscriptionFormat>;
|
|
375
|
+
runtime: SubscriptionRuntime | null;
|
|
376
|
+
url: string;
|
|
377
|
+
signing_secret_enc: Buffer;
|
|
378
|
+
auth_config: Generated<unknown>;
|
|
379
|
+
max_retries: Generated<number>;
|
|
380
|
+
timeout_ms: Generated<number>;
|
|
381
|
+
concurrency: Generated<number>;
|
|
382
|
+
circuit_failures: Generated<number>;
|
|
383
|
+
circuit_opened_at: Date | null;
|
|
384
|
+
last_delivery_at: Date | null;
|
|
385
|
+
last_success_at: Date | null;
|
|
386
|
+
last_error: string | null;
|
|
433
387
|
created_at: Generated<Date>;
|
|
434
388
|
updated_at: Generated<Date>;
|
|
435
389
|
}
|
|
436
|
-
|
|
390
|
+
type OutboxStatus = "pending" | "delivered" | "dead";
|
|
391
|
+
interface SubscriptionOutboxTable {
|
|
437
392
|
id: Generated<string>;
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
393
|
+
subscription_id: string;
|
|
394
|
+
subgraph_name: string;
|
|
395
|
+
table_name: string;
|
|
396
|
+
block_height: number | bigint;
|
|
397
|
+
tx_id: string | null;
|
|
398
|
+
row_pk: unknown;
|
|
399
|
+
event_type: string;
|
|
400
|
+
payload: unknown;
|
|
401
|
+
dedup_key: string;
|
|
402
|
+
attempt: Generated<number>;
|
|
403
|
+
next_attempt_at: Generated<Date>;
|
|
404
|
+
status: ColumnType<OutboxStatus, OutboxStatus | undefined, OutboxStatus>;
|
|
405
|
+
is_replay: Generated<boolean>;
|
|
406
|
+
delivered_at: Date | null;
|
|
407
|
+
failed_at: Date | null;
|
|
408
|
+
locked_by: string | null;
|
|
409
|
+
locked_until: Date | null;
|
|
442
410
|
created_at: Generated<Date>;
|
|
443
|
-
updated_at: Generated<Date>;
|
|
444
411
|
}
|
|
445
|
-
|
|
412
|
+
interface SubscriptionDeliveriesTable {
|
|
413
|
+
id: Generated<string>;
|
|
414
|
+
outbox_id: string;
|
|
415
|
+
subscription_id: string;
|
|
416
|
+
attempt: number;
|
|
417
|
+
status_code: number | null;
|
|
418
|
+
response_headers: unknown | null;
|
|
419
|
+
response_body: string | null;
|
|
420
|
+
error_message: string | null;
|
|
421
|
+
duration_ms: number | null;
|
|
422
|
+
dispatched_at: Generated<Date>;
|
|
423
|
+
}
|
|
446
424
|
declare function upsertAccount(db: Kysely<Database>, email: string): Promise<Account>;
|
|
447
425
|
declare function getAccountById(db: Kysely<Database>, id: string): Promise<Account | null>;
|
|
448
426
|
declare function updateAccountProfile(db: Kysely<Database>, id: string, data: {
|
|
@@ -450,6 +428,19 @@ declare function updateAccountProfile(db: Kysely<Database>, id: string, data: {
|
|
|
450
428
|
bio?: string
|
|
451
429
|
slug?: string
|
|
452
430
|
}): Promise<Account>;
|
|
431
|
+
/** Persist the Stripe customer id on first upgrade (lazy customer model). */
|
|
432
|
+
declare function setStripeCustomerId(db: Kysely<Database>, accountId: string, stripeCustomerId: string): Promise<void>;
|
|
433
|
+
/**
|
|
434
|
+
* Set the plan tier on an account. Called by the Stripe webhook on
|
|
435
|
+
* subscription lifecycle events + by the billing page's fast-resolve
|
|
436
|
+
* after a successful Checkout redirect. Returns true if a row was
|
|
437
|
+
* updated (account exists).
|
|
438
|
+
*/
|
|
439
|
+
declare function setAccountPlan(db: Kysely<Database>, accountId: string, plan: string): Promise<boolean>;
|
|
440
|
+
/** Resolve an account by its Stripe customer id. Null if no match. */
|
|
441
|
+
declare function getAccountByStripeCustomerId(db: Kysely<Database>, stripeCustomerId: string): Promise<{
|
|
442
|
+
id: string
|
|
443
|
+
} | null>;
|
|
453
444
|
declare function isSlugTaken(db: Kysely<Database>, slug: string, excludeAccountId: string): Promise<boolean>;
|
|
454
445
|
declare function isEmailAllowed(db: Kysely<Database>, email: string): Promise<boolean>;
|
|
455
446
|
declare function createMagicLink(db: Kysely<Database>, email: string, token: string, code: string, expiresInMs?: number): Promise<void>;
|
|
@@ -472,4 +463,4 @@ declare function approveWaitlistEntry(db: Kysely<Database>, email: string): Prom
|
|
|
472
463
|
code: string
|
|
473
464
|
status: "approved" | "already_approved" | "not_found"
|
|
474
465
|
}>;
|
|
475
|
-
export { verifyMagicLinkByCode, verifyMagicLink, upsertAccount, updateAccountProfile, listWaitlist, isSlugTaken, isEmailAllowed, getWaitlistById, getAccountById, createMagicLink, approveWaitlistEntry, WaitlistEntry };
|
|
466
|
+
export { verifyMagicLinkByCode, verifyMagicLink, upsertAccount, updateAccountProfile, setStripeCustomerId, setAccountPlan, listWaitlist, isSlugTaken, isEmailAllowed, getWaitlistById, getAccountByStripeCustomerId, getAccountById, createMagicLink, approveWaitlistEntry, WaitlistEntry };
|
|
@@ -32,6 +32,17 @@ async function updateAccountProfile(db, id, data) {
|
|
|
32
32
|
set.slug = data.slug;
|
|
33
33
|
return db.updateTable("accounts").set(set).where("id", "=", id).returningAll().executeTakeFirstOrThrow();
|
|
34
34
|
}
|
|
35
|
+
async function setStripeCustomerId(db, accountId, stripeCustomerId) {
|
|
36
|
+
await db.updateTable("accounts").set({ stripe_customer_id: stripeCustomerId }).where("id", "=", accountId).execute();
|
|
37
|
+
}
|
|
38
|
+
async function setAccountPlan(db, accountId, plan) {
|
|
39
|
+
const result = await db.updateTable("accounts").set({ plan }).where("id", "=", accountId).executeTakeFirst();
|
|
40
|
+
return (result.numUpdatedRows ?? 0n) > 0n;
|
|
41
|
+
}
|
|
42
|
+
async function getAccountByStripeCustomerId(db, stripeCustomerId) {
|
|
43
|
+
const row = await db.selectFrom("accounts").select("id").where("stripe_customer_id", "=", stripeCustomerId).executeTakeFirst();
|
|
44
|
+
return row ?? null;
|
|
45
|
+
}
|
|
35
46
|
async function isSlugTaken(db, slug, excludeAccountId) {
|
|
36
47
|
const row = await db.selectFrom("accounts").select("id").where("slug", "=", slug).where("id", "!=", excludeAccountId).executeTakeFirst();
|
|
37
48
|
return !!row;
|
|
@@ -94,14 +105,17 @@ export {
|
|
|
94
105
|
verifyMagicLink,
|
|
95
106
|
upsertAccount,
|
|
96
107
|
updateAccountProfile,
|
|
108
|
+
setStripeCustomerId,
|
|
109
|
+
setAccountPlan,
|
|
97
110
|
listWaitlist,
|
|
98
111
|
isSlugTaken,
|
|
99
112
|
isEmailAllowed,
|
|
100
113
|
getWaitlistById,
|
|
114
|
+
getAccountByStripeCustomerId,
|
|
101
115
|
getAccountById,
|
|
102
116
|
createMagicLink,
|
|
103
117
|
approveWaitlistEntry
|
|
104
118
|
};
|
|
105
119
|
|
|
106
|
-
//# debugId=
|
|
120
|
+
//# debugId=E1E89B16EA4EC82664756E2164756E21
|
|
107
121
|
//# sourceMappingURL=accounts.js.map
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/db/queries/accounts.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"import { type Kysely, sql } from \"kysely\";\nimport type { Selectable } from \"kysely\";\nimport type { Account, Database, WaitlistTable } from \"../types.ts\";\n\nexport async function upsertAccount(\n\tdb: Kysely<Database>,\n\temail: string,\n): Promise<Account> {\n\treturn await db\n\t\t.insertInto(\"accounts\")\n\t\t.values({ email })\n\t\t.onConflict(\n\t\t\t(oc) => oc.column(\"email\").doUpdateSet({ email }), // no-op update to return existing\n\t\t)\n\t\t.returningAll()\n\t\t.executeTakeFirstOrThrow();\n}\n\nexport async function getAccountById(\n\tdb: Kysely<Database>,\n\tid: string,\n): Promise<Account | null> {\n\treturn (\n\t\t(await db\n\t\t\t.selectFrom(\"accounts\")\n\t\t\t.selectAll()\n\t\t\t.where(\"id\", \"=\", id)\n\t\t\t.executeTakeFirst()) ?? null\n\t);\n}\n\nexport async function updateAccountProfile(\n\tdb: Kysely<Database>,\n\tid: string,\n\tdata: {\n\t\tdisplay_name?: string;\n\t\tbio?: string;\n\t\tslug?: string;\n\t},\n): Promise<Account> {\n\tconst set: Record<string, unknown> = {};\n\tif (data.display_name !== undefined) set.display_name = data.display_name;\n\tif (data.bio !== undefined) set.bio = data.bio;\n\tif (data.slug !== undefined) set.slug = data.slug;\n\n\treturn db\n\t\t.updateTable(\"accounts\")\n\t\t.set(set)\n\t\t.where(\"id\", \"=\", id)\n\t\t.returningAll()\n\t\t.executeTakeFirstOrThrow();\n}\n\nexport async function isSlugTaken(\n\tdb: Kysely<Database>,\n\tslug: string,\n\texcludeAccountId: string,\n): Promise<boolean> {\n\tconst row = await db\n\t\t.selectFrom(\"accounts\")\n\t\t.select(\"id\")\n\t\t.where(\"slug\", \"=\", slug)\n\t\t.where(\"id\", \"!=\", excludeAccountId)\n\t\t.executeTakeFirst();\n\treturn !!row;\n}\n\nexport async function isEmailAllowed(\n\tdb: Kysely<Database>,\n\temail: string,\n): Promise<boolean> {\n\tconst result = await sql<{ found: number }>`\n SELECT 1 AS found FROM accounts WHERE email = ${email}\n UNION ALL\n SELECT 1 AS found FROM waitlist WHERE email = ${email} AND status = 'approved'\n LIMIT 1\n `.execute(db);\n\n\treturn result.rows.length > 0;\n}\n\nexport async function createMagicLink(\n\tdb: Kysely<Database>,\n\temail: string,\n\ttoken: string,\n\tcode: string,\n\texpiresInMs: number = 15 * 60 * 1000,\n): Promise<void> {\n\tawait db\n\t\t.insertInto(\"magic_links\")\n\t\t.values({\n\t\t\temail,\n\t\t\ttoken,\n\t\t\tcode,\n\t\t\texpires_at: new Date(Date.now() + expiresInMs),\n\t\t})\n\t\t.execute();\n}\n\n/**\n * Verify a magic link token. Returns the email if valid, null otherwise.\n * Marks the token as used atomically. Rejects after 3 failed attempts.\n */\nexport async function verifyMagicLink(\n\tdb: Kysely<Database>,\n\ttoken: string,\n): Promise<string | null> {\n\tconst result = await db\n\t\t.updateTable(\"magic_links\")\n\t\t.set({ used_at: new Date() })\n\t\t.where(\"token\", \"=\", token)\n\t\t.where(\"used_at\", \"is\", null)\n\t\t.where(\"expires_at\", \">\", new Date())\n\t\t.where(\"failed_attempts\", \"<\", 3)\n\t\t.returning(\"email\")\n\t\t.executeTakeFirst();\n\n\tif (result?.email) return result.email;\n\n\t// Increment failed attempts if token exists but didn't verify\n\tawait db\n\t\t.updateTable(\"magic_links\")\n\t\t.set({ failed_attempts: sql`failed_attempts + 1` })\n\t\t.where(\"token\", \"=\", token)\n\t\t.where(\"used_at\", \"is\", null)\n\t\t.where(\"expires_at\", \">\", new Date())\n\t\t.execute();\n\n\treturn null;\n}\n\n/**\n * Verify by 6-digit code + email. Same atomic pattern as verifyMagicLink.\n * Rejects after 3 failed attempts. Increments failed_attempts on all\n * active codes for this email on failure (prevents parallel brute-force).\n */\nexport async function verifyMagicLinkByCode(\n\tdb: Kysely<Database>,\n\temail: string,\n\tcode: string,\n): Promise<string | null> {\n\tconst result = await db\n\t\t.updateTable(\"magic_links\")\n\t\t.set({ used_at: new Date() })\n\t\t.where(\"email\", \"=\", email)\n\t\t.where(\"code\", \"=\", code)\n\t\t.where(\"used_at\", \"is\", null)\n\t\t.where(\"expires_at\", \">\", new Date())\n\t\t.where(\"failed_attempts\", \"<\", 3)\n\t\t.returning(\"email\")\n\t\t.executeTakeFirst();\n\n\tif (result?.email) return result.email;\n\n\t// Increment failed attempts on all active codes for this email\n\tawait db\n\t\t.updateTable(\"magic_links\")\n\t\t.set({ failed_attempts: sql`failed_attempts + 1` })\n\t\t.where(\"email\", \"=\", email)\n\t\t.where(\"used_at\", \"is\", null)\n\t\t.where(\"expires_at\", \">\", new Date())\n\t\t.execute();\n\n\treturn null;\n}\n\n// ── Waitlist ──\n\nexport type WaitlistEntry = Selectable<WaitlistTable>;\n\nexport async function listWaitlist(\n\tdb: Kysely<Database>,\n\tstatus?: string,\n): Promise<WaitlistEntry[]> {\n\tlet query = db\n\t\t.selectFrom(\"waitlist\")\n\t\t.selectAll()\n\t\t.orderBy(\"created_at\", \"desc\");\n\tif (status) {\n\t\tquery = query.where(\"status\", \"=\", status);\n\t}\n\treturn query.execute();\n}\n\nexport async function getWaitlistById(\n\tdb: Kysely<Database>,\n\tid: string,\n): Promise<WaitlistEntry | null> {\n\treturn (\n\t\t(await db\n\t\t\t.selectFrom(\"waitlist\")\n\t\t\t.selectAll()\n\t\t\t.where(\"id\", \"=\", id)\n\t\t\t.executeTakeFirst()) ?? null\n\t);\n}\n\nexport async function approveWaitlistEntry(\n\tdb: Kysely<Database>,\n\temail: string,\n): Promise<{\n\ttoken: string;\n\tcode: string;\n\tstatus: \"approved\" | \"already_approved\" | \"not_found\";\n}> {\n\tconst row = await db\n\t\t.selectFrom(\"waitlist\")\n\t\t.select(\"status\")\n\t\t.where(\"email\", \"=\", email)\n\t\t.executeTakeFirst();\n\n\tif (!row) return { token: \"\", code: \"\", status: \"not_found\" };\n\tif (row.status !== \"pending\")\n\t\treturn { token: \"\", code: \"\", status: \"already_approved\" };\n\n\tawait db\n\t\t.updateTable(\"waitlist\")\n\t\t.set({ status: \"approved\" })\n\t\t.where(\"email\", \"=\", email)\n\t\t.execute();\n\n\tconst token = Math.floor(100000 + Math.random() * 900000).toString();\n\tconst code = String(Math.floor(Math.random() * 1_000_000)).padStart(6, \"0\");\n\tawait createMagicLink(db, email, token, code, 7 * 24 * 60 * 60 * 1000);\n\n\treturn { token, code, status: \"approved\" };\n}\n"
|
|
5
|
+
"import { type Kysely, sql } from \"kysely\";\nimport type { Selectable } from \"kysely\";\nimport type { Account, Database, WaitlistTable } from \"../types.ts\";\n\nexport async function upsertAccount(\n\tdb: Kysely<Database>,\n\temail: string,\n): Promise<Account> {\n\treturn await db\n\t\t.insertInto(\"accounts\")\n\t\t.values({ email })\n\t\t.onConflict(\n\t\t\t(oc) => oc.column(\"email\").doUpdateSet({ email }), // no-op update to return existing\n\t\t)\n\t\t.returningAll()\n\t\t.executeTakeFirstOrThrow();\n}\n\nexport async function getAccountById(\n\tdb: Kysely<Database>,\n\tid: string,\n): Promise<Account | null> {\n\treturn (\n\t\t(await db\n\t\t\t.selectFrom(\"accounts\")\n\t\t\t.selectAll()\n\t\t\t.where(\"id\", \"=\", id)\n\t\t\t.executeTakeFirst()) ?? null\n\t);\n}\n\nexport async function updateAccountProfile(\n\tdb: Kysely<Database>,\n\tid: string,\n\tdata: {\n\t\tdisplay_name?: string;\n\t\tbio?: string;\n\t\tslug?: string;\n\t},\n): Promise<Account> {\n\tconst set: Record<string, unknown> = {};\n\tif (data.display_name !== undefined) set.display_name = data.display_name;\n\tif (data.bio !== undefined) set.bio = data.bio;\n\tif (data.slug !== undefined) set.slug = data.slug;\n\n\treturn db\n\t\t.updateTable(\"accounts\")\n\t\t.set(set)\n\t\t.where(\"id\", \"=\", id)\n\t\t.returningAll()\n\t\t.executeTakeFirstOrThrow();\n}\n\n/** Persist the Stripe customer id on first upgrade (lazy customer model). */\nexport async function setStripeCustomerId(\n\tdb: Kysely<Database>,\n\taccountId: string,\n\tstripeCustomerId: string,\n): Promise<void> {\n\tawait db\n\t\t.updateTable(\"accounts\")\n\t\t.set({ stripe_customer_id: stripeCustomerId })\n\t\t.where(\"id\", \"=\", accountId)\n\t\t.execute();\n}\n\n/**\n * Set the plan tier on an account. Called by the Stripe webhook on\n * subscription lifecycle events + by the billing page's fast-resolve\n * after a successful Checkout redirect. Returns true if a row was\n * updated (account exists).\n */\nexport async function setAccountPlan(\n\tdb: Kysely<Database>,\n\taccountId: string,\n\tplan: string,\n): Promise<boolean> {\n\tconst result = await db\n\t\t.updateTable(\"accounts\")\n\t\t.set({ plan })\n\t\t.where(\"id\", \"=\", accountId)\n\t\t.executeTakeFirst();\n\treturn (result.numUpdatedRows ?? 0n) > 0n;\n}\n\n/** Resolve an account by its Stripe customer id. Null if no match. */\nexport async function getAccountByStripeCustomerId(\n\tdb: Kysely<Database>,\n\tstripeCustomerId: string,\n): Promise<{ id: string } | null> {\n\tconst row = await db\n\t\t.selectFrom(\"accounts\")\n\t\t.select(\"id\")\n\t\t.where(\"stripe_customer_id\", \"=\", stripeCustomerId)\n\t\t.executeTakeFirst();\n\treturn row ?? null;\n}\n\nexport async function isSlugTaken(\n\tdb: Kysely<Database>,\n\tslug: string,\n\texcludeAccountId: string,\n): Promise<boolean> {\n\tconst row = await db\n\t\t.selectFrom(\"accounts\")\n\t\t.select(\"id\")\n\t\t.where(\"slug\", \"=\", slug)\n\t\t.where(\"id\", \"!=\", excludeAccountId)\n\t\t.executeTakeFirst();\n\treturn !!row;\n}\n\nexport async function isEmailAllowed(\n\tdb: Kysely<Database>,\n\temail: string,\n): Promise<boolean> {\n\tconst result = await sql<{ found: number }>`\n SELECT 1 AS found FROM accounts WHERE email = ${email}\n UNION ALL\n SELECT 1 AS found FROM waitlist WHERE email = ${email} AND status = 'approved'\n LIMIT 1\n `.execute(db);\n\n\treturn result.rows.length > 0;\n}\n\nexport async function createMagicLink(\n\tdb: Kysely<Database>,\n\temail: string,\n\ttoken: string,\n\tcode: string,\n\texpiresInMs: number = 15 * 60 * 1000,\n): Promise<void> {\n\tawait db\n\t\t.insertInto(\"magic_links\")\n\t\t.values({\n\t\t\temail,\n\t\t\ttoken,\n\t\t\tcode,\n\t\t\texpires_at: new Date(Date.now() + expiresInMs),\n\t\t})\n\t\t.execute();\n}\n\n/**\n * Verify a magic link token. Returns the email if valid, null otherwise.\n * Marks the token as used atomically. Rejects after 3 failed attempts.\n */\nexport async function verifyMagicLink(\n\tdb: Kysely<Database>,\n\ttoken: string,\n): Promise<string | null> {\n\tconst result = await db\n\t\t.updateTable(\"magic_links\")\n\t\t.set({ used_at: new Date() })\n\t\t.where(\"token\", \"=\", token)\n\t\t.where(\"used_at\", \"is\", null)\n\t\t.where(\"expires_at\", \">\", new Date())\n\t\t.where(\"failed_attempts\", \"<\", 3)\n\t\t.returning(\"email\")\n\t\t.executeTakeFirst();\n\n\tif (result?.email) return result.email;\n\n\t// Increment failed attempts if token exists but didn't verify\n\tawait db\n\t\t.updateTable(\"magic_links\")\n\t\t.set({ failed_attempts: sql`failed_attempts + 1` })\n\t\t.where(\"token\", \"=\", token)\n\t\t.where(\"used_at\", \"is\", null)\n\t\t.where(\"expires_at\", \">\", new Date())\n\t\t.execute();\n\n\treturn null;\n}\n\n/**\n * Verify by 6-digit code + email. Same atomic pattern as verifyMagicLink.\n * Rejects after 3 failed attempts. Increments failed_attempts on all\n * active codes for this email on failure (prevents parallel brute-force).\n */\nexport async function verifyMagicLinkByCode(\n\tdb: Kysely<Database>,\n\temail: string,\n\tcode: string,\n): Promise<string | null> {\n\tconst result = await db\n\t\t.updateTable(\"magic_links\")\n\t\t.set({ used_at: new Date() })\n\t\t.where(\"email\", \"=\", email)\n\t\t.where(\"code\", \"=\", code)\n\t\t.where(\"used_at\", \"is\", null)\n\t\t.where(\"expires_at\", \">\", new Date())\n\t\t.where(\"failed_attempts\", \"<\", 3)\n\t\t.returning(\"email\")\n\t\t.executeTakeFirst();\n\n\tif (result?.email) return result.email;\n\n\t// Increment failed attempts on all active codes for this email\n\tawait db\n\t\t.updateTable(\"magic_links\")\n\t\t.set({ failed_attempts: sql`failed_attempts + 1` })\n\t\t.where(\"email\", \"=\", email)\n\t\t.where(\"used_at\", \"is\", null)\n\t\t.where(\"expires_at\", \">\", new Date())\n\t\t.execute();\n\n\treturn null;\n}\n\n// ── Waitlist ──\n\nexport type WaitlistEntry = Selectable<WaitlistTable>;\n\nexport async function listWaitlist(\n\tdb: Kysely<Database>,\n\tstatus?: string,\n): Promise<WaitlistEntry[]> {\n\tlet query = db\n\t\t.selectFrom(\"waitlist\")\n\t\t.selectAll()\n\t\t.orderBy(\"created_at\", \"desc\");\n\tif (status) {\n\t\tquery = query.where(\"status\", \"=\", status);\n\t}\n\treturn query.execute();\n}\n\nexport async function getWaitlistById(\n\tdb: Kysely<Database>,\n\tid: string,\n): Promise<WaitlistEntry | null> {\n\treturn (\n\t\t(await db\n\t\t\t.selectFrom(\"waitlist\")\n\t\t\t.selectAll()\n\t\t\t.where(\"id\", \"=\", id)\n\t\t\t.executeTakeFirst()) ?? null\n\t);\n}\n\nexport async function approveWaitlistEntry(\n\tdb: Kysely<Database>,\n\temail: string,\n): Promise<{\n\ttoken: string;\n\tcode: string;\n\tstatus: \"approved\" | \"already_approved\" | \"not_found\";\n}> {\n\tconst row = await db\n\t\t.selectFrom(\"waitlist\")\n\t\t.select(\"status\")\n\t\t.where(\"email\", \"=\", email)\n\t\t.executeTakeFirst();\n\n\tif (!row) return { token: \"\", code: \"\", status: \"not_found\" };\n\tif (row.status !== \"pending\")\n\t\treturn { token: \"\", code: \"\", status: \"already_approved\" };\n\n\tawait db\n\t\t.updateTable(\"waitlist\")\n\t\t.set({ status: \"approved\" })\n\t\t.where(\"email\", \"=\", email)\n\t\t.execute();\n\n\tconst token = Math.floor(100000 + Math.random() * 900000).toString();\n\tconst code = String(Math.floor(Math.random() * 1_000_000)).padStart(6, \"0\");\n\tawait createMagicLink(db, email, token, code, 7 * 24 * 60 * 60 * 1000);\n\n\treturn { token, code, status: \"approved\" };\n}\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": ";;;;;;;;;;;;;;;;;AAAA;AAIA,eAAsB,aAAa,CAClC,IACA,OACmB;AAAA,EACnB,OAAO,MAAM,GACX,WAAW,UAAU,EACrB,OAAO,EAAE,MAAM,CAAC,EAChB,WACA,CAAC,OAAO,GAAG,OAAO,OAAO,EAAE,YAAY,EAAE,MAAM,CAAC,CACjD,EACC,aAAa,EACb,wBAAwB;AAAA;AAG3B,eAAsB,cAAc,CACnC,IACA,IAC0B;AAAA,EAC1B,OACE,MAAM,GACL,WAAW,UAAU,EACrB,UAAU,EACV,MAAM,MAAM,KAAK,EAAE,EACnB,iBAAiB,KAAM;AAAA;AAI3B,eAAsB,oBAAoB,CACzC,IACA,IACA,MAKmB;AAAA,EACnB,MAAM,MAA+B,CAAC;AAAA,EACtC,IAAI,KAAK,iBAAiB;AAAA,IAAW,IAAI,eAAe,KAAK;AAAA,EAC7D,IAAI,KAAK,QAAQ;AAAA,IAAW,IAAI,MAAM,KAAK;AAAA,EAC3C,IAAI,KAAK,SAAS;AAAA,IAAW,IAAI,OAAO,KAAK;AAAA,EAE7C,OAAO,GACL,YAAY,UAAU,EACtB,IAAI,GAAG,EACP,MAAM,MAAM,KAAK,EAAE,EACnB,aAAa,EACb,wBAAwB;AAAA;
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "E1E89B16EA4EC82664756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|