@secondlayer/shared 2.1.0 → 3.0.0-alpha.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/README.md +2 -2
- package/dist/src/db/index.d.ts +39 -137
- package/dist/src/db/index.js.map +2 -2
- package/dist/src/db/jsonb.d.ts +5 -1
- package/dist/src/db/jsonb.js.map +2 -2
- package/dist/src/db/queries/account-spend-caps.d.ts +379 -0
- package/dist/src/db/queries/account-spend-caps.js +60 -0
- package/dist/src/db/queries/account-spend-caps.js.map +10 -0
- package/dist/src/db/queries/account-usage.d.ts +403 -0
- package/dist/src/db/queries/account-usage.js +222 -0
- package/dist/src/db/queries/account-usage.js.map +11 -0
- package/dist/src/db/queries/accounts.d.ts +41 -115
- package/dist/src/db/queries/accounts.js +15 -1
- package/dist/src/db/queries/accounts.js.map +3 -3
- package/dist/src/db/queries/integrity.d.ts +27 -114
- package/dist/src/db/queries/projects.d.ts +27 -114
- package/dist/src/db/queries/provisioning-audit.d.ts +27 -114
- package/dist/src/db/queries/subgraph-gaps.d.ts +27 -114
- package/dist/src/db/queries/subgraphs.d.ts +27 -115
- package/dist/src/db/queries/subgraphs.js +2 -3
- package/dist/src/db/queries/subgraphs.js.map +4 -4
- package/dist/src/db/queries/{workflows.d.ts → tenant-compute-addons.d.ts} +50 -149
- package/dist/src/db/queries/tenant-compute-addons.js +47 -0
- package/dist/src/db/queries/tenant-compute-addons.js.map +10 -0
- package/dist/src/db/queries/tenants.d.ts +40 -117
- package/dist/src/db/queries/tenants.js +9 -6
- package/dist/src/db/queries/tenants.js.map +3 -3
- package/dist/src/db/queries/usage.d.ts +28 -139
- package/dist/src/db/queries/usage.js +5 -64
- package/dist/src/db/queries/usage.js.map +4 -5
- package/dist/src/db/schema.d.ts +34 -136
- package/dist/src/errors.d.ts +8 -7
- package/dist/src/errors.js +11 -12
- package/dist/src/errors.js.map +3 -3
- package/dist/src/index.d.ts +46 -143
- package/dist/src/index.js +11 -12
- package/dist/src/index.js.map +4 -4
- package/dist/src/node/local-client.d.ts +27 -114
- package/dist/src/pricing.d.ts +20 -1
- package/dist/src/pricing.js +58 -1
- package/dist/src/pricing.js.map +3 -3
- package/migrations/0045_drop_marketplace_columns.ts +47 -0
- package/migrations/0046_tenant_activity_signal.ts +47 -0
- package/migrations/0047_usage_daily_tenant_id.ts +73 -0
- package/migrations/0048_tenant_compute_addons.ts +49 -0
- package/migrations/0049_accounts_stripe_customer_id.ts +30 -0
- package/migrations/0050_account_spend_caps.ts +45 -0
- package/migrations/0051_workflow_ai_usage_daily.ts +40 -0
- package/migrations/0052_sentries.ts +61 -0
- package/migrations/0053_workflow_runtime.ts +88 -0
- package/migrations/0054_accounts_plan_hobby.ts +32 -0
- package/migrations/0055_ai_usage_account_scope.ts +108 -0
- package/migrations/0056_drop_workflow_sentry_residuals.ts +23 -0
- package/package.json +26 -14
- package/dist/src/db/queries/workflows.js +0 -260
- package/dist/src/db/queries/workflows.js.map +0 -12
- package/dist/src/lib/plans.d.ts +0 -9
- package/dist/src/lib/plans.js +0 -37
- package/dist/src/lib/plans.js.map +0 -10
- package/dist/src/schemas/workflows.d.ts +0 -70
- package/dist/src/schemas/workflows.js +0 -43
- package/dist/src/schemas/workflows.js.map +0 -10
|
@@ -60,10 +60,6 @@ interface SubgraphsTable {
|
|
|
60
60
|
handler_code: string | null;
|
|
61
61
|
source_code: string | null;
|
|
62
62
|
project_id: string | null;
|
|
63
|
-
is_public: Generated<boolean>;
|
|
64
|
-
tags: Generated<string[]>;
|
|
65
|
-
description: string | null;
|
|
66
|
-
forked_from_id: string | null;
|
|
67
63
|
created_at: Generated<Date>;
|
|
68
64
|
updated_at: Generated<Date>;
|
|
69
65
|
}
|
|
@@ -98,6 +94,7 @@ interface AccountsTable {
|
|
|
98
94
|
bio: string | null;
|
|
99
95
|
avatar_url: string | null;
|
|
100
96
|
slug: string | null;
|
|
97
|
+
stripe_customer_id: string | null;
|
|
101
98
|
created_at: Generated<Date>;
|
|
102
99
|
}
|
|
103
100
|
interface SessionsTable {
|
|
@@ -123,6 +120,7 @@ interface MagicLinksTable {
|
|
|
123
120
|
}
|
|
124
121
|
interface UsageDailyTable {
|
|
125
122
|
account_id: string;
|
|
123
|
+
tenant_id: string | null;
|
|
126
124
|
date: string;
|
|
127
125
|
api_requests: Generated<number>;
|
|
128
126
|
deliveries: Generated<number>;
|
|
@@ -249,83 +247,6 @@ interface ChatMessagesTable {
|
|
|
249
247
|
metadata: unknown | null;
|
|
250
248
|
created_at: Generated<Date>;
|
|
251
249
|
}
|
|
252
|
-
interface WorkflowDefinitionsTable {
|
|
253
|
-
id: Generated<string>;
|
|
254
|
-
name: string;
|
|
255
|
-
version: Generated<string>;
|
|
256
|
-
status: Generated<string>;
|
|
257
|
-
trigger_type: string;
|
|
258
|
-
trigger_config: unknown;
|
|
259
|
-
handler_path: string;
|
|
260
|
-
source_code: string | null;
|
|
261
|
-
retries_config: unknown | null;
|
|
262
|
-
timeout_ms: number | null;
|
|
263
|
-
api_key_id: string;
|
|
264
|
-
project_id: string | null;
|
|
265
|
-
created_at: Generated<Date>;
|
|
266
|
-
updated_at: Generated<Date>;
|
|
267
|
-
}
|
|
268
|
-
interface WorkflowRunsTable {
|
|
269
|
-
id: Generated<string>;
|
|
270
|
-
definition_id: string;
|
|
271
|
-
status: Generated<string>;
|
|
272
|
-
trigger_type: string;
|
|
273
|
-
trigger_data: unknown | null;
|
|
274
|
-
dedup_key: string | null;
|
|
275
|
-
error: string | null;
|
|
276
|
-
started_at: Date | null;
|
|
277
|
-
completed_at: Date | null;
|
|
278
|
-
duration_ms: number | null;
|
|
279
|
-
total_ai_tokens: Generated<number>;
|
|
280
|
-
created_at: Generated<Date>;
|
|
281
|
-
}
|
|
282
|
-
interface WorkflowStepsTable {
|
|
283
|
-
id: Generated<string>;
|
|
284
|
-
run_id: string;
|
|
285
|
-
step_index: number;
|
|
286
|
-
step_id: string;
|
|
287
|
-
step_type: string;
|
|
288
|
-
status: Generated<string>;
|
|
289
|
-
input: unknown | null;
|
|
290
|
-
output: unknown | null;
|
|
291
|
-
error: string | null;
|
|
292
|
-
retry_count: Generated<number>;
|
|
293
|
-
ai_tokens_used: Generated<number>;
|
|
294
|
-
started_at: Date | null;
|
|
295
|
-
completed_at: Date | null;
|
|
296
|
-
duration_ms: number | null;
|
|
297
|
-
memo_key: string | null;
|
|
298
|
-
parent_step_id: string | null;
|
|
299
|
-
created_at: Generated<Date>;
|
|
300
|
-
}
|
|
301
|
-
interface WorkflowQueueTable {
|
|
302
|
-
id: Generated<string>;
|
|
303
|
-
run_id: string;
|
|
304
|
-
status: Generated<string>;
|
|
305
|
-
attempts: Generated<number>;
|
|
306
|
-
max_attempts: Generated<number>;
|
|
307
|
-
scheduled_for: Generated<Date>;
|
|
308
|
-
locked_at: Date | null;
|
|
309
|
-
locked_by: string | null;
|
|
310
|
-
error: string | null;
|
|
311
|
-
created_at: Generated<Date>;
|
|
312
|
-
completed_at: Date | null;
|
|
313
|
-
}
|
|
314
|
-
interface WorkflowSchedulesTable {
|
|
315
|
-
id: Generated<string>;
|
|
316
|
-
definition_id: string;
|
|
317
|
-
cron_expr: string;
|
|
318
|
-
timezone: Generated<string>;
|
|
319
|
-
next_run_at: Date;
|
|
320
|
-
last_run_at: Date | null;
|
|
321
|
-
enabled: Generated<boolean>;
|
|
322
|
-
created_at: Generated<Date>;
|
|
323
|
-
}
|
|
324
|
-
interface WorkflowCursorsTable {
|
|
325
|
-
name: string;
|
|
326
|
-
block_height: Generated<number>;
|
|
327
|
-
updated_at: Generated<Date>;
|
|
328
|
-
}
|
|
329
250
|
interface Database {
|
|
330
251
|
blocks: BlocksTable;
|
|
331
252
|
transactions: TransactionsTable;
|
|
@@ -351,16 +272,10 @@ interface Database {
|
|
|
351
272
|
team_invitations: TeamInvitationsTable;
|
|
352
273
|
chat_sessions: ChatSessionsTable;
|
|
353
274
|
chat_messages: ChatMessagesTable;
|
|
354
|
-
workflow_definitions: WorkflowDefinitionsTable;
|
|
355
|
-
workflow_runs: WorkflowRunsTable;
|
|
356
|
-
workflow_steps: WorkflowStepsTable;
|
|
357
|
-
workflow_queue: WorkflowQueueTable;
|
|
358
|
-
workflow_schedules: WorkflowSchedulesTable;
|
|
359
|
-
workflow_cursors: WorkflowCursorsTable;
|
|
360
|
-
workflow_signer_secrets: WorkflowSignerSecretsTable;
|
|
361
|
-
workflow_budgets: WorkflowBudgetsTable;
|
|
362
275
|
tenants: TenantsTable;
|
|
363
276
|
tenant_usage_monthly: TenantUsageMonthlyTable;
|
|
277
|
+
tenant_compute_addons: TenantComputeAddonsTable;
|
|
278
|
+
account_spend_caps: AccountSpendCapsTable;
|
|
364
279
|
provisioning_audit_log: ProvisioningAuditLogTable;
|
|
365
280
|
}
|
|
366
281
|
type TenantStatus = "provisioning" | "active" | "suspended" | "error" | "deleted";
|
|
@@ -383,9 +298,9 @@ interface TenantsTable {
|
|
|
383
298
|
service_key_enc: Buffer;
|
|
384
299
|
api_url_internal: string;
|
|
385
300
|
api_url_public: string;
|
|
386
|
-
trial_ends_at: Date;
|
|
387
301
|
suspended_at: Date | null;
|
|
388
302
|
last_health_check_at: Date | null;
|
|
303
|
+
last_active_at: Generated<Date>;
|
|
389
304
|
service_gen: Generated<number>;
|
|
390
305
|
anon_gen: Generated<number>;
|
|
391
306
|
project_id: string | null;
|
|
@@ -404,6 +319,28 @@ interface TenantUsageMonthlyTable {
|
|
|
404
319
|
first_at: Generated<Date>;
|
|
405
320
|
last_at: Generated<Date>;
|
|
406
321
|
}
|
|
322
|
+
interface TenantComputeAddonsTable {
|
|
323
|
+
id: Generated<string>;
|
|
324
|
+
tenant_id: string;
|
|
325
|
+
memory_mb_delta: Generated<number>;
|
|
326
|
+
cpu_delta: Generated<number | string>;
|
|
327
|
+
storage_mb_delta: Generated<number>;
|
|
328
|
+
effective_from: Generated<Date>;
|
|
329
|
+
effective_until: Date | null;
|
|
330
|
+
stripe_subscription_item_id: string | null;
|
|
331
|
+
created_at: Generated<Date>;
|
|
332
|
+
}
|
|
333
|
+
interface AccountSpendCapsTable {
|
|
334
|
+
account_id: string;
|
|
335
|
+
monthly_cap_cents: number | null;
|
|
336
|
+
compute_cap_cents: number | null;
|
|
337
|
+
storage_cap_cents: number | null;
|
|
338
|
+
ai_cap_cents: number | null;
|
|
339
|
+
alert_threshold_pct: Generated<number>;
|
|
340
|
+
alert_sent_at: Date | null;
|
|
341
|
+
frozen_at: Date | null;
|
|
342
|
+
updated_at: Generated<Date>;
|
|
343
|
+
}
|
|
407
344
|
type ProvisioningAuditEvent = "provision.start" | "provision.success" | "provision.failure" | "suspend" | "resume" | "resize" | "keys.rotate" | "bastion.key.upload" | "bastion.key.revoke" | "teardown";
|
|
408
345
|
type ProvisioningAuditStatus = "ok" | "error";
|
|
409
346
|
interface ProvisioningAuditLogTable {
|
|
@@ -418,30 +355,6 @@ interface ProvisioningAuditLogTable {
|
|
|
418
355
|
error: string | null;
|
|
419
356
|
created_at: Generated<Date>;
|
|
420
357
|
}
|
|
421
|
-
interface WorkflowBudgetsTable {
|
|
422
|
-
id: Generated<string>;
|
|
423
|
-
workflow_definition_id: string;
|
|
424
|
-
/** Period key: "daily:YYYY-MM-DD" | "weekly:YYYY-Www" | "per-run:<uuid>". */
|
|
425
|
-
period: string;
|
|
426
|
-
ai_usd_used: Generated<string>;
|
|
427
|
-
ai_tokens_used: Generated<string>;
|
|
428
|
-
chain_microstx_used: Generated<string>;
|
|
429
|
-
chain_tx_count: Generated<number>;
|
|
430
|
-
run_count: Generated<number>;
|
|
431
|
-
step_count: Generated<number>;
|
|
432
|
-
reset_at: Date;
|
|
433
|
-
created_at: Generated<Date>;
|
|
434
|
-
updated_at: Generated<Date>;
|
|
435
|
-
}
|
|
436
|
-
interface WorkflowSignerSecretsTable {
|
|
437
|
-
id: Generated<string>;
|
|
438
|
-
account_id: string;
|
|
439
|
-
name: string;
|
|
440
|
-
/** AES-GCM ciphertext bytes produced by the runner's KMS on write. */
|
|
441
|
-
encrypted_value: Buffer;
|
|
442
|
-
created_at: Generated<Date>;
|
|
443
|
-
updated_at: Generated<Date>;
|
|
444
|
-
}
|
|
445
358
|
/**
|
|
446
359
|
* Tenant registry queries. Encrypted columns are stored as `bytea` and
|
|
447
360
|
* transparently encrypted/decrypted via `encryptSecret`/`decryptSecret`.
|
|
@@ -466,14 +379,24 @@ interface NewTenantInput {
|
|
|
466
379
|
serviceKey: string;
|
|
467
380
|
apiUrlInternal: string;
|
|
468
381
|
apiUrlPublic: string;
|
|
469
|
-
trialEndsAt: Date;
|
|
470
382
|
projectId?: string;
|
|
471
383
|
}
|
|
472
384
|
declare function insertTenant(db: Kysely<Database>, input: NewTenantInput): Promise<Tenant>;
|
|
473
385
|
declare function getTenantByAccount(db: Kysely<Database>, accountId: string): Promise<Tenant | null>;
|
|
474
386
|
declare function getTenantBySlug(db: Kysely<Database>, slug: string): Promise<Tenant | null>;
|
|
475
387
|
declare function listTenantsByStatus(db: Kysely<Database>, status: TenantStatus): Promise<Tenant[]>;
|
|
476
|
-
|
|
388
|
+
/**
|
|
389
|
+
* Tenants considered "idle" for auto-pause on the Hobby tier. Active = any
|
|
390
|
+
* successful tenant-API query OR workflow run wrote `last_active_at` within
|
|
391
|
+
* the threshold.
|
|
392
|
+
*/
|
|
393
|
+
declare function listIdleHobbyTenants(db: Kysely<Database>, idleSince: Date): Promise<Tenant[]>;
|
|
394
|
+
/**
|
|
395
|
+
* Bump `last_active_at` for a tenant. Callers are expected to throttle
|
|
396
|
+
* (don't hammer on every request) — the activity middleware + workflow-
|
|
397
|
+
* runner enforce a 60s per-tenant min between writes.
|
|
398
|
+
*/
|
|
399
|
+
declare function bumpTenantActivity(db: Kysely<Database>, slug: string): Promise<void>;
|
|
477
400
|
declare function listSuspendedOlderThan(db: Kysely<Database>, olderThan: Date): Promise<Tenant[]>;
|
|
478
401
|
declare function setTenantStatus(db: Kysely<Database>, slug: string, status: TenantStatus): Promise<void>;
|
|
479
402
|
declare function recordHealthCheck(db: Kysely<Database>, slug: string, storageUsedMb: number | null): Promise<void>;
|
|
@@ -524,4 +447,4 @@ interface TenantCredentials {
|
|
|
524
447
|
* CLI). Never log the returned object.
|
|
525
448
|
*/
|
|
526
449
|
declare function getTenantCredentials(db: Kysely<Database>, slug: string): Promise<TenantCredentials | null>;
|
|
527
|
-
export { updateTenantPlan, updateTenantKeys, setTenantStatus, recordMonthlyUsage, recordHealthCheck, listTenantsByStatus, listSuspendedOlderThan,
|
|
450
|
+
export { updateTenantPlan, updateTenantKeys, setTenantStatus, recordMonthlyUsage, recordHealthCheck, listTenantsByStatus, listSuspendedOlderThan, listIdleHobbyTenants, insertTenant, getTenantCredentials, getTenantBySlug, getTenantByAccount, deleteTenant, bumpTenantKeyGen, bumpTenantActivity, TenantCredentials, RotateType, NewTenantInput };
|
|
@@ -80,7 +80,6 @@ async function insertTenant(db, input) {
|
|
|
80
80
|
service_key_enc: encryptSecret(input.serviceKey),
|
|
81
81
|
api_url_internal: input.apiUrlInternal,
|
|
82
82
|
api_url_public: input.apiUrlPublic,
|
|
83
|
-
trial_ends_at: input.trialEndsAt,
|
|
84
83
|
project_id: input.projectId ?? null
|
|
85
84
|
};
|
|
86
85
|
return db.insertInto("tenants").values(row).returningAll().executeTakeFirstOrThrow();
|
|
@@ -96,8 +95,11 @@ async function getTenantBySlug(db, slug) {
|
|
|
96
95
|
async function listTenantsByStatus(db, status) {
|
|
97
96
|
return db.selectFrom("tenants").selectAll().where("status", "=", status).execute();
|
|
98
97
|
}
|
|
99
|
-
async function
|
|
100
|
-
return db.selectFrom("tenants").selectAll().where("status", "
|
|
98
|
+
async function listIdleHobbyTenants(db, idleSince) {
|
|
99
|
+
return db.selectFrom("tenants").selectAll().where("status", "=", "active").where("plan", "=", "hobby").where("last_active_at", "<", idleSince).execute();
|
|
100
|
+
}
|
|
101
|
+
async function bumpTenantActivity(db, slug) {
|
|
102
|
+
await db.updateTable("tenants").set({ last_active_at: new Date }).where("slug", "=", slug).execute();
|
|
101
103
|
}
|
|
102
104
|
async function listSuspendedOlderThan(db, olderThan) {
|
|
103
105
|
return db.selectFrom("tenants").selectAll().where("status", "=", "suspended").where("suspended_at", "<", olderThan).execute();
|
|
@@ -207,14 +209,15 @@ export {
|
|
|
207
209
|
recordHealthCheck,
|
|
208
210
|
listTenantsByStatus,
|
|
209
211
|
listSuspendedOlderThan,
|
|
210
|
-
|
|
212
|
+
listIdleHobbyTenants,
|
|
211
213
|
insertTenant,
|
|
212
214
|
getTenantCredentials,
|
|
213
215
|
getTenantBySlug,
|
|
214
216
|
getTenantByAccount,
|
|
215
217
|
deleteTenant,
|
|
216
|
-
bumpTenantKeyGen
|
|
218
|
+
bumpTenantKeyGen,
|
|
219
|
+
bumpTenantActivity
|
|
217
220
|
};
|
|
218
221
|
|
|
219
|
-
//# debugId=
|
|
222
|
+
//# debugId=D0CC1ED1445094FE64756E2164756E21
|
|
220
223
|
//# sourceMappingURL=tenants.js.map
|
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
"sources": ["../src/crypto/secrets.ts", "../src/db/queries/tenants.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
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"
|
|
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\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\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\n/**\n * Tenants considered \"idle\" for auto-pause on the Hobby tier. Active = any\n * successful tenant-API query OR workflow run wrote `last_active_at` within\n * the threshold.\n */\nexport async function listIdleHobbyTenants(\n\tdb: Kysely<Database>,\n\tidleSince: Date,\n): Promise<Tenant[]> {\n\treturn db\n\t\t.selectFrom(\"tenants\")\n\t\t.selectAll()\n\t\t.where(\"status\", \"=\", \"active\")\n\t\t.where(\"plan\", \"=\", \"hobby\")\n\t\t.where(\"last_active_at\", \"<\", idleSince)\n\t\t.execute();\n}\n\n/**\n * Bump `last_active_at` for a tenant. Callers are expected to throttle\n * (don't hammer on every request) — the activity middleware + workflow-\n * runner enforce a 60s per-tenant min between writes.\n */\nexport async function bumpTenantActivity(\n\tdb: Kysely<Database>,\n\tslug: string,\n): Promise<void> {\n\tawait db\n\t\t.updateTable(\"tenants\")\n\t\t.set({ last_active_at: new Date() })\n\t\t.where(\"slug\", \"=\", slug)\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
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;
|
|
9
|
-
"debugId": "
|
|
8
|
+
"mappings": "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",
|
|
9
|
+
"debugId": "D0CC1ED1445094FE64756E2164756E21",
|
|
10
10
|
"names": []
|
|
11
11
|
}
|
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
interface PlanLimits {
|
|
2
|
-
subgraphs: number;
|
|
3
|
-
apiRequestsPerDay: number;
|
|
4
|
-
deliveriesPerMonth: number;
|
|
5
|
-
storageBytes: number;
|
|
6
|
-
}
|
|
7
|
-
declare function getPlanLimits(plan: string): PlanLimits;
|
|
8
1
|
import { Kysely } from "kysely";
|
|
9
2
|
import { ColumnType, Generated } from "kysely";
|
|
10
3
|
interface BlocksTable {
|
|
@@ -67,10 +60,6 @@ interface SubgraphsTable {
|
|
|
67
60
|
handler_code: string | null;
|
|
68
61
|
source_code: string | null;
|
|
69
62
|
project_id: string | null;
|
|
70
|
-
is_public: Generated<boolean>;
|
|
71
|
-
tags: Generated<string[]>;
|
|
72
|
-
description: string | null;
|
|
73
|
-
forked_from_id: string | null;
|
|
74
63
|
created_at: Generated<Date>;
|
|
75
64
|
updated_at: Generated<Date>;
|
|
76
65
|
}
|
|
@@ -105,6 +94,7 @@ interface AccountsTable {
|
|
|
105
94
|
bio: string | null;
|
|
106
95
|
avatar_url: string | null;
|
|
107
96
|
slug: string | null;
|
|
97
|
+
stripe_customer_id: string | null;
|
|
108
98
|
created_at: Generated<Date>;
|
|
109
99
|
}
|
|
110
100
|
interface SessionsTable {
|
|
@@ -130,6 +120,7 @@ interface MagicLinksTable {
|
|
|
130
120
|
}
|
|
131
121
|
interface UsageDailyTable {
|
|
132
122
|
account_id: string;
|
|
123
|
+
tenant_id: string | null;
|
|
133
124
|
date: string;
|
|
134
125
|
api_requests: Generated<number>;
|
|
135
126
|
deliveries: Generated<number>;
|
|
@@ -256,83 +247,6 @@ interface ChatMessagesTable {
|
|
|
256
247
|
metadata: unknown | null;
|
|
257
248
|
created_at: Generated<Date>;
|
|
258
249
|
}
|
|
259
|
-
interface WorkflowDefinitionsTable {
|
|
260
|
-
id: Generated<string>;
|
|
261
|
-
name: string;
|
|
262
|
-
version: Generated<string>;
|
|
263
|
-
status: Generated<string>;
|
|
264
|
-
trigger_type: string;
|
|
265
|
-
trigger_config: unknown;
|
|
266
|
-
handler_path: string;
|
|
267
|
-
source_code: string | null;
|
|
268
|
-
retries_config: unknown | null;
|
|
269
|
-
timeout_ms: number | null;
|
|
270
|
-
api_key_id: string;
|
|
271
|
-
project_id: string | null;
|
|
272
|
-
created_at: Generated<Date>;
|
|
273
|
-
updated_at: Generated<Date>;
|
|
274
|
-
}
|
|
275
|
-
interface WorkflowRunsTable {
|
|
276
|
-
id: Generated<string>;
|
|
277
|
-
definition_id: string;
|
|
278
|
-
status: Generated<string>;
|
|
279
|
-
trigger_type: string;
|
|
280
|
-
trigger_data: unknown | null;
|
|
281
|
-
dedup_key: string | null;
|
|
282
|
-
error: string | null;
|
|
283
|
-
started_at: Date | null;
|
|
284
|
-
completed_at: Date | null;
|
|
285
|
-
duration_ms: number | null;
|
|
286
|
-
total_ai_tokens: Generated<number>;
|
|
287
|
-
created_at: Generated<Date>;
|
|
288
|
-
}
|
|
289
|
-
interface WorkflowStepsTable {
|
|
290
|
-
id: Generated<string>;
|
|
291
|
-
run_id: string;
|
|
292
|
-
step_index: number;
|
|
293
|
-
step_id: string;
|
|
294
|
-
step_type: string;
|
|
295
|
-
status: Generated<string>;
|
|
296
|
-
input: unknown | null;
|
|
297
|
-
output: unknown | null;
|
|
298
|
-
error: string | null;
|
|
299
|
-
retry_count: Generated<number>;
|
|
300
|
-
ai_tokens_used: Generated<number>;
|
|
301
|
-
started_at: Date | null;
|
|
302
|
-
completed_at: Date | null;
|
|
303
|
-
duration_ms: number | null;
|
|
304
|
-
memo_key: string | null;
|
|
305
|
-
parent_step_id: string | null;
|
|
306
|
-
created_at: Generated<Date>;
|
|
307
|
-
}
|
|
308
|
-
interface WorkflowQueueTable {
|
|
309
|
-
id: Generated<string>;
|
|
310
|
-
run_id: string;
|
|
311
|
-
status: Generated<string>;
|
|
312
|
-
attempts: Generated<number>;
|
|
313
|
-
max_attempts: Generated<number>;
|
|
314
|
-
scheduled_for: Generated<Date>;
|
|
315
|
-
locked_at: Date | null;
|
|
316
|
-
locked_by: string | null;
|
|
317
|
-
error: string | null;
|
|
318
|
-
created_at: Generated<Date>;
|
|
319
|
-
completed_at: Date | null;
|
|
320
|
-
}
|
|
321
|
-
interface WorkflowSchedulesTable {
|
|
322
|
-
id: Generated<string>;
|
|
323
|
-
definition_id: string;
|
|
324
|
-
cron_expr: string;
|
|
325
|
-
timezone: Generated<string>;
|
|
326
|
-
next_run_at: Date;
|
|
327
|
-
last_run_at: Date | null;
|
|
328
|
-
enabled: Generated<boolean>;
|
|
329
|
-
created_at: Generated<Date>;
|
|
330
|
-
}
|
|
331
|
-
interface WorkflowCursorsTable {
|
|
332
|
-
name: string;
|
|
333
|
-
block_height: Generated<number>;
|
|
334
|
-
updated_at: Generated<Date>;
|
|
335
|
-
}
|
|
336
250
|
interface Database {
|
|
337
251
|
blocks: BlocksTable;
|
|
338
252
|
transactions: TransactionsTable;
|
|
@@ -358,16 +272,10 @@ interface Database {
|
|
|
358
272
|
team_invitations: TeamInvitationsTable;
|
|
359
273
|
chat_sessions: ChatSessionsTable;
|
|
360
274
|
chat_messages: ChatMessagesTable;
|
|
361
|
-
workflow_definitions: WorkflowDefinitionsTable;
|
|
362
|
-
workflow_runs: WorkflowRunsTable;
|
|
363
|
-
workflow_steps: WorkflowStepsTable;
|
|
364
|
-
workflow_queue: WorkflowQueueTable;
|
|
365
|
-
workflow_schedules: WorkflowSchedulesTable;
|
|
366
|
-
workflow_cursors: WorkflowCursorsTable;
|
|
367
|
-
workflow_signer_secrets: WorkflowSignerSecretsTable;
|
|
368
|
-
workflow_budgets: WorkflowBudgetsTable;
|
|
369
275
|
tenants: TenantsTable;
|
|
370
276
|
tenant_usage_monthly: TenantUsageMonthlyTable;
|
|
277
|
+
tenant_compute_addons: TenantComputeAddonsTable;
|
|
278
|
+
account_spend_caps: AccountSpendCapsTable;
|
|
371
279
|
provisioning_audit_log: ProvisioningAuditLogTable;
|
|
372
280
|
}
|
|
373
281
|
type TenantStatus = "provisioning" | "active" | "suspended" | "error" | "deleted";
|
|
@@ -390,9 +298,9 @@ interface TenantsTable {
|
|
|
390
298
|
service_key_enc: Buffer;
|
|
391
299
|
api_url_internal: string;
|
|
392
300
|
api_url_public: string;
|
|
393
|
-
trial_ends_at: Date;
|
|
394
301
|
suspended_at: Date | null;
|
|
395
302
|
last_health_check_at: Date | null;
|
|
303
|
+
last_active_at: Generated<Date>;
|
|
396
304
|
service_gen: Generated<number>;
|
|
397
305
|
anon_gen: Generated<number>;
|
|
398
306
|
project_id: string | null;
|
|
@@ -410,6 +318,28 @@ interface TenantUsageMonthlyTable {
|
|
|
410
318
|
first_at: Generated<Date>;
|
|
411
319
|
last_at: Generated<Date>;
|
|
412
320
|
}
|
|
321
|
+
interface TenantComputeAddonsTable {
|
|
322
|
+
id: Generated<string>;
|
|
323
|
+
tenant_id: string;
|
|
324
|
+
memory_mb_delta: Generated<number>;
|
|
325
|
+
cpu_delta: Generated<number | string>;
|
|
326
|
+
storage_mb_delta: Generated<number>;
|
|
327
|
+
effective_from: Generated<Date>;
|
|
328
|
+
effective_until: Date | null;
|
|
329
|
+
stripe_subscription_item_id: string | null;
|
|
330
|
+
created_at: Generated<Date>;
|
|
331
|
+
}
|
|
332
|
+
interface AccountSpendCapsTable {
|
|
333
|
+
account_id: string;
|
|
334
|
+
monthly_cap_cents: number | null;
|
|
335
|
+
compute_cap_cents: number | null;
|
|
336
|
+
storage_cap_cents: number | null;
|
|
337
|
+
ai_cap_cents: number | null;
|
|
338
|
+
alert_threshold_pct: Generated<number>;
|
|
339
|
+
alert_sent_at: Date | null;
|
|
340
|
+
frozen_at: Date | null;
|
|
341
|
+
updated_at: Generated<Date>;
|
|
342
|
+
}
|
|
413
343
|
type ProvisioningAuditEvent = "provision.start" | "provision.success" | "provision.failure" | "suspend" | "resume" | "resize" | "keys.rotate" | "bastion.key.upload" | "bastion.key.revoke" | "teardown";
|
|
414
344
|
type ProvisioningAuditStatus = "ok" | "error";
|
|
415
345
|
interface ProvisioningAuditLogTable {
|
|
@@ -424,30 +354,6 @@ interface ProvisioningAuditLogTable {
|
|
|
424
354
|
error: string | null;
|
|
425
355
|
created_at: Generated<Date>;
|
|
426
356
|
}
|
|
427
|
-
interface WorkflowBudgetsTable {
|
|
428
|
-
id: Generated<string>;
|
|
429
|
-
workflow_definition_id: string;
|
|
430
|
-
/** Period key: "daily:YYYY-MM-DD" | "weekly:YYYY-Www" | "per-run:<uuid>". */
|
|
431
|
-
period: string;
|
|
432
|
-
ai_usd_used: Generated<string>;
|
|
433
|
-
ai_tokens_used: Generated<string>;
|
|
434
|
-
chain_microstx_used: Generated<string>;
|
|
435
|
-
chain_tx_count: Generated<number>;
|
|
436
|
-
run_count: Generated<number>;
|
|
437
|
-
step_count: Generated<number>;
|
|
438
|
-
reset_at: Date;
|
|
439
|
-
created_at: Generated<Date>;
|
|
440
|
-
updated_at: Generated<Date>;
|
|
441
|
-
}
|
|
442
|
-
interface WorkflowSignerSecretsTable {
|
|
443
|
-
id: Generated<string>;
|
|
444
|
-
account_id: string;
|
|
445
|
-
name: string;
|
|
446
|
-
/** AES-GCM ciphertext bytes produced by the runner's KMS on write. */
|
|
447
|
-
encrypted_value: Buffer;
|
|
448
|
-
created_at: Generated<Date>;
|
|
449
|
-
updated_at: Generated<Date>;
|
|
450
|
-
}
|
|
451
357
|
/** Increment API request counter for today. Fire-and-forget safe. */
|
|
452
358
|
declare function incrementApiRequests(db: Kysely<Database>, accountId: string): Promise<void>;
|
|
453
359
|
interface UsageSummary {
|
|
@@ -457,26 +363,9 @@ interface UsageSummary {
|
|
|
457
363
|
}
|
|
458
364
|
/** Get current usage for an account. */
|
|
459
365
|
declare function getUsage(db: Kysely<Database>, accountId: string): Promise<UsageSummary>;
|
|
460
|
-
interface DailyUsage {
|
|
461
|
-
date: string;
|
|
462
|
-
apiRequests: number;
|
|
463
|
-
deliveries: number;
|
|
464
|
-
}
|
|
465
|
-
/** Get last 7 days of daily usage, filling missing days with 0. */
|
|
466
|
-
declare function getDailyUsage(db: Kysely<Database>, accountId: string): Promise<DailyUsage[]>;
|
|
467
|
-
interface LimitCheck {
|
|
468
|
-
allowed: boolean;
|
|
469
|
-
limits: ReturnType<typeof getPlanLimits>;
|
|
470
|
-
current: UsageSummary & {
|
|
471
|
-
subgraphs: number
|
|
472
|
-
};
|
|
473
|
-
exceeded?: string;
|
|
474
|
-
}
|
|
475
|
-
/** Check if an account is within plan limits. */
|
|
476
|
-
declare function checkLimits(db: Kysely<Database>, accountId: string, plan: string): Promise<LimitCheck>;
|
|
477
366
|
/**
|
|
478
367
|
* Measure storage for all accounts by querying pg_total_relation_size
|
|
479
368
|
* for each tenant's subgraph schemas.
|
|
480
369
|
*/
|
|
481
370
|
declare function measureStorage(db: Kysely<Database>): Promise<void>;
|
|
482
|
-
export { measureStorage, incrementApiRequests, getUsage,
|
|
371
|
+
export { measureStorage, incrementApiRequests, getUsage, UsageSummary };
|