shinroute / src /lib /usage /migrations.ts
shinmentakezo07
Add OmniRoute codebase without binary assets
9e4583c
Raw
History Blame Contribute Delete
7.64 kB
/**
* 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";
import { getLegacyDotDataDir, isSamePath } from "../dataPaths";
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 */
}
}