mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-03 22:02:08 +03:00
T-15 — Decompose usageDb.js (969→40 lines): - Extract src/lib/usage/migrations.js (legacy + JSON→SQLite migration) - Extract src/lib/usage/usageHistory.js (tracking, pending, log.txt) - Extract src/lib/usage/costCalculator.js (pure cost calculation) - Extract src/lib/usage/usageStats.js (dashboard aggregation) - Extract src/lib/usage/callLogs.js (structured logs, CRUD, rotation) - usageDb.js is now a thin facade re-exporting all functions T-28 — Decompose handleSingleModelChat (183→80 lines): - Extract handleNoCredentials() — credential error responses - Extract safeResolveProxy() — proxy resolution with error handling - Extract safeLogEvents() — fire-and-forget proxy + translation logging - Also created chatHelpers.js with standalone helper exports T-29 — Extract shared UI primitives (3230 total lines): - FilterBar.js — search input + filter chips dropdown - ColumnToggle.js — table column visibility toggle - DataTable.js — generic data table with sticky header, loading/empty Tests: 88/88 pass (no regressions)
187 lines
7.5 KiB
JavaScript
187 lines
7.5 KiB
JavaScript
/**
|
|
* Usage Migrations — extracted from usageDb.js (T-15)
|
|
*
|
|
* Handles legacy file migration (.data → data/) and JSON → SQLite migration.
|
|
* Runs automatically on module load when shouldPersistToDisk is true.
|
|
*
|
|
* @module lib/usage/migrations
|
|
*/
|
|
|
|
import path from "path";
|
|
import fs from "fs";
|
|
import { getDbInstance, isCloud, isBuildPhase, DATA_DIR } from "../db/core.js";
|
|
import { getLegacyDotDataDir, isSamePath } from "../dataPaths.js";
|
|
|
|
export const shouldPersistToDisk = !isCloud && !isBuildPhase;
|
|
|
|
// ──────────────── File Paths ────────────────
|
|
|
|
const LEGACY_DATA_DIR = isCloud ? null : getLegacyDotDataDir();
|
|
|
|
export const LOG_FILE = isCloud ? null : path.join(DATA_DIR, "log.txt");
|
|
export const CALL_LOGS_DIR = isCloud ? null : path.join(DATA_DIR, "call_logs");
|
|
|
|
// Legacy paths
|
|
const LEGACY_DB_FILE =
|
|
isCloud || !LEGACY_DATA_DIR ? null : path.join(LEGACY_DATA_DIR, "usage.json");
|
|
const LEGACY_LOG_FILE =
|
|
isCloud || !LEGACY_DATA_DIR ? null : path.join(LEGACY_DATA_DIR, "log.txt");
|
|
const LEGACY_CALL_LOGS_DB_FILE =
|
|
isCloud || !LEGACY_DATA_DIR ? null : path.join(LEGACY_DATA_DIR, "call_logs.json");
|
|
const LEGACY_CALL_LOGS_DIR =
|
|
isCloud || !LEGACY_DATA_DIR ? null : path.join(LEGACY_DATA_DIR, "call_logs");
|
|
|
|
// Current-location JSON files (for migration into SQLite)
|
|
const USAGE_JSON_FILE = isCloud ? null : path.join(DATA_DIR, "usage.json");
|
|
const CALL_LOGS_JSON_FILE = isCloud ? null : path.join(DATA_DIR, "call_logs.json");
|
|
|
|
// ──────────────── Legacy File Migration ────────────────
|
|
|
|
function copyIfMissing(fromPath, toPath, label) {
|
|
if (!fromPath || !toPath) return;
|
|
if (!fs.existsSync(fromPath) || fs.existsSync(toPath)) return;
|
|
|
|
if (fs.statSync(fromPath).isDirectory()) {
|
|
fs.cpSync(fromPath, toPath, { recursive: true });
|
|
} else {
|
|
fs.copyFileSync(fromPath, toPath);
|
|
}
|
|
console.log(`[usageDb] Migrated ${label}: ${fromPath} -> ${toPath}`);
|
|
}
|
|
|
|
export function migrateLegacyUsageFiles() {
|
|
if (!shouldPersistToDisk || !LEGACY_DATA_DIR) return;
|
|
if (isSamePath(DATA_DIR, LEGACY_DATA_DIR)) return;
|
|
|
|
try {
|
|
copyIfMissing(LEGACY_DB_FILE, USAGE_JSON_FILE, "usage history");
|
|
copyIfMissing(LEGACY_LOG_FILE, LOG_FILE, "request log");
|
|
copyIfMissing(LEGACY_CALL_LOGS_DB_FILE, CALL_LOGS_JSON_FILE, "call log index");
|
|
copyIfMissing(LEGACY_CALL_LOGS_DIR, CALL_LOGS_DIR, "call log files");
|
|
} catch (error) {
|
|
console.error("[usageDb] Legacy migration failed:", error.message);
|
|
}
|
|
}
|
|
|
|
// ──────────────── JSON → SQLite Migration ────────────────
|
|
|
|
export function migrateUsageJsonToSqlite() {
|
|
if (!shouldPersistToDisk) return;
|
|
const db = getDbInstance();
|
|
|
|
// 1. Migrate usage.json
|
|
if (USAGE_JSON_FILE && fs.existsSync(USAGE_JSON_FILE)) {
|
|
try {
|
|
const raw = fs.readFileSync(USAGE_JSON_FILE, "utf-8");
|
|
const data = JSON.parse(raw);
|
|
const history = data.history || [];
|
|
|
|
if (history.length > 0) {
|
|
console.log(`[usageDb] Migrating ${history.length} usage entries from JSON → SQLite...`);
|
|
|
|
const insert = db.prepare(`
|
|
INSERT INTO usage_history (provider, model, connection_id, api_key_id, api_key_name,
|
|
tokens_input, tokens_output, tokens_cache_read, tokens_cache_creation, tokens_reasoning,
|
|
status, timestamp)
|
|
VALUES (@provider, @model, @connectionId, @apiKeyId, @apiKeyName,
|
|
@tokensInput, @tokensOutput, @tokensCacheRead, @tokensCacheCreation, @tokensReasoning,
|
|
@status, @timestamp)
|
|
`);
|
|
|
|
const tx = db.transaction(() => {
|
|
for (const entry of history) {
|
|
insert.run({
|
|
provider: entry.provider || null,
|
|
model: entry.model || null,
|
|
connectionId: entry.connectionId || null,
|
|
apiKeyId: entry.apiKeyId || null,
|
|
apiKeyName: entry.apiKeyName || null,
|
|
tokensInput: entry.tokens?.input ?? entry.tokens?.prompt_tokens ?? 0,
|
|
tokensOutput: entry.tokens?.output ?? entry.tokens?.completion_tokens ?? 0,
|
|
tokensCacheRead: entry.tokens?.cacheRead ?? entry.tokens?.cached_tokens ?? 0,
|
|
tokensCacheCreation:
|
|
entry.tokens?.cacheCreation ?? entry.tokens?.cache_creation_input_tokens ?? 0,
|
|
tokensReasoning: entry.tokens?.reasoning ?? entry.tokens?.reasoning_tokens ?? 0,
|
|
status: entry.status || null,
|
|
timestamp: entry.timestamp || new Date().toISOString(),
|
|
});
|
|
}
|
|
});
|
|
tx();
|
|
console.log(`[usageDb] ✓ Migrated ${history.length} usage entries`);
|
|
}
|
|
|
|
fs.renameSync(USAGE_JSON_FILE, USAGE_JSON_FILE + ".migrated");
|
|
} catch (err) {
|
|
console.error("[usageDb] Failed to migrate usage.json:", err.message);
|
|
}
|
|
}
|
|
|
|
// 2. Migrate call_logs.json
|
|
if (CALL_LOGS_JSON_FILE && fs.existsSync(CALL_LOGS_JSON_FILE)) {
|
|
try {
|
|
const raw = fs.readFileSync(CALL_LOGS_JSON_FILE, "utf-8");
|
|
const data = JSON.parse(raw);
|
|
const logs = data.logs || [];
|
|
|
|
if (logs.length > 0) {
|
|
console.log(`[usageDb] Migrating ${logs.length} call log entries from JSON → SQLite...`);
|
|
|
|
const insert = db.prepare(`
|
|
INSERT OR IGNORE INTO call_logs (id, timestamp, method, path, status, model, provider,
|
|
account, connection_id, duration, tokens_in, tokens_out, source_format, target_format,
|
|
api_key_id, api_key_name, combo_name, request_body, response_body, error)
|
|
VALUES (@id, @timestamp, @method, @path, @status, @model, @provider,
|
|
@account, @connectionId, @duration, @tokensIn, @tokensOut, @sourceFormat, @targetFormat,
|
|
@apiKeyId, @apiKeyName, @comboName, @requestBody, @responseBody, @error)
|
|
`);
|
|
|
|
const tx = db.transaction(() => {
|
|
for (const log of logs) {
|
|
insert.run({
|
|
id: log.id || `${Date.now()}-${Math.random().toString(36).slice(2, 6)}`,
|
|
timestamp: log.timestamp || new Date().toISOString(),
|
|
method: log.method || "POST",
|
|
path: log.path || null,
|
|
status: log.status || 0,
|
|
model: log.model || null,
|
|
provider: log.provider || null,
|
|
account: log.account || null,
|
|
connectionId: log.connectionId || null,
|
|
duration: log.duration || 0,
|
|
tokensIn: log.tokens?.in ?? 0,
|
|
tokensOut: log.tokens?.out ?? 0,
|
|
sourceFormat: log.sourceFormat || null,
|
|
targetFormat: log.targetFormat || null,
|
|
apiKeyId: log.apiKeyId || null,
|
|
apiKeyName: log.apiKeyName || null,
|
|
comboName: log.comboName || null,
|
|
requestBody: log.requestBody ? JSON.stringify(log.requestBody) : null,
|
|
responseBody: log.responseBody ? JSON.stringify(log.responseBody) : null,
|
|
error: log.error || null,
|
|
});
|
|
}
|
|
});
|
|
tx();
|
|
console.log(`[usageDb] ✓ Migrated ${logs.length} call log entries`);
|
|
}
|
|
|
|
fs.renameSync(CALL_LOGS_JSON_FILE, CALL_LOGS_JSON_FILE + ".migrated");
|
|
} catch (err) {
|
|
console.error("[usageDb] Failed to migrate call_logs.json:", err.message);
|
|
}
|
|
}
|
|
}
|
|
|
|
// ──────────────── Run on load ────────────────
|
|
|
|
migrateLegacyUsageFiles();
|
|
|
|
if (shouldPersistToDisk) {
|
|
try {
|
|
migrateUsageJsonToSqlite();
|
|
} catch {
|
|
/* ok */
|
|
}
|
|
}
|