/** Version manager tool state persistence. */ import { getDbInstance } from "./core"; interface VersionManagerRow { id?: unknown; tool?: unknown; current_version?: unknown; installed_version?: unknown; pinned_version?: unknown; binary_path?: unknown; status?: unknown; pid?: unknown; port?: unknown; api_key?: unknown; management_key?: unknown; auto_update?: unknown; auto_start?: unknown; last_health_check?: unknown; last_update_check?: unknown; health_status?: unknown; config_overrides?: unknown; error_message?: unknown; created_at?: unknown; updated_at?: unknown; } function toRecord(value: unknown): Record { return value && typeof value === "object" ? (value as Record) : {}; } function parseConfigOverrides(value: unknown): Record | null { if (!value || typeof value !== "string" || value.trim() === "") return null; try { const parsed = JSON.parse(value); return typeof parsed === "object" && parsed !== null ? parsed : null; } catch { return null; } } function stringifyConfigOverrides(value: Record | null): string | null { if (value === null) return null; try { return JSON.stringify(value); } catch { return null; } } interface VersionManagerTool { id: number; tool: string; currentVersion: string | null; installedVersion: string | null; pinnedVersion: string | null; binaryPath: string | null; status: string; pid: number | null; port: number; apiKey: string | null; managementKey: string | null; autoUpdate: boolean; autoStart: boolean; lastHealthCheck: string | null; lastUpdateCheck: string | null; healthStatus: string; configOverrides: Record | null; errorMessage: string | null; createdAt: string; updatedAt: string; } function rowToVersionManager(row: VersionManagerRow): VersionManagerTool { const record = toRecord(row); return { id: typeof record.id === "number" ? record.id : 0, tool: typeof record.tool === "string" ? record.tool : "", currentVersion: record.current_version === null ? null : typeof record.current_version === "string" ? record.current_version : null, installedVersion: record.installed_version === null ? null : typeof record.installed_version === "string" ? record.installed_version : null, pinnedVersion: record.pinned_version === null ? null : typeof record.pinned_version === "string" ? record.pinned_version : null, binaryPath: record.binary_path === null ? null : typeof record.binary_path === "string" ? record.binary_path : null, status: typeof record.status === "string" ? record.status : "not_installed", pid: record.pid === null ? null : typeof record.pid === "number" ? record.pid : null, port: typeof record.port === "number" ? record.port : 8317, apiKey: record.api_key === null ? null : typeof record.api_key === "string" ? record.api_key : null, managementKey: record.management_key === null ? null : typeof record.management_key === "string" ? record.management_key : null, autoUpdate: record.auto_update === 1 || record.auto_update === true || record.auto_update === "1", autoStart: record.auto_start === 1 || record.auto_start === true || record.auto_start === "1", lastHealthCheck: record.last_health_check === null ? null : typeof record.last_health_check === "string" ? record.last_health_check : null, lastUpdateCheck: record.last_update_check === null ? null : typeof record.last_update_check === "string" ? record.last_update_check : null, healthStatus: typeof record.health_status === "string" ? record.health_status : "unknown", configOverrides: parseConfigOverrides(record.config_overrides), errorMessage: record.error_message === null ? null : typeof record.error_message === "string" ? record.error_message : null, createdAt: typeof record.created_at === "string" ? record.created_at : "", updatedAt: typeof record.updated_at === "string" ? record.updated_at : "", }; } export async function getVersionManagerStatus(): Promise { const db = getDbInstance(); const rows = db.prepare("SELECT * FROM version_manager").all() as VersionManagerRow[]; return rows.map(rowToVersionManager); } export async function getVersionManagerTool(tool: string): Promise { const db = getDbInstance(); const row = db.prepare("SELECT * FROM version_manager WHERE tool = ?").get(tool) as | VersionManagerRow | undefined; if (!row) return null; return rowToVersionManager(row); } export async function upsertVersionManagerTool(data: { tool: string; currentVersion?: string | null; installedVersion?: string | null; pinnedVersion?: string | null; binaryPath?: string | null; status?: string; pid?: number | null; port?: number; apiKey?: string | null; managementKey?: string | null; autoUpdate?: boolean; autoStart?: boolean; healthStatus?: string; configOverrides?: Record | null; errorMessage?: string | null; }): Promise { const db = getDbInstance(); db.prepare( ` INSERT INTO version_manager ( tool, current_version, installed_version, pinned_version, binary_path, status, pid, port, api_key, management_key, auto_update, auto_start, health_status, config_overrides, error_message, created_at, updated_at ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now'), datetime('now')) ON CONFLICT(tool) DO UPDATE SET current_version = excluded.current_version, installed_version = excluded.installed_version, pinned_version = excluded.pinned_version, binary_path = excluded.binary_path, status = excluded.status, pid = excluded.pid, port = excluded.port, api_key = excluded.api_key, management_key = excluded.management_key, auto_update = excluded.auto_update, auto_start = excluded.auto_start, health_status = excluded.health_status, config_overrides = excluded.config_overrides, error_message = excluded.error_message, updated_at = datetime('now') ` ).run( data.tool, data.currentVersion ?? null, data.installedVersion ?? null, data.pinnedVersion ?? null, data.binaryPath ?? null, data.status ?? "not_installed", data.pid ?? null, data.port ?? 8317, data.apiKey ?? null, data.managementKey ?? null, data.autoUpdate !== undefined ? (data.autoUpdate ? 1 : 0) : 1, data.autoStart !== undefined ? (data.autoStart ? 1 : 0) : 0, data.healthStatus ?? "unknown", stringifyConfigOverrides(data.configOverrides ?? null), data.errorMessage ?? null ); const result = await getVersionManagerTool(data.tool); if (!result) throw new Error("Failed to retrieve inserted version manager tool"); return result; } export async function updateVersionManagerTool( tool: string, updates: Record ): Promise { const db = getDbInstance(); const existing = await getVersionManagerTool(tool); if (!existing) return null; const ALLOWED_COLUMNS = new Set([ "currentVersion", "installedVersion", "pinnedVersion", "binaryPath", "status", "pid", "port", "apiKey", "managementKey", "autoUpdate", "autoStart", "healthStatus", "configOverrides", "errorMessage", ]); const sets: string[] = ["updated_at = datetime('now')"]; const params: Record = { tool }; for (const [key, value] of Object.entries(updates)) { if (!ALLOWED_COLUMNS.has(key)) continue; const dbKey = key.replace(/([A-Z])/g, "_$1").toLowerCase(); if (key === "configOverrides") { sets.push("config_overrides = @configOverrides"); params.configOverrides = stringifyConfigOverrides(value as Record | null); } else if (key === "autoUpdate" || key === "autoStart") { sets.push(`${dbKey} = @${key}`); params[key] = value === true ? 1 : 0; } else if (value === null) { sets.push(`${dbKey} = null`); } else { sets.push(`${dbKey} = @${key}`); params[key] = value; } } db.prepare(`UPDATE version_manager SET ${sets.join(", ")} WHERE tool = @tool`).run(params); return getVersionManagerTool(tool); } export async function deleteVersionManagerTool(tool: string): Promise { const db = getDbInstance(); const result = db.prepare("DELETE FROM version_manager WHERE tool = ?").run(tool); return result.changes > 0; } export async function updateToolHealth(tool: string, healthStatus: string): Promise { const db = getDbInstance(); const result = db .prepare( "UPDATE version_manager SET health_status = ?, last_health_check = datetime('now') WHERE tool = ?" ) .run(healthStatus, tool); return result.changes > 0; } export async function updateToolVersion( tool: string, field: "current_version" | "installed_version", version: string ): Promise { const db = getDbInstance(); const result = db .prepare(`UPDATE version_manager SET ${field} = ?, updated_at = datetime('now') WHERE tool = ?`) .run(version, tool); return result.changes > 0; } export async function setToolStatus( tool: string, status: string, pid?: number, errorMessage?: string ): Promise { const db = getDbInstance(); const result = db .prepare( pid !== undefined ? "UPDATE version_manager SET status = ?, pid = ?, error_message = ?, updated_at = datetime('now') WHERE tool = ?" : "UPDATE version_manager SET status = ?, error_message = ?, updated_at = datetime('now') WHERE tool = ?" ) .run( ...(pid !== undefined ? [status, pid, errorMessage ?? null, tool] : [status, errorMessage ?? null, tool]) ); return result.changes > 0; }