451 lines
13 KiB
TypeScript
451 lines
13 KiB
TypeScript
/**
|
|
* Typed endpoint functions for the rpki_query_service API.
|
|
* Every function validates the response envelope with zod before returning.
|
|
*/
|
|
import { z } from "zod";
|
|
import { apiFetch, apiPost, withQuery, type QueryParams } from "./client";
|
|
import {
|
|
chainEdgeRecordSchema,
|
|
envelopeSchema,
|
|
exportJobRecordSchema,
|
|
explainRecordSchema,
|
|
healthSchema,
|
|
manifestFileEntrySchema,
|
|
objectInstanceRecordSchema,
|
|
projectionRecordSchema,
|
|
publicationPointRecordSchema,
|
|
repositoryRecordSchema,
|
|
revokedCertEntrySchema,
|
|
runRecordSchema,
|
|
runSummarySchema,
|
|
searchResultSchema,
|
|
serviceInfoSchema,
|
|
statsMapSchema,
|
|
validationSummarySchema,
|
|
type ChainEdgeRecord,
|
|
type ExportJobRecord,
|
|
type ExplainRecord,
|
|
type HealthStatus,
|
|
type ListResult,
|
|
type ManifestFileEntry,
|
|
type ObjectInstanceRecord,
|
|
type ProjectionRecord,
|
|
type PublicationPointRecord,
|
|
type RepositoryRecord,
|
|
type RevokedCertEntry,
|
|
type RunRecord,
|
|
type RunSummary,
|
|
type SearchResult,
|
|
type ServiceInfo,
|
|
type ValidationSummary,
|
|
} from "./schemas";
|
|
|
|
const API = "/api/v1";
|
|
|
|
function runBase(runId: string): string {
|
|
return `${API}/runs/${encodeURIComponent(runId)}`;
|
|
}
|
|
|
|
async function getData<S extends z.ZodTypeAny>(
|
|
path: string,
|
|
schema: S,
|
|
params?: QueryParams,
|
|
): Promise<z.infer<S>> {
|
|
const raw = await apiFetch<unknown>(withQuery(path, params));
|
|
return envelopeSchema(schema).parse(raw).data;
|
|
}
|
|
|
|
async function getList<S extends z.ZodTypeAny>(
|
|
path: string,
|
|
schema: S,
|
|
params?: QueryParams,
|
|
): Promise<ListResult<z.infer<S>>> {
|
|
const raw = await apiFetch<unknown>(withQuery(path, params));
|
|
const parsed = envelopeSchema(z.array(schema)).parse(raw);
|
|
return {
|
|
items: parsed.data,
|
|
nextCursor: parsed.page?.nextCursor ?? null,
|
|
runId: parsed.meta?.runId ?? undefined,
|
|
};
|
|
}
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* Service / health */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
export function getServiceInfo(): Promise<ServiceInfo> {
|
|
return getData(`${API}`, serviceInfoSchema);
|
|
}
|
|
|
|
export function getHealth(): Promise<HealthStatus> {
|
|
return getData(`${API}/healthz`, healthSchema);
|
|
}
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* Runs */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
export type PageParams = {
|
|
limit?: number;
|
|
cursor?: string | null;
|
|
};
|
|
|
|
export function listRuns(params: PageParams = {}): Promise<ListResult<RunRecord>> {
|
|
return getList(`${API}/runs`, runRecordSchema, params);
|
|
}
|
|
|
|
export function getLatestRun(): Promise<RunRecord> {
|
|
return getData(`${API}/latest_run`, runRecordSchema);
|
|
}
|
|
|
|
export function getRun(runId: string): Promise<RunRecord> {
|
|
return getData(runBase(runId), runRecordSchema);
|
|
}
|
|
|
|
export function getRunArtifacts(runId: string): Promise<Record<string, string>> {
|
|
return getData(`${runBase(runId)}/artifacts`, z.record(z.string()));
|
|
}
|
|
|
|
export function getRunSummary(runId: string): Promise<RunSummary> {
|
|
return getData(`${runBase(runId)}/summary`, runSummarySchema);
|
|
}
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* Repositories */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
export function listRepos(
|
|
runId: string,
|
|
params: PageParams = {},
|
|
): Promise<ListResult<RepositoryRecord>> {
|
|
return getList(`${runBase(runId)}/repos`, repositoryRecordSchema, params);
|
|
}
|
|
|
|
export function getRepo(runId: string, repoId: string): Promise<RepositoryRecord> {
|
|
return getData(
|
|
`${runBase(runId)}/repos/${encodeURIComponent(repoId)}`,
|
|
repositoryRecordSchema,
|
|
);
|
|
}
|
|
|
|
export interface RepoStats {
|
|
publicationPoints?: number;
|
|
objects?: number;
|
|
rejectedObjects?: number;
|
|
syncDurationMsTotal?: number;
|
|
phases?: Record<string, number>;
|
|
terminalStates?: Record<string, number>;
|
|
}
|
|
|
|
export function getRepoStats(runId: string, repoId: string): Promise<RepoStats> {
|
|
return getData(
|
|
`${runBase(runId)}/repos/${encodeURIComponent(repoId)}/stats`,
|
|
z
|
|
.object({
|
|
publicationPoints: z.number().optional(),
|
|
objects: z.number().optional(),
|
|
rejectedObjects: z.number().optional(),
|
|
syncDurationMsTotal: z.number().optional(),
|
|
phases: z.record(z.number()).optional(),
|
|
terminalStates: z.record(z.number()).optional(),
|
|
})
|
|
.passthrough(),
|
|
);
|
|
}
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* Publication points */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
export function listPublicationPoints(
|
|
runId: string,
|
|
params: PageParams = {},
|
|
): Promise<ListResult<PublicationPointRecord>> {
|
|
return getList(`${runBase(runId)}/publication-points`, publicationPointRecordSchema, params);
|
|
}
|
|
|
|
export function listRepoPublicationPoints(
|
|
runId: string,
|
|
repoId: string,
|
|
params: PageParams = {},
|
|
): Promise<ListResult<PublicationPointRecord>> {
|
|
return getList(
|
|
`${runBase(runId)}/repos/${encodeURIComponent(repoId)}/publication-points`,
|
|
publicationPointRecordSchema,
|
|
params,
|
|
);
|
|
}
|
|
|
|
export function getPublicationPoint(
|
|
runId: string,
|
|
ppId: string,
|
|
): Promise<PublicationPointRecord> {
|
|
return getData(
|
|
`${runBase(runId)}/publication-points/${encodeURIComponent(ppId)}`,
|
|
publicationPointRecordSchema,
|
|
);
|
|
}
|
|
|
|
export interface PpStats {
|
|
objects?: number;
|
|
rejectedObjects?: number;
|
|
warnings?: number;
|
|
repoSyncSource?: string;
|
|
repoSyncPhase?: string;
|
|
repoSyncDurationMs?: number;
|
|
repoTerminalState?: string;
|
|
}
|
|
|
|
export function getPublicationPointStats(runId: string, ppId: string): Promise<PpStats> {
|
|
return getData(
|
|
`${runBase(runId)}/publication-points/${encodeURIComponent(ppId)}/stats`,
|
|
z
|
|
.object({
|
|
objects: z.number().optional(),
|
|
rejectedObjects: z.number().optional(),
|
|
warnings: z.number().optional(),
|
|
repoSyncSource: z.string().optional(),
|
|
repoSyncPhase: z.string().optional(),
|
|
repoSyncDurationMs: z.number().optional(),
|
|
repoTerminalState: z.string().optional(),
|
|
})
|
|
.passthrough(),
|
|
);
|
|
}
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* Objects */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
export interface ObjectFilters extends PageParams {
|
|
type?: string;
|
|
result?: string;
|
|
rejected?: boolean;
|
|
reason?: string;
|
|
q?: string;
|
|
}
|
|
|
|
function filterParams(filters: ObjectFilters): QueryParams {
|
|
return {
|
|
limit: filters.limit,
|
|
cursor: filters.cursor,
|
|
type: filters.type,
|
|
result: filters.result,
|
|
rejected: filters.rejected === undefined ? undefined : String(filters.rejected),
|
|
reason: filters.reason,
|
|
q: filters.q,
|
|
};
|
|
}
|
|
|
|
export function listObjects(
|
|
runId: string,
|
|
filters: ObjectFilters = {},
|
|
): Promise<ListResult<ObjectInstanceRecord>> {
|
|
return getList(`${runBase(runId)}/objects`, objectInstanceRecordSchema, filterParams(filters));
|
|
}
|
|
|
|
export function listRepoObjects(
|
|
runId: string,
|
|
repoId: string,
|
|
filters: ObjectFilters = {},
|
|
): Promise<ListResult<ObjectInstanceRecord>> {
|
|
return getList(
|
|
`${runBase(runId)}/repos/${encodeURIComponent(repoId)}/objects`,
|
|
objectInstanceRecordSchema,
|
|
filterParams(filters),
|
|
);
|
|
}
|
|
|
|
export function listPublicationPointObjects(
|
|
runId: string,
|
|
ppId: string,
|
|
filters: ObjectFilters = {},
|
|
): Promise<ListResult<ObjectInstanceRecord>> {
|
|
return getList(
|
|
`${runBase(runId)}/publication-points/${encodeURIComponent(ppId)}/objects`,
|
|
objectInstanceRecordSchema,
|
|
filterParams(filters),
|
|
);
|
|
}
|
|
|
|
export interface IssueFilters extends PageParams {
|
|
reason?: string;
|
|
type?: string;
|
|
repoId?: string;
|
|
ppId?: string;
|
|
}
|
|
|
|
export function listIssues(
|
|
runId: string,
|
|
filters: IssueFilters = {},
|
|
): Promise<ListResult<ObjectInstanceRecord>> {
|
|
return getList(`${runBase(runId)}/issues`, objectInstanceRecordSchema, {
|
|
limit: filters.limit,
|
|
cursor: filters.cursor,
|
|
reason: filters.reason,
|
|
type: filters.type,
|
|
repoId: filters.repoId,
|
|
ppId: filters.ppId,
|
|
});
|
|
}
|
|
|
|
export function getObjectByUri(
|
|
runId: string,
|
|
uri: string,
|
|
): Promise<ObjectInstanceRecord> {
|
|
return getData(`${runBase(runId)}/objects/by-uri`, objectInstanceRecordSchema, { uri });
|
|
}
|
|
|
|
export function getObject(
|
|
runId: string,
|
|
objectInstanceId: string,
|
|
): Promise<ObjectInstanceRecord> {
|
|
return getData(
|
|
`${runBase(runId)}/objects/${encodeURIComponent(objectInstanceId)}`,
|
|
objectInstanceRecordSchema,
|
|
);
|
|
}
|
|
|
|
export function getObjectProjection(
|
|
runId: string,
|
|
objectInstanceId: string,
|
|
): Promise<ProjectionRecord | null> {
|
|
return getData(
|
|
`${runBase(runId)}/objects/${encodeURIComponent(objectInstanceId)}/parsed`,
|
|
projectionRecordSchema.nullable(),
|
|
);
|
|
}
|
|
|
|
export function getObjectValidation(
|
|
runId: string,
|
|
objectInstanceId: string,
|
|
): Promise<ValidationSummary> {
|
|
return getData(
|
|
`${runBase(runId)}/objects/${encodeURIComponent(objectInstanceId)}/validation`,
|
|
validationSummarySchema,
|
|
);
|
|
}
|
|
|
|
export function getObjectChain(
|
|
runId: string,
|
|
objectInstanceId: string,
|
|
): Promise<ChainEdgeRecord[]> {
|
|
return getData(
|
|
`${runBase(runId)}/objects/${encodeURIComponent(objectInstanceId)}/chain`,
|
|
z.array(chainEdgeRecordSchema),
|
|
);
|
|
}
|
|
|
|
export function listManifestFiles(
|
|
runId: string,
|
|
objectInstanceId: string,
|
|
params: PageParams = {},
|
|
): Promise<ListResult<ManifestFileEntry>> {
|
|
return getList(
|
|
`${runBase(runId)}/objects/${encodeURIComponent(objectInstanceId)}/parsed/manifest-files`,
|
|
manifestFileEntrySchema,
|
|
params,
|
|
);
|
|
}
|
|
|
|
export function listRevokedCertificates(
|
|
runId: string,
|
|
objectInstanceId: string,
|
|
params: PageParams = {},
|
|
): Promise<ListResult<RevokedCertEntry>> {
|
|
return getList(
|
|
`${runBase(runId)}/objects/${encodeURIComponent(objectInstanceId)}/parsed/revoked-certs`,
|
|
revokedCertEntrySchema,
|
|
params,
|
|
);
|
|
}
|
|
|
|
export function explainObjectValidation(
|
|
runId: string,
|
|
objectInstanceId: string,
|
|
options: { forceRefresh?: boolean } = {},
|
|
): Promise<ExplainRecord> {
|
|
return apiPost<unknown>(
|
|
`${runBase(runId)}/objects/${encodeURIComponent(objectInstanceId)}/validation/explain`,
|
|
{ forceRefresh: options.forceRefresh ?? false },
|
|
).then((raw) => envelopeSchema(explainRecordSchema).parse(raw).data);
|
|
}
|
|
|
|
export function rawObjectUrl(runId: string, objectInstanceId: string): string {
|
|
return `${runBase(runId)}/objects/${encodeURIComponent(objectInstanceId)}/raw`;
|
|
}
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* Search */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
export function searchRun(
|
|
runId: string,
|
|
q: string,
|
|
limit = 10,
|
|
): Promise<SearchResult> {
|
|
return getData(`${runBase(runId)}/search`, searchResultSchema, { q, limit });
|
|
}
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* Stats */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
export function getStatsObjectTypes(runId: string): Promise<Record<string, number>> {
|
|
return getData(`${runBase(runId)}/stats/object-types`, statsMapSchema);
|
|
}
|
|
|
|
export function getStatsValidation(runId: string): Promise<Record<string, number>> {
|
|
return getData(`${runBase(runId)}/stats/validation`, statsMapSchema);
|
|
}
|
|
|
|
export function getStatsReasons(runId: string): Promise<Record<string, number>> {
|
|
return getData(`${runBase(runId)}/stats/reasons`, statsMapSchema);
|
|
}
|
|
|
|
export function getStatsDownloads(runId: string): Promise<Record<string, unknown>> {
|
|
return getData(`${runBase(runId)}/stats/downloads`, z.record(z.unknown()));
|
|
}
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* Exports */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
export interface ExportRequest {
|
|
scope: "repo" | "publication_point" | "object_set";
|
|
repoId?: string;
|
|
ppId?: string;
|
|
objectInstanceIds?: string[];
|
|
}
|
|
|
|
export function createExport(
|
|
runId: string,
|
|
request: ExportRequest,
|
|
): Promise<ExportJobRecord> {
|
|
const body: Record<string, unknown> = { scope: request.scope };
|
|
if (request.repoId) body.repoId = request.repoId;
|
|
if (request.ppId) body.ppId = request.ppId;
|
|
if (request.objectInstanceIds) body.objectInstanceIds = request.objectInstanceIds;
|
|
return apiPost<unknown>(`${runBase(runId)}/exports`, body).then(
|
|
(raw) => envelopeSchema(exportJobRecordSchema).parse(raw).data,
|
|
);
|
|
}
|
|
|
|
export function listExports(
|
|
runId: string,
|
|
params: PageParams = {},
|
|
): Promise<ListResult<ExportJobRecord>> {
|
|
return getList(`${runBase(runId)}/exports`, exportJobRecordSchema, params);
|
|
}
|
|
|
|
export function getExportJob(runId: string, jobId: string): Promise<ExportJobRecord> {
|
|
return getData(
|
|
`${runBase(runId)}/exports/${encodeURIComponent(jobId)}`,
|
|
exportJobRecordSchema,
|
|
);
|
|
}
|
|
|
|
export function exportDownloadUrl(runId: string, jobId: string): string {
|
|
return `${runBase(runId)}/exports/${encodeURIComponent(jobId)}/download`;
|
|
}
|