import { apiFetch, assertSameOriginRequestUrl, BASE } from "./client"; import { joinBackendPath } from "./runtime-config"; export type DatabaseTransport = "postgres_direct" | "rest_api" | "ssh_tunnel"; export type ConnectionStatus = "untested" | "reachable" | "failed"; export type WorkspaceEvidenceState = | "not_declared" | "materialized_current_revision" | "configuration_required" | "configured_unverified" | "workspace_unavailable"; export type CatalogSecretName = | "password" | "apiKey" | "sshPrivateKey" | "sshPrivateKeyPassphrase" | "sshKnownHosts" | "tlsCa"; export interface DatabaseBinding { transport: DatabaseTransport; host?: string; port?: number; username?: string; baseUrl?: string; restPath?: string; restAuth?: "none" | "bearer" | "x-api-key"; tlsServername?: string; sshHost?: string; sshPort?: number; sshUsername?: string; sshTargetHost?: string; sshTargetPort?: number; } export interface CatalogDatabase { id?: string; workspaceId: string; workspaceName: string; workspaceDescription?: string; workspaceAvailable: boolean; workspaceRevision: { commit: string; blob: string } | null; workspaceEvidence: { sourceType: "filesystem" | "http" | "s3" | null; state: WorkspaceEvidenceState; }; runtimeBinding: { transport: DatabaseTransport; configurationState: "ready" | "configuration_required"; sessionTransportSupported: boolean; } | null; configured: boolean; engine: "postgres"; databaseName: string; schema: string; version: number; createdAt: string; updatedAt: string; binding: DatabaseBinding; connectionStatus: ConnectionStatus; testedVersion?: number; lastTestedAt?: string; lastErrorCode?: string; lastErrorMessage?: string; schemaSyncedVersion?: number; schemaSyncedAt?: string; activeSyncRun?: CatalogSyncRun; secrets: Record; } export interface CatalogMetrics { scope: "global" | "database"; databaseId: string | null; tables: number; columns: number; sensitiveColumns: number; relationships: number; descriptionTargets: number; describedTargets: number; descriptionCoverage: number; updatedAt: string | null; } export interface DatabaseConfiguration { workspaceId: string; engine: "postgres"; databaseName: string; schema: string; binding: DatabaseBinding; } export interface CatalogTable { id: string; databaseId: string; name: string; sourceComment: string | null; description: string | null; generatedDescription: string | null; lastSyncedDatabaseVersion?: number | null; lastSyncedAt?: string | null; version: number; createdAt: string; updatedAt: string; } export interface CatalogColumn { id: string; tableId: string; name: string; ordinalPosition: number; dataType: string; isNullable: boolean; defaultExpression: string | null; primaryKeyPosition: number | null; isPrimaryKey: boolean; isForeignKey: boolean; foreignKeyCount: number; sourceComment: string | null; description: string | null; generatedDescription: string | null; sensitive: boolean; lastSyncedDatabaseVersion: number | null; lastSyncedAt: string | null; version: number; createdAt: string; updatedAt: string; } export interface SensitivityReviewItem { columnId: string; tableId: string; tableName: string; columnName: string; version: number; currentSensitive: boolean; sensitive: boolean; assessment: "sensitive" | "non_sensitive"; evidence: Array<{ kind: "metadata" | "content" | "length" | "ner" | "coverage" | "type"; ruleId: string; label?: string; confidence?: number; }>; observedValues: number; coverage: "metadata" | "complete" | "sampled" | "no_values"; } export interface SensitivityAnalysisResult { suggestions: SensitivityReviewItem[]; run: SensitivityAnalysisRun; } export type SensitivityAnalysisRequest = | { scope: "all" } | { scope: "selected_tables"; targetIds: string[] } | { scope: "selected_columns"; targetIds: string[] }; export type SensitivityAnalysisStatus = "running" | "completed" | "failed" | "interrupted"; export interface SensitivityAnalysisRun { id: string; databaseId: string; scope: SensitivityAnalysisRequest["scope"]; engine: "llm" | "local"; modelId: string | null; policyVersion: string | null; status: SensitivityAnalysisStatus; total: number; suggestedSensitive: number; suggestedNonSensitive: number; unknown: number; inputTokens?: number; cacheReadTokens?: number; outputTokens?: number; createdAt: string; startedAt: string | null; updatedAt: string; finishedAt: string | null; errorSummary: string | null; } export interface SensitivityAnalysisEvent { runId: string; sequence: number; level: "info" | "warning" | "error"; message: string; createdAt: string; } export interface CatalogRelationshipColumn { position: number; sourceColumnId: string; sourceColumnName: string; targetColumnId: string; targetColumnName: string; } export type CatalogRelationshipOrigin = "physical" | "generated" | "manual"; export type CatalogRelationshipStatus = "active" | "excluded"; export interface CatalogRelationship { id: string; databaseId: string; constraintName: string | null; sourceTableId: string; sourceTableName: string; targetTableId: string; targetTableName: string; updateRule: string | null; deleteRule: string | null; deferrable: boolean; initiallyDeferred: boolean; columns: CatalogRelationshipColumn[]; origin: CatalogRelationshipOrigin; status: CatalogRelationshipStatus; lastSyncedDatabaseVersion: number | null; lastSyncedAt: string | null; createdAt: string; updatedAt: string; } export interface CatalogRelationshipRebuildResult { added: number; alreadyPresent: number; excluded: number; ambiguous: number; } export type CatalogDatabaseMetadataDeleteTarget = "tables" | "relationships"; export type CatalogTableMetadataDeleteTarget = "columns" | "relationships"; export interface CatalogMetadataDeleteCounts { tables: number; columns: number; relationships: number; } export type CatalogDescriptionTarget = "tables" | "columns"; export interface CatalogDescriptionConsolidationCounts { copied: number; skipped: number; } export type CatalogSyncScope = "tables" | "columns" | "relationships" | "all"; export type CatalogSyncState = "queued" | "running" | "awaiting_confirmation" | "applying" | "succeeded" | "failed" | "cancelled" | "interrupted"; export type CatalogSyncPhase = "queued" | "connecting" | "scanning_tables" | "scanning_columns" | "scanning_relationships" | "planning" | "awaiting_confirmation" | "applying" | "completed"; export interface CatalogSchemaDiff { deletedTables: string[]; deletedColumns: Array<{ tableName: string; columnName: string }>; deletedRelationships: Array<{ sourceTableName: string; constraintName: string }>; } export interface CatalogSyncRun { id: string; databaseId: string; scope: CatalogSyncScope; tableIds: string[]; state: CatalogSyncState; phase: CatalogSyncPhase; requestedDatabaseVersion: number; plannedDiff: CatalogSchemaDiff | null; confirmationToken: string | null; counts: { tables?: number; columns?: number; relationships?: number; created?: number; updated?: number; deleted?: number }; errorCode: string | null; errorMessage: string | null; cancelRequested: boolean; createdAt: string; startedAt: string | null; updatedAt: string; finishedAt: string | null; heartbeatAt: string | null; } export interface CatalogSyncEvent { id: number; runId: string; sequence: number; level: "info" | "warning" | "error"; eventType: string; message: string; data: Record; createdAt: string; } export interface MetadataGenerationModel { id: string; label: string; } export interface MetadataGenerationModels { models: MetadataGenerationModel[]; default: string | null; } export type DescriptionGenerationScope = "selected_columns" | "selected_tables" | "all" | "missing"; export type DescriptionGenerationStatus = "queued" | "running" | "completed" | "completed_with_errors" | "cancelled" | "failed" | "interrupted"; export interface DescriptionGenerationRun { id: string; databaseId: string; scope: DescriptionGenerationScope; modelId: string; language: "en" | "it"; status: DescriptionGenerationStatus; total: number; processed: number; generated: number; nonGeneratable: number; failed: number; inputTokens?: number; cacheReadTokens?: number; outputTokens?: number; createdAt: string; startedAt: string | null; updatedAt: string; finishedAt: string | null; errorSummary: string | null; } export interface DescriptionGenerationEvent { sequence: number; level: "info" | "warning" | "error"; message: string; createdAt: string; } export const listCatalogDatabases = () => apiFetch("/catalog/databases"); export const getCatalogMetrics = (databaseId?: string) => apiFetch( databaseId ? `/catalog/metrics?databaseId=${encodeURIComponent(databaseId)}` : "/catalog/metrics", ); export const listMetadataGenerationModels = () => apiFetch("/catalog/metadata-generation/models"); export function startDescriptionGenerationRun( databaseId: string, modelId: string, scope: Extract, targetIds: string[], ): Promise; export function startDescriptionGenerationRun( databaseId: string, modelId: string, scope: Extract, ): Promise; export function startDescriptionGenerationRun( databaseId: string, modelId: string, scope: DescriptionGenerationScope, targetIds?: string[], ): Promise { const body = scope === "all" || scope === "missing" ? { modelId, scope } : { modelId, scope, targetIds: targetIds ?? [] }; return apiFetch( `/catalog/databases/${encodeURIComponent(databaseId)}/description-generation-runs`, { method: "POST", body: JSON.stringify(body) }, ); } export const getDescriptionGenerationRun = (runId: string) => apiFetch( `/catalog/description-generation-runs/${encodeURIComponent(runId)}`, ); export const listDescriptionGenerationRuns = (limit = 50) => apiFetch( `/catalog/description-generation-runs?limit=${encodeURIComponent(String(limit))}`, ); export const listDescriptionGenerationEvents = (runId: string, after = 0) => apiFetch( `/catalog/description-generation-runs/${encodeURIComponent(runId)}/events-list?after=${after}`, ); export const cancelDescriptionGenerationRun = (runId: string) => apiFetch( `/catalog/description-generation-runs/${encodeURIComponent(runId)}/cancel`, { method: "POST" }, ); export const unlockDescriptionGenerationRun = () => apiFetch( "/catalog/description-generation-runs/unlock", { method: "POST" }, ); export function descriptionGenerationEventsUrl(runId: string, after = 0): string { const url = joinBackendPath( BASE, `/catalog/description-generation-runs/${encodeURIComponent(runId)}/events?after=${after}`, ); assertSameOriginRequestUrl(url); return url; } export const createCatalogDatabase = (input: DatabaseConfiguration) => apiFetch("/catalog/databases", { method: "POST", body: JSON.stringify(input) }); export const updateCatalogDatabase = (id: string, version: number, input: DatabaseConfiguration) => apiFetch(`/catalog/databases/${encodeURIComponent(id)}`, { method: "PATCH", body: JSON.stringify({ ...input, version }), }); export const replaceCatalogDatabaseSecrets = ( id: string, version: number, values: Partial>, ) => apiFetch(`/catalog/databases/${encodeURIComponent(id)}/secrets`, { method: "PUT", body: JSON.stringify({ version, values }), }); export const testCatalogDatabase = (id: string, version: number) => apiFetch(`/catalog/databases/${encodeURIComponent(id)}/test`, { method: "POST", body: JSON.stringify({ version }), }); export const deleteCatalogDatabase = (id: string, version: number) => apiFetch(`/catalog/databases/${encodeURIComponent(id)}?version=${version}`, { method: "DELETE" }); export const listCatalogTables = (databaseId: string) => apiFetch(`/catalog/databases/${encodeURIComponent(databaseId)}/tables`); export const updateCatalogTableMetadata = ( databaseId: string, tableId: string, version: number, description: string | null, generatedDescription: string | null, ) => apiFetch( `/catalog/databases/${encodeURIComponent(databaseId)}/tables/${encodeURIComponent(tableId)}`, { method: "PATCH", body: JSON.stringify({ version, description, generatedDescription }) }, ); export const listCatalogColumns = (databaseId: string, tableId: string) => apiFetch(`/catalog/databases/${encodeURIComponent(databaseId)}/tables/${encodeURIComponent(tableId)}/columns`); export const updateCatalogColumnMetadata = ( databaseId: string, tableId: string, columnId: string, version: number, description: string | null, generatedDescription: string | null, sensitive?: boolean, ) => apiFetch( `/catalog/databases/${encodeURIComponent(databaseId)}/tables/${encodeURIComponent(tableId)}/columns/${encodeURIComponent(columnId)}`, { method: "PATCH", body: JSON.stringify({ version, description, generatedDescription, sensitive }) }, ); export const updateCatalogColumnSensitive = ( databaseId: string, tableId: string, columnId: string, version: number, sensitive: boolean, ) => apiFetch( `/catalog/databases/${encodeURIComponent(databaseId)}/tables/${encodeURIComponent(tableId)}/columns/${encodeURIComponent(columnId)}`, { method: "PATCH", body: JSON.stringify({ version, sensitive }) }, ); export const runSensitivityAnalysis = ( databaseId: string, selection: SensitivityAnalysisRequest, ) => apiFetch( `/catalog/databases/${encodeURIComponent(databaseId)}/sensitive-data-suggestions`, { method: "POST", body: JSON.stringify(selection) }, ); export const getSensitivityAnalysisRun = (runId: string) => apiFetch( `/catalog/sensitive-data-suggestion-runs/${encodeURIComponent(runId)}`, ); export const listSensitivityAnalysisRuns = (limit = 50) => apiFetch( `/catalog/sensitive-data-suggestion-runs?limit=${encodeURIComponent(String(limit))}`, ); export const listSensitivityAnalysisEvents = (runId: string, after = 0) => apiFetch( `/catalog/sensitive-data-suggestion-runs/${encodeURIComponent(runId)}/events-list?after=${after}`, ); export const listCatalogRelationships = (databaseId: string) => apiFetch(`/catalog/databases/${encodeURIComponent(databaseId)}/relationships`); export const createCatalogRelationship = ( databaseId: string, sourceColumnId: string, targetColumnId: string, ) => apiFetch( `/catalog/databases/${encodeURIComponent(databaseId)}/relationships`, { method: "POST", body: JSON.stringify({ sourceColumnId, targetColumnId }) }, ); export const rebuildGeneratedRelationships = (databaseId: string) => apiFetch( `/catalog/databases/${encodeURIComponent(databaseId)}/relationships/rebuild-generated`, { method: "POST" }, ); export const setCatalogRelationshipStatus = ( databaseId: string, relationshipId: string, status: CatalogRelationshipStatus, ) => apiFetch( `/catalog/databases/${encodeURIComponent(databaseId)}/relationships/${encodeURIComponent(relationshipId)}`, { method: "PATCH", body: JSON.stringify({ status }) }, ); export const deleteCatalogRelationship = (databaseId: string, relationshipId: string) => apiFetch( `/catalog/databases/${encodeURIComponent(databaseId)}/relationships/${encodeURIComponent(relationshipId)}`, { method: "DELETE" }, ); export const deleteCatalogDatabaseMetadata = ( databaseIds: string[], target: CatalogDatabaseMetadataDeleteTarget, ) => apiFetch("/catalog/databases/metadata-cleanup", { method: "POST", body: JSON.stringify({ databaseIds, target }), }); export const deleteCatalogTableMetadata = ( databaseId: string, tableIds: string[], target: CatalogTableMetadataDeleteTarget, ) => apiFetch( `/catalog/databases/${encodeURIComponent(databaseId)}/tables/metadata-cleanup`, { method: "POST", body: JSON.stringify({ tableIds, target }) }, ); export const consolidateCatalogDescriptions = ( databaseId: string, target: CatalogDescriptionTarget, targetIds: string[], ) => apiFetch( `/catalog/databases/${encodeURIComponent(databaseId)}/descriptions/consolidate`, { method: "POST", body: JSON.stringify({ target, targetIds }) }, ); export const startCatalogSync = ( databaseId: string, version: number, scope: CatalogSyncScope, tableIds: string[] = [], ) => apiFetch(`/catalog/databases/${encodeURIComponent(databaseId)}/sync-runs`, { method: "POST", body: JSON.stringify({ version, scope, tableIds }), }); export const listCatalogSyncRuns = (databaseId: string) => apiFetch(`/catalog/databases/${encodeURIComponent(databaseId)}/sync-runs`); export const getCatalogSyncRun = (runId: string) => apiFetch(`/catalog/sync-runs/${encodeURIComponent(runId)}`); export const listCatalogSyncEvents = (runId: string, after = 0) => apiFetch(`/catalog/sync-runs/${encodeURIComponent(runId)}/events-list?after=${after}`); export const confirmCatalogSync = (runId: string, confirmationToken: string) => apiFetch(`/catalog/sync-runs/${encodeURIComponent(runId)}/confirm`, { method: "POST", body: JSON.stringify({ confirmationToken }), }); export const cancelCatalogSync = (runId: string) => apiFetch(`/catalog/sync-runs/${encodeURIComponent(runId)}/cancel`, { method: "POST" }); export const retryCatalogSync = (runId: string) => apiFetch(`/catalog/sync-runs/${encodeURIComponent(runId)}/retry`, { method: "POST" }); export function catalogSyncEventsUrl(runId: string, after = 0): string { const url = joinBackendPath(BASE, `/catalog/sync-runs/${encodeURIComponent(runId)}/events?after=${after}`); assertSameOriginRequestUrl(url); return url; }