/** * Compliance Client * * Main entry point for the SDK. Domain methods delegate to HttpTransport * for retry/timeout/abort handling. Transport primitives live in client-http.ts. */ import type { APIResponse, StateResponse, CheckpointValidationResult, AuthTokenRequest, AuthTokenResponse, RAGSearchRequest, RAGSearchResponse, RAGAskRequest, RAGAskResponse, ExportFormat, SDKState, CheckpointStatus, } from '@breakpilot/compliance-sdk-types' import { HttpTransport, createHttpError, type APIError, } from './client-http' // ============================================================================= // TYPES // ============================================================================= export interface ComplianceClientOptions { apiEndpoint: string apiKey?: string tenantId: string timeout?: number maxRetries?: number onError?: (error: Error) => void onAuthError?: () => void } // ============================================================================= // COMPLIANCE CLIENT // ============================================================================= export class ComplianceClient { private http: HttpTransport constructor(options: ComplianceClientOptions) { this.http = new HttpTransport({ apiEndpoint: options.apiEndpoint, tenantId: options.tenantId, timeout: options.timeout, maxRetries: options.maxRetries, onError: options.onError, onAuthError: options.onAuthError, }) if (options.apiKey) { this.http.setApiKey(options.apiKey) } } private get apiEndpoint(): string { return this.http.apiEndpoint } private get tenantId(): string { return this.http.getTenantId() } // --------------------------------------------------------------------------- // Authentication // --------------------------------------------------------------------------- async authenticate(request: AuthTokenRequest): Promise { const response = await this.http.fetchWithRetry>( `${this.apiEndpoint}/auth/token`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(request), } ) if (response.success && response.data) { this.http.setAccessToken(response.data.accessToken) return response.data } throw createHttpError(response.error || 'Authentication failed', 401, false) } async refreshToken(refreshToken: string): Promise { return this.authenticate({ grantType: 'refresh_token', clientId: '', refreshToken, }) } setAccessToken(token: string): void { this.http.setAccessToken(token) } clearAccessToken(): void { this.http.setAccessToken(null) } // --------------------------------------------------------------------------- // State Management // --------------------------------------------------------------------------- async getState(): Promise { try { const response = await this.http.fetchWithRetry>( `${this.apiEndpoint}/state?tenantId=${encodeURIComponent(this.tenantId)}`, { method: 'GET', headers: this.http.getHeaders(), } ) if (response.success && response.data) { return response.data } return null } catch (error) { const apiError = error as APIError if (apiError.status === 404) { return null } throw error } } async saveState(state: SDKState, version?: number): Promise { const response = await this.http.fetchWithRetry>( `${this.apiEndpoint}/state`, { method: 'POST', headers: { ...this.http.getHeaders(), ...(version !== undefined && { 'If-Match': String(version) }), }, body: JSON.stringify({ tenantId: this.tenantId, state, version, }), } ) if (!response.success) { throw createHttpError(response.error || 'Failed to save state', 500, true) } return response.data! } async deleteState(): Promise { await this.http.fetchWithRetry>( `${this.apiEndpoint}/state?tenantId=${encodeURIComponent(this.tenantId)}`, { method: 'DELETE', headers: this.http.getHeaders(), } ) } // --------------------------------------------------------------------------- // Checkpoints // --------------------------------------------------------------------------- async validateCheckpoint( checkpointId: string, data?: unknown ): Promise { const response = await this.http.fetchWithRetry< APIResponse >(`${this.apiEndpoint}/checkpoints/validate`, { method: 'POST', headers: this.http.getHeaders(), body: JSON.stringify({ tenantId: this.tenantId, checkpointId, data, }), }) if (!response.success || !response.data) { throw createHttpError(response.error || 'Checkpoint validation failed', 500, true) } return response.data } async getCheckpoints(): Promise> { const response = await this.http.fetchWithRetry< APIResponse> >(`${this.apiEndpoint}/checkpoints?tenantId=${encodeURIComponent(this.tenantId)}`, { method: 'GET', headers: this.http.getHeaders(), }) return response.data || {} } // --------------------------------------------------------------------------- // RAG // --------------------------------------------------------------------------- async searchRAG(request: RAGSearchRequest): Promise { const response = await this.http.fetchWithRetry>( `${this.apiEndpoint}/rag/search`, { method: 'POST', headers: this.http.getHeaders(), body: JSON.stringify(request), } ) if (!response.success || !response.data) { throw createHttpError(response.error || 'RAG search failed', 500, true) } return response.data } async askRAG(request: RAGAskRequest): Promise { const response = await this.http.fetchWithRetry>( `${this.apiEndpoint}/rag/ask`, { method: 'POST', headers: this.http.getHeaders(), body: JSON.stringify(request), } ) if (!response.success || !response.data) { throw createHttpError(response.error || 'RAG query failed', 500, true) } return response.data } // --------------------------------------------------------------------------- // Export // --------------------------------------------------------------------------- async exportState(format: ExportFormat): Promise { const response = await this.http.fetchWithTimeout( `${this.apiEndpoint}/export?tenantId=${encodeURIComponent(this.tenantId)}&format=${format}`, { method: 'GET', headers: { ...this.http.getHeaders(), Accept: format === 'json' ? 'application/json' : format === 'pdf' ? 'application/pdf' : 'application/octet-stream', }, }, `export-${Date.now()}` ) if (!response.ok) { throw createHttpError(`Export failed: ${response.statusText}`, response.status, true) } return response.blob() } // --------------------------------------------------------------------------- // Document Generation // --------------------------------------------------------------------------- async generateDocument( type: 'dsfa' | 'tom' | 'vvt' | 'gutachten' | 'privacy_policy' | 'cookie_banner', options?: Record ): Promise<{ id: string; status: string; content?: string }> { const response = await this.http.fetchWithRetry< APIResponse<{ id: string; status: string; content?: string }> >(`${this.apiEndpoint}/generate/${type}`, { method: 'POST', headers: this.http.getHeaders(), body: JSON.stringify({ tenantId: this.tenantId, options, }), }) if (!response.success || !response.data) { throw createHttpError(response.error || 'Document generation failed', 500, true) } return response.data } // --------------------------------------------------------------------------- // Security Scan // --------------------------------------------------------------------------- async startSecurityScan(options?: { tools?: string[] targetPath?: string severityThreshold?: string generateSBOM?: boolean }): Promise<{ id: string; status: string }> { const response = await this.http.fetchWithRetry< APIResponse<{ id: string; status: string }> >(`${this.apiEndpoint}/security/scan`, { method: 'POST', headers: this.http.getHeaders(), body: JSON.stringify({ tenantId: this.tenantId, ...options, }), }) if (!response.success || !response.data) { throw createHttpError(response.error || 'Security scan failed', 500, true) } return response.data } async getSecurityScanResult(scanId: string): Promise { const response = await this.http.fetchWithRetry>( `${this.apiEndpoint}/security/scan/${scanId}`, { method: 'GET', headers: this.http.getHeaders(), } ) return response.data } // --------------------------------------------------------------------------- // Utility // --------------------------------------------------------------------------- cancelAllRequests(): void { this.http.cancelAllRequests() } setTenantId(tenantId: string): void { this.http.setTenantId(tenantId) } getTenantId(): string { return this.http.getTenantId() } async healthCheck(): Promise { try { const response = await this.http.fetchWithTimeout( `${this.apiEndpoint}/health`, { method: 'GET' }, `health-${Date.now()}` ) return response.ok } catch { return false } } } // ============================================================================= // FACTORY // ============================================================================= let clientInstance: ComplianceClient | null = null export function getComplianceClient(options?: ComplianceClientOptions): ComplianceClient { if (!clientInstance && !options) { throw new Error('ComplianceClient not initialized. Provide options on first call.') } if (!clientInstance && options) { clientInstance = new ComplianceClient(options) } return clientInstance! } export function resetComplianceClient(): void { if (clientInstance) { clientInstance.cancelAllRequests() } clientInstance = null }