Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
212 changes: 8 additions & 204 deletions src/lib/db/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,21 @@
* All domain modules import `getDbInstance` and helpers from here.
*/

import type { SqliteAdapter } from "./adapters/types";

Check failure on line 7 in src/lib/db/core.ts

View workflow job for this annotation

GitHub Actions / dast-smoke

Relative import paths need explicit file extensions in ECMAScript imports when '--moduleResolution' is 'node16' or 'nodenext'. Did you mean './adapters/types.js'?
import {
tryOpenSync,
getSqlJsAdapter,
preInitSqlJs,
openDatabaseAsync,
} from "./adapters/driverFactory";

Check failure on line 13 in src/lib/db/core.ts

View workflow job for this annotation

GitHub Actions / dast-smoke

Relative import paths need explicit file extensions in ECMAScript imports when '--moduleResolution' is 'node16' or 'nodenext'. Did you mean './adapters/driverFactory.js'?
import path from "path";
import fs from "fs";
import { resolveDataDir, getLegacyDotDataDir } from "../dataPaths";

Check failure on line 16 in src/lib/db/core.ts

View workflow job for this annotation

GitHub Actions / dast-smoke

Relative import paths need explicit file extensions in ECMAScript imports when '--moduleResolution' is 'node16' or 'nodenext'. Did you mean '../dataPaths.js'?
import { runMigrations } from "./migrationRunner";

Check failure on line 17 in src/lib/db/core.ts

View workflow job for this annotation

GitHub Actions / dast-smoke

Relative import paths need explicit file extensions in ECMAScript imports when '--moduleResolution' is 'node16' or 'nodenext'. Did you mean './migrationRunner.js'?
import { runDbHealthCheck } from "./healthCheck";

Check failure on line 18 in src/lib/db/core.ts

View workflow job for this annotation

GitHub Actions / dast-smoke

Relative import paths need explicit file extensions in ECMAScript imports when '--moduleResolution' is 'node16' or 'nodenext'. Did you mean './healthCheck.js'?
import { resetAllDbModuleState } from "./stateReset";

Check failure on line 19 in src/lib/db/core.ts

View workflow job for this annotation

GitHub Actions / dast-smoke

Relative import paths need explicit file extensions in ECMAScript imports when '--moduleResolution' is 'node16' or 'nodenext'. Did you mean './stateReset.js'?
import { parseStoredPayload } from "../logPayloads";

Check failure on line 20 in src/lib/db/core.ts

View workflow job for this annotation

GitHub Actions / dast-smoke

Relative import paths need explicit file extensions in ECMAScript imports when '--moduleResolution' is 'node16' or 'nodenext'. Did you mean '../logPayloads.js'?
import { DEFAULT_DATABASE_SETTINGS, type DatabaseSettings } from "@/types/databaseSettings";

Check failure on line 21 in src/lib/db/core.ts

View workflow job for this annotation

GitHub Actions / dast-smoke

Cannot find module '@/types/databaseSettings' or its corresponding type declarations.
import {
applyDatabaseOptimizationSettingsForDb,
applyStoredDatabaseOptimizationSettings,
Expand All @@ -37,6 +37,14 @@
import { rowToCamel } from "./caseMapping";
// Re-exported so existing call sites that pull these helpers off the core module keep working.
export { toSnakeCase, toCamelCase, objToSnake, rowToCamel, cleanNulls } from "./caseMapping";
import {
ensureProviderConnectionsColumns,
ensureUsageHistoryColumns,
ensureCallLogsColumns,
hasTable,
quoteIdentifier,
getTableColumns,
} from "./schemaColumns";

type SqliteDatabase = SqliteAdapter;
type JsonRecord = Record<string, unknown>;
Expand Down Expand Up @@ -473,210 +481,6 @@
return true;
}

function ensureProviderConnectionsColumns(db: SqliteDatabase) {
try {
const columns = db.prepare("PRAGMA table_info(provider_connections)").all() as Array<{
name?: string;
}>;
const columnNames = new Set(columns.map((column) => String(column.name ?? "")));
if (!columnNames.has("rate_limit_protection")) {
db.exec(
"ALTER TABLE provider_connections ADD COLUMN rate_limit_protection INTEGER DEFAULT 0"
);
console.log("[DB] Added provider_connections.rate_limit_protection column");
}
if (!columnNames.has("last_used_at")) {
db.exec("ALTER TABLE provider_connections ADD COLUMN last_used_at TEXT");
console.log("[DB] Added provider_connections.last_used_at column");
}
if (!columnNames.has("group")) {
db.exec('ALTER TABLE provider_connections ADD COLUMN "group" TEXT');
console.log('[DB] Added provider_connections."group" column');
}
if (!columnNames.has("max_concurrent")) {
db.exec("ALTER TABLE provider_connections ADD COLUMN max_concurrent INTEGER");
console.log("[DB] Added provider_connections.max_concurrent column");
}
if (!columnNames.has("proxy_enabled")) {
db.exec(
"ALTER TABLE provider_connections ADD COLUMN proxy_enabled INTEGER NOT NULL DEFAULT 1"
);
console.log("[DB] Added provider_connections.proxy_enabled column");
}
if (!columnNames.has("per_key_proxy_enabled")) {
db.exec(
"ALTER TABLE provider_connections ADD COLUMN per_key_proxy_enabled INTEGER NOT NULL DEFAULT 0"
);
console.log("[DB] Added provider_connections.per_key_proxy_enabled column");
}
if (!columnNames.has("quota_window_thresholds_json")) {
db.exec("ALTER TABLE provider_connections ADD COLUMN quota_window_thresholds_json TEXT");
console.log("[DB] Added provider_connections.quota_window_thresholds_json column");
}
if (!columnNames.has("rate_limit_overrides_json")) {
db.exec("ALTER TABLE provider_connections ADD COLUMN rate_limit_overrides_json TEXT");
console.log("[DB] Added provider_connections.rate_limit_overrides_json column");
}
db.exec(
"CREATE INDEX IF NOT EXISTS idx_pc_max_concurrent ON provider_connections(provider, max_concurrent)"
);
} catch (error: unknown) {
const message = error instanceof Error ? error.message : String(error);
console.warn("[DB] Failed to verify provider_connections schema:", message);
}
}

function ensureUsageHistoryColumns(db: SqliteDatabase) {
try {
const columns = db.prepare("PRAGMA table_info(usage_history)").all() as Array<{
name?: string;
}>;
const columnNames = new Set(columns.map((column) => String(column.name ?? "")));

if (!columnNames.has("success")) {
db.exec("ALTER TABLE usage_history ADD COLUMN success INTEGER DEFAULT 1");
console.log("[DB] Added usage_history.success column");
}
if (!columnNames.has("latency_ms")) {
db.exec("ALTER TABLE usage_history ADD COLUMN latency_ms INTEGER DEFAULT 0");
console.log("[DB] Added usage_history.latency_ms column");
}
if (!columnNames.has("ttft_ms")) {
db.exec("ALTER TABLE usage_history ADD COLUMN ttft_ms INTEGER DEFAULT 0");
console.log("[DB] Added usage_history.ttft_ms column");
}
if (!columnNames.has("error_code")) {
db.exec("ALTER TABLE usage_history ADD COLUMN error_code TEXT");
console.log("[DB] Added usage_history.error_code column");
}
if (!columnNames.has("service_tier")) {
db.exec("ALTER TABLE usage_history ADD COLUMN service_tier TEXT DEFAULT 'standard'");
console.log("[DB] Added usage_history.service_tier column");
}
db.exec("CREATE INDEX IF NOT EXISTS idx_uh_service_tier ON usage_history(service_tier)");
if (!columnNames.has("combo_strategy")) {
db.exec("ALTER TABLE usage_history ADD COLUMN combo_strategy TEXT DEFAULT 'direct'");
console.log("[DB] Added usage_history.combo_strategy column");
}
db.exec("CREATE INDEX IF NOT EXISTS idx_uh_combo_strategy ON usage_history(combo_strategy)");
} catch (error: unknown) {
const message = error instanceof Error ? error.message : String(error);
console.warn("[DB] Failed to verify usage_history schema:", message);
}
}

function ensureCallLogsColumns(db: SqliteDatabase) {
try {
const columns = db.prepare("PRAGMA table_info(call_logs)").all() as Array<{
name?: string;
}>;
const columnNames = new Set(columns.map((column) => String(column.name ?? "")));

if (!columnNames.has("artifact_relpath")) {
db.exec("ALTER TABLE call_logs ADD COLUMN artifact_relpath TEXT");
console.log("[DB] Added call_logs.artifact_relpath column");
}
if (!columnNames.has("has_pipeline_details")) {
db.exec("ALTER TABLE call_logs ADD COLUMN has_pipeline_details INTEGER DEFAULT 0");
console.log("[DB] Added call_logs.has_pipeline_details column");
}
if (!columnNames.has("requested_model")) {
db.exec("ALTER TABLE call_logs ADD COLUMN requested_model TEXT DEFAULT NULL");
console.log("[DB] Added call_logs.requested_model column");
}
if (!columnNames.has("request_type")) {
db.exec("ALTER TABLE call_logs ADD COLUMN request_type TEXT DEFAULT NULL");
console.log("[DB] Added call_logs.request_type column");
}
if (!columnNames.has("tokens_cache_read")) {
db.exec("ALTER TABLE call_logs ADD COLUMN tokens_cache_read INTEGER DEFAULT NULL");
console.log("[DB] Added call_logs.tokens_cache_read column");
}
if (!columnNames.has("tokens_cache_creation")) {
db.exec("ALTER TABLE call_logs ADD COLUMN tokens_cache_creation INTEGER DEFAULT NULL");
console.log("[DB] Added call_logs.tokens_cache_creation column");
}
if (!columnNames.has("tokens_reasoning")) {
db.exec("ALTER TABLE call_logs ADD COLUMN tokens_reasoning INTEGER DEFAULT NULL");
console.log("[DB] Added call_logs.tokens_reasoning column");
}
if (!columnNames.has("cache_source")) {
db.exec("ALTER TABLE call_logs ADD COLUMN cache_source TEXT DEFAULT 'upstream'");
console.log("[DB] Added call_logs.cache_source column");
}
if (!columnNames.has("combo_step_id")) {
db.exec("ALTER TABLE call_logs ADD COLUMN combo_step_id TEXT DEFAULT NULL");
console.log("[DB] Added call_logs.combo_step_id column");
}
if (!columnNames.has("combo_execution_key")) {
db.exec("ALTER TABLE call_logs ADD COLUMN combo_execution_key TEXT DEFAULT NULL");
console.log("[DB] Added call_logs.combo_execution_key column");
}
if (!columnNames.has("error_summary")) {
db.exec("ALTER TABLE call_logs ADD COLUMN error_summary TEXT DEFAULT NULL");
console.log("[DB] Added call_logs.error_summary column");
}
if (!columnNames.has("detail_state")) {
db.exec("ALTER TABLE call_logs ADD COLUMN detail_state TEXT DEFAULT 'none'");
console.log("[DB] Added call_logs.detail_state column");
}
if (!columnNames.has("artifact_size_bytes")) {
db.exec("ALTER TABLE call_logs ADD COLUMN artifact_size_bytes INTEGER DEFAULT NULL");
console.log("[DB] Added call_logs.artifact_size_bytes column");
}
if (!columnNames.has("artifact_sha256")) {
db.exec("ALTER TABLE call_logs ADD COLUMN artifact_sha256 TEXT DEFAULT NULL");
console.log("[DB] Added call_logs.artifact_sha256 column");
}
if (!columnNames.has("has_request_body")) {
db.exec("ALTER TABLE call_logs ADD COLUMN has_request_body INTEGER DEFAULT 0");
console.log("[DB] Added call_logs.has_request_body column");
}
if (!columnNames.has("has_response_body")) {
db.exec("ALTER TABLE call_logs ADD COLUMN has_response_body INTEGER DEFAULT 0");
console.log("[DB] Added call_logs.has_response_body column");
}
if (!columnNames.has("request_summary")) {
db.exec("ALTER TABLE call_logs ADD COLUMN request_summary TEXT DEFAULT NULL");
console.log("[DB] Added call_logs.request_summary column");
}

db.exec(
"CREATE INDEX IF NOT EXISTS idx_call_logs_requested_model ON call_logs(requested_model)"
);
db.exec("CREATE INDEX IF NOT EXISTS idx_call_logs_request_type ON call_logs(request_type)");
db.exec(
"CREATE INDEX IF NOT EXISTS idx_cl_combo_target ON call_logs(combo_name, combo_execution_key, timestamp)"
);
} catch (error: unknown) {
const message = error instanceof Error ? error.message : String(error);
console.warn("[DB] Failed to verify call_logs schema:", message);
}
}

function hasColumn(db: SqliteDatabase, tableName: string, columnName: string): boolean {
const rows = db.prepare(`PRAGMA table_info(${tableName})`).all() as Array<{ name?: string }>;
return rows.some((row) => row.name === columnName);
}

function hasTable(db: SqliteDatabase, tableName: string): boolean {
return Boolean(
db.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ?").get(tableName)
);
}

function quoteIdentifier(identifier: string): string {
return `"${identifier.replace(/"/g, '""')}"`;
}

function getTableColumns(db: SqliteDatabase, tableName: string): string[] {
return (
db.prepare(`PRAGMA table_info(${quoteIdentifier(tableName)})`).all() as Array<{ name?: string }>
)
.map((column) => String(column.name ?? ""))
.filter((column) => column.length > 0);
}

function summarizePreservedTables(tables: PreservedTableSnapshot[]): string {
if (tables.length === 0) return "none";
return tables.map((table) => `${table.table}(${table.rowCount})`).join(", ");
Expand Down
Loading
Loading