@secondlayer/shared 6.4.5 → 6.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/db/index.js.map +2 -2
- package/dist/src/db/queries/chain-reorgs.js.map +2 -2
- package/dist/src/index.js.map +2 -2
- package/package.json +1 -49
- package/dist/src/db/queries/account-usage.d.ts +0 -741
- package/dist/src/db/queries/account-usage.js +0 -266
- package/dist/src/db/queries/account-usage.js.map +0 -11
- package/dist/src/db/queries/provisioning-audit.d.ts +0 -729
- package/dist/src/db/queries/provisioning-audit.js +0 -40
- package/dist/src/db/queries/provisioning-audit.js.map +0 -10
- package/dist/src/db/queries/tenant-compute-addons.d.ts +0 -729
- package/dist/src/db/queries/tenant-compute-addons.js +0 -47
- package/dist/src/db/queries/tenant-compute-addons.js.map +0 -10
- package/dist/src/pricing.d.ts +0 -60
- package/dist/src/pricing.js +0 -164
- package/dist/src/pricing.js.map +0 -10
|
@@ -1,266 +0,0 @@
|
|
|
1
|
-
import { createRequire } from "node:module";
|
|
2
|
-
var __defProp = Object.defineProperty;
|
|
3
|
-
var __returnValue = (v) => v;
|
|
4
|
-
function __exportSetter(name, newValue) {
|
|
5
|
-
this[name] = __returnValue.bind(null, newValue);
|
|
6
|
-
}
|
|
7
|
-
var __export = (target, all) => {
|
|
8
|
-
for (var name in all)
|
|
9
|
-
__defProp(target, name, {
|
|
10
|
-
get: all[name],
|
|
11
|
-
enumerable: true,
|
|
12
|
-
configurable: true,
|
|
13
|
-
set: __exportSetter.bind(all, name)
|
|
14
|
-
});
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
// src/pricing.ts
|
|
18
|
-
var BYTES_PER_GB = 1024 ** 3;
|
|
19
|
-
function alloc(totalMb, totalCpus) {
|
|
20
|
-
return {
|
|
21
|
-
postgres: {
|
|
22
|
-
memoryMb: Math.floor(totalMb * 0.25),
|
|
23
|
-
cpus: round2(totalCpus * 0.25)
|
|
24
|
-
},
|
|
25
|
-
processor: {
|
|
26
|
-
memoryMb: Math.floor(totalMb * 0.55),
|
|
27
|
-
cpus: round2(totalCpus * 0.55)
|
|
28
|
-
},
|
|
29
|
-
api: {
|
|
30
|
-
memoryMb: Math.floor(totalMb * 0.2),
|
|
31
|
-
cpus: round2(totalCpus * 0.2)
|
|
32
|
-
}
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
function allocTight(totalMb, totalCpus) {
|
|
36
|
-
return {
|
|
37
|
-
postgres: {
|
|
38
|
-
memoryMb: Math.floor(totalMb * 0.6),
|
|
39
|
-
cpus: round2(totalCpus * 0.6)
|
|
40
|
-
},
|
|
41
|
-
processor: {
|
|
42
|
-
memoryMb: Math.floor(totalMb * 0.25),
|
|
43
|
-
cpus: round2(totalCpus * 0.25)
|
|
44
|
-
},
|
|
45
|
-
api: {
|
|
46
|
-
memoryMb: Math.floor(totalMb * 0.15),
|
|
47
|
-
cpus: round2(totalCpus * 0.15)
|
|
48
|
-
}
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
function round2(n) {
|
|
52
|
-
return Math.round(n * 100) / 100;
|
|
53
|
-
}
|
|
54
|
-
function allocForTotals(totalMemoryMb, totalCpus) {
|
|
55
|
-
return totalMemoryMb < 1024 ? allocTight(totalMemoryMb, totalCpus) : alloc(totalMemoryMb, totalCpus);
|
|
56
|
-
}
|
|
57
|
-
var PLANS = {
|
|
58
|
-
launch: {
|
|
59
|
-
id: "launch",
|
|
60
|
-
displayName: "Launch",
|
|
61
|
-
monthlyPriceCents: 9900,
|
|
62
|
-
annualPriceCents: 99000,
|
|
63
|
-
totalCpus: 2,
|
|
64
|
-
totalMemoryMb: 6144,
|
|
65
|
-
storageLimitMb: 102400,
|
|
66
|
-
containers: alloc(6144, 2),
|
|
67
|
-
tagline: "Real product",
|
|
68
|
-
features: [
|
|
69
|
-
"2 vCPU · 6 GB RAM",
|
|
70
|
-
"100 GB storage · always-on",
|
|
71
|
-
"3-5 contracts",
|
|
72
|
-
"Production reindex windows",
|
|
73
|
-
"Spend caps + alerts",
|
|
74
|
-
"Email support"
|
|
75
|
-
],
|
|
76
|
-
stripeLookupKey: "secondlayer_launch_monthly",
|
|
77
|
-
stripeAnnualLookupKey: "secondlayer_launch_yearly"
|
|
78
|
-
},
|
|
79
|
-
scale: {
|
|
80
|
-
id: "scale",
|
|
81
|
-
displayName: "Scale",
|
|
82
|
-
monthlyPriceCents: 29900,
|
|
83
|
-
annualPriceCents: 299000,
|
|
84
|
-
totalCpus: 8,
|
|
85
|
-
totalMemoryMb: 24576,
|
|
86
|
-
storageLimitMb: 512000,
|
|
87
|
-
containers: alloc(24576, 8),
|
|
88
|
-
tagline: "Full indexing",
|
|
89
|
-
features: [
|
|
90
|
-
"8 vCPU · 24 GB RAM",
|
|
91
|
-
"500 GB storage · always-on",
|
|
92
|
-
"Heavy history + replay",
|
|
93
|
-
"24h SLA · priority support"
|
|
94
|
-
],
|
|
95
|
-
stripeLookupKey: "secondlayer_scale_monthly",
|
|
96
|
-
stripeAnnualLookupKey: "secondlayer_scale_yearly"
|
|
97
|
-
},
|
|
98
|
-
enterprise: {
|
|
99
|
-
id: "enterprise",
|
|
100
|
-
displayName: "Enterprise",
|
|
101
|
-
monthlyPriceCents: null,
|
|
102
|
-
annualPriceCents: null,
|
|
103
|
-
totalCpus: 16,
|
|
104
|
-
totalMemoryMb: 65536,
|
|
105
|
-
storageLimitMb: -1,
|
|
106
|
-
containers: alloc(65536, 16),
|
|
107
|
-
tagline: "Whatever needed",
|
|
108
|
-
features: [
|
|
109
|
-
"Custom compute + storage",
|
|
110
|
-
"SLAs · regions · SSO",
|
|
111
|
-
"Dedicated success engineer"
|
|
112
|
-
],
|
|
113
|
-
stripeLookupKey: null,
|
|
114
|
-
stripeAnnualLookupKey: null
|
|
115
|
-
}
|
|
116
|
-
};
|
|
117
|
-
var PLAN_IDS = ["launch", "scale", "enterprise"];
|
|
118
|
-
function getPlan(id) {
|
|
119
|
-
const plan = PLANS[id];
|
|
120
|
-
if (!plan)
|
|
121
|
-
throw new Error(`Unknown plan: ${id}`);
|
|
122
|
-
return plan;
|
|
123
|
-
}
|
|
124
|
-
function isValidPlanId(id) {
|
|
125
|
-
return id in PLANS;
|
|
126
|
-
}
|
|
127
|
-
function getComputeAllowanceHours(_plan) {
|
|
128
|
-
return Number.POSITIVE_INFINITY;
|
|
129
|
-
}
|
|
130
|
-
function getStorageAllowanceBytes(plan) {
|
|
131
|
-
const planDef = PLANS[plan];
|
|
132
|
-
if (!planDef)
|
|
133
|
-
return 0;
|
|
134
|
-
if (planDef.storageLimitMb < 0)
|
|
135
|
-
return Number.POSITIVE_INFINITY;
|
|
136
|
-
return planDef.storageLimitMb * 1024 * 1024;
|
|
137
|
-
}
|
|
138
|
-
function hasStorageOverage(plan) {
|
|
139
|
-
return plan !== "none" && plan !== "enterprise";
|
|
140
|
-
}
|
|
141
|
-
function getBasePriceCents(plan) {
|
|
142
|
-
const planDef = PLANS[plan];
|
|
143
|
-
return planDef?.monthlyPriceCents ?? 0;
|
|
144
|
-
}
|
|
145
|
-
function getPlanDisplayName(plan) {
|
|
146
|
-
const planDef = PLANS[plan];
|
|
147
|
-
return planDef?.displayName ?? plan.charAt(0).toUpperCase() + plan.slice(1);
|
|
148
|
-
}
|
|
149
|
-
// src/db/queries/account-usage.ts
|
|
150
|
-
import { sql } from "kysely";
|
|
151
|
-
var IDLE_GRACE_MS = 2 * 60 * 60 * 1000;
|
|
152
|
-
var BYTES_PER_MB = 1024 * 1024;
|
|
153
|
-
function toDayKey(d) {
|
|
154
|
-
return d.toISOString().slice(0, 10);
|
|
155
|
-
}
|
|
156
|
-
function* lastNDays(n, endInclusive) {
|
|
157
|
-
const end = new Date(endInclusive);
|
|
158
|
-
for (let i = n - 1;i >= 0; i--) {
|
|
159
|
-
const d = new Date(end);
|
|
160
|
-
d.setUTCDate(d.getUTCDate() - i);
|
|
161
|
-
yield toDayKey(d);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
function computeActiveHours(periodStart, now, tenant) {
|
|
165
|
-
if (tenant.status !== "active" && tenant.status !== "limit_warning")
|
|
166
|
-
return 0;
|
|
167
|
-
const rangeStart = Math.max(periodStart.getTime(), tenant.created_at.getTime());
|
|
168
|
-
const rangeEnd = Math.min(now.getTime(), tenant.last_active_at.getTime() + IDLE_GRACE_MS);
|
|
169
|
-
if (rangeEnd <= rangeStart)
|
|
170
|
-
return 0;
|
|
171
|
-
return (rangeEnd - rangeStart) / (1000 * 60 * 60);
|
|
172
|
-
}
|
|
173
|
-
function pct(used, allowance) {
|
|
174
|
-
if (!Number.isFinite(allowance) || allowance <= 0)
|
|
175
|
-
return 0;
|
|
176
|
-
return Math.min(used / allowance * 100, 100);
|
|
177
|
-
}
|
|
178
|
-
async function getComputeUsage(db, accountId, plan, periodStart, now = new Date) {
|
|
179
|
-
const tenants = await db.selectFrom("tenants").select(["id", "cpus", "status", "created_at", "last_active_at"]).where("account_id", "=", accountId).where("status", "!=", "deleted").execute();
|
|
180
|
-
let totalHours = 0;
|
|
181
|
-
for (const t of tenants) {
|
|
182
|
-
const hours = computeActiveHours(periodStart, now, {
|
|
183
|
-
created_at: t.created_at,
|
|
184
|
-
last_active_at: t.last_active_at,
|
|
185
|
-
status: String(t.status)
|
|
186
|
-
});
|
|
187
|
-
totalHours += hours * Number(t.cpus);
|
|
188
|
-
}
|
|
189
|
-
const allowance = getComputeAllowanceHours(plan);
|
|
190
|
-
const sparkline = [];
|
|
191
|
-
for (const day of lastNDays(14, now)) {
|
|
192
|
-
const dayStart = new Date(`${day}T00:00:00.000Z`);
|
|
193
|
-
const dayEnd = new Date(`${day}T23:59:59.999Z`);
|
|
194
|
-
let dayHours = 0;
|
|
195
|
-
for (const t of tenants) {
|
|
196
|
-
const hours = computeActiveHours(dayStart, dayEnd, {
|
|
197
|
-
created_at: t.created_at,
|
|
198
|
-
last_active_at: t.last_active_at,
|
|
199
|
-
status: String(t.status)
|
|
200
|
-
});
|
|
201
|
-
dayHours += Math.min(hours, 24) * Number(t.cpus);
|
|
202
|
-
}
|
|
203
|
-
sparkline.push({ day, value: dayHours });
|
|
204
|
-
}
|
|
205
|
-
return {
|
|
206
|
-
usedHours: totalHours,
|
|
207
|
-
allowanceHours: allowance,
|
|
208
|
-
pct: pct(totalHours, allowance),
|
|
209
|
-
sparkline
|
|
210
|
-
};
|
|
211
|
-
}
|
|
212
|
-
async function getStorageUsage(db, accountId, plan, now = new Date) {
|
|
213
|
-
const current = await db.selectFrom("tenants").select(sql`COALESCE(SUM(storage_used_mb), 0)`.as("mb")).where("account_id", "=", accountId).where("status", "!=", "deleted").executeTakeFirst();
|
|
214
|
-
const usedBytes = Number(current?.mb ?? 0) * BYTES_PER_MB;
|
|
215
|
-
const allowance = getStorageAllowanceBytes(plan);
|
|
216
|
-
const sparkline = [];
|
|
217
|
-
for (const day of lastNDays(14, now)) {
|
|
218
|
-
sparkline.push({ day, value: Number(current?.mb ?? 0) });
|
|
219
|
-
}
|
|
220
|
-
return {
|
|
221
|
-
usedBytes,
|
|
222
|
-
allowanceBytes: allowance,
|
|
223
|
-
pct: pct(usedBytes, allowance),
|
|
224
|
-
sparkline
|
|
225
|
-
};
|
|
226
|
-
}
|
|
227
|
-
async function getProjectBreakdown(db, accountId, plan, periodStart, now = new Date) {
|
|
228
|
-
const tenants = await db.selectFrom("tenants").select([
|
|
229
|
-
"id",
|
|
230
|
-
"slug",
|
|
231
|
-
"status",
|
|
232
|
-
"cpus",
|
|
233
|
-
"storage_used_mb",
|
|
234
|
-
"created_at",
|
|
235
|
-
"last_active_at"
|
|
236
|
-
]).where("account_id", "=", accountId).where("status", "!=", "deleted").orderBy("created_at", "desc").execute();
|
|
237
|
-
if (tenants.length === 0)
|
|
238
|
-
return [];
|
|
239
|
-
const computeAllowance = getComputeAllowanceHours(plan);
|
|
240
|
-
const storageAllowance = getStorageAllowanceBytes(plan);
|
|
241
|
-
return tenants.map((t) => {
|
|
242
|
-
const hours = computeActiveHours(periodStart, now, {
|
|
243
|
-
created_at: t.created_at,
|
|
244
|
-
last_active_at: t.last_active_at,
|
|
245
|
-
status: String(t.status)
|
|
246
|
-
}) * Number(t.cpus);
|
|
247
|
-
const bytes = Number(t.storage_used_mb ?? 0) * BYTES_PER_MB;
|
|
248
|
-
return {
|
|
249
|
-
id: t.id,
|
|
250
|
-
slug: t.slug,
|
|
251
|
-
name: t.slug,
|
|
252
|
-
status: String(t.status),
|
|
253
|
-
subgraphCount: 0,
|
|
254
|
-
compute: { hours, pct: pct(hours, computeAllowance) },
|
|
255
|
-
storage: { bytes, pct: pct(bytes, storageAllowance) }
|
|
256
|
-
};
|
|
257
|
-
});
|
|
258
|
-
}
|
|
259
|
-
export {
|
|
260
|
-
getStorageUsage,
|
|
261
|
-
getProjectBreakdown,
|
|
262
|
-
getComputeUsage
|
|
263
|
-
};
|
|
264
|
-
|
|
265
|
-
//# debugId=EDCFAE397F4E86AB64756E2164756E21
|
|
266
|
-
//# sourceMappingURL=account-usage.js.map
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../src/pricing.ts", "../src/db/queries/account-usage.ts"],
|
|
4
|
-
"sourcesContent": [
|
|
5
|
-
"/**\n * Single source of truth for plan tiers — capacity, price, display copy,\n * Stripe binding, and container allocations.\n *\n * Consumed by:\n * - Provisioner (`packages/provisioner/src/plans.ts` re-exports)\n * - API (`/api/accounts/usage` for allowance math + display)\n * - Web app (`/billing` page renders plan cards from this)\n *\n * Adding a tier? Add an entry to PLANS. Removing one? Drop here, run\n * the Stripe-side cleanup (archive lookup_key), and update env vars.\n */\n\nconst BYTES_PER_GB: number = 1024 ** 3;\n\nexport type AccountPlanId = \"none\" | PlanId;\nexport type PlanId = \"launch\" | \"scale\" | \"enterprise\";\n\nexport interface ContainerAlloc {\n\tmemoryMb: number;\n\tcpus: number;\n}\n\nexport interface Plan {\n\tid: PlanId;\n\tdisplayName: string;\n\t/** Monthly subscription price in cents. null = custom (Enterprise). */\n\tmonthlyPriceCents: number | null;\n\t/** Annual subscription price in cents. null = no self-serve annual price. */\n\tannualPriceCents: number | null;\n\ttotalCpus: number;\n\ttotalMemoryMb: number;\n\t/** Hard cap. -1 = unlimited (Enterprise). Storage overage bills past this. */\n\tstorageLimitMb: number;\n\tcontainers: {\n\t\tpostgres: ContainerAlloc;\n\t\tapi: ContainerAlloc;\n\t\tprocessor: ContainerAlloc;\n\t};\n\t/** Display-only. Marketing/short pitch. */\n\ttagline: string;\n\t/** Display-only. Bullet list on the plan card. */\n\tfeatures: string[];\n\t/** Stripe `lookup_key` for monthly recurring tier price. null for enterprise. */\n\tstripeLookupKey: string | null;\n\t/** Stripe `lookup_key` for annual recurring tier price. null for enterprise. */\n\tstripeAnnualLookupKey: string | null;\n}\n\n// ── Allocation helpers ──────────────────────────────────────────────\n//\n// Allocation within a plan (3 containers per tenant):\n// Default split (paid tiers) — PG 25% / proc 55% / api 20%\n// Sub-1GB total — PG 60% / proc 25% / api 15%\n//\n// Why proc-heavy: the subgraph processor is CPU-bound during backfill\n// (event decode + handler exec + DB writes), while PG idles at <1% CPU\n// observed during steady-state indexing on Launch tier. The 2026-05-13\n// rebalance shifts CPU from idle PG to active proc to recover backfill\n// throughput regressed by the move from shared infra to per-tenant\n// containers — Launch went from 5 blocks/min → ~100 blocks/min just by\n// raising the proc's `--cpus` allotment.\n//\n// Docker memory limit is a hard cap (OOM kill on overage). CPU is a soft\n// cap via `--cpus` (throttling, not killing). Storage is monitored\n// separately and billed as overage — PG crashes if we hard-cap it.\n\nfunction alloc(totalMb: number, totalCpus: number): Plan[\"containers\"] {\n\treturn {\n\t\tpostgres: {\n\t\t\tmemoryMb: Math.floor(totalMb * 0.25),\n\t\t\tcpus: round2(totalCpus * 0.25),\n\t\t},\n\t\tprocessor: {\n\t\t\tmemoryMb: Math.floor(totalMb * 0.55),\n\t\t\tcpus: round2(totalCpus * 0.55),\n\t\t},\n\t\tapi: {\n\t\t\tmemoryMb: Math.floor(totalMb * 0.2),\n\t\t\tcpus: round2(totalCpus * 0.2),\n\t\t},\n\t};\n}\n\nfunction allocTight(totalMb: number, totalCpus: number): Plan[\"containers\"] {\n\treturn {\n\t\tpostgres: {\n\t\t\tmemoryMb: Math.floor(totalMb * 0.6),\n\t\t\tcpus: round2(totalCpus * 0.6),\n\t\t},\n\t\tprocessor: {\n\t\t\tmemoryMb: Math.floor(totalMb * 0.25),\n\t\t\tcpus: round2(totalCpus * 0.25),\n\t\t},\n\t\tapi: {\n\t\t\tmemoryMb: Math.floor(totalMb * 0.15),\n\t\t\tcpus: round2(totalCpus * 0.15),\n\t\t},\n\t};\n}\n\nfunction round2(n: number): number {\n\treturn Math.round(n * 100) / 100;\n}\n\n/**\n * Split a compute envelope across (postgres, processor, api) containers.\n * Auto-biases PG-heavy (60/25/15) for sub-1GB totals.\n */\nexport function allocForTotals(\n\ttotalMemoryMb: number,\n\ttotalCpus: number,\n): Plan[\"containers\"] {\n\treturn totalMemoryMb < 1024\n\t\t? allocTight(totalMemoryMb, totalCpus)\n\t\t: alloc(totalMemoryMb, totalCpus);\n}\n\n// ── Canonical plan data ─────────────────────────────────────────────\n\nexport const PLANS: Record<PlanId, Plan> = {\n\tlaunch: {\n\t\tid: \"launch\",\n\t\tdisplayName: \"Launch\",\n\t\tmonthlyPriceCents: 9_900, // $99\n\t\tannualPriceCents: 99_000, // 2 months free\n\t\ttotalCpus: 2,\n\t\ttotalMemoryMb: 6_144,\n\t\tstorageLimitMb: 102_400, // 100 GB\n\t\tcontainers: alloc(6_144, 2),\n\t\ttagline: \"Real product\",\n\t\tfeatures: [\n\t\t\t\"2 vCPU · 6 GB RAM\",\n\t\t\t\"100 GB storage · always-on\",\n\t\t\t\"3-5 contracts\",\n\t\t\t\"Production reindex windows\",\n\t\t\t\"Spend caps + alerts\",\n\t\t\t\"Email support\",\n\t\t],\n\t\tstripeLookupKey: \"secondlayer_launch_monthly\",\n\t\tstripeAnnualLookupKey: \"secondlayer_launch_yearly\",\n\t},\n\tscale: {\n\t\tid: \"scale\",\n\t\tdisplayName: \"Scale\",\n\t\tmonthlyPriceCents: 29_900, // $299\n\t\tannualPriceCents: 299_000, // 2 months free\n\t\ttotalCpus: 8,\n\t\ttotalMemoryMb: 24_576,\n\t\tstorageLimitMb: 512_000, // 500 GB\n\t\tcontainers: alloc(24_576, 8),\n\t\ttagline: \"Full indexing\",\n\t\tfeatures: [\n\t\t\t\"8 vCPU · 24 GB RAM\",\n\t\t\t\"500 GB storage · always-on\",\n\t\t\t\"Heavy history + replay\",\n\t\t\t\"24h SLA · priority support\",\n\t\t],\n\t\tstripeLookupKey: \"secondlayer_scale_monthly\",\n\t\tstripeAnnualLookupKey: \"secondlayer_scale_yearly\",\n\t},\n\tenterprise: {\n\t\tid: \"enterprise\",\n\t\tdisplayName: \"Enterprise\",\n\t\tmonthlyPriceCents: null,\n\t\tannualPriceCents: null,\n\t\ttotalCpus: 16,\n\t\ttotalMemoryMb: 65_536,\n\t\tstorageLimitMb: -1,\n\t\tcontainers: alloc(65_536, 16),\n\t\ttagline: \"Whatever needed\",\n\t\tfeatures: [\n\t\t\t\"Custom compute + storage\",\n\t\t\t\"SLAs · regions · SSO\",\n\t\t\t\"Dedicated success engineer\",\n\t\t],\n\t\tstripeLookupKey: null,\n\t\tstripeAnnualLookupKey: null,\n\t},\n};\n\nexport const PLAN_IDS: readonly PlanId[] = [\"launch\", \"scale\", \"enterprise\"];\n\nexport function getPlan(id: string): Plan {\n\tconst plan = (PLANS as Record<string, Plan | undefined>)[id];\n\tif (!plan) throw new Error(`Unknown plan: ${id}`);\n\treturn plan;\n}\n\nexport function isValidPlanId(id: string): id is PlanId {\n\treturn id in PLANS;\n}\n\n// ── Allowance helpers (used by /api/accounts/usage display) ─────────\n//\n// Compute is hard-capped by Docker `--cpus`, so there's no compute\n// overage billing. The function below returns ∞ for paid plans (display-\n// only — no metering).\n//\n// Storage IS metered and billed past the plan's allowance via the\n// `storage_gb_months` Stripe meter at $2/GB-mo.\n\nexport function getComputeAllowanceHours(_plan: string): number {\n\t// Compute overage was killed when we removed the `compute_hours` meter.\n\t// All plans are now hard-capped by Docker `--cpus`. ∞ here means \"no\n\t// overage tracked\" for display purposes.\n\treturn Number.POSITIVE_INFINITY;\n}\n\nexport function getStorageAllowanceBytes(plan: string): number {\n\tconst planDef = (PLANS as Record<string, Plan | undefined>)[plan];\n\tif (!planDef) return 0;\n\tif (planDef.storageLimitMb < 0) return Number.POSITIVE_INFINITY;\n\treturn planDef.storageLimitMb * 1024 * 1024;\n}\n\n/** Paid tiers bill $2/GB over allowance. Accounts with no plan do not accrue overage. */\nexport function hasStorageOverage(plan: string): boolean {\n\treturn plan !== \"none\" && plan !== \"enterprise\";\n}\n\nexport function getBasePriceCents(plan: string): number {\n\tconst planDef = (PLANS as Record<string, Plan | undefined>)[plan];\n\treturn planDef?.monthlyPriceCents ?? 0;\n}\n\nexport function getPlanDisplayName(plan: string): string {\n\tconst planDef = (PLANS as Record<string, Plan | undefined>)[plan];\n\treturn planDef?.displayName ?? plan.charAt(0).toUpperCase() + plan.slice(1);\n}\n\n// Re-export bytes-per-GB constant for callers that compute display values.\nexport { BYTES_PER_GB };\n",
|
|
6
|
-
"import { type Kysely, sql } from \"kysely\";\nimport {\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 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}\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\" && tenant.status !== \"limit_warning\") 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 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 computeAllowance = getComputeAllowanceHours(plan);\n\tconst storageAllowance = getStorageAllowanceBytes(plan);\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\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};\n\t});\n}\n"
|
|
7
|
-
],
|
|
8
|
-
"mappings": "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",
|
|
9
|
-
"debugId": "EDCFAE397F4E86AB64756E2164756E21",
|
|
10
|
-
"names": []
|
|
11
|
-
}
|