refactor(types): Wave 3b — usage, CLI runtime, SSE auth/logger typed

- callLogs.ts: typed all CRUD params + error catches
- usageHistory.ts: typed pending requests, filter/entry params
- cliRuntime.ts: typed CLI_TOOLS map + all arrow function params
- sse/auth.ts: typed credentials/fallback/mutex params + Date.getTime()
- sse/utils/logger.ts: typed all logger functions + made data optional

TS errors: 578 → 490 (-88)
Total reduction: 984 → 490 (-494, 50.2%)
Build:   Tests: 368/368 
This commit is contained in:
diegosouzapw
2026-02-17 03:41:21 -03:00
parent 859f6db36c
commit 52b025ca5a
5 changed files with 65 additions and 66 deletions

View File

@@ -24,7 +24,7 @@ function generateLogId() {
/**
* Save a structured call log entry.
*/
export async function saveCallLog(entry) {
export async function saveCallLog(entry: any) {
if (!shouldPersistToDisk) return;
try {
@@ -38,7 +38,7 @@ export async function saveCallLog(entry) {
} catch {}
// Truncate large payloads for DB storage (keep under 8KB each)
const truncatePayload = (obj) => {
const truncatePayload = (obj: any) => {
if (!obj) return null;
const str = JSON.stringify(obj);
if (str.length <= 8192) return str;
@@ -107,7 +107,7 @@ export async function saveCallLog(entry) {
entry.requestBody,
entry.responseBody
);
} catch (error) {
} catch (error: any) {
console.error("[callLogs] Failed to save call log:", error.message);
}
}
@@ -115,7 +115,7 @@ export async function saveCallLog(entry) {
/**
* Write call log as JSON file to disk (full payloads, not truncated).
*/
function writeCallLogToDisk(logEntry, requestBody, responseBody) {
function writeCallLogToDisk(logEntry: any, requestBody: any, responseBody: any) {
if (!CALL_LOGS_DIR) return;
try {
@@ -136,7 +136,7 @@ function writeCallLogToDisk(logEntry, requestBody, responseBody) {
};
fs.writeFileSync(path.join(dir, filename), JSON.stringify(fullEntry, null, 2));
} catch (err) {
} catch (err: any) {
console.error("[callLogs] Failed to write disk log:", err.message);
}
}
@@ -160,7 +160,7 @@ export function rotateCallLogs() {
console.log(`[callLogs] Rotated old logs: ${entry}`);
}
}
} catch (err) {
} catch (err: any) {
console.error("[callLogs] Failed to rotate logs:", err.message);
}
}
@@ -175,11 +175,11 @@ if (shouldPersistToDisk) {
/**
* Get call logs with optional filtering.
*/
export async function getCallLogs(filter = {}) {
export async function getCallLogs(filter: any = {}) {
const db = getDbInstance();
let sql = "SELECT * FROM call_logs";
const conditions = [];
const params = {};
const conditions: string[] = [];
const params: Record<string, unknown> = {};
if (filter.status) {
if (filter.status === "error") {
@@ -257,7 +257,7 @@ export async function getCallLogs(filter = {}) {
/**
* Get a single call log by ID (with full payloads from disk when available).
*/
export async function getCallLogById(id) {
export async function getCallLogById(id: string) {
const db = getDbInstance();
const row = db.prepare("SELECT * FROM call_logs WHERE id = ?").get(id);
if (!row) return null;
@@ -296,7 +296,7 @@ export async function getCallLogById(id) {
responseBody: diskEntry.responseBody ?? entry.responseBody,
};
}
} catch (err) {
} catch (err: any) {
console.error("[callLogs] Failed to read full log from disk:", err.message);
}
}
@@ -307,7 +307,7 @@ export async function getCallLogById(id) {
/**
* Read the full (untruncated) log entry from disk.
*/
function readFullLogFromDisk(entry) {
function readFullLogFromDisk(entry: any) {
if (!CALL_LOGS_DIR || !entry.timestamp) return null;
try {
@@ -332,7 +332,7 @@ function readFullLogFromDisk(entry) {
if (files.length > 0) {
return JSON.parse(fs.readFileSync(path.join(dir, files[0]), "utf8"));
}
} catch (err) {
} catch (err: any) {
console.error("[callLogs] Disk log read error:", err.message);
}

View File

@@ -13,7 +13,10 @@ import { shouldPersistToDisk } from "./migrations";
// ──────────────── Pending Requests (in-memory) ────────────────
const pendingRequests = {
const pendingRequests: {
byModel: Record<string, number>;
byAccount: Record<string, Record<string, number>>;
} = {
byModel: {},
byAccount: {},
};
@@ -21,7 +24,7 @@ const pendingRequests = {
/**
* Track a pending request.
*/
export function trackPendingRequest(model, provider, connectionId, started) {
export function trackPendingRequest(model: string, provider: string, connectionId: string | null, started: boolean) {
const modelKey = provider ? `${model} (${provider})` : model;
if (!pendingRequests.byModel[modelKey]) pendingRequests.byModel[modelKey] = 0;
@@ -84,7 +87,7 @@ export async function getUsageDb() {
/**
* Save request usage entry to SQLite.
*/
export async function saveRequestUsage(entry) {
export async function saveRequestUsage(entry: any) {
if (!shouldPersistToDisk) return;
try {
@@ -122,11 +125,11 @@ export async function saveRequestUsage(entry) {
/**
* Get usage history with optional filters.
*/
export async function getUsageHistory(filter = {}) {
export async function getUsageHistory(filter: any = {}) {
const db = getDbInstance();
let sql = "SELECT * FROM usage_history";
const conditions = [];
const params = {};
const conditions: string[] = [];
const params: Record<string, unknown> = {};
if (filter.provider) {
conditions.push("provider = @provider");
@@ -175,7 +178,7 @@ import fs from "fs";
import { LOG_FILE } from "./migrations";
function formatLogDate(date = new Date()) {
const pad = (n) => String(n).padStart(2, "0");
const pad = (n: number) => String(n).padStart(2, "0");
const d = pad(date.getDate());
const m = pad(date.getMonth() + 1);
const y = date.getFullYear();
@@ -188,7 +191,7 @@ function formatLogDate(date = new Date()) {
/**
* Append to log.txt.
*/
export async function appendRequestLog({ model, provider, connectionId, tokens, status }) {
export async function appendRequestLog({ model, provider, connectionId, tokens, status }: { model?: string; provider?: string; connectionId?: string; tokens?: any; status?: string | number }) {
if (!shouldPersistToDisk) return;
try {
@@ -225,7 +228,7 @@ export async function appendRequestLog({ model, provider, connectionId, tokens,
if (lines.length > 200) {
fs.writeFileSync(LOG_FILE, lines.slice(-200).join("\n") + "\n");
}
} catch (error) {
} catch (error: any) {
console.error("Failed to append to log.txt:", error.message);
}
}
@@ -243,7 +246,7 @@ export async function getRecentLogs(limit = 200) {
const content = fs.readFileSync(LOG_FILE, "utf-8");
const lines = content.trim().split("\n");
return lines.slice(-limit).reverse();
} catch (error) {
} catch (error: any) {
console.error("[usageDb] Failed to read log.txt:", error.message);
return [];
}

View File

@@ -6,7 +6,7 @@ import { spawn } from "child_process";
const VALID_RUNTIME_MODES = new Set(["auto", "host", "container"]);
const FALSE_VALUES = new Set(["0", "false", "no", "off"]);
const CLI_TOOLS = {
const CLI_TOOLS: Record<string, any> = {
claude: {
defaultCommand: "claude",
envBinKey: "CLI_CLAUDE_BIN",
@@ -90,12 +90,12 @@ const CLI_TOOLS = {
const isWindows = () => process.platform === "win32";
const parseBoolean = (value, defaultValue = true) => {
const parseBoolean = (value: unknown, defaultValue = true) => {
if (value == null || value === "") return defaultValue;
return !FALSE_VALUES.has(String(value).trim().toLowerCase());
};
const runProcess = (command, args, { env, timeoutMs = 3000 } = {}) =>
const runProcess = (command: string, args: string[], { env, timeoutMs = 3000 }: { env?: Record<string, string | undefined>; timeoutMs?: number } = {}): Promise<any> =>
new Promise((resolve) => {
let stdout = "";
let stderr = "";
@@ -108,7 +108,7 @@ const runProcess = (command, args, { env, timeoutMs = 3000 } = {}) =>
child.kill("SIGKILL");
}, timeoutMs);
const done = (result) => {
const done = (result: any) => {
if (settled) return;
settled = true;
clearTimeout(timer);
@@ -168,7 +168,7 @@ const getLookupEnv = () => {
return env;
};
const resolveToolCommands = (toolId) => {
const resolveToolCommands = (toolId: string): string[] => {
const tool = CLI_TOOLS[toolId];
if (!tool) return [];
const envCommand = String(process.env[tool.envBinKey] || "").trim();
@@ -179,7 +179,7 @@ const resolveToolCommands = (toolId) => {
return tool.defaultCommand ? [tool.defaultCommand] : [];
};
const checkExplicitPath = async (commandPath) => {
const checkExplicitPath = async (commandPath: string) => {
try {
await fs.access(commandPath, fs.constants.F_OK);
} catch {
@@ -194,7 +194,7 @@ const checkExplicitPath = async (commandPath) => {
}
};
const locateCommand = async (command, env) => {
const locateCommand = async (command: string, env: Record<string, string | undefined>) => {
if (!command) {
return { installed: false, commandPath: null, reason: "missing_command" };
}
@@ -231,7 +231,7 @@ const locateCommand = async (command, env) => {
return { installed: !!first, commandPath: first, reason: first ? null : "not_found" };
};
const locateCommandCandidate = async (commands, env) => {
const locateCommandCandidate = async (commands: string[], env: Record<string, string | undefined>) => {
if (!Array.isArray(commands) || commands.length === 0) {
return { command: null, installed: false, commandPath: null, reason: "missing_command" };
}
@@ -246,7 +246,7 @@ const locateCommandCandidate = async (commands, env) => {
return { command: commands[0], installed: false, commandPath: null, reason: "not_found" };
};
const checkRunnable = async (commandPath, env, timeoutMs = 4000) => {
const checkRunnable = async (commandPath: string, env: Record<string, string | undefined>, timeoutMs = 4000) => {
for (const args of [["--version"], ["-v"]]) {
const result = await runProcess(commandPath, args, { env, timeoutMs });
if (result.ok) {
@@ -267,23 +267,23 @@ export const ensureCliConfigWriteAllowed = () => {
export const getCliConfigHome = () =>
String(process.env.CLI_CONFIG_HOME || "").trim() || os.homedir();
export const getCliConfigPaths = (toolId) => {
export const getCliConfigPaths = (toolId: string) => {
const tool = CLI_TOOLS[toolId];
if (!tool) return null;
const home = getCliConfigHome();
return Object.fromEntries(
Object.entries(tool.paths).map(([key, relativePath]) => [key, path.join(home, relativePath)])
Object.entries(tool.paths).map(([key, relativePath]) => [key, path.join(home, relativePath as string)])
);
};
export const getCliPrimaryConfigPath = (toolId) => {
export const getCliPrimaryConfigPath = (toolId: string) => {
const paths = getCliConfigPaths(toolId);
if (!paths) return null;
const firstKey = Object.keys(paths)[0];
return firstKey ? paths[firstKey] : null;
};
export const getCliRuntimeStatus = async (toolId) => {
export const getCliRuntimeStatus = async (toolId: string) => {
const tool = CLI_TOOLS[toolId];
const runtimeMode = getRuntimeMode();
if (!tool) {

View File

@@ -21,7 +21,7 @@ let selectionMutex = Promise.resolve();
// ─── Anti-Thundering Herd: per-connection mutex for markAccountUnavailable ───
// Prevents multiple concurrent requests from marking the same connection
// unavailable in parallel, which was the root cause of cascading 502 lockouts.
const markMutexes = new Map();
const markMutexes = new Map<string, Promise<void>>();
/**
* Get provider credentials from localDb
@@ -29,10 +29,10 @@ const markMutexes = new Map();
* @param {string} provider - Provider name
* @param {string|null} excludeConnectionId - Connection ID to exclude (for retry with next account)
*/
export async function getProviderCredentials(provider, excludeConnectionId = null) {
export async function getProviderCredentials(provider: string, excludeConnectionId: string | null = null) {
// Acquire mutex to prevent race conditions
const currentMutex = selectionMutex;
let resolveMutex;
let resolveMutex: (() => void) | undefined;
selectionMutex = new Promise((resolve) => {
resolveMutex = resolve;
});
@@ -105,7 +105,7 @@ export async function getProviderCredentials(provider, excludeConnectionId = nul
(c) => c.rateLimitedUntil && new Date(c.rateLimitedUntil).getTime() > Date.now()
);
const earliestConn = rateLimitedConns.sort(
(a, b) => new Date(a.rateLimitedUntil) - new Date(b.rateLimitedUntil)
(a: any, b: any) => new Date(a.rateLimitedUntil).getTime() - new Date(b.rateLimitedUntil).getTime()
)[0];
log.warn(
"AUTH",
@@ -131,11 +131,11 @@ export async function getProviderCredentials(provider, excludeConnectionId = nul
const stickyLimit = settings.stickyRoundRobinLimit || 3;
// Sort by lastUsed (most recent first) to find current candidate
const byRecency = [...availableConnections].sort((a, b) => {
const byRecency = [...availableConnections].sort((a: any, b: any) => {
if (!a.lastUsedAt && !b.lastUsedAt) return (a.priority || 999) - (b.priority || 999);
if (!a.lastUsedAt) return 1;
if (!b.lastUsedAt) return -1;
return new Date(b.lastUsedAt) - new Date(a.lastUsedAt);
return new Date(b.lastUsedAt).getTime() - new Date(a.lastUsedAt).getTime();
});
const current = byRecency[0];
@@ -151,11 +151,11 @@ export async function getProviderCredentials(provider, excludeConnectionId = nul
});
} else {
// Pick the least recently used (excluding current if possible)
const sortedByOldest = [...availableConnections].sort((a, b) => {
const sortedByOldest = [...availableConnections].sort((a: any, b: any) => {
if (!a.lastUsedAt && !b.lastUsedAt) return (a.priority || 999) - (b.priority || 999);
if (!a.lastUsedAt) return -1;
if (!b.lastUsedAt) return 1;
return new Date(a.lastUsedAt) - new Date(b.lastUsedAt);
return new Date(a.lastUsedAt).getTime() - new Date(b.lastUsedAt).getTime();
});
connection = sortedByOldest[0];
@@ -200,18 +200,14 @@ export async function getProviderCredentials(provider, excludeConnectionId = nul
* @returns {{ shouldFallback: boolean, cooldownMs: number }}
*/
export async function markAccountUnavailable(
connectionId,
status,
errorText,
provider = null,
model = null
connectionId: string,
status: number,
errorText: string,
provider: string | null = null,
model: string | null = null
) {
// ─── Anti-Thundering Herd Mutex ─────────────────────────────────
// Wait for any in-flight markAccountUnavailable call for the SAME connection.
// This prevents 5 concurrent 502s from each independently marking the account
// with backoffLevel 0 → 1, when only the first should.
const currentMutex = markMutexes.get(connectionId) || Promise.resolve();
let resolveMutex;
let resolveMutex: (() => void) | undefined;
markMutexes.set(
connectionId,
new Promise((resolve) => {
@@ -284,7 +280,7 @@ export async function markAccountUnavailable(
* Clear account error status (only if currently has error)
* Optimized to avoid unnecessary DB updates
*/
export async function clearAccountError(connectionId, currentConnection) {
export async function clearAccountError(connectionId: string, currentConnection: any) {
// Only update if currently has error status
const hasError =
currentConnection.testStatus === "unavailable" ||
@@ -306,7 +302,7 @@ export async function clearAccountError(connectionId, currentConnection) {
/**
* Extract API key from request headers
*/
export function extractApiKey(request) {
export function extractApiKey(request: Request) {
const authHeader = request.headers.get("Authorization");
if (authHeader?.startsWith("Bearer ")) {
return authHeader.slice(7);
@@ -317,7 +313,7 @@ export function extractApiKey(request) {
/**
* Validate API key (optional - for local use can skip)
*/
export async function isValidApiKey(apiKey) {
export async function isValidApiKey(apiKey: string) {
if (!apiKey) return false;
return await validateApiKey(apiKey);
}

View File

@@ -8,27 +8,27 @@ import { createLogger, logger as rootLogger } from "@/shared/utils/logger";
const log = createLogger("sse");
export function debug(tag, message, data) {
export function debug(tag: string, message: string, data?: unknown) {
log.debug({ tag, ...spreadData(data) }, message);
}
export function info(tag, message, data) {
export function info(tag: string, message: string, data?: unknown) {
log.info({ tag, ...spreadData(data) }, message);
}
export function warn(tag, message, data) {
export function warn(tag: string, message: string, data?: unknown) {
log.warn({ tag, ...spreadData(data) }, message);
}
export function error(tag, message, data) {
export function error(tag: string, message: string, data?: unknown) {
log.error({ tag, ...spreadData(data) }, message);
}
export function request(method, path, extra) {
export function request(method: string, path: string, extra?: unknown) {
log.info({ tag: "HTTP", method, path, ...spreadData(extra) }, `📥 ${method} ${path}`);
}
export function response(status, duration, extra) {
export function response(status: number, duration: number, extra?: unknown) {
const level = status < 400 ? "info" : "error";
log[level](
{ tag: "HTTP", status, duration, ...spreadData(extra) },
@@ -36,7 +36,7 @@ export function response(status, duration, extra) {
);
}
export function stream(event, data) {
export function stream(event: string, data?: unknown) {
log.debug({ tag: "STREAM", event, ...spreadData(data) }, `🌊 ${event}`);
}
@@ -44,9 +44,9 @@ export function stream(event, data) {
export { maskKey } from "@/shared/utils/formatting";
// Helper to spread data into structured fields
function spreadData(data) {
function spreadData(data: unknown): Record<string, unknown> {
if (!data) return {};
if (typeof data === "string") return { detail: data };
if (typeof data === "object") return data;
if (typeof data === "object") return data as Record<string, unknown>;
return { detail: String(data) };
}