@secondlayer/shared 2.0.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.
- package/dist/src/db/index.d.ts +33 -1
- package/dist/src/db/queries/accounts.d.ts +27 -0
- package/dist/src/db/queries/integrity.d.ts +27 -0
- package/dist/src/db/queries/projects.d.ts +27 -0
- package/dist/src/db/queries/{marketplace.d.ts → provisioning-audit.d.ts} +48 -54
- package/dist/src/db/queries/provisioning-audit.js +40 -0
- package/dist/src/db/queries/provisioning-audit.js.map +10 -0
- package/dist/src/db/queries/subgraph-gaps.d.ts +27 -0
- package/dist/src/db/queries/subgraphs.d.ts +27 -0
- package/dist/src/db/queries/tenants.d.ts +35 -1
- package/dist/src/db/queries/tenants.js +27 -1
- package/dist/src/db/queries/tenants.js.map +3 -3
- package/dist/src/db/queries/usage.d.ts +27 -0
- package/dist/src/db/queries/workflows.d.ts +27 -0
- package/dist/src/db/schema.d.ts +33 -1
- package/dist/src/index.d.ts +60 -77
- package/dist/src/index.js +59 -69
- package/dist/src/index.js.map +4 -4
- package/dist/src/mode.d.ts +4 -5
- package/dist/src/mode.js.map +2 -2
- package/dist/src/node/local-client.d.ts +27 -0
- package/dist/src/schemas/accounts.d.ts +14 -0
- package/dist/src/schemas/{marketplace.js → accounts.js} +4 -14
- package/dist/src/schemas/accounts.js.map +10 -0
- package/dist/src/schemas/index.d.ts +28 -77
- package/dist/src/schemas/index.js +59 -69
- package/dist/src/schemas/index.js.map +4 -4
- package/migrations/0043_tenant_usage_monthly.ts +36 -0
- package/migrations/0044_provisioning_audit_log.ts +40 -0
- package/package.json +8 -8
- package/dist/src/db/queries/marketplace.js +0 -139
- package/dist/src/db/queries/marketplace.js.map +0 -10
- package/dist/src/schemas/marketplace.d.ts +0 -63
- package/dist/src/schemas/marketplace.js.map +0 -10
package/dist/src/db/index.d.ts
CHANGED
|
@@ -374,6 +374,8 @@ interface Database {
|
|
|
374
374
|
workflow_signer_secrets: WorkflowSignerSecretsTable;
|
|
375
375
|
workflow_budgets: WorkflowBudgetsTable;
|
|
376
376
|
tenants: TenantsTable;
|
|
377
|
+
tenant_usage_monthly: TenantUsageMonthlyTable;
|
|
378
|
+
provisioning_audit_log: ProvisioningAuditLogTable;
|
|
377
379
|
}
|
|
378
380
|
type TenantStatus = "provisioning" | "active" | "suspended" | "error" | "deleted";
|
|
379
381
|
interface TenantsTable {
|
|
@@ -407,6 +409,36 @@ interface TenantsTable {
|
|
|
407
409
|
type Tenant = Selectable<TenantsTable>;
|
|
408
410
|
type InsertTenant = Insertable<TenantsTable>;
|
|
409
411
|
type UpdateTenant = Updateable<TenantsTable>;
|
|
412
|
+
interface TenantUsageMonthlyTable {
|
|
413
|
+
id: Generated<string>;
|
|
414
|
+
tenant_id: string;
|
|
415
|
+
period_month: Date;
|
|
416
|
+
storage_peak_mb: Generated<number>;
|
|
417
|
+
storage_avg_mb: Generated<number>;
|
|
418
|
+
storage_last_mb: Generated<number>;
|
|
419
|
+
measurements: Generated<number>;
|
|
420
|
+
first_at: Generated<Date>;
|
|
421
|
+
last_at: Generated<Date>;
|
|
422
|
+
}
|
|
423
|
+
type TenantUsageMonthly = Selectable<TenantUsageMonthlyTable>;
|
|
424
|
+
type InsertTenantUsageMonthly = Insertable<TenantUsageMonthlyTable>;
|
|
425
|
+
type UpdateTenantUsageMonthly = Updateable<TenantUsageMonthlyTable>;
|
|
426
|
+
type ProvisioningAuditEvent = "provision.start" | "provision.success" | "provision.failure" | "suspend" | "resume" | "resize" | "keys.rotate" | "bastion.key.upload" | "bastion.key.revoke" | "teardown";
|
|
427
|
+
type ProvisioningAuditStatus = "ok" | "error";
|
|
428
|
+
interface ProvisioningAuditLogTable {
|
|
429
|
+
id: Generated<string>;
|
|
430
|
+
tenant_id: string | null;
|
|
431
|
+
tenant_slug: string | null;
|
|
432
|
+
account_id: string | null;
|
|
433
|
+
actor: string;
|
|
434
|
+
event: ProvisioningAuditEvent;
|
|
435
|
+
status: ProvisioningAuditStatus;
|
|
436
|
+
detail: unknown | null;
|
|
437
|
+
error: string | null;
|
|
438
|
+
created_at: Generated<Date>;
|
|
439
|
+
}
|
|
440
|
+
type ProvisioningAuditLog = Selectable<ProvisioningAuditLogTable>;
|
|
441
|
+
type InsertProvisioningAuditLog = Insertable<ProvisioningAuditLogTable>;
|
|
410
442
|
interface WorkflowBudgetsTable {
|
|
411
443
|
id: Generated<string>;
|
|
412
444
|
workflow_definition_id: string;
|
|
@@ -525,4 +557,4 @@ declare function getDb(connectionString?: string): Kysely<Database>;
|
|
|
525
557
|
declare function getRawClient(role?: "source" | "target"): ReturnType<typeof postgres>;
|
|
526
558
|
/** Close all DB connection pools. Call in CLI commands to allow process exit. */
|
|
527
559
|
declare function closeDb(): Promise<void>;
|
|
528
|
-
export { sql, parseJsonb, jsonb, getTargetDb, getSourceDb, getRawClient, getDb, closeDb, WorkflowStepsTable, WorkflowStep, WorkflowSignerSecretsTable, WorkflowSignerSecret, WorkflowSchedulesTable, WorkflowSchedule, WorkflowRunsTable, WorkflowRun, WorkflowQueueTable, WorkflowQueueItem, WorkflowDefinitionsTable, WorkflowDefinition, WorkflowCursorsTable, WorkflowCursor, WorkflowBudgetsTable, WorkflowBudget, WaitlistTable, UsageSnapshotsTable, UsageSnapshot, UsageDailyTable, UsageDaily, UpdateWorkflowStep, UpdateWorkflowSignerSecret, UpdateWorkflowSchedule, UpdateWorkflowRun, UpdateWorkflowDefinition, UpdateWorkflowBudget, UpdateTransaction, UpdateTenant, UpdateSubgraph, UpdateProject, UpdateIndexProgress, UpdateEvent, UpdateChatSession, UpdateBlock, UpdateApiKey, TransactionsTable, Transaction, TenantsTable, TenantStatus, Tenant, TeamMembersTable, TeamMember, TeamInvitationsTable, TeamInvitation, SubgraphsTable, SubgraphUsageDailyTable, SubgraphUsageDaily, SubgraphTableSnapshotsTable, SubgraphProcessingStatsTable, SubgraphHealthSnapshotsTable, SubgraphHealthSnapshot, SubgraphGapsTable, SubgraphGap, Subgraph, SessionsTable, Session, ProjectsTable, Project, MagicLinksTable, MagicLink, InsertWorkflowStep, InsertWorkflowSignerSecret, InsertWorkflowSchedule, InsertWorkflowRun, InsertWorkflowQueueItem, InsertWorkflowDefinition, InsertWorkflowBudget, InsertTransaction, InsertTenant, InsertTeamMember, InsertTeamInvitation, InsertSubgraphUsageDaily, InsertSubgraphHealthSnapshot, InsertSubgraphGap, InsertSubgraph, InsertSession, InsertProject, InsertMagicLink, InsertIndexProgress, InsertEvent, InsertChatSession, InsertChatMessage, InsertBlock, InsertApiKey, InsertAccountInsight, InsertAccountAgentRun, InsertAccount, IndexProgressTable, IndexProgress, EventsTable, Event, Database, ChatSessionsTable, ChatSession, ChatMessagesTable, ChatMessage, BlocksTable, Block, ApiKeysTable, ApiKey, AccountsTable, AccountInsightsTable, AccountInsight, AccountAgentRunsTable, AccountAgentRun, Account };
|
|
560
|
+
export { sql, parseJsonb, jsonb, getTargetDb, getSourceDb, getRawClient, getDb, closeDb, WorkflowStepsTable, WorkflowStep, WorkflowSignerSecretsTable, WorkflowSignerSecret, WorkflowSchedulesTable, WorkflowSchedule, WorkflowRunsTable, WorkflowRun, WorkflowQueueTable, WorkflowQueueItem, WorkflowDefinitionsTable, WorkflowDefinition, WorkflowCursorsTable, WorkflowCursor, WorkflowBudgetsTable, WorkflowBudget, WaitlistTable, UsageSnapshotsTable, UsageSnapshot, UsageDailyTable, UsageDaily, UpdateWorkflowStep, UpdateWorkflowSignerSecret, UpdateWorkflowSchedule, UpdateWorkflowRun, UpdateWorkflowDefinition, UpdateWorkflowBudget, UpdateTransaction, UpdateTenantUsageMonthly, UpdateTenant, UpdateSubgraph, UpdateProject, UpdateIndexProgress, UpdateEvent, UpdateChatSession, UpdateBlock, UpdateApiKey, TransactionsTable, Transaction, TenantsTable, TenantUsageMonthlyTable, TenantUsageMonthly, TenantStatus, Tenant, TeamMembersTable, TeamMember, TeamInvitationsTable, TeamInvitation, SubgraphsTable, SubgraphUsageDailyTable, SubgraphUsageDaily, SubgraphTableSnapshotsTable, SubgraphProcessingStatsTable, SubgraphHealthSnapshotsTable, SubgraphHealthSnapshot, SubgraphGapsTable, SubgraphGap, Subgraph, SessionsTable, Session, ProvisioningAuditStatus, ProvisioningAuditLogTable, ProvisioningAuditLog, ProvisioningAuditEvent, ProjectsTable, Project, MagicLinksTable, MagicLink, InsertWorkflowStep, InsertWorkflowSignerSecret, InsertWorkflowSchedule, InsertWorkflowRun, InsertWorkflowQueueItem, InsertWorkflowDefinition, InsertWorkflowBudget, InsertTransaction, InsertTenantUsageMonthly, InsertTenant, InsertTeamMember, InsertTeamInvitation, InsertSubgraphUsageDaily, InsertSubgraphHealthSnapshot, InsertSubgraphGap, InsertSubgraph, InsertSession, InsertProvisioningAuditLog, InsertProject, InsertMagicLink, InsertIndexProgress, InsertEvent, InsertChatSession, InsertChatMessage, InsertBlock, InsertApiKey, InsertAccountInsight, InsertAccountAgentRun, InsertAccount, IndexProgressTable, IndexProgress, EventsTable, Event, Database, ChatSessionsTable, ChatSession, ChatMessagesTable, ChatMessage, BlocksTable, Block, ApiKeysTable, ApiKey, AccountsTable, AccountInsightsTable, AccountInsight, AccountAgentRunsTable, AccountAgentRun, Account };
|
|
@@ -361,6 +361,8 @@ interface Database {
|
|
|
361
361
|
workflow_signer_secrets: WorkflowSignerSecretsTable;
|
|
362
362
|
workflow_budgets: WorkflowBudgetsTable;
|
|
363
363
|
tenants: TenantsTable;
|
|
364
|
+
tenant_usage_monthly: TenantUsageMonthlyTable;
|
|
365
|
+
provisioning_audit_log: ProvisioningAuditLogTable;
|
|
364
366
|
}
|
|
365
367
|
type TenantStatus = "provisioning" | "active" | "suspended" | "error" | "deleted";
|
|
366
368
|
interface TenantsTable {
|
|
@@ -391,6 +393,31 @@ interface TenantsTable {
|
|
|
391
393
|
created_at: Generated<Date>;
|
|
392
394
|
updated_at: Generated<Date>;
|
|
393
395
|
}
|
|
396
|
+
interface TenantUsageMonthlyTable {
|
|
397
|
+
id: Generated<string>;
|
|
398
|
+
tenant_id: string;
|
|
399
|
+
period_month: Date;
|
|
400
|
+
storage_peak_mb: Generated<number>;
|
|
401
|
+
storage_avg_mb: Generated<number>;
|
|
402
|
+
storage_last_mb: Generated<number>;
|
|
403
|
+
measurements: Generated<number>;
|
|
404
|
+
first_at: Generated<Date>;
|
|
405
|
+
last_at: Generated<Date>;
|
|
406
|
+
}
|
|
407
|
+
type ProvisioningAuditEvent = "provision.start" | "provision.success" | "provision.failure" | "suspend" | "resume" | "resize" | "keys.rotate" | "bastion.key.upload" | "bastion.key.revoke" | "teardown";
|
|
408
|
+
type ProvisioningAuditStatus = "ok" | "error";
|
|
409
|
+
interface ProvisioningAuditLogTable {
|
|
410
|
+
id: Generated<string>;
|
|
411
|
+
tenant_id: string | null;
|
|
412
|
+
tenant_slug: string | null;
|
|
413
|
+
account_id: string | null;
|
|
414
|
+
actor: string;
|
|
415
|
+
event: ProvisioningAuditEvent;
|
|
416
|
+
status: ProvisioningAuditStatus;
|
|
417
|
+
detail: unknown | null;
|
|
418
|
+
error: string | null;
|
|
419
|
+
created_at: Generated<Date>;
|
|
420
|
+
}
|
|
394
421
|
interface WorkflowBudgetsTable {
|
|
395
422
|
id: Generated<string>;
|
|
396
423
|
workflow_definition_id: string;
|
|
@@ -360,6 +360,8 @@ interface Database {
|
|
|
360
360
|
workflow_signer_secrets: WorkflowSignerSecretsTable;
|
|
361
361
|
workflow_budgets: WorkflowBudgetsTable;
|
|
362
362
|
tenants: TenantsTable;
|
|
363
|
+
tenant_usage_monthly: TenantUsageMonthlyTable;
|
|
364
|
+
provisioning_audit_log: ProvisioningAuditLogTable;
|
|
363
365
|
}
|
|
364
366
|
type TenantStatus = "provisioning" | "active" | "suspended" | "error" | "deleted";
|
|
365
367
|
interface TenantsTable {
|
|
@@ -390,6 +392,31 @@ interface TenantsTable {
|
|
|
390
392
|
created_at: Generated<Date>;
|
|
391
393
|
updated_at: Generated<Date>;
|
|
392
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>;
|
|
419
|
+
}
|
|
393
420
|
interface WorkflowBudgetsTable {
|
|
394
421
|
id: Generated<string>;
|
|
395
422
|
workflow_definition_id: string;
|
|
@@ -360,6 +360,8 @@ interface Database {
|
|
|
360
360
|
workflow_signer_secrets: WorkflowSignerSecretsTable;
|
|
361
361
|
workflow_budgets: WorkflowBudgetsTable;
|
|
362
362
|
tenants: TenantsTable;
|
|
363
|
+
tenant_usage_monthly: TenantUsageMonthlyTable;
|
|
364
|
+
provisioning_audit_log: ProvisioningAuditLogTable;
|
|
363
365
|
}
|
|
364
366
|
type TenantStatus = "provisioning" | "active" | "suspended" | "error" | "deleted";
|
|
365
367
|
interface TenantsTable {
|
|
@@ -390,6 +392,31 @@ interface TenantsTable {
|
|
|
390
392
|
created_at: Generated<Date>;
|
|
391
393
|
updated_at: Generated<Date>;
|
|
392
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>;
|
|
419
|
+
}
|
|
393
420
|
interface WorkflowBudgetsTable {
|
|
394
421
|
id: Generated<string>;
|
|
395
422
|
workflow_definition_id: string;
|
|
@@ -360,6 +360,8 @@ interface Database {
|
|
|
360
360
|
workflow_signer_secrets: WorkflowSignerSecretsTable;
|
|
361
361
|
workflow_budgets: WorkflowBudgetsTable;
|
|
362
362
|
tenants: TenantsTable;
|
|
363
|
+
tenant_usage_monthly: TenantUsageMonthlyTable;
|
|
364
|
+
provisioning_audit_log: ProvisioningAuditLogTable;
|
|
363
365
|
}
|
|
364
366
|
type TenantStatus = "provisioning" | "active" | "suspended" | "error" | "deleted";
|
|
365
367
|
interface TenantsTable {
|
|
@@ -390,6 +392,32 @@ interface TenantsTable {
|
|
|
390
392
|
created_at: Generated<Date>;
|
|
391
393
|
updated_at: Generated<Date>;
|
|
392
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>;
|
|
419
|
+
}
|
|
420
|
+
type ProvisioningAuditLog = Selectable<ProvisioningAuditLogTable>;
|
|
393
421
|
interface WorkflowBudgetsTable {
|
|
394
422
|
id: Generated<string>;
|
|
395
423
|
workflow_definition_id: string;
|
|
@@ -414,59 +442,25 @@ interface WorkflowSignerSecretsTable {
|
|
|
414
442
|
created_at: Generated<Date>;
|
|
415
443
|
updated_at: Generated<Date>;
|
|
416
444
|
}
|
|
417
|
-
type Subgraph = Selectable<SubgraphsTable>;
|
|
418
|
-
/**
|
|
419
|
-
* List public subgraphs with creator info and usage stats.
|
|
420
|
-
*/
|
|
421
|
-
declare function listPublicSubgraphs(db: Kysely<Database>, opts?: {
|
|
422
|
-
limit?: number
|
|
423
|
-
offset?: number
|
|
424
|
-
tags?: string[]
|
|
425
|
-
search?: string
|
|
426
|
-
sort?: "recent" | "popular" | "name"
|
|
427
|
-
}): Promise<{
|
|
428
|
-
data: any[]
|
|
429
|
-
meta: {
|
|
430
|
-
total: number
|
|
431
|
-
limit: number
|
|
432
|
-
offset: number
|
|
433
|
-
}
|
|
434
|
-
}>;
|
|
435
|
-
/**
|
|
436
|
-
* Get a single public subgraph by name with creator info.
|
|
437
|
-
*/
|
|
438
|
-
declare function getPublicSubgraph(db: Kysely<Database>, name: string): Promise<any | undefined>;
|
|
439
|
-
/**
|
|
440
|
-
* Get a creator profile by slug with their public subgraphs.
|
|
441
|
-
*/
|
|
442
|
-
declare function getCreatorProfile(db: Kysely<Database>, slug: string): Promise<{
|
|
443
|
-
account: any
|
|
444
|
-
subgraphs: any[]
|
|
445
|
-
} | null>;
|
|
446
|
-
/**
|
|
447
|
-
* Publish a subgraph (set is_public = true).
|
|
448
|
-
*/
|
|
449
|
-
declare function publishSubgraph(db: Kysely<Database>, subgraphId: string, opts?: {
|
|
450
|
-
tags?: string[]
|
|
451
|
-
description?: string
|
|
452
|
-
}): Promise<Subgraph>;
|
|
453
|
-
/**
|
|
454
|
-
* Unpublish a subgraph (set is_public = false).
|
|
455
|
-
*/
|
|
456
|
-
declare function unpublishSubgraph(db: Kysely<Database>, subgraphId: string): Promise<Subgraph>;
|
|
457
|
-
/**
|
|
458
|
-
* Increment per-subgraph query count for today. Fire-and-forget safe.
|
|
459
|
-
*/
|
|
460
|
-
declare function incrementSubgraphQueryCount(db: Kysely<Database>, subgraphId: string): Promise<void>;
|
|
461
|
-
/**
|
|
462
|
-
* Get daily usage history for a subgraph.
|
|
463
|
-
*/
|
|
464
|
-
declare function getSubgraphUsageHistory(db: Kysely<Database>, subgraphId: string, days: number): Promise<Array<{
|
|
465
|
-
date: string
|
|
466
|
-
query_count: number
|
|
467
|
-
}>>;
|
|
468
445
|
/**
|
|
469
|
-
*
|
|
446
|
+
* Provisioning audit trail — every lifecycle event that mutates a tenant
|
|
447
|
+
* lands here. Write on both happy and sad paths so we can reconstruct
|
|
448
|
+
* what was attempted and what failed.
|
|
449
|
+
*
|
|
450
|
+
* `actor` is the logical source (e.g. `account:<uuid>`, `worker:tenant-health`,
|
|
451
|
+
* `admin:<uuid>`). Keep it grep-able — this is the only breadcrumb when
|
|
452
|
+
* something goes sideways.
|
|
470
453
|
*/
|
|
471
|
-
|
|
472
|
-
|
|
454
|
+
interface AuditInput {
|
|
455
|
+
tenantId?: string | null;
|
|
456
|
+
tenantSlug?: string | null;
|
|
457
|
+
accountId?: string | null;
|
|
458
|
+
actor: string;
|
|
459
|
+
event: ProvisioningAuditEvent;
|
|
460
|
+
status: ProvisioningAuditStatus;
|
|
461
|
+
detail?: unknown;
|
|
462
|
+
error?: string;
|
|
463
|
+
}
|
|
464
|
+
declare function recordProvisioningAudit(db: Kysely<Database>, input: AuditInput): Promise<void>;
|
|
465
|
+
declare function listAuditForTenant(db: Kysely<Database>, tenantId: string, limit?: number): Promise<ProvisioningAuditLog[]>;
|
|
466
|
+
export { recordProvisioningAudit, listAuditForTenant, AuditInput };
|
|
@@ -0,0 +1,40 @@
|
|
|
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/db/queries/provisioning-audit.ts
|
|
18
|
+
async function recordProvisioningAudit(db, input) {
|
|
19
|
+
const row = {
|
|
20
|
+
tenant_id: input.tenantId ?? null,
|
|
21
|
+
tenant_slug: input.tenantSlug ?? null,
|
|
22
|
+
account_id: input.accountId ?? null,
|
|
23
|
+
actor: input.actor,
|
|
24
|
+
event: input.event,
|
|
25
|
+
status: input.status,
|
|
26
|
+
detail: input.detail ?? null,
|
|
27
|
+
error: input.error ?? null
|
|
28
|
+
};
|
|
29
|
+
await db.insertInto("provisioning_audit_log").values(row).execute();
|
|
30
|
+
}
|
|
31
|
+
async function listAuditForTenant(db, tenantId, limit = 50) {
|
|
32
|
+
return db.selectFrom("provisioning_audit_log").selectAll().where("tenant_id", "=", tenantId).orderBy("created_at", "desc").limit(limit).execute();
|
|
33
|
+
}
|
|
34
|
+
export {
|
|
35
|
+
recordProvisioningAudit,
|
|
36
|
+
listAuditForTenant
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
//# debugId=DF2744A82ED5118E64756E2164756E21
|
|
40
|
+
//# sourceMappingURL=provisioning-audit.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/db/queries/provisioning-audit.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"import type { Kysely } from \"kysely\";\nimport type {\n\tDatabase,\n\tInsertProvisioningAuditLog,\n\tProvisioningAuditEvent,\n\tProvisioningAuditLog,\n\tProvisioningAuditStatus,\n} from \"../types.ts\";\n\n/**\n * Provisioning audit trail — every lifecycle event that mutates a tenant\n * lands here. Write on both happy and sad paths so we can reconstruct\n * what was attempted and what failed.\n *\n * `actor` is the logical source (e.g. `account:<uuid>`, `worker:tenant-health`,\n * `admin:<uuid>`). Keep it grep-able — this is the only breadcrumb when\n * something goes sideways.\n */\n\nexport interface AuditInput {\n\ttenantId?: string | null;\n\ttenantSlug?: string | null;\n\taccountId?: string | null;\n\tactor: string;\n\tevent: ProvisioningAuditEvent;\n\tstatus: ProvisioningAuditStatus;\n\tdetail?: unknown;\n\terror?: string;\n}\n\nexport async function recordProvisioningAudit(\n\tdb: Kysely<Database>,\n\tinput: AuditInput,\n): Promise<void> {\n\tconst row: InsertProvisioningAuditLog = {\n\t\ttenant_id: input.tenantId ?? null,\n\t\ttenant_slug: input.tenantSlug ?? null,\n\t\taccount_id: input.accountId ?? null,\n\t\tactor: input.actor,\n\t\tevent: input.event,\n\t\tstatus: input.status,\n\t\tdetail: input.detail ?? null,\n\t\terror: input.error ?? null,\n\t};\n\tawait db.insertInto(\"provisioning_audit_log\").values(row).execute();\n}\n\nexport async function listAuditForTenant(\n\tdb: Kysely<Database>,\n\ttenantId: string,\n\tlimit = 50,\n): Promise<ProvisioningAuditLog[]> {\n\treturn db\n\t\t.selectFrom(\"provisioning_audit_log\")\n\t\t.selectAll()\n\t\t.where(\"tenant_id\", \"=\", tenantId)\n\t\t.orderBy(\"created_at\", \"desc\")\n\t\t.limit(limit)\n\t\t.execute();\n}\n"
|
|
6
|
+
],
|
|
7
|
+
"mappings": ";;;;;;;;;;;;;;;;;AA8BA,eAAsB,uBAAuB,CAC5C,IACA,OACgB;AAAA,EAChB,MAAM,MAAkC;AAAA,IACvC,WAAW,MAAM,YAAY;AAAA,IAC7B,aAAa,MAAM,cAAc;AAAA,IACjC,YAAY,MAAM,aAAa;AAAA,IAC/B,OAAO,MAAM;AAAA,IACb,OAAO,MAAM;AAAA,IACb,QAAQ,MAAM;AAAA,IACd,QAAQ,MAAM,UAAU;AAAA,IACxB,OAAO,MAAM,SAAS;AAAA,EACvB;AAAA,EACA,MAAM,GAAG,WAAW,wBAAwB,EAAE,OAAO,GAAG,EAAE,QAAQ;AAAA;AAGnE,eAAsB,kBAAkB,CACvC,IACA,UACA,QAAQ,IAC0B;AAAA,EAClC,OAAO,GACL,WAAW,wBAAwB,EACnC,UAAU,EACV,MAAM,aAAa,KAAK,QAAQ,EAChC,QAAQ,cAAc,MAAM,EAC5B,MAAM,KAAK,EACX,QAAQ;AAAA;",
|
|
8
|
+
"debugId": "DF2744A82ED5118E64756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|
|
@@ -360,6 +360,8 @@ interface Database {
|
|
|
360
360
|
workflow_signer_secrets: WorkflowSignerSecretsTable;
|
|
361
361
|
workflow_budgets: WorkflowBudgetsTable;
|
|
362
362
|
tenants: TenantsTable;
|
|
363
|
+
tenant_usage_monthly: TenantUsageMonthlyTable;
|
|
364
|
+
provisioning_audit_log: ProvisioningAuditLogTable;
|
|
363
365
|
}
|
|
364
366
|
type TenantStatus = "provisioning" | "active" | "suspended" | "error" | "deleted";
|
|
365
367
|
interface TenantsTable {
|
|
@@ -390,6 +392,31 @@ interface TenantsTable {
|
|
|
390
392
|
created_at: Generated<Date>;
|
|
391
393
|
updated_at: Generated<Date>;
|
|
392
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>;
|
|
419
|
+
}
|
|
393
420
|
interface WorkflowBudgetsTable {
|
|
394
421
|
id: Generated<string>;
|
|
395
422
|
workflow_definition_id: string;
|
|
@@ -360,6 +360,8 @@ interface Database {
|
|
|
360
360
|
workflow_signer_secrets: WorkflowSignerSecretsTable;
|
|
361
361
|
workflow_budgets: WorkflowBudgetsTable;
|
|
362
362
|
tenants: TenantsTable;
|
|
363
|
+
tenant_usage_monthly: TenantUsageMonthlyTable;
|
|
364
|
+
provisioning_audit_log: ProvisioningAuditLogTable;
|
|
363
365
|
}
|
|
364
366
|
type TenantStatus = "provisioning" | "active" | "suspended" | "error" | "deleted";
|
|
365
367
|
interface TenantsTable {
|
|
@@ -390,6 +392,31 @@ interface TenantsTable {
|
|
|
390
392
|
created_at: Generated<Date>;
|
|
391
393
|
updated_at: Generated<Date>;
|
|
392
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>;
|
|
419
|
+
}
|
|
393
420
|
interface WorkflowBudgetsTable {
|
|
394
421
|
id: Generated<string>;
|
|
395
422
|
workflow_definition_id: string;
|
|
@@ -360,6 +360,8 @@ interface Database {
|
|
|
360
360
|
workflow_signer_secrets: WorkflowSignerSecretsTable;
|
|
361
361
|
workflow_budgets: WorkflowBudgetsTable;
|
|
362
362
|
tenants: TenantsTable;
|
|
363
|
+
tenant_usage_monthly: TenantUsageMonthlyTable;
|
|
364
|
+
provisioning_audit_log: ProvisioningAuditLogTable;
|
|
363
365
|
}
|
|
364
366
|
type TenantStatus = "provisioning" | "active" | "suspended" | "error" | "deleted";
|
|
365
367
|
interface TenantsTable {
|
|
@@ -391,6 +393,31 @@ interface TenantsTable {
|
|
|
391
393
|
updated_at: Generated<Date>;
|
|
392
394
|
}
|
|
393
395
|
type Tenant = Selectable<TenantsTable>;
|
|
396
|
+
interface TenantUsageMonthlyTable {
|
|
397
|
+
id: Generated<string>;
|
|
398
|
+
tenant_id: string;
|
|
399
|
+
period_month: Date;
|
|
400
|
+
storage_peak_mb: Generated<number>;
|
|
401
|
+
storage_avg_mb: Generated<number>;
|
|
402
|
+
storage_last_mb: Generated<number>;
|
|
403
|
+
measurements: Generated<number>;
|
|
404
|
+
first_at: Generated<Date>;
|
|
405
|
+
last_at: Generated<Date>;
|
|
406
|
+
}
|
|
407
|
+
type ProvisioningAuditEvent = "provision.start" | "provision.success" | "provision.failure" | "suspend" | "resume" | "resize" | "keys.rotate" | "bastion.key.upload" | "bastion.key.revoke" | "teardown";
|
|
408
|
+
type ProvisioningAuditStatus = "ok" | "error";
|
|
409
|
+
interface ProvisioningAuditLogTable {
|
|
410
|
+
id: Generated<string>;
|
|
411
|
+
tenant_id: string | null;
|
|
412
|
+
tenant_slug: string | null;
|
|
413
|
+
account_id: string | null;
|
|
414
|
+
actor: string;
|
|
415
|
+
event: ProvisioningAuditEvent;
|
|
416
|
+
status: ProvisioningAuditStatus;
|
|
417
|
+
detail: unknown | null;
|
|
418
|
+
error: string | null;
|
|
419
|
+
created_at: Generated<Date>;
|
|
420
|
+
}
|
|
394
421
|
interface WorkflowBudgetsTable {
|
|
395
422
|
id: Generated<string>;
|
|
396
423
|
workflow_definition_id: string;
|
|
@@ -450,6 +477,13 @@ declare function listExpiredTrials(db: Kysely<Database>, now?: Date): Promise<Te
|
|
|
450
477
|
declare function listSuspendedOlderThan(db: Kysely<Database>, olderThan: Date): Promise<Tenant[]>;
|
|
451
478
|
declare function setTenantStatus(db: Kysely<Database>, slug: string, status: TenantStatus): Promise<void>;
|
|
452
479
|
declare function recordHealthCheck(db: Kysely<Database>, slug: string, storageUsedMb: number | null): Promise<void>;
|
|
480
|
+
/**
|
|
481
|
+
* Record a storage measurement into the current calendar month's bucket.
|
|
482
|
+
* Maintains peak, running average, and the most recent value in a single
|
|
483
|
+
* upsert. Billing will consume this later; for now the table just gives
|
|
484
|
+
* us evidence of usage over time.
|
|
485
|
+
*/
|
|
486
|
+
declare function recordMonthlyUsage(db: Kysely<Database>, tenantId: string, storageMb: number): Promise<void>;
|
|
453
487
|
declare function updateTenantPlan(db: Kysely<Database>, slug: string, plan: string, cpus: number, memoryMb: number, storageLimitMb: number): Promise<void>;
|
|
454
488
|
type RotateType = "service" | "anon" | "both";
|
|
455
489
|
/**
|
|
@@ -490,4 +524,4 @@ interface TenantCredentials {
|
|
|
490
524
|
* CLI). Never log the returned object.
|
|
491
525
|
*/
|
|
492
526
|
declare function getTenantCredentials(db: Kysely<Database>, slug: string): Promise<TenantCredentials | null>;
|
|
493
|
-
export { updateTenantPlan, updateTenantKeys, setTenantStatus, recordHealthCheck, listTenantsByStatus, listSuspendedOlderThan, listExpiredTrials, insertTenant, getTenantCredentials, getTenantBySlug, getTenantByAccount, deleteTenant, bumpTenantKeyGen, TenantCredentials, RotateType, NewTenantInput };
|
|
527
|
+
export { updateTenantPlan, updateTenantKeys, setTenantStatus, recordMonthlyUsage, recordHealthCheck, listTenantsByStatus, listSuspendedOlderThan, listExpiredTrials, insertTenant, getTenantCredentials, getTenantBySlug, getTenantByAccount, deleteTenant, bumpTenantKeyGen, TenantCredentials, RotateType, NewTenantInput };
|
|
@@ -61,6 +61,7 @@ function generateSecretsKey() {
|
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
// src/db/queries/tenants.ts
|
|
64
|
+
import { sql } from "kysely";
|
|
64
65
|
async function insertTenant(db, input) {
|
|
65
66
|
const row = {
|
|
66
67
|
account_id: input.accountId,
|
|
@@ -119,6 +120,30 @@ async function recordHealthCheck(db, slug, storageUsedMb) {
|
|
|
119
120
|
updated_at: new Date
|
|
120
121
|
}).where("slug", "=", slug).execute();
|
|
121
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
|
+
}
|
|
122
147
|
async function updateTenantPlan(db, slug, plan, cpus, memoryMb, storageLimitMb) {
|
|
123
148
|
await db.updateTable("tenants").set({
|
|
124
149
|
plan,
|
|
@@ -178,6 +203,7 @@ export {
|
|
|
178
203
|
updateTenantPlan,
|
|
179
204
|
updateTenantKeys,
|
|
180
205
|
setTenantStatus,
|
|
206
|
+
recordMonthlyUsage,
|
|
181
207
|
recordHealthCheck,
|
|
182
208
|
listTenantsByStatus,
|
|
183
209
|
listSuspendedOlderThan,
|
|
@@ -190,5 +216,5 @@ export {
|
|
|
190
216
|
bumpTenantKeyGen
|
|
191
217
|
};
|
|
192
218
|
|
|
193
|
-
//# debugId=
|
|
219
|
+
//# debugId=06506A808C9D324064756E2164756E21
|
|
194
220
|
//# 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
|
|
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
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;;;
|
|
9
|
-
"debugId": "
|
|
8
|
+
"mappings": "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",
|
|
9
|
+
"debugId": "06506A808C9D324064756E2164756E21",
|
|
10
10
|
"names": []
|
|
11
11
|
}
|