import { ApiError, type HealthResponse, type SkepticResponse, type LayeredResponse, type QuarantineResponse, type CircuitBreakerResponse, type AuditResponse, type ListSourcesResponse, type SourceImpactResponse, type QuarantineSourceResponse, type RestoreSourceResponse, type GetPatternsResponse, type ScanRequest, type ScanResponse, type ListScansResponse, type ListClaimsRequest, type ListClaimsResponse, type CreateClaimRequest, type CreateClaimResponse, type UpdateClaimRequest, type UpdateClaimResponse, type DeprecateClaimRequest, type DeprecateClaimResponse, type VerifyClaimsRequest, type VerifyReportResponse, type CoverageRequest, type CoverageReportResponse, type AcknowledgeViolationRequest, type AcknowledgeViolationResponse, } from "./types"; export class StemeDBClient { private baseUrl: string; private apiKey: string | null; constructor(baseUrl?: string, apiKey?: string) { // Support empty string for relative URLs (proxied setup) // If NEXT_PUBLIC_STEMEDB_API_URL is set to empty string, use "" // Otherwise default to localhost for local dev const envUrl = process.env.NEXT_PUBLIC_STEMEDB_API_URL; this.baseUrl = baseUrl !== undefined ? baseUrl : envUrl !== undefined ? envUrl : "http://127.0.0.1:18180"; this.apiKey = apiKey || process.env.STEMEDB_API_KEY || null; } private async fetch(path: string, options?: RequestInit): Promise { const headers: HeadersInit = { "Content-Type": "application/json" }; if (this.apiKey) { headers["X-API-Key"] = this.apiKey; } const response = await fetch(`${this.baseUrl}${path}`, { ...options, headers: { ...headers, ...options?.headers }, cache: "no-store", }); if (!response.ok) { throw new ApiError(response.status, await response.text()); } return response.json(); } async health(): Promise { return this.fetch("/health"); } async skeptic( subject: string, predicate: string, includeSourceMetadata = true, asOf?: number ): Promise { const params = new URLSearchParams({ subject, predicate, include_source_metadata: String(includeSourceMetadata), }); if (asOf !== undefined) { params.set("as_of", String(asOf)); } return this.fetch(`/v1/skeptic?${params}`); } async layered( subject: string, predicate: string, asOf?: number ): Promise { const params = new URLSearchParams({ subject, predicate }); if (asOf !== undefined) { params.set("as_of", String(asOf)); } return this.fetch(`/v1/layered?${params}`); } async quarantine(limit = 50, offset = 0): Promise { const params = new URLSearchParams({ limit: String(limit), }); // Note: offset not yet supported by backend void offset; return this.fetch(`/v1/admin/quarantine?${params}`); } async restoreFromQuarantine(hash: string): Promise { await this.fetch(`/v1/admin/quarantine/${hash}/approve`, { method: "POST" }); } async deleteFromQuarantine(hash: string): Promise { await this.fetch(`/v1/admin/quarantine/${hash}/reject`, { method: "POST" }); } async circuitBreakers(): Promise { return this.fetch("/v1/admin/circuit-breakers/tripped"); } async resetCircuitBreaker(agentId: string): Promise { await this.fetch(`/v1/admin/circuit-breaker/reset`, { method: "POST", body: JSON.stringify({ agent_id: agentId }), }); } async auditQueries(params: { limit?: number; agentId?: string; subject?: string; predicate?: string; from?: number; to?: number; } = {}): Promise { const searchParams = new URLSearchParams({ limit: String(params.limit ?? 100) }); if (params.agentId) searchParams.set("agent_id", params.agentId); if (params.subject) searchParams.set("subject", params.subject); if (params.predicate) searchParams.set("predicate", params.predicate); if (params.from !== undefined) searchParams.set("from", String(params.from)); if (params.to !== undefined) searchParams.set("to", String(params.to)); return this.fetch(`/v1/audit/queries?${searchParams}`); } // Source Registry methods async listSources(limit = 100): Promise { const params = new URLSearchParams({ limit: String(limit) }); return this.fetch(`/v1/sources?${params}`); } async getSourceImpact(hash: string): Promise { return this.fetch(`/v1/sources/${hash}/impact`); } async quarantineSource( hash: string, preview: boolean, reason?: string ): Promise { return this.fetch( `/v1/sources/${hash}/quarantine`, { method: "POST", body: JSON.stringify({ preview, reason }), } ); } async restoreSource( hash: string, reason?: string ): Promise { return this.fetch(`/v1/sources/${hash}/restore`, { method: "POST", body: JSON.stringify({ reason }), }); } getSourceImpactExportUrl(hash: string, format: "csv" | "json"): string { return `${this.baseUrl}/v1/sources/${hash}/impact/export?format=${format}`; } getApiKey(): string | null { return this.apiKey; } // Aphoria methods async getPatterns(params: { subjectPrefix?: string; minProjects?: number; limit?: number; } = {}): Promise { const searchParams = new URLSearchParams(); if (params.subjectPrefix) searchParams.set("subject_prefix", params.subjectPrefix); if (params.minProjects !== undefined) searchParams.set("min_projects", String(params.minProjects)); if (params.limit !== undefined) searchParams.set("limit", String(params.limit)); const query = searchParams.toString(); return this.fetch(`/v1/aphoria/patterns${query ? `?${query}` : ""}`); } async runScan(request: ScanRequest): Promise { return this.fetch("/v1/aphoria/scan", { method: "POST", body: JSON.stringify(request), }); } async listScans(): Promise { return this.fetch("/v1/aphoria/scans"); } // Claims Management methods async listClaims(request: ListClaimsRequest): Promise { return this.fetch("/v1/aphoria/claims/list", { method: "POST", body: JSON.stringify(request), }); } async createClaim(request: CreateClaimRequest): Promise { return this.fetch("/v1/aphoria/claims/create", { method: "POST", body: JSON.stringify(request), }); } async updateClaim(request: UpdateClaimRequest): Promise { return this.fetch("/v1/aphoria/claims/update", { method: "POST", body: JSON.stringify(request), }); } async deprecateClaim(request: DeprecateClaimRequest): Promise { return this.fetch("/v1/aphoria/claims/deprecate", { method: "POST", body: JSON.stringify(request), }); } async verifyClaims(request: VerifyClaimsRequest): Promise { return this.fetch("/v1/aphoria/claims/verify", { method: "POST", body: JSON.stringify(request), }); } async getCoverage(request: CoverageRequest): Promise { return this.fetch("/v1/aphoria/claims/coverage", { method: "POST", body: JSON.stringify(request), }); } async acknowledgeViolation( request: AcknowledgeViolationRequest ): Promise { return this.fetch("/v1/aphoria/claims/acknowledge", { method: "POST", body: JSON.stringify(request), }); } } // Singleton client for server components let _client: StemeDBClient | null = null; export function getClient(): StemeDBClient { if (!_client) { _client = new StemeDBClient(); } return _client; }