Files
govoplan-datasources/webui/src/api/datasources.ts
T

328 lines
8.9 KiB
TypeScript

import {
apiFetch,
type ApiSettings
} from "@govoplan/core-webui";
export type DatasourceMode = "live" | "cached" | "static";
export type DatasourceShape = "tabular" | "document" | "binary" | "directory" | "stream";
export type SourceAuthorityMode =
| "native_authoritative"
| "external_authoritative"
| "external_mirror"
| "governed_sync"
| "governance_overlay"
| "linked_reference";
export type DatasourceGovernance = {
owner_ref?: string | null;
steward_ref?: string | null;
responsible_organization_ref?: string | null;
responsible_function_ref?: string | null;
authoritative_source_ref?: string | null;
authority_mode: SourceAuthorityMode;
legal_basis_refs: string[];
purposes: string[];
semantic_definition?: string | null;
schema_owner_ref?: string | null;
official_keys: string[];
classification: string;
privacy_profile_ref?: string | null;
retention_policy_ref?: string | null;
hold_refs: string[];
publication_state: string;
transfer_agreement_ref?: string | null;
freshness_policy: Record<string, unknown>;
quality_policy: Record<string, unknown>;
known_limits: string[];
correction_procedure_ref?: string | null;
affected_refs: string[];
dependency_refs: string[];
};
export type DatasourceField = {
name: string;
data_type: string;
nullable: boolean;
};
export type Datasource = {
ref: string;
source_name: string;
name: string;
description?: string | null;
kind: string;
mode: DatasourceMode;
shape: DatasourceShape;
status: string;
provider?: string | null;
provider_ref?: string | null;
schema: DatasourceField[];
schema_version: string;
fingerprint: string;
current_materialization_ref?: string | null;
row_count?: number | null;
byte_count?: number | null;
updated_at?: string | null;
capabilities: string[];
provenance: Record<string, unknown>;
metadata: Record<string, unknown>;
governance: DatasourceGovernance;
};
export type DatasourceMaterialization = {
ref: string;
datasource_ref: string;
revision: number;
state: string;
fingerprint: string;
schema: DatasourceField[];
row_count?: number | null;
byte_count?: number | null;
frozen_at?: string | null;
frozen_label?: string | null;
source_timestamp?: string | null;
created_at?: string | null;
provenance: Record<string, unknown>;
metadata: Record<string, unknown>;
governance: DatasourceGovernance;
};
export type DatasourceValidationDiagnostic = {
severity: "error" | "warning";
code: string;
message: string;
rule_id?: string;
affected_rows?: number;
row_numbers?: number[];
row_numbers_truncated?: boolean;
details?: Record<string, unknown>;
};
export type DatasourceSchemaChange = {
code: string;
classification: "compatible" | "warning" | "breaking";
message: string;
field?: string;
before?: DatasourceField;
after?: DatasourceField;
};
export type DatasourceStageValidation = {
version?: number;
policy_version?: string;
policy_hash?: string;
valid?: boolean;
errors?: DatasourceValidationDiagnostic[];
warnings?: DatasourceValidationDiagnostic[];
quality?: {
rules_evaluated?: number;
rules_passed?: number;
rules_failed?: number;
};
schema_change?: {
classification?: "new" | "compatible" | "warning" | "breaking";
changes?: DatasourceSchemaChange[];
};
};
export type DatasourceStage = {
ref: string;
name: string;
source_name: string;
kind: string;
mode: "static" | "cached";
shape: DatasourceShape;
state: string;
target_datasource_ref?: string | null;
fingerprint: string;
schema: DatasourceField[];
row_count?: number | null;
byte_count?: number | null;
validation: DatasourceStageValidation;
created_at?: string | null;
promoted_at?: string | null;
promoted_materialization_ref?: string | null;
provenance: Record<string, unknown>;
metadata: Record<string, unknown>;
governance: DatasourceGovernance;
};
export type DatasourceOrigin = {
ref: string;
source_name: string;
name: string;
description?: string | null;
kind: string;
shape: DatasourceShape;
supported_modes: DatasourceMode[];
provider: string;
schema: DatasourceField[];
schema_version: string;
fingerprint: string;
row_count?: number | null;
byte_count?: number | null;
updated_at?: string | null;
capabilities: string[];
metadata: Record<string, unknown>;
};
export type DatasourcePreview = {
datasource: Datasource;
rows: Record<string, unknown>[];
total_rows: number;
truncated: boolean;
materialization?: DatasourceMaterialization | null;
};
export async function listDatasources(
settings: ApiSettings,
query = "",
filters: Partial<Pick<DatasourceGovernance, "authority_mode" | "classification" | "publication_state" | "owner_ref" | "responsible_organization_ref">> & {
affected_ref?: string;
dependency_ref?: string;
} = {}
): Promise<Datasource[]> {
const params = new URLSearchParams();
if (query.trim()) params.set("query", query.trim());
for (const [key, value] of Object.entries(filters)) {
if (String(value ?? "").trim()) params.set(key, String(value).trim());
}
const suffix = params.size ? `?${params.toString()}` : "";
const response = await apiFetch<{ datasources: Datasource[] }>(
settings,
`/api/v1/datasources${suffix}`
);
return response.datasources;
}
export async function listDatasourceStages(
settings: ApiSettings
): Promise<DatasourceStage[]> {
const response = await apiFetch<{ stages: DatasourceStage[] }>(
settings,
"/api/v1/datasources/stages"
);
return response.stages;
}
export async function listDatasourceOrigins(
settings: ApiSettings
): Promise<{ available: boolean; origins: DatasourceOrigin[] }> {
return apiFetch(settings, "/api/v1/datasources/origins");
}
export async function listDatasourceMaterializations(
settings: ApiSettings,
datasourceRef: string
): Promise<DatasourceMaterialization[]> {
const response = await apiFetch<{ materializations: DatasourceMaterialization[] }>(
settings,
`/api/v1/datasources/${refId(datasourceRef)}/materializations`
);
return response.materializations;
}
export function previewDatasource(
settings: ApiSettings,
datasourceRef: string,
consistency: "current" | "live" | "frozen" = "current"
): Promise<DatasourcePreview> {
return apiFetch(
settings,
`/api/v1/datasources/${refId(datasourceRef)}/preview?limit=100&consistency=${consistency}`
);
}
export function createDatasourceStage(
settings: ApiSettings,
payload: {
name: string;
source_name: string;
description?: string | null;
mode: "static" | "cached";
target_datasource_ref?: string | null;
governance?: DatasourceGovernance | null;
} & (
{ format: "json"; rows: Record<string, unknown>[] }
| { format: "csv"; csv_text: string; delimiter: string }
)
): Promise<DatasourceStage> {
return apiFetch(settings, "/api/v1/datasources/stages", {
method: "POST",
body: JSON.stringify(payload)
});
}
export function promoteDatasourceStage(
settings: ApiSettings,
stageRef: string,
payload: { freeze?: boolean; frozen_label?: string | null } = {}
): Promise<{ datasource: Datasource; materialization: DatasourceMaterialization }> {
return apiFetch(settings, `/api/v1/datasources/stages/${refId(stageRef)}/promote`, {
method: "POST",
body: JSON.stringify(payload)
});
}
export function registerDatasourceOrigin(
settings: ApiSettings,
payload: {
origin_ref: string;
name: string;
source_name: string;
mode: "live" | "cached";
description?: string | null;
governance?: DatasourceGovernance | null;
}
): Promise<Datasource> {
return apiFetch(settings, "/api/v1/datasources/origins/register", {
method: "POST",
body: JSON.stringify(payload)
});
}
export function refreshDatasource(
settings: ApiSettings,
datasourceRef: string
): Promise<{ datasource: Datasource; materialization: DatasourceMaterialization }> {
return apiFetch(settings, `/api/v1/datasources/${refId(datasourceRef)}/refresh`, {
method: "POST"
});
}
export function freezeDatasource(
settings: ApiSettings,
datasourceRef: string,
label: string
): Promise<DatasourceMaterialization> {
return apiFetch(settings, `/api/v1/datasources/${refId(datasourceRef)}/freeze`, {
method: "POST",
body: JSON.stringify({ label: label.trim() || null })
});
}
export function retireDatasource(
settings: ApiSettings,
datasourceRef: string
): Promise<{ retired: boolean; datasource_ref: string }> {
return apiFetch(settings, `/api/v1/datasources/${refId(datasourceRef)}`, {
method: "DELETE"
});
}
export function updateDatasourceGovernance(
settings: ApiSettings,
datasourceRef: string,
governance: DatasourceGovernance
): Promise<Datasource> {
return apiFetch(settings, `/api/v1/datasources/${refId(datasourceRef)}/governance`, {
method: "PATCH",
body: JSON.stringify({ governance })
});
}
function refId(ref: string): string {
const separator = ref.indexOf(":");
return encodeURIComponent(separator >= 0 ? ref.slice(separator + 1) : ref);
}