@secondlayer/shared 1.1.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/dist/src/db/index.d.ts +89 -6
  2. package/dist/src/db/index.js +53 -29
  3. package/dist/src/db/index.js.map +4 -4
  4. package/dist/src/db/jsonb.js.map +2 -2
  5. package/dist/src/db/queries/accounts.d.ts +58 -2
  6. package/dist/src/db/queries/integrity.d.ts +58 -2
  7. package/dist/src/db/queries/projects.d.ts +58 -2
  8. package/dist/src/db/queries/projects.js.map +2 -2
  9. package/dist/src/db/queries/{marketplace.d.ts → provisioning-audit.d.ts} +79 -56
  10. package/dist/src/db/queries/provisioning-audit.js +40 -0
  11. package/dist/src/db/queries/provisioning-audit.js.map +10 -0
  12. package/dist/src/db/queries/subgraph-gaps.d.ts +58 -2
  13. package/dist/src/db/queries/subgraphs.d.ts +62 -5
  14. package/dist/src/db/queries/subgraphs.js +3 -9
  15. package/dist/src/db/queries/subgraphs.js.map +4 -4
  16. package/dist/src/db/queries/tenants.d.ts +527 -0
  17. package/dist/src/db/queries/tenants.js +220 -0
  18. package/dist/src/db/queries/tenants.js.map +11 -0
  19. package/dist/src/db/queries/usage.d.ts +58 -2
  20. package/dist/src/db/queries/usage.js +3 -3
  21. package/dist/src/db/queries/usage.js.map +3 -3
  22. package/dist/src/db/queries/workflows.d.ts +58 -2
  23. package/dist/src/db/queries/workflows.js +31 -3
  24. package/dist/src/db/queries/workflows.js.map +4 -4
  25. package/dist/src/db/schema.d.ts +67 -3
  26. package/dist/src/env.d.ts +10 -0
  27. package/dist/src/env.js +3 -1
  28. package/dist/src/env.js.map +3 -3
  29. package/dist/src/errors.d.ts +17 -3
  30. package/dist/src/errors.js +34 -3
  31. package/dist/src/errors.js.map +3 -3
  32. package/dist/src/index.d.ts +142 -84
  33. package/dist/src/index.js +146 -99
  34. package/dist/src/index.js.map +8 -8
  35. package/dist/src/logger.js +3 -1
  36. package/dist/src/logger.js.map +3 -3
  37. package/dist/src/mode.d.ts +29 -0
  38. package/dist/src/mode.js +43 -0
  39. package/dist/src/mode.js.map +10 -0
  40. package/dist/src/node/archive-client.js +3 -1
  41. package/dist/src/node/archive-client.js.map +3 -3
  42. package/dist/src/node/hiro-client.js +3 -1
  43. package/dist/src/node/hiro-client.js.map +3 -3
  44. package/dist/src/node/local-client.d.ts +58 -2
  45. package/dist/src/queue/listener.d.ts +11 -2
  46. package/dist/src/queue/listener.js +11 -12
  47. package/dist/src/queue/listener.js.map +3 -3
  48. package/dist/src/schemas/accounts.d.ts +14 -0
  49. package/dist/src/schemas/{marketplace.js → accounts.js} +4 -14
  50. package/dist/src/schemas/accounts.js.map +10 -0
  51. package/dist/src/schemas/index.d.ts +28 -77
  52. package/dist/src/schemas/index.js +59 -69
  53. package/dist/src/schemas/index.js.map +4 -4
  54. package/dist/src/types.d.ts +10 -0
  55. package/migrations/0037_nullable_api_key.ts +35 -0
  56. package/migrations/0038_drop_workflow_tables.ts +46 -0
  57. package/migrations/0039_tenants.ts +66 -0
  58. package/migrations/0040_tenant_key_generations.ts +29 -0
  59. package/migrations/0041_subgraphs_drop_api_key_id.ts +49 -0
  60. package/migrations/0042_tenant_project_id.ts +25 -0
  61. package/migrations/0043_tenant_usage_monthly.ts +36 -0
  62. package/migrations/0044_provisioning_audit_log.ts +40 -0
  63. package/package.json +15 -7
  64. package/dist/src/db/queries/marketplace.js +0 -142
  65. package/dist/src/db/queries/marketplace.js.map +0 -10
  66. package/dist/src/schemas/marketplace.d.ts +0 -63
  67. package/dist/src/schemas/marketplace.js.map +0 -10
@@ -0,0 +1,220 @@
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/crypto/secrets.ts
18
+ import { createCipheriv, createDecipheriv, randomBytes } from "node:crypto";
19
+ var KEY_ENV = "SECONDLAYER_SECRETS_KEY";
20
+ var IV_LEN = 12;
21
+ var TAG_LEN = 16;
22
+ function loadKey() {
23
+ const hex = process.env[KEY_ENV];
24
+ if (!hex) {
25
+ throw new Error(`${KEY_ENV} not set. Generate one with: openssl rand -hex 32`);
26
+ }
27
+ const key = Buffer.from(hex, "hex");
28
+ if (key.length !== 32) {
29
+ throw new Error(`${KEY_ENV} must be 32 bytes hex (got ${key.length})`);
30
+ }
31
+ return key;
32
+ }
33
+ var _cachedKey = null;
34
+ function getKey() {
35
+ if (!_cachedKey)
36
+ _cachedKey = loadKey();
37
+ return _cachedKey;
38
+ }
39
+ function encryptSecret(plaintext) {
40
+ const key = getKey();
41
+ const iv = randomBytes(IV_LEN);
42
+ const cipher = createCipheriv("aes-256-gcm", key, iv);
43
+ const ciphertext = Buffer.concat([
44
+ cipher.update(plaintext, "utf8"),
45
+ cipher.final()
46
+ ]);
47
+ const tag = cipher.getAuthTag();
48
+ return Buffer.concat([iv, tag, ciphertext]);
49
+ }
50
+ function decryptSecret(envelope) {
51
+ const key = getKey();
52
+ const iv = envelope.subarray(0, IV_LEN);
53
+ const tag = envelope.subarray(IV_LEN, IV_LEN + TAG_LEN);
54
+ const ciphertext = envelope.subarray(IV_LEN + TAG_LEN);
55
+ const decipher = createDecipheriv("aes-256-gcm", key, iv);
56
+ decipher.setAuthTag(tag);
57
+ return decipher.update(ciphertext).toString("utf8") + decipher.final("utf8");
58
+ }
59
+ function generateSecretsKey() {
60
+ return randomBytes(32).toString("hex");
61
+ }
62
+
63
+ // src/db/queries/tenants.ts
64
+ import { sql } from "kysely";
65
+ async function insertTenant(db, input) {
66
+ const row = {
67
+ account_id: input.accountId,
68
+ slug: input.slug,
69
+ status: "active",
70
+ plan: input.plan,
71
+ cpus: input.cpus,
72
+ memory_mb: input.memoryMb,
73
+ storage_limit_mb: input.storageLimitMb,
74
+ pg_container_id: input.pgContainerId,
75
+ api_container_id: input.apiContainerId,
76
+ processor_container_id: input.processorContainerId,
77
+ target_database_url_enc: encryptSecret(input.targetDatabaseUrl),
78
+ tenant_jwt_secret_enc: encryptSecret(input.tenantJwtSecret),
79
+ anon_key_enc: encryptSecret(input.anonKey),
80
+ service_key_enc: encryptSecret(input.serviceKey),
81
+ api_url_internal: input.apiUrlInternal,
82
+ api_url_public: input.apiUrlPublic,
83
+ trial_ends_at: input.trialEndsAt,
84
+ project_id: input.projectId ?? null
85
+ };
86
+ return db.insertInto("tenants").values(row).returningAll().executeTakeFirstOrThrow();
87
+ }
88
+ async function getTenantByAccount(db, accountId) {
89
+ const row = await db.selectFrom("tenants").selectAll().where("account_id", "=", accountId).where("status", "<>", "deleted").orderBy("created_at", "desc").executeTakeFirst();
90
+ return row ?? null;
91
+ }
92
+ async function getTenantBySlug(db, slug) {
93
+ const row = await db.selectFrom("tenants").selectAll().where("slug", "=", slug).executeTakeFirst();
94
+ return row ?? null;
95
+ }
96
+ async function listTenantsByStatus(db, status) {
97
+ return db.selectFrom("tenants").selectAll().where("status", "=", status).execute();
98
+ }
99
+ async function listExpiredTrials(db, now = new Date) {
100
+ return db.selectFrom("tenants").selectAll().where("status", "in", ["provisioning", "active"]).where("trial_ends_at", "<", now).execute();
101
+ }
102
+ async function listSuspendedOlderThan(db, olderThan) {
103
+ return db.selectFrom("tenants").selectAll().where("status", "=", "suspended").where("suspended_at", "<", olderThan).execute();
104
+ }
105
+ async function setTenantStatus(db, slug, status) {
106
+ const patch = {
107
+ status,
108
+ updated_at: new Date
109
+ };
110
+ if (status === "suspended")
111
+ patch.suspended_at = new Date;
112
+ if (status === "active")
113
+ patch.suspended_at = null;
114
+ await db.updateTable("tenants").set(patch).where("slug", "=", slug).execute();
115
+ }
116
+ async function recordHealthCheck(db, slug, storageUsedMb) {
117
+ await db.updateTable("tenants").set({
118
+ last_health_check_at: new Date,
119
+ storage_used_mb: storageUsedMb,
120
+ updated_at: new Date
121
+ }).where("slug", "=", slug).execute();
122
+ }
123
+ async function recordMonthlyUsage(db, tenantId, storageMb) {
124
+ const now = new Date;
125
+ const periodMonth = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1));
126
+ await sql`
127
+ INSERT INTO tenant_usage_monthly (
128
+ tenant_id, period_month,
129
+ storage_peak_mb, storage_avg_mb, storage_last_mb,
130
+ measurements, first_at, last_at
131
+ ) VALUES (
132
+ ${tenantId}, ${periodMonth},
133
+ ${storageMb}, ${storageMb}, ${storageMb},
134
+ 1, now(), now()
135
+ )
136
+ ON CONFLICT (tenant_id, period_month) DO UPDATE SET
137
+ storage_peak_mb = GREATEST(tenant_usage_monthly.storage_peak_mb, EXCLUDED.storage_last_mb),
138
+ storage_avg_mb = (
139
+ (tenant_usage_monthly.storage_avg_mb * tenant_usage_monthly.measurements + EXCLUDED.storage_last_mb)
140
+ / (tenant_usage_monthly.measurements + 1)
141
+ ),
142
+ storage_last_mb = EXCLUDED.storage_last_mb,
143
+ measurements = tenant_usage_monthly.measurements + 1,
144
+ last_at = now()
145
+ `.execute(db);
146
+ }
147
+ async function updateTenantPlan(db, slug, plan, cpus, memoryMb, storageLimitMb) {
148
+ await db.updateTable("tenants").set({
149
+ plan,
150
+ cpus,
151
+ memory_mb: memoryMb,
152
+ storage_limit_mb: storageLimitMb,
153
+ updated_at: new Date
154
+ }).where("slug", "=", slug).execute();
155
+ }
156
+ async function bumpTenantKeyGen(db, slug, type) {
157
+ const bumpService = type === "service" || type === "both";
158
+ const bumpAnon = type === "anon" || type === "both";
159
+ const row = await db.updateTable("tenants").set((eb) => ({
160
+ service_gen: bumpService ? eb("service_gen", "+", 1) : eb.ref("service_gen"),
161
+ anon_gen: bumpAnon ? eb("anon_gen", "+", 1) : eb.ref("anon_gen"),
162
+ updated_at: new Date
163
+ })).where("slug", "=", slug).returning(["service_gen", "anon_gen"]).executeTakeFirstOrThrow();
164
+ return { serviceGen: row.service_gen, anonGen: row.anon_gen };
165
+ }
166
+ async function updateTenantKeys(db, slug, keys) {
167
+ const patch = { updated_at: new Date };
168
+ if (keys.serviceKey)
169
+ patch.service_key_enc = encryptSecret(keys.serviceKey);
170
+ if (keys.anonKey)
171
+ patch.anon_key_enc = encryptSecret(keys.anonKey);
172
+ if (Object.keys(patch).length === 1)
173
+ return;
174
+ await db.updateTable("tenants").set(patch).where("slug", "=", slug).execute();
175
+ }
176
+ async function deleteTenant(db, slug) {
177
+ const res = await db.deleteFrom("tenants").where("slug", "=", slug).executeTakeFirst();
178
+ return (res.numDeletedRows ?? 0n) > 0n;
179
+ }
180
+ async function getTenantCredentials(db, slug) {
181
+ const row = await db.selectFrom("tenants").select([
182
+ "slug",
183
+ "target_database_url_enc",
184
+ "tenant_jwt_secret_enc",
185
+ "anon_key_enc",
186
+ "service_key_enc",
187
+ "api_url_internal",
188
+ "api_url_public"
189
+ ]).where("slug", "=", slug).executeTakeFirst();
190
+ if (!row)
191
+ return null;
192
+ return {
193
+ slug: row.slug,
194
+ targetDatabaseUrl: decryptSecret(row.target_database_url_enc),
195
+ tenantJwtSecret: decryptSecret(row.tenant_jwt_secret_enc),
196
+ anonKey: decryptSecret(row.anon_key_enc),
197
+ serviceKey: decryptSecret(row.service_key_enc),
198
+ apiUrlInternal: row.api_url_internal,
199
+ apiUrlPublic: row.api_url_public
200
+ };
201
+ }
202
+ export {
203
+ updateTenantPlan,
204
+ updateTenantKeys,
205
+ setTenantStatus,
206
+ recordMonthlyUsage,
207
+ recordHealthCheck,
208
+ listTenantsByStatus,
209
+ listSuspendedOlderThan,
210
+ listExpiredTrials,
211
+ insertTenant,
212
+ getTenantCredentials,
213
+ getTenantBySlug,
214
+ getTenantByAccount,
215
+ deleteTenant,
216
+ bumpTenantKeyGen
217
+ };
218
+
219
+ //# debugId=06506A808C9D324064756E2164756E21
220
+ //# sourceMappingURL=tenants.js.map
@@ -0,0 +1,11 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/crypto/secrets.ts", "../src/db/queries/tenants.ts"],
4
+ "sourcesContent": [
5
+ "import { createCipheriv, createDecipheriv, randomBytes } from \"node:crypto\";\n\n/**\n * AES-256-GCM symmetric envelope for workflow signer secrets.\n *\n * Ciphertext layout: `iv (12 bytes) || authTag (16 bytes) || ciphertext`\n *\n * The key comes from `SECONDLAYER_SECRETS_KEY` — 32 bytes hex. Callers must\n * load + cache the key once per process. Rotation strategy: when a customer\n * wants to rotate keys, re-encrypt all rows with the new key and swap the\n * env var. Not zero-downtime, but acceptable at v2 scale.\n *\n * For real KMS (AWS KMS, HashiCorp Vault, GCP KMS), wrap the same byte\n * layout behind an `EncryptSecret` / `DecryptSecret` interface in the\n * runner and swap the implementation at startup.\n */\n\nconst KEY_ENV = \"SECONDLAYER_SECRETS_KEY\";\nconst IV_LEN = 12;\nconst TAG_LEN = 16;\n\nfunction loadKey(): Buffer {\n\tconst hex = process.env[KEY_ENV];\n\tif (!hex) {\n\t\tthrow new Error(\n\t\t\t`${KEY_ENV} not set. Generate one with: openssl rand -hex 32`,\n\t\t);\n\t}\n\tconst key = Buffer.from(hex, \"hex\");\n\tif (key.length !== 32) {\n\t\tthrow new Error(`${KEY_ENV} must be 32 bytes hex (got ${key.length})`);\n\t}\n\treturn key;\n}\n\nlet _cachedKey: Buffer | null = null;\nfunction getKey(): Buffer {\n\tif (!_cachedKey) _cachedKey = loadKey();\n\treturn _cachedKey;\n}\n\nexport function encryptSecret(plaintext: string): Buffer {\n\tconst key = getKey();\n\tconst iv = randomBytes(IV_LEN);\n\tconst cipher = createCipheriv(\"aes-256-gcm\", key, iv);\n\tconst ciphertext = Buffer.concat([\n\t\tcipher.update(plaintext, \"utf8\"),\n\t\tcipher.final(),\n\t]);\n\tconst tag = cipher.getAuthTag();\n\treturn Buffer.concat([iv, tag, ciphertext]);\n}\n\nexport function decryptSecret(envelope: Buffer): string {\n\tconst key = getKey();\n\tconst iv = envelope.subarray(0, IV_LEN);\n\tconst tag = envelope.subarray(IV_LEN, IV_LEN + TAG_LEN);\n\tconst ciphertext = envelope.subarray(IV_LEN + TAG_LEN);\n\tconst decipher = createDecipheriv(\"aes-256-gcm\", key, iv);\n\tdecipher.setAuthTag(tag);\n\treturn decipher.update(ciphertext).toString(\"utf8\") + decipher.final(\"utf8\");\n}\n\n/** Generate a fresh 32-byte hex key suitable for `SECONDLAYER_SECRETS_KEY`. */\nexport function generateSecretsKey(): string {\n\treturn randomBytes(32).toString(\"hex\");\n}\n",
6
+ "import { type Kysely, sql } from \"kysely\";\nimport { decryptSecret, encryptSecret } from \"../../crypto/secrets.ts\";\nimport type { Database, InsertTenant, Tenant, TenantStatus } from \"../types.ts\";\n\n/**\n * Tenant registry queries. Encrypted columns are stored as `bytea` and\n * transparently encrypted/decrypted via `encryptSecret`/`decryptSecret`.\n *\n * Never return decrypted values from listTenants — only `getTenantCredentials`\n * surfaces plaintext, and only when explicitly called by a caller that\n * needs to hand creds to a CLI or dashboard session.\n */\n\nexport interface NewTenantInput {\n\taccountId: string;\n\tslug: string;\n\tplan: string;\n\tcpus: number;\n\tmemoryMb: number;\n\tstorageLimitMb: number;\n\tpgContainerId: string;\n\tapiContainerId: string;\n\tprocessorContainerId: string;\n\ttargetDatabaseUrl: string;\n\ttenantJwtSecret: string;\n\tanonKey: string;\n\tserviceKey: string;\n\tapiUrlInternal: string;\n\tapiUrlPublic: string;\n\ttrialEndsAt: Date;\n\tprojectId?: string;\n}\n\nexport async function insertTenant(\n\tdb: Kysely<Database>,\n\tinput: NewTenantInput,\n): Promise<Tenant> {\n\tconst row: InsertTenant = {\n\t\taccount_id: input.accountId,\n\t\tslug: input.slug,\n\t\tstatus: \"active\",\n\t\tplan: input.plan,\n\t\tcpus: input.cpus,\n\t\tmemory_mb: input.memoryMb,\n\t\tstorage_limit_mb: input.storageLimitMb,\n\t\tpg_container_id: input.pgContainerId,\n\t\tapi_container_id: input.apiContainerId,\n\t\tprocessor_container_id: input.processorContainerId,\n\t\ttarget_database_url_enc: encryptSecret(input.targetDatabaseUrl),\n\t\ttenant_jwt_secret_enc: encryptSecret(input.tenantJwtSecret),\n\t\tanon_key_enc: encryptSecret(input.anonKey),\n\t\tservice_key_enc: encryptSecret(input.serviceKey),\n\t\tapi_url_internal: input.apiUrlInternal,\n\t\tapi_url_public: input.apiUrlPublic,\n\t\ttrial_ends_at: input.trialEndsAt,\n\t\tproject_id: input.projectId ?? null,\n\t};\n\treturn db\n\t\t.insertInto(\"tenants\")\n\t\t.values(row)\n\t\t.returningAll()\n\t\t.executeTakeFirstOrThrow();\n}\n\nexport async function getTenantByAccount(\n\tdb: Kysely<Database>,\n\taccountId: string,\n): Promise<Tenant | null> {\n\tconst row = await db\n\t\t.selectFrom(\"tenants\")\n\t\t.selectAll()\n\t\t.where(\"account_id\", \"=\", accountId)\n\t\t.where(\"status\", \"<>\", \"deleted\")\n\t\t.orderBy(\"created_at\", \"desc\")\n\t\t.executeTakeFirst();\n\treturn row ?? null;\n}\n\nexport async function getTenantBySlug(\n\tdb: Kysely<Database>,\n\tslug: string,\n): Promise<Tenant | null> {\n\tconst row = await db\n\t\t.selectFrom(\"tenants\")\n\t\t.selectAll()\n\t\t.where(\"slug\", \"=\", slug)\n\t\t.executeTakeFirst();\n\treturn row ?? null;\n}\n\nexport async function listTenantsByStatus(\n\tdb: Kysely<Database>,\n\tstatus: TenantStatus,\n): Promise<Tenant[]> {\n\treturn db\n\t\t.selectFrom(\"tenants\")\n\t\t.selectAll()\n\t\t.where(\"status\", \"=\", status)\n\t\t.execute();\n}\n\nexport async function listExpiredTrials(\n\tdb: Kysely<Database>,\n\tnow: Date = new Date(),\n): Promise<Tenant[]> {\n\treturn db\n\t\t.selectFrom(\"tenants\")\n\t\t.selectAll()\n\t\t.where(\"status\", \"in\", [\"provisioning\", \"active\"])\n\t\t.where(\"trial_ends_at\", \"<\", now)\n\t\t.execute();\n}\n\nexport async function listSuspendedOlderThan(\n\tdb: Kysely<Database>,\n\tolderThan: Date,\n): Promise<Tenant[]> {\n\treturn db\n\t\t.selectFrom(\"tenants\")\n\t\t.selectAll()\n\t\t.where(\"status\", \"=\", \"suspended\")\n\t\t.where(\"suspended_at\", \"<\", olderThan)\n\t\t.execute();\n}\n\nexport async function setTenantStatus(\n\tdb: Kysely<Database>,\n\tslug: string,\n\tstatus: TenantStatus,\n): Promise<void> {\n\tconst patch: Record<string, unknown> = {\n\t\tstatus,\n\t\tupdated_at: new Date(),\n\t};\n\tif (status === \"suspended\") patch.suspended_at = new Date();\n\tif (status === \"active\") patch.suspended_at = null;\n\tawait db.updateTable(\"tenants\").set(patch).where(\"slug\", \"=\", slug).execute();\n}\n\nexport async function recordHealthCheck(\n\tdb: Kysely<Database>,\n\tslug: string,\n\tstorageUsedMb: number | null,\n): Promise<void> {\n\tawait db\n\t\t.updateTable(\"tenants\")\n\t\t.set({\n\t\t\tlast_health_check_at: new Date(),\n\t\t\tstorage_used_mb: storageUsedMb,\n\t\t\tupdated_at: new Date(),\n\t\t})\n\t\t.where(\"slug\", \"=\", slug)\n\t\t.execute();\n}\n\n/**\n * Record a storage measurement into the current calendar month's bucket.\n * Maintains peak, running average, and the most recent value in a single\n * upsert. Billing will consume this later; for now the table just gives\n * us evidence of usage over time.\n */\nexport async function recordMonthlyUsage(\n\tdb: Kysely<Database>,\n\ttenantId: string,\n\tstorageMb: number,\n): Promise<void> {\n\t// Bucket is the first day of the current month (UTC), so the unique\n\t// (tenant_id, period_month) constraint groups all samples cleanly.\n\tconst now = new Date();\n\tconst periodMonth = new Date(\n\t\tDate.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1),\n\t);\n\n\t// Running mean: avg_new = (avg_old * n + x) / (n + 1). Doing it in SQL\n\t// keeps the write atomic — no read-modify-write race between ticks.\n\tawait sql`\n\t\tINSERT INTO tenant_usage_monthly (\n\t\t\ttenant_id, period_month,\n\t\t\tstorage_peak_mb, storage_avg_mb, storage_last_mb,\n\t\t\tmeasurements, first_at, last_at\n\t\t) VALUES (\n\t\t\t${tenantId}, ${periodMonth},\n\t\t\t${storageMb}, ${storageMb}, ${storageMb},\n\t\t\t1, now(), now()\n\t\t)\n\t\tON CONFLICT (tenant_id, period_month) DO UPDATE SET\n\t\t\tstorage_peak_mb = GREATEST(tenant_usage_monthly.storage_peak_mb, EXCLUDED.storage_last_mb),\n\t\t\tstorage_avg_mb = (\n\t\t\t\t(tenant_usage_monthly.storage_avg_mb * tenant_usage_monthly.measurements + EXCLUDED.storage_last_mb)\n\t\t\t\t/ (tenant_usage_monthly.measurements + 1)\n\t\t\t),\n\t\t\tstorage_last_mb = EXCLUDED.storage_last_mb,\n\t\t\tmeasurements = tenant_usage_monthly.measurements + 1,\n\t\t\tlast_at = now()\n\t`.execute(db);\n}\n\nexport async function updateTenantPlan(\n\tdb: Kysely<Database>,\n\tslug: string,\n\tplan: string,\n\tcpus: number,\n\tmemoryMb: number,\n\tstorageLimitMb: number,\n): Promise<void> {\n\tawait db\n\t\t.updateTable(\"tenants\")\n\t\t.set({\n\t\t\tplan,\n\t\t\tcpus,\n\t\t\tmemory_mb: memoryMb,\n\t\t\tstorage_limit_mb: storageLimitMb,\n\t\t\tupdated_at: new Date(),\n\t\t})\n\t\t.where(\"slug\", \"=\", slug)\n\t\t.execute();\n}\n\nexport type RotateType = \"service\" | \"anon\" | \"both\";\n\n/**\n * Bump the selected gen counter(s) by 1 and return the new values.\n * Used by the key-rotate endpoint to force the tenant API to reject\n * previously-issued tokens of the rotated role(s).\n */\nexport async function bumpTenantKeyGen(\n\tdb: Kysely<Database>,\n\tslug: string,\n\ttype: RotateType,\n): Promise<{ serviceGen: number; anonGen: number }> {\n\tconst bumpService = type === \"service\" || type === \"both\";\n\tconst bumpAnon = type === \"anon\" || type === \"both\";\n\tconst row = await db\n\t\t.updateTable(\"tenants\")\n\t\t.set((eb) => ({\n\t\t\tservice_gen: bumpService\n\t\t\t\t? eb(\"service_gen\", \"+\", 1)\n\t\t\t\t: eb.ref(\"service_gen\"),\n\t\t\tanon_gen: bumpAnon ? eb(\"anon_gen\", \"+\", 1) : eb.ref(\"anon_gen\"),\n\t\t\tupdated_at: new Date(),\n\t\t}))\n\t\t.where(\"slug\", \"=\", slug)\n\t\t.returning([\"service_gen\", \"anon_gen\"])\n\t\t.executeTakeFirstOrThrow();\n\treturn { serviceGen: row.service_gen, anonGen: row.anon_gen };\n}\n\n/**\n * Replace the encrypted key columns after a successful rotate. Only the\n * rotated column(s) are written — the other stays untouched.\n */\nexport async function updateTenantKeys(\n\tdb: Kysely<Database>,\n\tslug: string,\n\tkeys: { serviceKey?: string; anonKey?: string },\n): Promise<void> {\n\tconst patch: Record<string, unknown> = { updated_at: new Date() };\n\tif (keys.serviceKey) patch.service_key_enc = encryptSecret(keys.serviceKey);\n\tif (keys.anonKey) patch.anon_key_enc = encryptSecret(keys.anonKey);\n\tif (Object.keys(patch).length === 1) return; // only updated_at — nothing to write\n\tawait db.updateTable(\"tenants\").set(patch).where(\"slug\", \"=\", slug).execute();\n}\n\n/**\n * Hard-delete a tenant row. Call only AFTER the provisioner has torn down\n * containers + volume; otherwise orphaned resources linger. Returns whether\n * a row was actually deleted.\n */\nexport async function deleteTenant(\n\tdb: Kysely<Database>,\n\tslug: string,\n): Promise<boolean> {\n\tconst res = await db\n\t\t.deleteFrom(\"tenants\")\n\t\t.where(\"slug\", \"=\", slug)\n\t\t.executeTakeFirst();\n\treturn (res.numDeletedRows ?? 0n) > 0n;\n}\n\nexport interface TenantCredentials {\n\tslug: string;\n\ttargetDatabaseUrl: string;\n\ttenantJwtSecret: string;\n\tanonKey: string;\n\tserviceKey: string;\n\tapiUrlInternal: string;\n\tapiUrlPublic: string;\n}\n\n/**\n * Decrypts the four encrypted columns and returns them plaintext. Call\n * this only when surfacing credentials to an authorized caller (dashboard,\n * CLI). Never log the returned object.\n */\nexport async function getTenantCredentials(\n\tdb: Kysely<Database>,\n\tslug: string,\n): Promise<TenantCredentials | null> {\n\tconst row = await db\n\t\t.selectFrom(\"tenants\")\n\t\t.select([\n\t\t\t\"slug\",\n\t\t\t\"target_database_url_enc\",\n\t\t\t\"tenant_jwt_secret_enc\",\n\t\t\t\"anon_key_enc\",\n\t\t\t\"service_key_enc\",\n\t\t\t\"api_url_internal\",\n\t\t\t\"api_url_public\",\n\t\t])\n\t\t.where(\"slug\", \"=\", slug)\n\t\t.executeTakeFirst();\n\tif (!row) return null;\n\treturn {\n\t\tslug: row.slug,\n\t\ttargetDatabaseUrl: decryptSecret(row.target_database_url_enc),\n\t\ttenantJwtSecret: decryptSecret(row.tenant_jwt_secret_enc),\n\t\tanonKey: decryptSecret(row.anon_key_enc),\n\t\tserviceKey: decryptSecret(row.service_key_enc),\n\t\tapiUrlInternal: row.api_url_internal,\n\t\tapiUrlPublic: row.api_url_public,\n\t};\n}\n"
7
+ ],
8
+ "mappings": ";;;;;;;;;;;;;;;;;AAAA;AAiBA,IAAM,UAAU;AAChB,IAAM,SAAS;AACf,IAAM,UAAU;AAEhB,SAAS,OAAO,GAAW;AAAA,EAC1B,MAAM,MAAM,QAAQ,IAAI;AAAA,EACxB,IAAI,CAAC,KAAK;AAAA,IACT,MAAM,IAAI,MACT,GAAG,0DACJ;AAAA,EACD;AAAA,EACA,MAAM,MAAM,OAAO,KAAK,KAAK,KAAK;AAAA,EAClC,IAAI,IAAI,WAAW,IAAI;AAAA,IACtB,MAAM,IAAI,MAAM,GAAG,qCAAqC,IAAI,SAAS;AAAA,EACtE;AAAA,EACA,OAAO;AAAA;AAGR,IAAI,aAA4B;AAChC,SAAS,MAAM,GAAW;AAAA,EACzB,IAAI,CAAC;AAAA,IAAY,aAAa,QAAQ;AAAA,EACtC,OAAO;AAAA;AAGD,SAAS,aAAa,CAAC,WAA2B;AAAA,EACxD,MAAM,MAAM,OAAO;AAAA,EACnB,MAAM,KAAK,YAAY,MAAM;AAAA,EAC7B,MAAM,SAAS,eAAe,eAAe,KAAK,EAAE;AAAA,EACpD,MAAM,aAAa,OAAO,OAAO;AAAA,IAChC,OAAO,OAAO,WAAW,MAAM;AAAA,IAC/B,OAAO,MAAM;AAAA,EACd,CAAC;AAAA,EACD,MAAM,MAAM,OAAO,WAAW;AAAA,EAC9B,OAAO,OAAO,OAAO,CAAC,IAAI,KAAK,UAAU,CAAC;AAAA;AAGpC,SAAS,aAAa,CAAC,UAA0B;AAAA,EACvD,MAAM,MAAM,OAAO;AAAA,EACnB,MAAM,KAAK,SAAS,SAAS,GAAG,MAAM;AAAA,EACtC,MAAM,MAAM,SAAS,SAAS,QAAQ,SAAS,OAAO;AAAA,EACtD,MAAM,aAAa,SAAS,SAAS,SAAS,OAAO;AAAA,EACrD,MAAM,WAAW,iBAAiB,eAAe,KAAK,EAAE;AAAA,EACxD,SAAS,WAAW,GAAG;AAAA,EACvB,OAAO,SAAS,OAAO,UAAU,EAAE,SAAS,MAAM,IAAI,SAAS,MAAM,MAAM;AAAA;AAIrE,SAAS,kBAAkB,GAAW;AAAA,EAC5C,OAAO,YAAY,EAAE,EAAE,SAAS,KAAK;AAAA;;;ACjEtC;AAiCA,eAAsB,YAAY,CACjC,IACA,OACkB;AAAA,EAClB,MAAM,MAAoB;AAAA,IACzB,YAAY,MAAM;AAAA,IAClB,MAAM,MAAM;AAAA,IACZ,QAAQ;AAAA,IACR,MAAM,MAAM;AAAA,IACZ,MAAM,MAAM;AAAA,IACZ,WAAW,MAAM;AAAA,IACjB,kBAAkB,MAAM;AAAA,IACxB,iBAAiB,MAAM;AAAA,IACvB,kBAAkB,MAAM;AAAA,IACxB,wBAAwB,MAAM;AAAA,IAC9B,yBAAyB,cAAc,MAAM,iBAAiB;AAAA,IAC9D,uBAAuB,cAAc,MAAM,eAAe;AAAA,IAC1D,cAAc,cAAc,MAAM,OAAO;AAAA,IACzC,iBAAiB,cAAc,MAAM,UAAU;AAAA,IAC/C,kBAAkB,MAAM;AAAA,IACxB,gBAAgB,MAAM;AAAA,IACtB,eAAe,MAAM;AAAA,IACrB,YAAY,MAAM,aAAa;AAAA,EAChC;AAAA,EACA,OAAO,GACL,WAAW,SAAS,EACpB,OAAO,GAAG,EACV,aAAa,EACb,wBAAwB;AAAA;AAG3B,eAAsB,kBAAkB,CACvC,IACA,WACyB;AAAA,EACzB,MAAM,MAAM,MAAM,GAChB,WAAW,SAAS,EACpB,UAAU,EACV,MAAM,cAAc,KAAK,SAAS,EAClC,MAAM,UAAU,MAAM,SAAS,EAC/B,QAAQ,cAAc,MAAM,EAC5B,iBAAiB;AAAA,EACnB,OAAO,OAAO;AAAA;AAGf,eAAsB,eAAe,CACpC,IACA,MACyB;AAAA,EACzB,MAAM,MAAM,MAAM,GAChB,WAAW,SAAS,EACpB,UAAU,EACV,MAAM,QAAQ,KAAK,IAAI,EACvB,iBAAiB;AAAA,EACnB,OAAO,OAAO;AAAA;AAGf,eAAsB,mBAAmB,CACxC,IACA,QACoB;AAAA,EACpB,OAAO,GACL,WAAW,SAAS,EACpB,UAAU,EACV,MAAM,UAAU,KAAK,MAAM,EAC3B,QAAQ;AAAA;AAGX,eAAsB,iBAAiB,CACtC,IACA,MAAY,IAAI,MACI;AAAA,EACpB,OAAO,GACL,WAAW,SAAS,EACpB,UAAU,EACV,MAAM,UAAU,MAAM,CAAC,gBAAgB,QAAQ,CAAC,EAChD,MAAM,iBAAiB,KAAK,GAAG,EAC/B,QAAQ;AAAA;AAGX,eAAsB,sBAAsB,CAC3C,IACA,WACoB;AAAA,EACpB,OAAO,GACL,WAAW,SAAS,EACpB,UAAU,EACV,MAAM,UAAU,KAAK,WAAW,EAChC,MAAM,gBAAgB,KAAK,SAAS,EACpC,QAAQ;AAAA;AAGX,eAAsB,eAAe,CACpC,IACA,MACA,QACgB;AAAA,EAChB,MAAM,QAAiC;AAAA,IACtC;AAAA,IACA,YAAY,IAAI;AAAA,EACjB;AAAA,EACA,IAAI,WAAW;AAAA,IAAa,MAAM,eAAe,IAAI;AAAA,EACrD,IAAI,WAAW;AAAA,IAAU,MAAM,eAAe;AAAA,EAC9C,MAAM,GAAG,YAAY,SAAS,EAAE,IAAI,KAAK,EAAE,MAAM,QAAQ,KAAK,IAAI,EAAE,QAAQ;AAAA;AAG7E,eAAsB,iBAAiB,CACtC,IACA,MACA,eACgB;AAAA,EAChB,MAAM,GACJ,YAAY,SAAS,EACrB,IAAI;AAAA,IACJ,sBAAsB,IAAI;AAAA,IAC1B,iBAAiB;AAAA,IACjB,YAAY,IAAI;AAAA,EACjB,CAAC,EACA,MAAM,QAAQ,KAAK,IAAI,EACvB,QAAQ;AAAA;AASX,eAAsB,kBAAkB,CACvC,IACA,UACA,WACgB;AAAA,EAGhB,MAAM,MAAM,IAAI;AAAA,EAChB,MAAM,cAAc,IAAI,KACvB,KAAK,IAAI,IAAI,eAAe,GAAG,IAAI,YAAY,GAAG,CAAC,CACpD;AAAA,EAIA,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,KAMF,aAAa;AAAA,KACb,cAAc,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,GAY9B,QAAQ,EAAE;AAAA;AAGb,eAAsB,gBAAgB,CACrC,IACA,MACA,MACA,MACA,UACA,gBACgB;AAAA,EAChB,MAAM,GACJ,YAAY,SAAS,EACrB,IAAI;AAAA,IACJ;AAAA,IACA;AAAA,IACA,WAAW;AAAA,IACX,kBAAkB;AAAA,IAClB,YAAY,IAAI;AAAA,EACjB,CAAC,EACA,MAAM,QAAQ,KAAK,IAAI,EACvB,QAAQ;AAAA;AAUX,eAAsB,gBAAgB,CACrC,IACA,MACA,MACmD;AAAA,EACnD,MAAM,cAAc,SAAS,aAAa,SAAS;AAAA,EACnD,MAAM,WAAW,SAAS,UAAU,SAAS;AAAA,EAC7C,MAAM,MAAM,MAAM,GAChB,YAAY,SAAS,EACrB,IAAI,CAAC,QAAQ;AAAA,IACb,aAAa,cACV,GAAG,eAAe,KAAK,CAAC,IACxB,GAAG,IAAI,aAAa;AAAA,IACvB,UAAU,WAAW,GAAG,YAAY,KAAK,CAAC,IAAI,GAAG,IAAI,UAAU;AAAA,IAC/D,YAAY,IAAI;AAAA,EACjB,EAAE,EACD,MAAM,QAAQ,KAAK,IAAI,EACvB,UAAU,CAAC,eAAe,UAAU,CAAC,EACrC,wBAAwB;AAAA,EAC1B,OAAO,EAAE,YAAY,IAAI,aAAa,SAAS,IAAI,SAAS;AAAA;AAO7D,eAAsB,gBAAgB,CACrC,IACA,MACA,MACgB;AAAA,EAChB,MAAM,QAAiC,EAAE,YAAY,IAAI,KAAO;AAAA,EAChE,IAAI,KAAK;AAAA,IAAY,MAAM,kBAAkB,cAAc,KAAK,UAAU;AAAA,EAC1E,IAAI,KAAK;AAAA,IAAS,MAAM,eAAe,cAAc,KAAK,OAAO;AAAA,EACjE,IAAI,OAAO,KAAK,KAAK,EAAE,WAAW;AAAA,IAAG;AAAA,EACrC,MAAM,GAAG,YAAY,SAAS,EAAE,IAAI,KAAK,EAAE,MAAM,QAAQ,KAAK,IAAI,EAAE,QAAQ;AAAA;AAQ7E,eAAsB,YAAY,CACjC,IACA,MACmB;AAAA,EACnB,MAAM,MAAM,MAAM,GAChB,WAAW,SAAS,EACpB,MAAM,QAAQ,KAAK,IAAI,EACvB,iBAAiB;AAAA,EACnB,QAAQ,IAAI,kBAAkB,MAAM;AAAA;AAkBrC,eAAsB,oBAAoB,CACzC,IACA,MACoC;AAAA,EACpC,MAAM,MAAM,MAAM,GAChB,WAAW,SAAS,EACpB,OAAO;AAAA,IACP;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACD,CAAC,EACA,MAAM,QAAQ,KAAK,IAAI,EACvB,iBAAiB;AAAA,EACnB,IAAI,CAAC;AAAA,IAAK,OAAO;AAAA,EACjB,OAAO;AAAA,IACN,MAAM,IAAI;AAAA,IACV,mBAAmB,cAAc,IAAI,uBAAuB;AAAA,IAC5D,iBAAiB,cAAc,IAAI,qBAAqB;AAAA,IACxD,SAAS,cAAc,IAAI,YAAY;AAAA,IACvC,YAAY,cAAc,IAAI,eAAe;AAAA,IAC7C,gBAAgB,IAAI;AAAA,IACpB,cAAc,IAAI;AAAA,EACnB;AAAA;",
9
+ "debugId": "06506A808C9D324064756E2164756E21",
10
+ "names": []
11
+ }
@@ -6,7 +6,7 @@ interface PlanLimits {
6
6
  }
7
7
  declare function getPlanLimits(plan: string): PlanLimits;
8
8
  import { Kysely } from "kysely";
9
- import { Generated } from "kysely";
9
+ import { ColumnType, Generated } from "kysely";
10
10
  interface BlocksTable {
11
11
  height: number;
12
12
  hash: string;
@@ -63,7 +63,6 @@ interface SubgraphsTable {
63
63
  last_error_at: Date | null;
64
64
  total_processed: Generated<number>;
65
65
  total_errors: Generated<number>;
66
- api_key_id: string | null;
67
66
  account_id: string;
68
67
  handler_code: string | null;
69
68
  source_code: string | null;
@@ -367,6 +366,63 @@ interface Database {
367
366
  workflow_cursors: WorkflowCursorsTable;
368
367
  workflow_signer_secrets: WorkflowSignerSecretsTable;
369
368
  workflow_budgets: WorkflowBudgetsTable;
369
+ tenants: TenantsTable;
370
+ tenant_usage_monthly: TenantUsageMonthlyTable;
371
+ provisioning_audit_log: ProvisioningAuditLogTable;
372
+ }
373
+ type TenantStatus = "provisioning" | "active" | "suspended" | "error" | "deleted";
374
+ interface TenantsTable {
375
+ id: Generated<string>;
376
+ account_id: string;
377
+ slug: string;
378
+ status: ColumnType<TenantStatus, TenantStatus | undefined, TenantStatus>;
379
+ plan: string;
380
+ cpus: ColumnType<number, number | string, number | string>;
381
+ memory_mb: number;
382
+ storage_limit_mb: number;
383
+ storage_used_mb: number | null;
384
+ pg_container_id: string | null;
385
+ api_container_id: string | null;
386
+ processor_container_id: string | null;
387
+ target_database_url_enc: Buffer;
388
+ tenant_jwt_secret_enc: Buffer;
389
+ anon_key_enc: Buffer;
390
+ service_key_enc: Buffer;
391
+ api_url_internal: string;
392
+ api_url_public: string;
393
+ trial_ends_at: Date;
394
+ suspended_at: Date | null;
395
+ last_health_check_at: Date | null;
396
+ service_gen: Generated<number>;
397
+ anon_gen: Generated<number>;
398
+ project_id: string | null;
399
+ created_at: Generated<Date>;
400
+ updated_at: Generated<Date>;
401
+ }
402
+ interface TenantUsageMonthlyTable {
403
+ id: Generated<string>;
404
+ tenant_id: string;
405
+ period_month: Date;
406
+ storage_peak_mb: Generated<number>;
407
+ storage_avg_mb: Generated<number>;
408
+ storage_last_mb: Generated<number>;
409
+ measurements: Generated<number>;
410
+ first_at: Generated<Date>;
411
+ last_at: Generated<Date>;
412
+ }
413
+ type ProvisioningAuditEvent = "provision.start" | "provision.success" | "provision.failure" | "suspend" | "resume" | "resize" | "keys.rotate" | "bastion.key.upload" | "bastion.key.revoke" | "teardown";
414
+ type ProvisioningAuditStatus = "ok" | "error";
415
+ interface ProvisioningAuditLogTable {
416
+ id: Generated<string>;
417
+ tenant_id: string | null;
418
+ tenant_slug: string | null;
419
+ account_id: string | null;
420
+ actor: string;
421
+ event: ProvisioningAuditEvent;
422
+ status: ProvisioningAuditStatus;
423
+ detail: unknown | null;
424
+ error: string | null;
425
+ created_at: Generated<Date>;
370
426
  }
371
427
  interface WorkflowBudgetsTable {
372
428
  id: Generated<string>;
@@ -78,7 +78,7 @@ async function getDailyUsage(db, accountId) {
78
78
  async function checkLimits(db, accountId, plan) {
79
79
  const limits = getPlanLimits(plan);
80
80
  const usage = await getUsage(db, accountId);
81
- const subgraphCount = await db.selectFrom("subgraphs").innerJoin("api_keys", "subgraphs.api_key_id", "api_keys.id").select(sql`count(*)`.as("count")).where("api_keys.account_id", "=", accountId).executeTakeFirst();
81
+ const subgraphCount = await db.selectFrom("subgraphs").select(sql`count(*)`.as("count")).where("account_id", "=", accountId).executeTakeFirst();
82
82
  const current = {
83
83
  ...usage,
84
84
  subgraphs: Number(subgraphCount?.count ?? 0)
@@ -98,7 +98,7 @@ async function checkLimits(db, accountId, plan) {
98
98
  return { allowed: true, limits, current };
99
99
  }
100
100
  async function measureStorage(db) {
101
- const accountSubgraphs = await db.selectFrom("subgraphs").innerJoin("api_keys", "subgraphs.api_key_id", "api_keys.id").select(["api_keys.account_id", "subgraphs.schema_name"]).where("subgraphs.schema_name", "is not", null).execute();
101
+ const accountSubgraphs = await db.selectFrom("subgraphs").select(["account_id", "schema_name"]).where("schema_name", "is not", null).execute();
102
102
  const byAccount = new Map;
103
103
  for (const row of accountSubgraphs) {
104
104
  const schemas = byAccount.get(row.account_id) ?? [];
@@ -131,5 +131,5 @@ export {
131
131
  checkLimits
132
132
  };
133
133
 
134
- //# debugId=0844CA82D7046A1564756E2164756E21
134
+ //# debugId=7A7AEE74ECF0C63664756E2164756E21
135
135
  //# sourceMappingURL=usage.js.map
@@ -3,9 +3,9 @@
3
3
  "sources": ["../src/lib/plans.ts", "../src/db/queries/usage.ts"],
4
4
  "sourcesContent": [
5
5
  "export interface PlanLimits {\n\tsubgraphs: number;\n\tapiRequestsPerDay: number;\n\tdeliveriesPerMonth: number;\n\tstorageBytes: number;\n}\n\nexport const FREE_PLAN: PlanLimits = {\n\tsubgraphs: 2,\n\tapiRequestsPerDay: 1_000,\n\tdeliveriesPerMonth: 5_000,\n\tstorageBytes: 100 * 1024 * 1024,\n};\n\nexport function getPlanLimits(plan: string): PlanLimits {\n\tswitch (plan) {\n\t\tcase \"free\":\n\t\tdefault:\n\t\t\treturn FREE_PLAN;\n\t}\n}\n",
6
- "import { type Kysely, sql } from \"kysely\";\nimport { getPlanLimits } from \"../../lib/plans.ts\";\nimport type { Database } from \"../types.ts\";\n\n/** Increment API request counter for today. Fire-and-forget safe. */\nexport async function incrementApiRequests(\n\tdb: Kysely<Database>,\n\taccountId: string,\n): Promise<void> {\n\tconst today = new Date().toISOString().slice(0, 10);\n\tawait db\n\t\t.insertInto(\"usage_daily\")\n\t\t.values({\n\t\t\taccount_id: accountId,\n\t\t\tdate: today,\n\t\t\tapi_requests: 1,\n\t\t\tdeliveries: 0,\n\t\t})\n\t\t.onConflict((oc) =>\n\t\t\toc.columns([\"account_id\", \"date\"]).doUpdateSet({\n\t\t\t\tapi_requests: sql`usage_daily.api_requests + 1`,\n\t\t\t}),\n\t\t)\n\t\t.execute();\n}\n\nexport interface UsageSummary {\n\tapiRequestsToday: number;\n\tdeliveriesThisMonth: number;\n\tstorageBytes: number;\n}\n\n/** Get current usage for an account. */\nexport async function getUsage(\n\tdb: Kysely<Database>,\n\taccountId: string,\n): Promise<UsageSummary> {\n\tconst today = new Date().toISOString().slice(0, 10);\n\tconst monthStart = today.slice(0, 7) + \"-01\"; // YYYY-MM-01\n\n\t// Today's API requests\n\tconst dailyRow = await db\n\t\t.selectFrom(\"usage_daily\")\n\t\t.select(\"api_requests\")\n\t\t.where(\"account_id\", \"=\", accountId)\n\t\t.where(\"date\", \"=\", today)\n\t\t.executeTakeFirst();\n\n\t// This month's deliveries\n\tconst monthlyRow = await db\n\t\t.selectFrom(\"usage_daily\")\n\t\t.select(sql<number>`COALESCE(SUM(deliveries), 0)`.as(\"total\"))\n\t\t.where(\"account_id\", \"=\", accountId)\n\t\t.where(\"date\", \">=\", monthStart)\n\t\t.executeTakeFirst();\n\n\t// Latest storage snapshot\n\tconst storageRow = await db\n\t\t.selectFrom(\"usage_snapshots\")\n\t\t.select(\"storage_bytes\")\n\t\t.where(\"account_id\", \"=\", accountId)\n\t\t.orderBy(\"measured_at\", \"desc\")\n\t\t.limit(1)\n\t\t.executeTakeFirst();\n\n\treturn {\n\t\tapiRequestsToday: dailyRow?.api_requests ?? 0,\n\t\tdeliveriesThisMonth: Number(monthlyRow?.total ?? 0),\n\t\tstorageBytes: Number(storageRow?.storage_bytes ?? 0),\n\t};\n}\n\nexport interface DailyUsage {\n\tdate: string;\n\tapiRequests: number;\n\tdeliveries: number;\n}\n\n/** Get last 7 days of daily usage, filling missing days with 0. */\nexport async function getDailyUsage(\n\tdb: Kysely<Database>,\n\taccountId: string,\n): Promise<DailyUsage[]> {\n\tconst rows = await db\n\t\t.selectFrom(\"usage_daily\")\n\t\t.select([\"date\", \"api_requests\", \"deliveries\"])\n\t\t.where(\"account_id\", \"=\", accountId)\n\t\t.where(\"date\", \">=\", sql<string>`NOW()::date - 6`)\n\t\t.orderBy(\"date\", \"asc\")\n\t\t.execute();\n\n\t// Fill missing days with 0 (normalize date to YYYY-MM-DD string)\n\tconst byDate = new Map(\n\t\trows.map((r) => {\n\t\t\tconst d = r.date as unknown;\n\t\t\tconst key =\n\t\t\t\td instanceof Date\n\t\t\t\t\t? d.toISOString().slice(0, 10)\n\t\t\t\t\t: String(d).slice(0, 10);\n\t\t\treturn [key, r];\n\t\t}),\n\t);\n\tconst result: DailyUsage[] = [];\n\tfor (let i = 6; i >= 0; i--) {\n\t\tconst d = new Date();\n\t\td.setDate(d.getDate() - i);\n\t\tconst dateStr = d.toISOString().slice(0, 10);\n\t\tconst row = byDate.get(dateStr);\n\t\tresult.push({\n\t\t\tdate: dateStr,\n\t\t\tapiRequests: row?.api_requests ?? 0,\n\t\t\tdeliveries: row?.deliveries ?? 0,\n\t\t});\n\t}\n\treturn result;\n}\n\nexport interface LimitCheck {\n\tallowed: boolean;\n\tlimits: ReturnType<typeof getPlanLimits>;\n\tcurrent: UsageSummary & { subgraphs: number };\n\texceeded?: string;\n}\n\n/** Check if an account is within plan limits. */\nexport async function checkLimits(\n\tdb: Kysely<Database>,\n\taccountId: string,\n\tplan: string,\n): Promise<LimitCheck> {\n\tconst limits = getPlanLimits(plan);\n\tconst usage = await getUsage(db, accountId);\n\n\tconst subgraphCount = await db\n\t\t.selectFrom(\"subgraphs\")\n\t\t.innerJoin(\"api_keys\", \"subgraphs.api_key_id\", \"api_keys.id\")\n\t\t.select(sql<number>`count(*)`.as(\"count\"))\n\t\t.where(\"api_keys.account_id\", \"=\", accountId)\n\t\t.executeTakeFirst();\n\n\tconst current = {\n\t\t...usage,\n\t\tsubgraphs: Number(subgraphCount?.count ?? 0),\n\t};\n\n\tif (current.subgraphs >= limits.subgraphs) {\n\t\treturn { allowed: false, limits, current, exceeded: \"subgraphs\" };\n\t}\n\tif (current.apiRequestsToday >= limits.apiRequestsPerDay) {\n\t\treturn { allowed: false, limits, current, exceeded: \"api_requests\" };\n\t}\n\tif (current.deliveriesThisMonth >= limits.deliveriesPerMonth) {\n\t\treturn { allowed: false, limits, current, exceeded: \"deliveries\" };\n\t}\n\tif (current.storageBytes >= limits.storageBytes) {\n\t\treturn { allowed: false, limits, current, exceeded: \"storage\" };\n\t}\n\n\treturn { allowed: true, limits, current };\n}\n\n/**\n * Measure storage for all accounts by querying pg_total_relation_size\n * for each tenant's subgraph schemas.\n */\nexport async function measureStorage(db: Kysely<Database>): Promise<void> {\n\t// Get all accounts with subgraphs\n\tconst accountSubgraphs = await db\n\t\t.selectFrom(\"subgraphs\")\n\t\t.innerJoin(\"api_keys\", \"subgraphs.api_key_id\", \"api_keys.id\")\n\t\t.select([\"api_keys.account_id\", \"subgraphs.schema_name\"])\n\t\t.where(\"subgraphs.schema_name\", \"is not\", null)\n\t\t.execute();\n\n\t// Group schemas by account\n\tconst byAccount = new Map<string, string[]>();\n\tfor (const row of accountSubgraphs) {\n\t\tconst schemas = byAccount.get(row.account_id) ?? [];\n\t\tif (row.schema_name) schemas.push(row.schema_name);\n\t\tbyAccount.set(row.account_id, schemas);\n\t}\n\n\tfor (const [accountId, schemas] of byAccount) {\n\t\tlet totalBytes = 0;\n\t\tfor (const schema of schemas) {\n\t\t\ttry {\n\t\t\t\tconst result = await sql<{ size: string }>`\n SELECT COALESCE(SUM(pg_total_relation_size(quote_ident(schemaname) || '.' || quote_ident(tablename))), 0)::text as size\n FROM pg_tables WHERE schemaname = ${schema}\n `.execute(db);\n\t\t\t\ttotalBytes += Number((result.rows[0] as any)?.size ?? 0);\n\t\t\t} catch {\n\t\t\t\t// Schema may not exist\n\t\t\t}\n\t\t}\n\n\t\tawait db\n\t\t\t.insertInto(\"usage_snapshots\")\n\t\t\t.values({\n\t\t\t\taccount_id: accountId,\n\t\t\t\tstorage_bytes: totalBytes,\n\t\t\t})\n\t\t\t.execute();\n\t}\n}\n"
6
+ "import { type Kysely, sql } from \"kysely\";\nimport { getPlanLimits } from \"../../lib/plans.ts\";\nimport type { Database } from \"../types.ts\";\n\n/** Increment API request counter for today. Fire-and-forget safe. */\nexport async function incrementApiRequests(\n\tdb: Kysely<Database>,\n\taccountId: string,\n): Promise<void> {\n\tconst today = new Date().toISOString().slice(0, 10);\n\tawait db\n\t\t.insertInto(\"usage_daily\")\n\t\t.values({\n\t\t\taccount_id: accountId,\n\t\t\tdate: today,\n\t\t\tapi_requests: 1,\n\t\t\tdeliveries: 0,\n\t\t})\n\t\t.onConflict((oc) =>\n\t\t\toc.columns([\"account_id\", \"date\"]).doUpdateSet({\n\t\t\t\tapi_requests: sql`usage_daily.api_requests + 1`,\n\t\t\t}),\n\t\t)\n\t\t.execute();\n}\n\nexport interface UsageSummary {\n\tapiRequestsToday: number;\n\tdeliveriesThisMonth: number;\n\tstorageBytes: number;\n}\n\n/** Get current usage for an account. */\nexport async function getUsage(\n\tdb: Kysely<Database>,\n\taccountId: string,\n): Promise<UsageSummary> {\n\tconst today = new Date().toISOString().slice(0, 10);\n\tconst monthStart = today.slice(0, 7) + \"-01\"; // YYYY-MM-01\n\n\t// Today's API requests\n\tconst dailyRow = await db\n\t\t.selectFrom(\"usage_daily\")\n\t\t.select(\"api_requests\")\n\t\t.where(\"account_id\", \"=\", accountId)\n\t\t.where(\"date\", \"=\", today)\n\t\t.executeTakeFirst();\n\n\t// This month's deliveries\n\tconst monthlyRow = await db\n\t\t.selectFrom(\"usage_daily\")\n\t\t.select(sql<number>`COALESCE(SUM(deliveries), 0)`.as(\"total\"))\n\t\t.where(\"account_id\", \"=\", accountId)\n\t\t.where(\"date\", \">=\", monthStart)\n\t\t.executeTakeFirst();\n\n\t// Latest storage snapshot\n\tconst storageRow = await db\n\t\t.selectFrom(\"usage_snapshots\")\n\t\t.select(\"storage_bytes\")\n\t\t.where(\"account_id\", \"=\", accountId)\n\t\t.orderBy(\"measured_at\", \"desc\")\n\t\t.limit(1)\n\t\t.executeTakeFirst();\n\n\treturn {\n\t\tapiRequestsToday: dailyRow?.api_requests ?? 0,\n\t\tdeliveriesThisMonth: Number(monthlyRow?.total ?? 0),\n\t\tstorageBytes: Number(storageRow?.storage_bytes ?? 0),\n\t};\n}\n\nexport interface DailyUsage {\n\tdate: string;\n\tapiRequests: number;\n\tdeliveries: number;\n}\n\n/** Get last 7 days of daily usage, filling missing days with 0. */\nexport async function getDailyUsage(\n\tdb: Kysely<Database>,\n\taccountId: string,\n): Promise<DailyUsage[]> {\n\tconst rows = await db\n\t\t.selectFrom(\"usage_daily\")\n\t\t.select([\"date\", \"api_requests\", \"deliveries\"])\n\t\t.where(\"account_id\", \"=\", accountId)\n\t\t.where(\"date\", \">=\", sql<string>`NOW()::date - 6`)\n\t\t.orderBy(\"date\", \"asc\")\n\t\t.execute();\n\n\t// Fill missing days with 0 (normalize date to YYYY-MM-DD string)\n\tconst byDate = new Map(\n\t\trows.map((r) => {\n\t\t\tconst d = r.date as unknown;\n\t\t\tconst key =\n\t\t\t\td instanceof Date\n\t\t\t\t\t? d.toISOString().slice(0, 10)\n\t\t\t\t\t: String(d).slice(0, 10);\n\t\t\treturn [key, r];\n\t\t}),\n\t);\n\tconst result: DailyUsage[] = [];\n\tfor (let i = 6; i >= 0; i--) {\n\t\tconst d = new Date();\n\t\td.setDate(d.getDate() - i);\n\t\tconst dateStr = d.toISOString().slice(0, 10);\n\t\tconst row = byDate.get(dateStr);\n\t\tresult.push({\n\t\t\tdate: dateStr,\n\t\t\tapiRequests: row?.api_requests ?? 0,\n\t\t\tdeliveries: row?.deliveries ?? 0,\n\t\t});\n\t}\n\treturn result;\n}\n\nexport interface LimitCheck {\n\tallowed: boolean;\n\tlimits: ReturnType<typeof getPlanLimits>;\n\tcurrent: UsageSummary & { subgraphs: number };\n\texceeded?: string;\n}\n\n/** Check if an account is within plan limits. */\nexport async function checkLimits(\n\tdb: Kysely<Database>,\n\taccountId: string,\n\tplan: string,\n): Promise<LimitCheck> {\n\tconst limits = getPlanLimits(plan);\n\tconst usage = await getUsage(db, accountId);\n\n\tconst subgraphCount = await db\n\t\t.selectFrom(\"subgraphs\")\n\t\t.select(sql<number>`count(*)`.as(\"count\"))\n\t\t.where(\"account_id\", \"=\", accountId)\n\t\t.executeTakeFirst();\n\n\tconst current = {\n\t\t...usage,\n\t\tsubgraphs: Number(subgraphCount?.count ?? 0),\n\t};\n\n\tif (current.subgraphs >= limits.subgraphs) {\n\t\treturn { allowed: false, limits, current, exceeded: \"subgraphs\" };\n\t}\n\tif (current.apiRequestsToday >= limits.apiRequestsPerDay) {\n\t\treturn { allowed: false, limits, current, exceeded: \"api_requests\" };\n\t}\n\tif (current.deliveriesThisMonth >= limits.deliveriesPerMonth) {\n\t\treturn { allowed: false, limits, current, exceeded: \"deliveries\" };\n\t}\n\tif (current.storageBytes >= limits.storageBytes) {\n\t\treturn { allowed: false, limits, current, exceeded: \"storage\" };\n\t}\n\n\treturn { allowed: true, limits, current };\n}\n\n/**\n * Measure storage for all accounts by querying pg_total_relation_size\n * for each tenant's subgraph schemas.\n */\nexport async function measureStorage(db: Kysely<Database>): Promise<void> {\n\t// Get all accounts with subgraphs\n\tconst accountSubgraphs = await db\n\t\t.selectFrom(\"subgraphs\")\n\t\t.select([\"account_id\", \"schema_name\"])\n\t\t.where(\"schema_name\", \"is not\", null)\n\t\t.execute();\n\n\t// Group schemas by account\n\tconst byAccount = new Map<string, string[]>();\n\tfor (const row of accountSubgraphs) {\n\t\tconst schemas = byAccount.get(row.account_id) ?? [];\n\t\tif (row.schema_name) schemas.push(row.schema_name);\n\t\tbyAccount.set(row.account_id, schemas);\n\t}\n\n\tfor (const [accountId, schemas] of byAccount) {\n\t\tlet totalBytes = 0;\n\t\tfor (const schema of schemas) {\n\t\t\ttry {\n\t\t\t\tconst result = await sql<{ size: string }>`\n SELECT COALESCE(SUM(pg_total_relation_size(quote_ident(schemaname) || '.' || quote_ident(tablename))), 0)::text as size\n FROM pg_tables WHERE schemaname = ${schema}\n `.execute(db);\n\t\t\t\ttotalBytes += Number((result.rows[0] as any)?.size ?? 0);\n\t\t\t} catch {\n\t\t\t\t// Schema may not exist\n\t\t\t}\n\t\t}\n\n\t\tawait db\n\t\t\t.insertInto(\"usage_snapshots\")\n\t\t\t.values({\n\t\t\t\taccount_id: accountId,\n\t\t\t\tstorage_bytes: totalBytes,\n\t\t\t})\n\t\t\t.execute();\n\t}\n}\n"
7
7
  ],
8
- "mappings": "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",
9
- "debugId": "0844CA82D7046A1564756E2164756E21",
8
+ "mappings": ";;;;;;;;;;;;;;;;;AAOO,IAAM,YAAwB;AAAA,EACpC,WAAW;AAAA,EACX,mBAAmB;AAAA,EACnB,oBAAoB;AAAA,EACpB,cAAc,MAAM,OAAO;AAC5B;AAEO,SAAS,aAAa,CAAC,MAA0B;AAAA,EACvD,QAAQ;AAAA,SACF;AAAA;AAAA,MAEJ,OAAO;AAAA;AAAA;;;AClBV;AAKA,eAAsB,oBAAoB,CACzC,IACA,WACgB;AAAA,EAChB,MAAM,QAAQ,IAAI,KAAK,EAAE,YAAY,EAAE,MAAM,GAAG,EAAE;AAAA,EAClD,MAAM,GACJ,WAAW,aAAa,EACxB,OAAO;AAAA,IACP,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,cAAc;AAAA,IACd,YAAY;AAAA,EACb,CAAC,EACA,WAAW,CAAC,OACZ,GAAG,QAAQ,CAAC,cAAc,MAAM,CAAC,EAAE,YAAY;AAAA,IAC9C,cAAc;AAAA,EACf,CAAC,CACF,EACC,QAAQ;AAAA;AAUX,eAAsB,QAAQ,CAC7B,IACA,WACwB;AAAA,EACxB,MAAM,QAAQ,IAAI,KAAK,EAAE,YAAY,EAAE,MAAM,GAAG,EAAE;AAAA,EAClD,MAAM,aAAa,MAAM,MAAM,GAAG,CAAC,IAAI;AAAA,EAGvC,MAAM,WAAW,MAAM,GACrB,WAAW,aAAa,EACxB,OAAO,cAAc,EACrB,MAAM,cAAc,KAAK,SAAS,EAClC,MAAM,QAAQ,KAAK,KAAK,EACxB,iBAAiB;AAAA,EAGnB,MAAM,aAAa,MAAM,GACvB,WAAW,aAAa,EACxB,OAAO,kCAA0C,GAAG,OAAO,CAAC,EAC5D,MAAM,cAAc,KAAK,SAAS,EAClC,MAAM,QAAQ,MAAM,UAAU,EAC9B,iBAAiB;AAAA,EAGnB,MAAM,aAAa,MAAM,GACvB,WAAW,iBAAiB,EAC5B,OAAO,eAAe,EACtB,MAAM,cAAc,KAAK,SAAS,EAClC,QAAQ,eAAe,MAAM,EAC7B,MAAM,CAAC,EACP,iBAAiB;AAAA,EAEnB,OAAO;AAAA,IACN,kBAAkB,UAAU,gBAAgB;AAAA,IAC5C,qBAAqB,OAAO,YAAY,SAAS,CAAC;AAAA,IAClD,cAAc,OAAO,YAAY,iBAAiB,CAAC;AAAA,EACpD;AAAA;AAUD,eAAsB,aAAa,CAClC,IACA,WACwB;AAAA,EACxB,MAAM,OAAO,MAAM,GACjB,WAAW,aAAa,EACxB,OAAO,CAAC,QAAQ,gBAAgB,YAAY,CAAC,EAC7C,MAAM,cAAc,KAAK,SAAS,EAClC,MAAM,QAAQ,MAAM,oBAA4B,EAChD,QAAQ,QAAQ,KAAK,EACrB,QAAQ;AAAA,EAGV,MAAM,SAAS,IAAI,IAClB,KAAK,IAAI,CAAC,MAAM;AAAA,IACf,MAAM,IAAI,EAAE;AAAA,IACZ,MAAM,MACL,aAAa,OACV,EAAE,YAAY,EAAE,MAAM,GAAG,EAAE,IAC3B,OAAO,CAAC,EAAE,MAAM,GAAG,EAAE;AAAA,IACzB,OAAO,CAAC,KAAK,CAAC;AAAA,GACd,CACF;AAAA,EACA,MAAM,SAAuB,CAAC;AAAA,EAC9B,SAAS,IAAI,EAAG,KAAK,GAAG,KAAK;AAAA,IAC5B,MAAM,IAAI,IAAI;AAAA,IACd,EAAE,QAAQ,EAAE,QAAQ,IAAI,CAAC;AAAA,IACzB,MAAM,UAAU,EAAE,YAAY,EAAE,MAAM,GAAG,EAAE;AAAA,IAC3C,MAAM,MAAM,OAAO,IAAI,OAAO;AAAA,IAC9B,OAAO,KAAK;AAAA,MACX,MAAM;AAAA,MACN,aAAa,KAAK,gBAAgB;AAAA,MAClC,YAAY,KAAK,cAAc;AAAA,IAChC,CAAC;AAAA,EACF;AAAA,EACA,OAAO;AAAA;AAWR,eAAsB,WAAW,CAChC,IACA,WACA,MACsB;AAAA,EACtB,MAAM,SAAS,cAAc,IAAI;AAAA,EACjC,MAAM,QAAQ,MAAM,SAAS,IAAI,SAAS;AAAA,EAE1C,MAAM,gBAAgB,MAAM,GAC1B,WAAW,WAAW,EACtB,OAAO,cAAsB,GAAG,OAAO,CAAC,EACxC,MAAM,cAAc,KAAK,SAAS,EAClC,iBAAiB;AAAA,EAEnB,MAAM,UAAU;AAAA,OACZ;AAAA,IACH,WAAW,OAAO,eAAe,SAAS,CAAC;AAAA,EAC5C;AAAA,EAEA,IAAI,QAAQ,aAAa,OAAO,WAAW;AAAA,IAC1C,OAAO,EAAE,SAAS,OAAO,QAAQ,SAAS,UAAU,YAAY;AAAA,EACjE;AAAA,EACA,IAAI,QAAQ,oBAAoB,OAAO,mBAAmB;AAAA,IACzD,OAAO,EAAE,SAAS,OAAO,QAAQ,SAAS,UAAU,eAAe;AAAA,EACpE;AAAA,EACA,IAAI,QAAQ,uBAAuB,OAAO,oBAAoB;AAAA,IAC7D,OAAO,EAAE,SAAS,OAAO,QAAQ,SAAS,UAAU,aAAa;AAAA,EAClE;AAAA,EACA,IAAI,QAAQ,gBAAgB,OAAO,cAAc;AAAA,IAChD,OAAO,EAAE,SAAS,OAAO,QAAQ,SAAS,UAAU,UAAU;AAAA,EAC/D;AAAA,EAEA,OAAO,EAAE,SAAS,MAAM,QAAQ,QAAQ;AAAA;AAOzC,eAAsB,cAAc,CAAC,IAAqC;AAAA,EAEzE,MAAM,mBAAmB,MAAM,GAC7B,WAAW,WAAW,EACtB,OAAO,CAAC,cAAc,aAAa,CAAC,EACpC,MAAM,eAAe,UAAU,IAAI,EACnC,QAAQ;AAAA,EAGV,MAAM,YAAY,IAAI;AAAA,EACtB,WAAW,OAAO,kBAAkB;AAAA,IACnC,MAAM,UAAU,UAAU,IAAI,IAAI,UAAU,KAAK,CAAC;AAAA,IAClD,IAAI,IAAI;AAAA,MAAa,QAAQ,KAAK,IAAI,WAAW;AAAA,IACjD,UAAU,IAAI,IAAI,YAAY,OAAO;AAAA,EACtC;AAAA,EAEA,YAAY,WAAW,YAAY,WAAW;AAAA,IAC7C,IAAI,aAAa;AAAA,IACjB,WAAW,UAAU,SAAS;AAAA,MAC7B,IAAI;AAAA,QACH,MAAM,SAAS,MAAM;AAAA;AAAA,8CAEqB;AAAA,UACpC,QAAQ,EAAE;AAAA,QAChB,cAAc,OAAQ,OAAO,KAAK,IAAY,QAAQ,CAAC;AAAA,QACtD,MAAM;AAAA,IAGT;AAAA,IAEA,MAAM,GACJ,WAAW,iBAAiB,EAC5B,OAAO;AAAA,MACP,YAAY;AAAA,MACZ,eAAe;AAAA,IAChB,CAAC,EACA,QAAQ;AAAA,EACX;AAAA;",
9
+ "debugId": "7A7AEE74ECF0C63664756E2164756E21",
10
10
  "names": []
11
11
  }
@@ -1,5 +1,5 @@
1
1
  import { Kysely } from "kysely";
2
- import { Generated, Selectable } from "kysely";
2
+ import { ColumnType, Generated, Selectable } from "kysely";
3
3
  interface BlocksTable {
4
4
  height: number;
5
5
  hash: string;
@@ -56,7 +56,6 @@ interface SubgraphsTable {
56
56
  last_error_at: Date | null;
57
57
  total_processed: Generated<number>;
58
58
  total_errors: Generated<number>;
59
- api_key_id: string | null;
60
59
  account_id: string;
61
60
  handler_code: string | null;
62
61
  source_code: string | null;
@@ -360,6 +359,63 @@ interface Database {
360
359
  workflow_cursors: WorkflowCursorsTable;
361
360
  workflow_signer_secrets: WorkflowSignerSecretsTable;
362
361
  workflow_budgets: WorkflowBudgetsTable;
362
+ tenants: TenantsTable;
363
+ tenant_usage_monthly: TenantUsageMonthlyTable;
364
+ provisioning_audit_log: ProvisioningAuditLogTable;
365
+ }
366
+ type TenantStatus = "provisioning" | "active" | "suspended" | "error" | "deleted";
367
+ interface TenantsTable {
368
+ id: Generated<string>;
369
+ account_id: string;
370
+ slug: string;
371
+ status: ColumnType<TenantStatus, TenantStatus | undefined, TenantStatus>;
372
+ plan: string;
373
+ cpus: ColumnType<number, number | string, number | string>;
374
+ memory_mb: number;
375
+ storage_limit_mb: number;
376
+ storage_used_mb: number | null;
377
+ pg_container_id: string | null;
378
+ api_container_id: string | null;
379
+ processor_container_id: string | null;
380
+ target_database_url_enc: Buffer;
381
+ tenant_jwt_secret_enc: Buffer;
382
+ anon_key_enc: Buffer;
383
+ service_key_enc: Buffer;
384
+ api_url_internal: string;
385
+ api_url_public: string;
386
+ trial_ends_at: Date;
387
+ suspended_at: Date | null;
388
+ last_health_check_at: Date | null;
389
+ service_gen: Generated<number>;
390
+ anon_gen: Generated<number>;
391
+ project_id: string | null;
392
+ created_at: Generated<Date>;
393
+ updated_at: Generated<Date>;
394
+ }
395
+ interface TenantUsageMonthlyTable {
396
+ id: Generated<string>;
397
+ tenant_id: string;
398
+ period_month: Date;
399
+ storage_peak_mb: Generated<number>;
400
+ storage_avg_mb: Generated<number>;
401
+ storage_last_mb: Generated<number>;
402
+ measurements: Generated<number>;
403
+ first_at: Generated<Date>;
404
+ last_at: Generated<Date>;
405
+ }
406
+ type ProvisioningAuditEvent = "provision.start" | "provision.success" | "provision.failure" | "suspend" | "resume" | "resize" | "keys.rotate" | "bastion.key.upload" | "bastion.key.revoke" | "teardown";
407
+ type ProvisioningAuditStatus = "ok" | "error";
408
+ interface ProvisioningAuditLogTable {
409
+ id: Generated<string>;
410
+ tenant_id: string | null;
411
+ tenant_slug: string | null;
412
+ account_id: string | null;
413
+ actor: string;
414
+ event: ProvisioningAuditEvent;
415
+ status: ProvisioningAuditStatus;
416
+ detail: unknown | null;
417
+ error: string | null;
418
+ created_at: Generated<Date>;
363
419
  }
364
420
  interface WorkflowBudgetsTable {
365
421
  id: Generated<string>;
@@ -40,7 +40,12 @@ var ErrorCodes = {
40
40
  RATE_LIMIT_ERROR: "RATE_LIMIT_ERROR",
41
41
  FORBIDDEN: "FORBIDDEN",
42
42
  VERSION_CONFLICT: "VERSION_CONFLICT",
43
- NOT_FOUND: "NOT_FOUND"
43
+ NOT_FOUND: "NOT_FOUND",
44
+ KEY_ROTATED: "KEY_ROTATED",
45
+ TRIAL_EXPIRED: "TRIAL_EXPIRED",
46
+ TENANT_SUSPENDED: "TENANT_SUSPENDED",
47
+ NO_TENANT_FOR_PROJECT: "NO_TENANT_FOR_PROJECT",
48
+ INSTANCE_EXISTS: "INSTANCE_EXISTS"
44
49
  };
45
50
 
46
51
  class SecondLayerError extends Error {
@@ -115,13 +120,36 @@ class VersionConflictError extends SecondLayerError {
115
120
  this.expectedVersion = expectedVersion;
116
121
  }
117
122
  }
123
+
124
+ class KeyRotatedError extends SecondLayerError {
125
+ constructor(message = "Token has been rotated") {
126
+ super("KEY_ROTATED", message);
127
+ }
128
+ }
129
+
130
+ class TrialExpiredError extends SecondLayerError {
131
+ constructor(message) {
132
+ super("TRIAL_EXPIRED", message);
133
+ }
134
+ }
135
+
136
+ class TenantSuspendedError extends SecondLayerError {
137
+ constructor(message = "Instance is suspended") {
138
+ super("TENANT_SUSPENDED", message);
139
+ }
140
+ }
118
141
  var CODE_TO_STATUS = {
119
142
  AUTHENTICATION_ERROR: 401,
120
143
  AUTHORIZATION_ERROR: 403,
121
144
  RATE_LIMIT_ERROR: 429,
122
145
  FORBIDDEN: 403,
123
146
  NOT_FOUND: 404,
124
- VALIDATION_ERROR: 400
147
+ VALIDATION_ERROR: 400,
148
+ KEY_ROTATED: 401,
149
+ TRIAL_EXPIRED: 402,
150
+ TENANT_SUSPENDED: 423,
151
+ NO_TENANT_FOR_PROJECT: 404,
152
+ INSTANCE_EXISTS: 409
125
153
  };
126
154
  function getErrorMessage(err) {
127
155
  return err instanceof Error ? err.message : String(err);
@@ -228,5 +256,5 @@ export {
228
256
  bumpPatch
229
257
  };
230
258
 
231
- //# debugId=A3CE9B539936424A64756E2164756E21
259
+ //# debugId=E33493756835BCAB64756E2164756E21
232
260
  //# sourceMappingURL=workflows.js.map
@@ -2,11 +2,11 @@
2
2
  "version": 3,
3
3
  "sources": ["../src/db/jsonb.ts", "../src/errors.ts", "../src/db/queries/workflows.ts"],
4
4
  "sourcesContent": [
5
- "import { type RawBuilder, sql } from \"kysely\";\n\n/**\n * Safely encode a JS value as a JSONB literal for Kysely inserts/updates.\n * Kysely + postgres.js double-encodes JSON when using parameterized queries\n * with ::jsonb casts. This uses sql.raw to inline a properly escaped literal.\n */\nexport function jsonb(value: unknown): RawBuilder<unknown> {\n\tconst escaped = JSON.stringify(value, (_k, v) => (typeof v === \"bigint\" ? v.toString() : v)).replace(/'/g, \"''\");\n\treturn sql`${sql.raw(`'${escaped}'::jsonb`)}`;\n}\n\n/**\n * Safely parse a JSONB value from the database.\n * Handles double-encoded strings where postgres.js returns a JSON string\n * instead of a parsed object.\n */\nexport function parseJsonb<T = unknown>(value: unknown): T {\n\tif (typeof value === \"string\") {\n\t\ttry {\n\t\t\treturn JSON.parse(value) as T;\n\t\t} catch {\n\t\t\treturn value as T;\n\t\t}\n\t}\n\treturn (value ?? {}) as T;\n}\n",
6
- "export const ErrorCodes = {\n\tVALIDATION_ERROR: \"VALIDATION_ERROR\",\n\tDATABASE_ERROR: \"DATABASE_ERROR\",\n\tAUTHENTICATION_ERROR: \"AUTHENTICATION_ERROR\",\n\tAUTHORIZATION_ERROR: \"AUTHORIZATION_ERROR\",\n\tRATE_LIMIT_ERROR: \"RATE_LIMIT_ERROR\",\n\tFORBIDDEN: \"FORBIDDEN\",\n\tVERSION_CONFLICT: \"VERSION_CONFLICT\",\n\tNOT_FOUND: \"NOT_FOUND\",\n} as const;\n\nexport type ErrorCode = (typeof ErrorCodes)[keyof typeof ErrorCodes];\n\n/** Base error class for all Secondlayer errors. */\nexport class SecondLayerError extends Error {\n\tpublic code: ErrorCode;\n\tpublic override cause?: unknown;\n\n\tconstructor(code: ErrorCode, message: string, cause?: unknown) {\n\t\tsuper(message);\n\t\tthis.code = code;\n\t\tthis.cause = cause;\n\t\tthis.name = this.constructor.name;\n\t\tError.captureStackTrace?.(this, this.constructor);\n\t}\n\n\ttoJSON(): {\n\t\tname: string;\n\t\tcode: string;\n\t\tmessage: string;\n\t\tstack: string | undefined;\n\t\tcause: unknown;\n\t} {\n\t\treturn {\n\t\t\tname: this.name,\n\t\t\tcode: this.code,\n\t\t\tmessage: this.message,\n\t\t\tstack: this.stack,\n\t\t\tcause: this.cause,\n\t\t};\n\t}\n}\n\nexport class NotFoundError extends SecondLayerError {\n\tconstructor(message: string) {\n\t\tsuper(\"NOT_FOUND\", message);\n\t}\n}\n\nexport class ValidationError extends SecondLayerError {\n\tconstructor(message: string, cause?: unknown) {\n\t\tsuper(\"VALIDATION_ERROR\", message, cause);\n\t}\n}\n\nexport class DatabaseError extends SecondLayerError {\n\tconstructor(message: string, cause?: unknown) {\n\t\tsuper(\"DATABASE_ERROR\", message, cause);\n\t}\n}\n\nexport class AuthenticationError extends SecondLayerError {\n\tconstructor(message: string) {\n\t\tsuper(\"AUTHENTICATION_ERROR\", message);\n\t}\n}\n\nexport class AuthorizationError extends SecondLayerError {\n\tconstructor(message: string) {\n\t\tsuper(\"AUTHORIZATION_ERROR\", message);\n\t}\n}\n\nexport class RateLimitError extends SecondLayerError {\n\tconstructor(message: string) {\n\t\tsuper(\"RATE_LIMIT_ERROR\", message);\n\t}\n}\n\nexport class ForbiddenError extends SecondLayerError {\n\tconstructor(message = \"Forbidden\") {\n\t\tsuper(\"FORBIDDEN\", message);\n\t}\n}\n\nexport class VersionConflictError extends SecondLayerError {\n\tpublic currentVersion: string;\n\tpublic expectedVersion: string;\n\n\tconstructor(currentVersion: string, expectedVersion: string) {\n\t\tsuper(\n\t\t\t\"VERSION_CONFLICT\",\n\t\t\t`Version conflict: expected ${expectedVersion}, current ${currentVersion}`,\n\t\t);\n\t\tthis.currentVersion = currentVersion;\n\t\tthis.expectedVersion = expectedVersion;\n\t}\n}\n\n/** Error code → HTTP status. Used by API middleware for code-based matching\n * (avoids cross-bundle instanceof failures from bunup class duplication). */\ntype MappedCode = Extract<\n\tErrorCode,\n\t| \"AUTHENTICATION_ERROR\"\n\t| \"AUTHORIZATION_ERROR\"\n\t| \"RATE_LIMIT_ERROR\"\n\t| \"FORBIDDEN\"\n\t| \"NOT_FOUND\"\n\t| \"VALIDATION_ERROR\"\n>;\nexport const CODE_TO_STATUS: Record<MappedCode, 400 | 401 | 403 | 404 | 429> = {\n\tAUTHENTICATION_ERROR: 401,\n\tAUTHORIZATION_ERROR: 403,\n\tRATE_LIMIT_ERROR: 429,\n\tFORBIDDEN: 403,\n\tNOT_FOUND: 404,\n\tVALIDATION_ERROR: 400,\n} as const;\n\nexport function getErrorMessage(err: unknown): string {\n\treturn err instanceof Error ? err.message : String(err);\n}\n",
5
+ "import { type RawBuilder, sql } from \"kysely\";\n\n/**\n * Safely encode a JS value as a JSONB literal for Kysely inserts/updates.\n * Kysely + postgres.js double-encodes JSON when using parameterized queries\n * with ::jsonb casts. This uses sql.raw to inline a properly escaped literal.\n */\nexport function jsonb(value: unknown): RawBuilder<unknown> {\n\tconst escaped = JSON.stringify(value, (_k, v) =>\n\t\ttypeof v === \"bigint\" ? v.toString() : v,\n\t).replace(/'/g, \"''\");\n\treturn sql`${sql.raw(`'${escaped}'::jsonb`)}`;\n}\n\n/**\n * Safely parse a JSONB value from the database.\n * Handles double-encoded strings where postgres.js returns a JSON string\n * instead of a parsed object.\n */\nexport function parseJsonb<T = unknown>(value: unknown): T {\n\tif (typeof value === \"string\") {\n\t\ttry {\n\t\t\treturn JSON.parse(value) as T;\n\t\t} catch {\n\t\t\treturn value as T;\n\t\t}\n\t}\n\treturn (value ?? {}) as T;\n}\n",
6
+ "export const ErrorCodes = {\n\tVALIDATION_ERROR: \"VALIDATION_ERROR\",\n\tDATABASE_ERROR: \"DATABASE_ERROR\",\n\tAUTHENTICATION_ERROR: \"AUTHENTICATION_ERROR\",\n\tAUTHORIZATION_ERROR: \"AUTHORIZATION_ERROR\",\n\tRATE_LIMIT_ERROR: \"RATE_LIMIT_ERROR\",\n\tFORBIDDEN: \"FORBIDDEN\",\n\tVERSION_CONFLICT: \"VERSION_CONFLICT\",\n\tNOT_FOUND: \"NOT_FOUND\",\n\t// Tenant lifecycle (CLI surfaces these verbatim)\n\tKEY_ROTATED: \"KEY_ROTATED\",\n\tTRIAL_EXPIRED: \"TRIAL_EXPIRED\",\n\tTENANT_SUSPENDED: \"TENANT_SUSPENDED\",\n\tNO_TENANT_FOR_PROJECT: \"NO_TENANT_FOR_PROJECT\",\n\tINSTANCE_EXISTS: \"INSTANCE_EXISTS\",\n} as const;\n\nexport type ErrorCode = (typeof ErrorCodes)[keyof typeof ErrorCodes];\n\n/** Base error class for all Secondlayer errors. */\nexport class SecondLayerError extends Error {\n\tpublic code: ErrorCode;\n\tpublic override cause?: unknown;\n\n\tconstructor(code: ErrorCode, message: string, cause?: unknown) {\n\t\tsuper(message);\n\t\tthis.code = code;\n\t\tthis.cause = cause;\n\t\tthis.name = this.constructor.name;\n\t\tError.captureStackTrace?.(this, this.constructor);\n\t}\n\n\ttoJSON(): {\n\t\tname: string;\n\t\tcode: string;\n\t\tmessage: string;\n\t\tstack: string | undefined;\n\t\tcause: unknown;\n\t} {\n\t\treturn {\n\t\t\tname: this.name,\n\t\t\tcode: this.code,\n\t\t\tmessage: this.message,\n\t\t\tstack: this.stack,\n\t\t\tcause: this.cause,\n\t\t};\n\t}\n}\n\nexport class NotFoundError extends SecondLayerError {\n\tconstructor(message: string) {\n\t\tsuper(\"NOT_FOUND\", message);\n\t}\n}\n\nexport class ValidationError extends SecondLayerError {\n\tconstructor(message: string, cause?: unknown) {\n\t\tsuper(\"VALIDATION_ERROR\", message, cause);\n\t}\n}\n\nexport class DatabaseError extends SecondLayerError {\n\tconstructor(message: string, cause?: unknown) {\n\t\tsuper(\"DATABASE_ERROR\", message, cause);\n\t}\n}\n\nexport class AuthenticationError extends SecondLayerError {\n\tconstructor(message: string) {\n\t\tsuper(\"AUTHENTICATION_ERROR\", message);\n\t}\n}\n\nexport class AuthorizationError extends SecondLayerError {\n\tconstructor(message: string) {\n\t\tsuper(\"AUTHORIZATION_ERROR\", message);\n\t}\n}\n\nexport class RateLimitError extends SecondLayerError {\n\tconstructor(message: string) {\n\t\tsuper(\"RATE_LIMIT_ERROR\", message);\n\t}\n}\n\nexport class ForbiddenError extends SecondLayerError {\n\tconstructor(message = \"Forbidden\") {\n\t\tsuper(\"FORBIDDEN\", message);\n\t}\n}\n\nexport class VersionConflictError extends SecondLayerError {\n\tpublic currentVersion: string;\n\tpublic expectedVersion: string;\n\n\tconstructor(currentVersion: string, expectedVersion: string) {\n\t\tsuper(\n\t\t\t\"VERSION_CONFLICT\",\n\t\t\t`Version conflict: expected ${expectedVersion}, current ${currentVersion}`,\n\t\t);\n\t\tthis.currentVersion = currentVersion;\n\t\tthis.expectedVersion = expectedVersion;\n\t}\n}\n\nexport class KeyRotatedError extends SecondLayerError {\n\tconstructor(message = \"Token has been rotated\") {\n\t\tsuper(\"KEY_ROTATED\", message);\n\t}\n}\n\nexport class TrialExpiredError extends SecondLayerError {\n\tconstructor(message: string) {\n\t\tsuper(\"TRIAL_EXPIRED\", message);\n\t}\n}\n\nexport class TenantSuspendedError extends SecondLayerError {\n\tconstructor(message = \"Instance is suspended\") {\n\t\tsuper(\"TENANT_SUSPENDED\", message);\n\t}\n}\n\n/** Error code → HTTP status. Used by API middleware for code-based matching\n * (avoids cross-bundle instanceof failures from bunup class duplication). */\ntype MappedCode = Extract<\n\tErrorCode,\n\t| \"AUTHENTICATION_ERROR\"\n\t| \"AUTHORIZATION_ERROR\"\n\t| \"RATE_LIMIT_ERROR\"\n\t| \"FORBIDDEN\"\n\t| \"NOT_FOUND\"\n\t| \"VALIDATION_ERROR\"\n\t| \"KEY_ROTATED\"\n\t| \"TRIAL_EXPIRED\"\n\t| \"TENANT_SUSPENDED\"\n\t| \"NO_TENANT_FOR_PROJECT\"\n\t| \"INSTANCE_EXISTS\"\n>;\nexport const CODE_TO_STATUS: Record<\n\tMappedCode,\n\t400 | 401 | 402 | 403 | 404 | 409 | 423 | 429\n> = {\n\tAUTHENTICATION_ERROR: 401,\n\tAUTHORIZATION_ERROR: 403,\n\tRATE_LIMIT_ERROR: 429,\n\tFORBIDDEN: 403,\n\tNOT_FOUND: 404,\n\tVALIDATION_ERROR: 400,\n\tKEY_ROTATED: 401,\n\tTRIAL_EXPIRED: 402,\n\tTENANT_SUSPENDED: 423,\n\tNO_TENANT_FOR_PROJECT: 404,\n\tINSTANCE_EXISTS: 409,\n} as const;\n\nexport function getErrorMessage(err: unknown): string {\n\treturn err instanceof Error ? err.message : String(err);\n}\n",
7
7
  "import type { Kysely } from \"kysely\";\nimport { VersionConflictError } from \"../../errors.ts\";\nimport { jsonb } from \"../jsonb.ts\";\nimport type {\n\tDatabase,\n\tWorkflowDefinition,\n\tWorkflowRun,\n\tWorkflowStep,\n} from \"../types.ts\";\n\n/** Bump the patch digit of a semver string. Falls back to \"1.0.1\" on malformed input. */\nexport function bumpPatch(version: string): string {\n\tconst parts = version.split(\".\");\n\tif (parts.length !== 3) return \"1.0.1\";\n\tconst [major, minor, patch] = parts.map((p) => Number.parseInt(p, 10));\n\tif (\n\t\tNumber.isNaN(major) ||\n\t\tNumber.isNaN(minor) ||\n\t\tNumber.isNaN(patch) ||\n\t\tmajor === undefined ||\n\t\tminor === undefined ||\n\t\tpatch === undefined\n\t) {\n\t\treturn \"1.0.1\";\n\t}\n\treturn `${major}.${minor}.${patch + 1}`;\n}\n\n// ── Definitions ──────────────────────────────────────────────────────\n\nexport async function listWorkflowDefinitions(\n\tdb: Kysely<Database>,\n\tapiKeyIds?: string[],\n): Promise<WorkflowDefinition[]> {\n\tlet query = db\n\t\t.selectFrom(\"workflow_definitions\")\n\t\t.selectAll()\n\t\t.where(\"status\", \"!=\", \"deleted\")\n\t\t.orderBy(\"created_at\", \"desc\");\n\n\tif (apiKeyIds?.length) {\n\t\tquery = query.where(\"api_key_id\", \"in\", apiKeyIds);\n\t}\n\n\treturn await query.execute();\n}\n\nexport async function getWorkflowDefinition(\n\tdb: Kysely<Database>,\n\tname: string,\n\tapiKeyIds?: string[],\n): Promise<WorkflowDefinition | null> {\n\tlet query = db\n\t\t.selectFrom(\"workflow_definitions\")\n\t\t.selectAll()\n\t\t.where(\"name\", \"=\", name);\n\n\tif (apiKeyIds?.length) {\n\t\tquery = query.where(\"api_key_id\", \"in\", apiKeyIds);\n\t}\n\n\treturn (await query.executeTakeFirst()) ?? null;\n}\n\nexport async function upsertWorkflowDefinition(\n\tdb: Kysely<Database>,\n\tdata: {\n\t\tname: string;\n\t\ttriggerType: string;\n\t\ttriggerConfig: Record<string, unknown>;\n\t\thandlerPath: string;\n\t\tapiKeyId: string;\n\t\tprojectId?: string;\n\t\tretriesConfig?: Record<string, unknown>;\n\t\ttimeoutMs?: number;\n\t\tsourceCode?: string;\n\t\texpectedVersion?: string;\n\t},\n): Promise<WorkflowDefinition> {\n\tconst existing = await db\n\t\t.selectFrom(\"workflow_definitions\")\n\t\t.selectAll()\n\t\t.where(\"name\", \"=\", data.name)\n\t\t.where(\"api_key_id\", \"=\", data.apiKeyId)\n\t\t.executeTakeFirst();\n\n\tif (existing) {\n\t\tif (\n\t\t\tdata.expectedVersion !== undefined &&\n\t\t\texisting.version !== data.expectedVersion\n\t\t) {\n\t\t\tthrow new VersionConflictError(existing.version, data.expectedVersion);\n\t\t}\n\n\t\tconst nextVersion = bumpPatch(existing.version);\n\n\t\treturn await db\n\t\t\t.updateTable(\"workflow_definitions\")\n\t\t\t.set({\n\t\t\t\ttrigger_type: data.triggerType,\n\t\t\t\ttrigger_config: jsonb(data.triggerConfig) as unknown as string,\n\t\t\t\thandler_path: data.handlerPath,\n\t\t\t\tsource_code: data.sourceCode ?? existing.source_code,\n\t\t\t\tretries_config: data.retriesConfig\n\t\t\t\t\t? (jsonb(data.retriesConfig) as unknown as string)\n\t\t\t\t\t: null,\n\t\t\t\ttimeout_ms: data.timeoutMs ?? null,\n\t\t\t\tversion: nextVersion,\n\t\t\t\tstatus: \"active\",\n\t\t\t\tupdated_at: new Date(),\n\t\t\t})\n\t\t\t.where(\"id\", \"=\", existing.id)\n\t\t\t.returningAll()\n\t\t\t.executeTakeFirstOrThrow();\n\t}\n\n\treturn await db\n\t\t.insertInto(\"workflow_definitions\")\n\t\t.values({\n\t\t\tname: data.name,\n\t\t\ttrigger_type: data.triggerType,\n\t\t\ttrigger_config: jsonb(data.triggerConfig) as unknown as string,\n\t\t\thandler_path: data.handlerPath,\n\t\t\tsource_code: data.sourceCode ?? null,\n\t\t\tapi_key_id: data.apiKeyId,\n\t\t\tproject_id: data.projectId ?? null,\n\t\t\tretries_config: data.retriesConfig\n\t\t\t\t? (jsonb(data.retriesConfig) as unknown as string)\n\t\t\t\t: null,\n\t\t\ttimeout_ms: data.timeoutMs ?? null,\n\t\t\tversion: \"1.0.0\",\n\t\t})\n\t\t.returningAll()\n\t\t.executeTakeFirstOrThrow();\n}\n\nexport async function updateWorkflowStatus(\n\tdb: Kysely<Database>,\n\tname: string,\n\tapiKeyId: string,\n\tstatus: string,\n): Promise<void> {\n\tawait db\n\t\t.updateTable(\"workflow_definitions\")\n\t\t.set({ status, updated_at: new Date() })\n\t\t.where(\"name\", \"=\", name)\n\t\t.where(\"api_key_id\", \"=\", apiKeyId)\n\t\t.execute();\n}\n\nexport async function deleteWorkflowDefinition(\n\tdb: Kysely<Database>,\n\tname: string,\n\tapiKeyId: string,\n): Promise<void> {\n\tawait db\n\t\t.updateTable(\"workflow_definitions\")\n\t\t.set({ status: \"deleted\", updated_at: new Date() })\n\t\t.where(\"name\", \"=\", name)\n\t\t.where(\"api_key_id\", \"=\", apiKeyId)\n\t\t.execute();\n}\n\n// ── Runs ─────────────────────────────────────────────────────────────\n\nexport async function createWorkflowRun(\n\tdb: Kysely<Database>,\n\tdata: {\n\t\tdefinitionId: string;\n\t\ttriggerType: string;\n\t\ttriggerData?: Record<string, unknown>;\n\t\tdedupKey?: string;\n\t},\n): Promise<WorkflowRun> {\n\treturn await db\n\t\t.insertInto(\"workflow_runs\")\n\t\t.values({\n\t\t\tdefinition_id: data.definitionId,\n\t\t\ttrigger_type: data.triggerType,\n\t\t\ttrigger_data: data.triggerData\n\t\t\t\t? (jsonb(data.triggerData) as unknown as string)\n\t\t\t\t: null,\n\t\t\tdedup_key: data.dedupKey ?? null,\n\t\t})\n\t\t.returningAll()\n\t\t.executeTakeFirstOrThrow();\n}\n\nexport async function getWorkflowRun(\n\tdb: Kysely<Database>,\n\trunId: string,\n): Promise<WorkflowRun | null> {\n\treturn (\n\t\t(await db\n\t\t\t.selectFrom(\"workflow_runs\")\n\t\t\t.selectAll()\n\t\t\t.where(\"id\", \"=\", runId)\n\t\t\t.executeTakeFirst()) ?? null\n\t);\n}\n\nexport async function listWorkflowRuns(\n\tdb: Kysely<Database>,\n\tdefinitionId: string,\n\tparams?: { status?: string; limit?: number; offset?: number },\n): Promise<WorkflowRun[]> {\n\tlet query = db\n\t\t.selectFrom(\"workflow_runs\")\n\t\t.selectAll()\n\t\t.where(\"definition_id\", \"=\", definitionId)\n\t\t.orderBy(\"created_at\", \"desc\");\n\n\tif (params?.status) {\n\t\tquery = query.where(\"status\", \"=\", params.status);\n\t}\n\n\tquery = query.limit(params?.limit ?? 20);\n\n\tif (params?.offset) {\n\t\tquery = query.offset(params.offset);\n\t}\n\n\treturn await query.execute();\n}\n\n// ── Steps ────────────────────────────────────────────────────────────\n\nexport async function getWorkflowSteps(\n\tdb: Kysely<Database>,\n\trunId: string,\n): Promise<WorkflowStep[]> {\n\treturn await db\n\t\t.selectFrom(\"workflow_steps\")\n\t\t.selectAll()\n\t\t.where(\"run_id\", \"=\", runId)\n\t\t.orderBy(\"step_index\", \"asc\")\n\t\t.execute();\n}\n"
8
8
  ],
9
- "mappings": ";;;;;;;;;;;;;;;;;AAAA;AAOO,SAAS,KAAK,CAAC,OAAqC;AAAA,EAC1D,MAAM,UAAU,KAAK,UAAU,OAAO,CAAC,IAAI,MAAO,OAAO,MAAM,WAAW,EAAE,SAAS,IAAI,CAAE,EAAE,QAAQ,MAAM,IAAI;AAAA,EAC/G,OAAO,MAAM,IAAI,IAAI,IAAI,iBAAiB;AAAA;AAQpC,SAAS,UAAuB,CAAC,OAAmB;AAAA,EAC1D,IAAI,OAAO,UAAU,UAAU;AAAA,IAC9B,IAAI;AAAA,MACH,OAAO,KAAK,MAAM,KAAK;AAAA,MACtB,MAAM;AAAA,MACP,OAAO;AAAA;AAAA,EAET;AAAA,EACA,OAAQ,SAAS,CAAC;AAAA;;;ACzBZ,IAAM,aAAa;AAAA,EACzB,kBAAkB;AAAA,EAClB,gBAAgB;AAAA,EAChB,sBAAsB;AAAA,EACtB,qBAAqB;AAAA,EACrB,kBAAkB;AAAA,EAClB,WAAW;AAAA,EACX,kBAAkB;AAAA,EAClB,WAAW;AACZ;AAAA;AAKO,MAAM,yBAAyB,MAAM;AAAA,EACpC;AAAA,EACS;AAAA,EAEhB,WAAW,CAAC,MAAiB,SAAiB,OAAiB;AAAA,IAC9D,MAAM,OAAO;AAAA,IACb,KAAK,OAAO;AAAA,IACZ,KAAK,QAAQ;AAAA,IACb,KAAK,OAAO,KAAK,YAAY;AAAA,IAC7B,MAAM,oBAAoB,MAAM,KAAK,WAAW;AAAA;AAAA,EAGjD,MAAM,GAMJ;AAAA,IACD,OAAO;AAAA,MACN,MAAM,KAAK;AAAA,MACX,MAAM,KAAK;AAAA,MACX,SAAS,KAAK;AAAA,MACd,OAAO,KAAK;AAAA,MACZ,OAAO,KAAK;AAAA,IACb;AAAA;AAEF;AAAA;AAEO,MAAM,sBAAsB,iBAAiB;AAAA,EACnD,WAAW,CAAC,SAAiB;AAAA,IAC5B,MAAM,aAAa,OAAO;AAAA;AAE5B;AAAA;AAEO,MAAM,wBAAwB,iBAAiB;AAAA,EACrD,WAAW,CAAC,SAAiB,OAAiB;AAAA,IAC7C,MAAM,oBAAoB,SAAS,KAAK;AAAA;AAE1C;AAAA;AAEO,MAAM,sBAAsB,iBAAiB;AAAA,EACnD,WAAW,CAAC,SAAiB,OAAiB;AAAA,IAC7C,MAAM,kBAAkB,SAAS,KAAK;AAAA;AAExC;AAAA;AAEO,MAAM,4BAA4B,iBAAiB;AAAA,EACzD,WAAW,CAAC,SAAiB;AAAA,IAC5B,MAAM,wBAAwB,OAAO;AAAA;AAEvC;AAAA;AAEO,MAAM,2BAA2B,iBAAiB;AAAA,EACxD,WAAW,CAAC,SAAiB;AAAA,IAC5B,MAAM,uBAAuB,OAAO;AAAA;AAEtC;AAAA;AAEO,MAAM,uBAAuB,iBAAiB;AAAA,EACpD,WAAW,CAAC,SAAiB;AAAA,IAC5B,MAAM,oBAAoB,OAAO;AAAA;AAEnC;AAAA;AAEO,MAAM,uBAAuB,iBAAiB;AAAA,EACpD,WAAW,CAAC,UAAU,aAAa;AAAA,IAClC,MAAM,aAAa,OAAO;AAAA;AAE5B;AAAA;AAEO,MAAM,6BAA6B,iBAAiB;AAAA,EACnD;AAAA,EACA;AAAA,EAEP,WAAW,CAAC,gBAAwB,iBAAyB;AAAA,IAC5D,MACC,oBACA,8BAA8B,4BAA4B,gBAC3D;AAAA,IACA,KAAK,iBAAiB;AAAA,IACtB,KAAK,kBAAkB;AAAA;AAEzB;AAaO,IAAM,iBAAkE;AAAA,EAC9E,sBAAsB;AAAA,EACtB,qBAAqB;AAAA,EACrB,kBAAkB;AAAA,EAClB,WAAW;AAAA,EACX,WAAW;AAAA,EACX,kBAAkB;AACnB;AAEO,SAAS,eAAe,CAAC,KAAsB;AAAA,EACrD,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA;;;AC7GhD,SAAS,SAAS,CAAC,SAAyB;AAAA,EAClD,MAAM,QAAQ,QAAQ,MAAM,GAAG;AAAA,EAC/B,IAAI,MAAM,WAAW;AAAA,IAAG,OAAO;AAAA,EAC/B,OAAO,OAAO,OAAO,SAAS,MAAM,IAAI,CAAC,MAAM,OAAO,SAAS,GAAG,EAAE,CAAC;AAAA,EACrE,IACC,OAAO,MAAM,KAAK,KAClB,OAAO,MAAM,KAAK,KAClB,OAAO,MAAM,KAAK,KAClB,UAAU,aACV,UAAU,aACV,UAAU,WACT;AAAA,IACD,OAAO;AAAA,EACR;AAAA,EACA,OAAO,GAAG,SAAS,SAAS,QAAQ;AAAA;AAKrC,eAAsB,uBAAuB,CAC5C,IACA,WACgC;AAAA,EAChC,IAAI,QAAQ,GACV,WAAW,sBAAsB,EACjC,UAAU,EACV,MAAM,UAAU,MAAM,SAAS,EAC/B,QAAQ,cAAc,MAAM;AAAA,EAE9B,IAAI,WAAW,QAAQ;AAAA,IACtB,QAAQ,MAAM,MAAM,cAAc,MAAM,SAAS;AAAA,EAClD;AAAA,EAEA,OAAO,MAAM,MAAM,QAAQ;AAAA;AAG5B,eAAsB,qBAAqB,CAC1C,IACA,MACA,WACqC;AAAA,EACrC,IAAI,QAAQ,GACV,WAAW,sBAAsB,EACjC,UAAU,EACV,MAAM,QAAQ,KAAK,IAAI;AAAA,EAEzB,IAAI,WAAW,QAAQ;AAAA,IACtB,QAAQ,MAAM,MAAM,cAAc,MAAM,SAAS;AAAA,EAClD;AAAA,EAEA,OAAQ,MAAM,MAAM,iBAAiB,KAAM;AAAA;AAG5C,eAAsB,wBAAwB,CAC7C,IACA,MAY8B;AAAA,EAC9B,MAAM,WAAW,MAAM,GACrB,WAAW,sBAAsB,EACjC,UAAU,EACV,MAAM,QAAQ,KAAK,KAAK,IAAI,EAC5B,MAAM,cAAc,KAAK,KAAK,QAAQ,EACtC,iBAAiB;AAAA,EAEnB,IAAI,UAAU;AAAA,IACb,IACC,KAAK,oBAAoB,aACzB,SAAS,YAAY,KAAK,iBACzB;AAAA,MACD,MAAM,IAAI,qBAAqB,SAAS,SAAS,KAAK,eAAe;AAAA,IACtE;AAAA,IAEA,MAAM,cAAc,UAAU,SAAS,OAAO;AAAA,IAE9C,OAAO,MAAM,GACX,YAAY,sBAAsB,EAClC,IAAI;AAAA,MACJ,cAAc,KAAK;AAAA,MACnB,gBAAgB,MAAM,KAAK,aAAa;AAAA,MACxC,cAAc,KAAK;AAAA,MACnB,aAAa,KAAK,cAAc,SAAS;AAAA,MACzC,gBAAgB,KAAK,gBACjB,MAAM,KAAK,aAAa,IACzB;AAAA,MACH,YAAY,KAAK,aAAa;AAAA,MAC9B,SAAS;AAAA,MACT,QAAQ;AAAA,MACR,YAAY,IAAI;AAAA,IACjB,CAAC,EACA,MAAM,MAAM,KAAK,SAAS,EAAE,EAC5B,aAAa,EACb,wBAAwB;AAAA,EAC3B;AAAA,EAEA,OAAO,MAAM,GACX,WAAW,sBAAsB,EACjC,OAAO;AAAA,IACP,MAAM,KAAK;AAAA,IACX,cAAc,KAAK;AAAA,IACnB,gBAAgB,MAAM,KAAK,aAAa;AAAA,IACxC,cAAc,KAAK;AAAA,IACnB,aAAa,KAAK,cAAc;AAAA,IAChC,YAAY,KAAK;AAAA,IACjB,YAAY,KAAK,aAAa;AAAA,IAC9B,gBAAgB,KAAK,gBACjB,MAAM,KAAK,aAAa,IACzB;AAAA,IACH,YAAY,KAAK,aAAa;AAAA,IAC9B,SAAS;AAAA,EACV,CAAC,EACA,aAAa,EACb,wBAAwB;AAAA;AAG3B,eAAsB,oBAAoB,CACzC,IACA,MACA,UACA,QACgB;AAAA,EAChB,MAAM,GACJ,YAAY,sBAAsB,EAClC,IAAI,EAAE,QAAQ,YAAY,IAAI,KAAO,CAAC,EACtC,MAAM,QAAQ,KAAK,IAAI,EACvB,MAAM,cAAc,KAAK,QAAQ,EACjC,QAAQ;AAAA;AAGX,eAAsB,wBAAwB,CAC7C,IACA,MACA,UACgB;AAAA,EAChB,MAAM,GACJ,YAAY,sBAAsB,EAClC,IAAI,EAAE,QAAQ,WAAW,YAAY,IAAI,KAAO,CAAC,EACjD,MAAM,QAAQ,KAAK,IAAI,EACvB,MAAM,cAAc,KAAK,QAAQ,EACjC,QAAQ;AAAA;AAKX,eAAsB,iBAAiB,CACtC,IACA,MAMuB;AAAA,EACvB,OAAO,MAAM,GACX,WAAW,eAAe,EAC1B,OAAO;AAAA,IACP,eAAe,KAAK;AAAA,IACpB,cAAc,KAAK;AAAA,IACnB,cAAc,KAAK,cACf,MAAM,KAAK,WAAW,IACvB;AAAA,IACH,WAAW,KAAK,YAAY;AAAA,EAC7B,CAAC,EACA,aAAa,EACb,wBAAwB;AAAA;AAG3B,eAAsB,cAAc,CACnC,IACA,OAC8B;AAAA,EAC9B,OACE,MAAM,GACL,WAAW,eAAe,EAC1B,UAAU,EACV,MAAM,MAAM,KAAK,KAAK,EACtB,iBAAiB,KAAM;AAAA;AAI3B,eAAsB,gBAAgB,CACrC,IACA,cACA,QACyB;AAAA,EACzB,IAAI,QAAQ,GACV,WAAW,eAAe,EAC1B,UAAU,EACV,MAAM,iBAAiB,KAAK,YAAY,EACxC,QAAQ,cAAc,MAAM;AAAA,EAE9B,IAAI,QAAQ,QAAQ;AAAA,IACnB,QAAQ,MAAM,MAAM,UAAU,KAAK,OAAO,MAAM;AAAA,EACjD;AAAA,EAEA,QAAQ,MAAM,MAAM,QAAQ,SAAS,EAAE;AAAA,EAEvC,IAAI,QAAQ,QAAQ;AAAA,IACnB,QAAQ,MAAM,OAAO,OAAO,MAAM;AAAA,EACnC;AAAA,EAEA,OAAO,MAAM,MAAM,QAAQ;AAAA;AAK5B,eAAsB,gBAAgB,CACrC,IACA,OAC0B;AAAA,EAC1B,OAAO,MAAM,GACX,WAAW,gBAAgB,EAC3B,UAAU,EACV,MAAM,UAAU,KAAK,KAAK,EAC1B,QAAQ,cAAc,KAAK,EAC3B,QAAQ;AAAA;",
10
- "debugId": "A3CE9B539936424A64756E2164756E21",
9
+ "mappings": "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",
10
+ "debugId": "E33493756835BCAB64756E2164756E21",
11
11
  "names": []
12
12
  }