Files
ThothII/frontend/src/api/catalog-databases.ts
T

591 lines
18 KiB
TypeScript

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<CatalogSecretName, boolean>;
}
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<string, unknown>;
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<CatalogDatabase[]>("/catalog/databases");
export const getCatalogMetrics = (databaseId?: string) => apiFetch<CatalogMetrics>(
databaseId
? `/catalog/metrics?databaseId=${encodeURIComponent(databaseId)}`
: "/catalog/metrics",
);
export const listMetadataGenerationModels = () =>
apiFetch<MetadataGenerationModels>("/catalog/metadata-generation/models");
export function startDescriptionGenerationRun(
databaseId: string,
modelId: string,
scope: Extract<DescriptionGenerationScope, "selected_columns" | "selected_tables">,
targetIds: string[],
): Promise<DescriptionGenerationRun>;
export function startDescriptionGenerationRun(
databaseId: string,
modelId: string,
scope: Extract<DescriptionGenerationScope, "all" | "missing">,
): Promise<DescriptionGenerationRun>;
export function startDescriptionGenerationRun(
databaseId: string,
modelId: string,
scope: DescriptionGenerationScope,
targetIds?: string[],
): Promise<DescriptionGenerationRun> {
const body = scope === "all" || scope === "missing"
? { modelId, scope }
: { modelId, scope, targetIds: targetIds ?? [] };
return apiFetch<DescriptionGenerationRun>(
`/catalog/databases/${encodeURIComponent(databaseId)}/description-generation-runs`,
{ method: "POST", body: JSON.stringify(body) },
);
}
export const getDescriptionGenerationRun = (runId: string) =>
apiFetch<DescriptionGenerationRun>(
`/catalog/description-generation-runs/${encodeURIComponent(runId)}`,
);
export const listDescriptionGenerationRuns = (limit = 50) =>
apiFetch<DescriptionGenerationRun[]>(
`/catalog/description-generation-runs?limit=${encodeURIComponent(String(limit))}`,
);
export const listDescriptionGenerationEvents = (runId: string, after = 0) =>
apiFetch<DescriptionGenerationEvent[]>(
`/catalog/description-generation-runs/${encodeURIComponent(runId)}/events-list?after=${after}`,
);
export const cancelDescriptionGenerationRun = (runId: string) =>
apiFetch<DescriptionGenerationRun>(
`/catalog/description-generation-runs/${encodeURIComponent(runId)}/cancel`,
{ method: "POST" },
);
export const unlockDescriptionGenerationRun = () =>
apiFetch<DescriptionGenerationRun>(
"/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<CatalogDatabase>("/catalog/databases", { method: "POST", body: JSON.stringify(input) });
export const updateCatalogDatabase = (id: string, version: number, input: DatabaseConfiguration) =>
apiFetch<CatalogDatabase>(`/catalog/databases/${encodeURIComponent(id)}`, {
method: "PATCH",
body: JSON.stringify({ ...input, version }),
});
export const replaceCatalogDatabaseSecrets = (
id: string,
version: number,
values: Partial<Record<CatalogSecretName, string>>,
) => apiFetch<CatalogDatabase>(`/catalog/databases/${encodeURIComponent(id)}/secrets`, {
method: "PUT",
body: JSON.stringify({ version, values }),
});
export const testCatalogDatabase = (id: string, version: number) =>
apiFetch<CatalogDatabase>(`/catalog/databases/${encodeURIComponent(id)}/test`, {
method: "POST",
body: JSON.stringify({ version }),
});
export const deleteCatalogDatabase = (id: string, version: number) =>
apiFetch<void>(`/catalog/databases/${encodeURIComponent(id)}?version=${version}`, { method: "DELETE" });
export const listCatalogTables = (databaseId: string) =>
apiFetch<CatalogTable[]>(`/catalog/databases/${encodeURIComponent(databaseId)}/tables`);
export const updateCatalogTableMetadata = (
databaseId: string,
tableId: string,
version: number,
description: string | null,
generatedDescription: string | null,
) => apiFetch<CatalogTable>(
`/catalog/databases/${encodeURIComponent(databaseId)}/tables/${encodeURIComponent(tableId)}`,
{ method: "PATCH", body: JSON.stringify({ version, description, generatedDescription }) },
);
export const listCatalogColumns = (databaseId: string, tableId: string) =>
apiFetch<CatalogColumn[]>(`/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<CatalogColumn>(
`/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<CatalogColumn>(
`/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<SensitivityAnalysisResult>(
`/catalog/databases/${encodeURIComponent(databaseId)}/sensitive-data-suggestions`,
{ method: "POST", body: JSON.stringify(selection) },
);
export const getSensitivityAnalysisRun = (runId: string) =>
apiFetch<SensitivityAnalysisRun>(
`/catalog/sensitive-data-suggestion-runs/${encodeURIComponent(runId)}`,
);
export const listSensitivityAnalysisRuns = (limit = 50) =>
apiFetch<SensitivityAnalysisRun[]>(
`/catalog/sensitive-data-suggestion-runs?limit=${encodeURIComponent(String(limit))}`,
);
export const listSensitivityAnalysisEvents = (runId: string, after = 0) =>
apiFetch<SensitivityAnalysisEvent[]>(
`/catalog/sensitive-data-suggestion-runs/${encodeURIComponent(runId)}/events-list?after=${after}`,
);
export const listCatalogRelationships = (databaseId: string) =>
apiFetch<CatalogRelationship[]>(`/catalog/databases/${encodeURIComponent(databaseId)}/relationships`);
export const createCatalogRelationship = (
databaseId: string,
sourceColumnId: string,
targetColumnId: string,
) => apiFetch<CatalogRelationship>(
`/catalog/databases/${encodeURIComponent(databaseId)}/relationships`,
{ method: "POST", body: JSON.stringify({ sourceColumnId, targetColumnId }) },
);
export const rebuildGeneratedRelationships = (databaseId: string) =>
apiFetch<CatalogRelationshipRebuildResult>(
`/catalog/databases/${encodeURIComponent(databaseId)}/relationships/rebuild-generated`,
{ method: "POST" },
);
export const setCatalogRelationshipStatus = (
databaseId: string,
relationshipId: string,
status: CatalogRelationshipStatus,
) => apiFetch<CatalogRelationship>(
`/catalog/databases/${encodeURIComponent(databaseId)}/relationships/${encodeURIComponent(relationshipId)}`,
{ method: "PATCH", body: JSON.stringify({ status }) },
);
export const deleteCatalogRelationship = (databaseId: string, relationshipId: string) =>
apiFetch<void>(
`/catalog/databases/${encodeURIComponent(databaseId)}/relationships/${encodeURIComponent(relationshipId)}`,
{ method: "DELETE" },
);
export const deleteCatalogDatabaseMetadata = (
databaseIds: string[],
target: CatalogDatabaseMetadataDeleteTarget,
) => apiFetch<CatalogMetadataDeleteCounts>("/catalog/databases/metadata-cleanup", {
method: "POST",
body: JSON.stringify({ databaseIds, target }),
});
export const deleteCatalogTableMetadata = (
databaseId: string,
tableIds: string[],
target: CatalogTableMetadataDeleteTarget,
) => apiFetch<CatalogMetadataDeleteCounts>(
`/catalog/databases/${encodeURIComponent(databaseId)}/tables/metadata-cleanup`,
{ method: "POST", body: JSON.stringify({ tableIds, target }) },
);
export const consolidateCatalogDescriptions = (
databaseId: string,
target: CatalogDescriptionTarget,
targetIds: string[],
) => apiFetch<CatalogDescriptionConsolidationCounts>(
`/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<CatalogSyncRun>(`/catalog/databases/${encodeURIComponent(databaseId)}/sync-runs`, {
method: "POST",
body: JSON.stringify({ version, scope, tableIds }),
});
export const listCatalogSyncRuns = (databaseId: string) =>
apiFetch<CatalogSyncRun[]>(`/catalog/databases/${encodeURIComponent(databaseId)}/sync-runs`);
export const getCatalogSyncRun = (runId: string) =>
apiFetch<CatalogSyncRun>(`/catalog/sync-runs/${encodeURIComponent(runId)}`);
export const listCatalogSyncEvents = (runId: string, after = 0) =>
apiFetch<CatalogSyncEvent[]>(`/catalog/sync-runs/${encodeURIComponent(runId)}/events-list?after=${after}`);
export const confirmCatalogSync = (runId: string, confirmationToken: string) =>
apiFetch<CatalogSyncRun>(`/catalog/sync-runs/${encodeURIComponent(runId)}/confirm`, {
method: "POST", body: JSON.stringify({ confirmationToken }),
});
export const cancelCatalogSync = (runId: string) =>
apiFetch<CatalogSyncRun>(`/catalog/sync-runs/${encodeURIComponent(runId)}/cancel`, { method: "POST" });
export const retryCatalogSync = (runId: string) =>
apiFetch<CatalogSyncRun>(`/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;
}