From 859f6db36c37583230cdec9b2b4bdabae60e5b25 Mon Sep 17 00:00:00 2001 From: diegosouzapw Date: Tue, 17 Feb 2026 03:35:11 -0300 Subject: [PATCH] =?UTF-8?q?refactor(types):=20Wave=203a=20=E2=80=94=20lib?= =?UTF-8?q?=20layer,=20db,=20compliance,=20domain=20typed?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - proxyLogger.ts: ProxyLogEntry/ProxyLogFilters interfaces + typed params - errorCodes.ts: ErrorCodeDef/ErrorDetails interfaces - usageAnalytics.ts: typed computeAnalytics + helper function params - policyEngine.ts: PolicyRequest/PolicyVerdict/Policy interfaces + typed PolicyEngine class - db/providers.ts: typed all CRUD function params - db/settings.ts: typed pricing/proxy/settings params + Record<> index types - compliance/index.ts: typed audit log and noLog params - domain/responses.ts: typed all response factory params TS errors: 654 → 578 (-76) Total reduction: 984 → 578 (-406, 41.3%) Build: ✅ Tests: 368/368 ✅ --- src/domain/policyEngine.ts | 120 +++++++++++------------------ src/domain/responses.ts | 8 +- src/lib/compliance/index.ts | 12 +-- src/lib/db/providers.ts | 38 ++++----- src/lib/db/settings.ts | 34 ++++---- src/lib/proxyLogger.ts | 67 ++++++++++------ src/lib/usageAnalytics.ts | 30 ++++---- src/shared/constants/errorCodes.ts | 44 ++++------- 8 files changed, 166 insertions(+), 187 deletions(-) diff --git a/src/domain/policyEngine.ts b/src/domain/policyEngine.ts index 2a3d3de93b..61dabee8e0 100644 --- a/src/domain/policyEngine.ts +++ b/src/domain/policyEngine.ts @@ -1,4 +1,3 @@ -// @ts-check /** * Policy Engine — FASE-06 Architecture Refactoring * @@ -6,9 +5,6 @@ * fallback, cost, lockout, and circuit-breaker modules into a single * verdict before forwarding a request to a provider. * - * Usage: Call `evaluateRequest(request)` before executing a chat request. - * The function returns `{ allowed, reason, adjustments }`. - * * @module domain/policyEngine */ @@ -16,36 +12,39 @@ import { checkLockout } from "./lockoutPolicy"; import { checkBudget } from "./costRules"; import { resolveFallbackChain } from "./fallbackPolicy"; -/** - * @typedef {Object} PolicyRequest - * @property {string} model - Requested model - * @property {string} [apiKeyId] - API key identifier for budget checks - * @property {string} [clientIp] - Client IP for lockout checks - * @property {string} [provider] - Target provider - */ +interface PolicyRequest { + model: string; + apiKeyId?: string; + clientIp?: string; + provider?: string; +} -/** - * @typedef {Object} PolicyVerdict - * @property {boolean} allowed - Whether the request is permitted - * @property {string|null} reason - Human-readable denial reason (null if allowed) - * @property {Object} adjustments - Optional flight-path adjustments - * @property {string} [adjustments.model] - Replaced model (from combo/fallback) - * @property {Array} [adjustments.fallbackChain] - Available fallbacks - * @property {string} policyPhase - Which policy phase determined the outcome - */ +interface PolicyVerdict { + allowed: boolean; + reason: string | null; + adjustments: Record; + policyPhase: string; +} -/** - * Evaluate a request against all domain policies. - * - * Evaluation order (short-circuits on first denial): - * 1. Lockout — is the client/IP locked out? - * 2. Budget — is the API key within budget? - * 3. Fallback — is there a fallback chain for the model? - * - * @param {PolicyRequest} request - * @returns {PolicyVerdict} - */ -export function evaluateRequest(request) { +interface Policy { + id: string; + name: string; + type: string; + enabled: boolean; + priority: number; + conditions?: { + model_pattern?: string; + [key: string]: unknown; + }; + actions?: { + prefer_provider?: string[]; + block_model?: string[]; + max_tokens?: number; + [key: string]: unknown; + }; +} + +export function evaluateRequest(request: PolicyRequest): PolicyVerdict { const { model, apiKeyId, clientIp } = request; // ── 1. Lockout Policy ────────────────────────────── @@ -88,15 +87,7 @@ export function evaluateRequest(request) { }; } -/** - * Evaluate a set of models against policies and return the first allowed one. - * Useful for combo/fallback scenarios where multiple models may be tried. - * - * @param {string[]} models - Models to evaluate in order - * @param {Omit} baseRequest - Base request without model - * @returns {{ model: string, verdict: PolicyVerdict } | { model: null, verdict: PolicyVerdict }} - */ -export function evaluateFirstAllowed(models, baseRequest) { +export function evaluateFirstAllowed(models: string[], baseRequest: Omit) { for (const model of models) { const verdict = evaluateRequest({ ...baseRequest, model }); if (verdict.allowed) { @@ -111,59 +102,42 @@ export function evaluateFirstAllowed(models, baseRequest) { // ─── Class-Based Policy Engine ─────────────────────────────────────────────── -/** - * Matches a value against a glob pattern (supports * wildcard). - * @param {string} pattern - Glob pattern (e.g. "gpt-*") - * @param {string} value - Value to test - * @returns {boolean} - */ -function globMatch(pattern, value) { +function globMatch(pattern: string, value: string): boolean { const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*"); return new RegExp(`^${escaped}$`).test(value); } -/** - * Declarative Policy Engine — supports routing, access, and budget policies - * with glob-based model matching and priority ordering. - * - * @example - * const engine = new PolicyEngine(); - * engine.loadPolicies([{ id: "1", name: "prefer-openai", type: "routing", enabled: true, priority: 1, conditions: { model_pattern: "gpt-*" }, actions: { prefer_provider: ["openai"] } }]); - * const result = engine.evaluate({ model: "gpt-4o" }); - */ export class PolicyEngine { + _policies: Policy[]; + constructor() { - /** @type {Array} */ this._policies = []; } - /** Load a full set of policies (replaces existing). */ - loadPolicies(policies) { + loadPolicies(policies: Policy[]) { this._policies = [...policies]; } - /** Add a single policy. */ - addPolicy(policy) { + addPolicy(policy: Policy) { this._policies.push(policy); } - /** Remove a policy by id. */ - removePolicy(id) { + removePolicy(id: string) { this._policies = this._policies.filter((p) => p.id !== id); } - /** Get current policies. */ - getPolicies() { + getPolicies(): Policy[] { return [...this._policies]; } - /** - * Evaluate a request context against all loaded policies. - * @param {{ model: string }} context - * @returns {{ allowed: boolean, reason?: string, preferredProviders: string[], appliedPolicies: string[], maxTokens?: number }} - */ - evaluate(context) { - const result = { + evaluate(context: { model: string }) { + const result: { + allowed: boolean; + reason: string | undefined; + preferredProviders: string[]; + appliedPolicies: string[]; + maxTokens: number | undefined; + } = { allowed: true, reason: undefined, preferredProviders: [], diff --git a/src/domain/responses.ts b/src/domain/responses.ts index 69aeb40266..00c71d3a42 100644 --- a/src/domain/responses.ts +++ b/src/domain/responses.ts @@ -15,7 +15,7 @@ * @param {Object} [headers={}] - Additional headers * @returns {Response} */ -export function successResponse(data, status = 200, headers = {}) { +export function successResponse(data: unknown, status = 200, headers: Record = {}) { return new Response(JSON.stringify(data), { status, headers: { @@ -34,7 +34,7 @@ export function successResponse(data, status = 200, headers = {}) { * @param {Object} [details] - Additional error details * @returns {Response} */ -export function apiErrorResponse(status, code, message, details) { +export function apiErrorResponse(status: number, code: string, message: string, details?: unknown) { return new Response( JSON.stringify({ error: { @@ -58,7 +58,7 @@ export function apiErrorResponse(status, code, message, details) { * @param {Object} [details] - Validation details * @returns {Response} */ -export function badRequest(message, details) { +export function badRequest(message: string, details?: unknown) { return apiErrorResponse(400, "BAD_REQUEST", message, details); } @@ -98,7 +98,7 @@ export function notFound(resource = "Resource") { * @param {string} message - Conflict description * @returns {Response} */ -export function conflict(message) { +export function conflict(message: string) { return apiErrorResponse(409, "CONFLICT", message); } diff --git a/src/lib/compliance/index.ts b/src/lib/compliance/index.ts index 14ccbab1d6..a6c8ec1d28 100644 --- a/src/lib/compliance/index.ts +++ b/src/lib/compliance/index.ts @@ -52,7 +52,7 @@ export function initAuditLog() { * @param {Object|string} [entry.details] - Additional details * @param {string} [entry.ipAddress] - Client IP */ -export function logAuditEvent(entry) { +export function logAuditEvent(entry: { action: string; actor?: string; target?: string; details?: unknown; ipAddress?: string }) { const db = getDb(); if (!db) return; @@ -83,12 +83,12 @@ export function logAuditEvent(entry) { * @param {number} [filter.offset=0] - Pagination offset * @returns {Array<{ id: number, timestamp: string, action: string, actor: string, target: string, details: any, ip_address: string }>} */ -export function getAuditLog(filter = {}) { +export function getAuditLog(filter: { action?: string; actor?: string; limit?: number; offset?: number } = {}) { const db = getDb(); if (!db) return []; - const conditions = []; - const params = []; + const conditions: string[] = []; + const params: (string | number)[] = []; if (filter.action) { conditions.push("action = ?"); @@ -125,7 +125,7 @@ const noLogKeys = new Set(); * @param {string} apiKeyId * @param {boolean} noLog */ -export function setNoLog(apiKeyId, noLog) { +export function setNoLog(apiKeyId: string, noLog: boolean) { if (noLog) { noLogKeys.add(apiKeyId); } else { @@ -139,7 +139,7 @@ export function setNoLog(apiKeyId, noLog) { * @param {string} apiKeyId * @returns {boolean} */ -export function isNoLog(apiKeyId) { +export function isNoLog(apiKeyId: string) { return noLogKeys.has(apiKeyId); } diff --git a/src/lib/db/providers.ts b/src/lib/db/providers.ts index 07b1daef7b..c6472a6f4e 100644 --- a/src/lib/db/providers.ts +++ b/src/lib/db/providers.ts @@ -9,11 +9,11 @@ import { encryptConnectionFields, decryptConnectionFields } from "./encryption"; // ──────────────── Provider Connections ──────────────── -export async function getProviderConnections(filter = {}) { +export async function getProviderConnections(filter: any = {}) { const db = getDbInstance(); let sql = "SELECT * FROM provider_connections"; - const conditions = []; - const params = {}; + const conditions: string[] = []; + const params: Record = {}; if (filter.provider) { conditions.push("provider = @provider"); @@ -33,13 +33,13 @@ export async function getProviderConnections(filter = {}) { return rows.map((r) => decryptConnectionFields(cleanNulls(rowToCamel(r)))); } -export async function getProviderConnectionById(id) { +export async function getProviderConnectionById(id: string) { const db = getDbInstance(); const row = db.prepare("SELECT * FROM provider_connections WHERE id = ?").get(id); return row ? decryptConnectionFields(cleanNulls(rowToCamel(row))) : null; } -export async function createProviderConnection(data) { +export async function createProviderConnection(data: any) { const db = getDbInstance(); const now = new Date().toISOString(); @@ -98,7 +98,7 @@ export async function createProviderConnection(data) { connectionPriority = (max?.maxP || 0) + 1; } - const connection = { + const connection: Record = { id: uuidv4(), provider: data.provider, authType: data.authType || "oauth", @@ -151,7 +151,7 @@ export async function createProviderConnection(data) { return cleanNulls(connection); } -function _insertConnectionRow(db, conn) { +function _insertConnectionRow(db: any, conn: any) { db.prepare( ` INSERT INTO provider_connections ( @@ -217,7 +217,7 @@ function _insertConnectionRow(db, conn) { }); } -function _updateConnectionRow(db, id, data) { +function _updateConnectionRow(db: any, id: string, data: any) { const now = data.updatedAt || new Date().toISOString(); db.prepare( ` @@ -280,7 +280,7 @@ function _updateConnectionRow(db, id, data) { }); } -export async function updateProviderConnection(id, data) { +export async function updateProviderConnection(id: string, data: any) { const db = getDbInstance(); const existing = db.prepare("SELECT * FROM provider_connections WHERE id = ?").get(id); if (!existing) return null; @@ -296,7 +296,7 @@ export async function updateProviderConnection(id, data) { return cleanNulls(merged); } -export async function deleteProviderConnection(id) { +export async function deleteProviderConnection(id: string) { const db = getDbInstance(); const existing = db.prepare("SELECT provider FROM provider_connections WHERE id = ?").get(id); if (!existing) return false; @@ -307,19 +307,19 @@ export async function deleteProviderConnection(id) { return true; } -export async function deleteProviderConnectionsByProvider(providerId) { +export async function deleteProviderConnectionsByProvider(providerId: string) { const db = getDbInstance(); const result = db.prepare("DELETE FROM provider_connections WHERE provider = ?").run(providerId); backupDbFile("pre-write"); return result.changes; } -export async function reorderProviderConnections(providerId) { +export async function reorderProviderConnections(providerId: string) { const db = getDbInstance(); _reorderConnections(db, providerId); } -function _reorderConnections(db, providerId) { +function _reorderConnections(db: any, providerId: string) { const rows = db .prepare( "SELECT id, priority, updated_at FROM provider_connections WHERE provider = ? ORDER BY priority ASC, updated_at DESC" @@ -338,10 +338,10 @@ export async function cleanupProviderConnections() { // ──────────────── Provider Nodes ──────────────── -export async function getProviderNodes(filter = {}) { +export async function getProviderNodes(filter: any = {}) { const db = getDbInstance(); let sql = "SELECT * FROM provider_nodes"; - const params = {}; + const params: Record = {}; if (filter.type) { sql += " WHERE type = @type"; @@ -351,13 +351,13 @@ export async function getProviderNodes(filter = {}) { return db.prepare(sql).all(params).map(rowToCamel); } -export async function getProviderNodeById(id) { +export async function getProviderNodeById(id: string) { const db = getDbInstance(); const row = db.prepare("SELECT * FROM provider_nodes WHERE id = ?").get(id); return row ? rowToCamel(row) : null; } -export async function createProviderNode(data) { +export async function createProviderNode(data: any) { const db = getDbInstance(); const now = new Date().toISOString(); @@ -383,7 +383,7 @@ export async function createProviderNode(data) { return node; } -export async function updateProviderNode(id, data) { +export async function updateProviderNode(id: string, data: any) { const db = getDbInstance(); const existing = db.prepare("SELECT * FROM provider_nodes WHERE id = ?").get(id); if (!existing) return null; @@ -410,7 +410,7 @@ export async function updateProviderNode(id, data) { return merged; } -export async function deleteProviderNode(id) { +export async function deleteProviderNode(id: string) { const db = getDbInstance(); const existing = db.prepare("SELECT * FROM provider_nodes WHERE id = ?").get(id); if (!existing) return null; diff --git a/src/lib/db/settings.ts b/src/lib/db/settings.ts index 531c06da0c..19bed2eabd 100644 --- a/src/lib/db/settings.ts +++ b/src/lib/db/settings.ts @@ -11,7 +11,7 @@ import { PROVIDER_ID_TO_ALIAS } from "@omniroute/open-sse/config/providerModels. export async function getSettings() { const db = getDbInstance(); const rows = db.prepare("SELECT key, value FROM key_value WHERE namespace = 'settings'").all(); - const settings = { cloudEnabled: false, stickyRoundRobinLimit: 3, requireLogin: true }; + const settings: Record = { cloudEnabled: false, stickyRoundRobinLimit: 3, requireLogin: true }; for (const row of rows) { settings[row.key] = JSON.parse(row.value); } @@ -32,7 +32,7 @@ export async function getSettings() { return settings; } -export async function updateSettings(updates) { +export async function updateSettings(updates: Record) { const db = getDbInstance(); const insert = db.prepare( "INSERT OR REPLACE INTO key_value (namespace, key, value) VALUES ('settings', ?, ?)" @@ -57,7 +57,7 @@ export async function isCloudEnabled() { export async function getPricing() { const db = getDbInstance(); const rows = db.prepare("SELECT key, value FROM key_value WHERE namespace = 'pricing'").all(); - const userPricing = {}; + const userPricing: Record = {}; for (const row of rows) { userPricing[row.key] = JSON.parse(row.value); } @@ -65,8 +65,8 @@ export async function getPricing() { const { getDefaultPricing } = await import("@/shared/constants/pricing"); const defaultPricing = getDefaultPricing(); - const mergedPricing = {}; - for (const [provider, models] of Object.entries(defaultPricing)) { + const mergedPricing: Record = {}; + for (const [provider, models] of Object.entries(defaultPricing) as [string, any][]) { mergedPricing[provider] = { ...models }; if (userPricing[provider]) { for (const [model, pricing] of Object.entries(userPricing[provider])) { @@ -92,7 +92,7 @@ export async function getPricing() { return mergedPricing; } -export async function getPricingForModel(provider, model) { +export async function getPricingForModel(provider: string, model: string) { const pricing = await getPricing(); if (pricing[provider]?.[model]) return pricing[provider][model]; @@ -106,14 +106,14 @@ export async function getPricingForModel(provider, model) { return null; } -export async function updatePricing(pricingData) { +export async function updatePricing(pricingData: Record) { const db = getDbInstance(); const insert = db.prepare( "INSERT OR REPLACE INTO key_value (namespace, key, value) VALUES ('pricing', ?, ?)" ); const rows = db.prepare("SELECT key, value FROM key_value WHERE namespace = 'pricing'").all(); - const existing = {}; + const existing: Record = {}; for (const row of rows) existing[row.key] = JSON.parse(row.value); const tx = db.transaction(() => { @@ -130,7 +130,7 @@ export async function updatePricing(pricingData) { return updated; } -export async function resetPricing(provider, model) { +export async function resetPricing(provider: string, model?: string) { const db = getDbInstance(); if (model) { @@ -177,14 +177,14 @@ const ALIAS_TO_PROVIDER_ID = Object.entries(PROVIDER_ID_TO_ALIAS).reduce( return acc; }, {} -); +) as Record; -function resolveProviderAliasOrId(providerOrAlias) { +function resolveProviderAliasOrId(providerOrAlias: string): string { if (typeof providerOrAlias !== "string") return providerOrAlias; return ALIAS_TO_PROVIDER_ID[providerOrAlias] || providerOrAlias; } -function getComboModelProvider(modelEntry) { +function getComboModelProvider(modelEntry: any): string | null { if (modelEntry && typeof modelEntry.provider === "string") { return resolveProviderAliasOrId(modelEntry.provider); } @@ -203,7 +203,7 @@ function getComboModelProvider(modelEntry) { return resolveProviderAliasOrId(providerOrAlias); } -function migrateProxyEntry(value) { +function migrateProxyEntry(value: any) { if (!value) return null; if (typeof value === "object" && value.type) return value; if (typeof value !== "string") return null; @@ -261,14 +261,14 @@ export async function getProxyConfig() { return raw; } -export async function getProxyForLevel(level, id) { +export async function getProxyForLevel(level: string, id?: string | null) { const config = await getProxyConfig(); if (level === "global") return config.global || null; const map = config[level + "s"] || config[level] || {}; return (id ? map[id] : null) || null; } -export async function setProxyForLevel(level, id, proxy) { +export async function setProxyForLevel(level: string, id: string | null, proxy: any) { const db = getDbInstance(); const config = await getProxyConfig(); @@ -298,7 +298,7 @@ export async function deleteProxyForLevel(level, id) { return setProxyForLevel(level, id, null); } -export async function resolveProxyForConnection(connectionId) { +export async function resolveProxyForConnection(connectionId: string) { const config = await getProxyConfig(); if (connectionId && config.keys?.[connectionId]) { @@ -346,7 +346,7 @@ export async function resolveProxyForConnection(connectionId) { return { proxy: null, level: "direct", levelId: null }; } -export async function setProxyConfig(config) { +export async function setProxyConfig(config: any) { if (config.level !== undefined) { return setProxyForLevel(config.level, config.id || null, config.proxy); } diff --git a/src/lib/proxyLogger.ts b/src/lib/proxyLogger.ts index b54343abf3..1364ed0833 100644 --- a/src/lib/proxyLogger.ts +++ b/src/lib/proxyLogger.ts @@ -12,7 +12,41 @@ import { getDbInstance, isCloud, isBuildPhase } from "./db/core"; const shouldPersistToDisk = !isCloud && !isBuildPhase; const MAX_ENTRIES = 500; -const proxyLogs = []; + +interface ProxyInfo { + type: string; + host: string; + port: number | string; +} + +interface ProxyLogEntry { + id: string; + timestamp: string; + status: string; + proxy: ProxyInfo | null; + level: string; + levelId: string | null; + provider: string | null; + targetUrl: string | null; + publicIp: string | null; + latencyMs: number; + error: string | null; + connectionId: string | null; + comboId: string | null; + account: string | null; + tlsFingerprint: boolean; +} + +interface ProxyLogFilters { + status?: string; + type?: string; + provider?: string; + level?: string; + search?: string; + limit?: number; +} + +const proxyLogs: ProxyLogEntry[] = []; // ──────────────── Startup: hydrate from DB ──────────────── @@ -22,7 +56,7 @@ function loadFromDb() { const db = getDbInstance(); const rows = db .prepare("SELECT * FROM proxy_logs ORDER BY timestamp DESC LIMIT ?") - .all(MAX_ENTRIES); + .all(MAX_ENTRIES) as any[]; for (const row of rows) { proxyLogs.push({ @@ -49,7 +83,7 @@ function loadFromDb() { if (proxyLogs.length > 0) { console.log(`[proxyLogger] Loaded ${proxyLogs.length} proxy logs from SQLite`); } - } catch (err) { + } catch (err: any) { console.warn("[proxyLogger] Failed to load from DB:", err.message); } } @@ -58,23 +92,8 @@ loadFromDb(); // ──────────────── Log a proxy event ──────────────── -/** - * @param {Object} entry - * @param {"success"|"error"|"timeout"} entry.status - * @param {Object} entry.proxy - { type, host, port } - * @param {"key"|"combo"|"provider"|"global"|"direct"} entry.level - * @param {string} [entry.levelId] - * @param {string} [entry.provider] - * @param {string} [entry.targetUrl] - * @param {string} [entry.publicIp] - * @param {number} [entry.latencyMs] - * @param {string} [entry.error] - * @param {string} [entry.connectionId] - * @param {string} [entry.comboId] - * @param {boolean} [entry.tlsFingerprint] - */ -export function logProxyEvent(entry) { - const log = { +export function logProxyEvent(entry: Partial) { + const log: ProxyLogEntry = { id: uuidv4(), timestamp: new Date().toISOString(), status: entry.status || "success", @@ -130,7 +149,7 @@ export function logProxyEvent(entry) { }); // Trim old entries - const count = db.prepare("SELECT COUNT(*) as cnt FROM proxy_logs").get()?.cnt || 0; + const count = (db.prepare("SELECT COUNT(*) as cnt FROM proxy_logs").get() as any)?.cnt || 0; if (count > MAX_ENTRIES) { db.prepare( `DELETE FROM proxy_logs WHERE id IN ( @@ -138,7 +157,7 @@ export function logProxyEvent(entry) { )` ).run(count - MAX_ENTRIES); } - } catch (err) { + } catch (err: any) { console.warn("[proxyLogger] Failed to persist:", err.message); } } @@ -152,7 +171,7 @@ export function logProxyEvent(entry) { * Get proxy logs with optional filters. * Reads from in-memory for speed (already hydrated from DB on startup). */ -export function getProxyLogs(filters = {}) { +export function getProxyLogs(filters: ProxyLogFilters = {}) { let logs = [...proxyLogs]; if (filters.status) { @@ -202,7 +221,7 @@ export function clearProxyLogs() { try { const db = getDbInstance(); db.prepare("DELETE FROM proxy_logs").run(); - } catch (err) { + } catch (err: any) { console.warn("[proxyLogger] Failed to clear DB:", err.message); } } diff --git a/src/lib/usageAnalytics.ts b/src/lib/usageAnalytics.ts index 1f3d3d7ca0..3d11533b71 100644 --- a/src/lib/usageAnalytics.ts +++ b/src/lib/usageAnalytics.ts @@ -12,7 +12,7 @@ import { calculateCost } from "@/lib/usageDb"; * @param {string} range - "1d" | "7d" | "30d" | "90d" | "ytd" | "all" * @returns {{ start: Date, end: Date }} */ -function getDateRange(range) { +function getDateRange(range: string) { const end = new Date(); let start; @@ -48,7 +48,7 @@ function getDateRange(range) { /** * Format a Date to "YYYY-MM-DD" string */ -function toDateKey(date) { +function toDateKey(date: Date) { const y = date.getFullYear(); const m = String(date.getMonth() + 1).padStart(2, "0"); const d = String(date.getDate()).padStart(2, "0"); @@ -58,7 +58,7 @@ function toDateKey(date) { /** * Short model name (strip provider prefix paths) */ -function shortModelName(model) { +function shortModelName(model: string) { if (!model) return "unknown"; // "accounts/fireworks/models/gpt-oss-120b" → "gpt-oss-120b" const parts = model.split("/"); @@ -72,7 +72,7 @@ function shortModelName(model) { * @param {Object} connectionMap - Map of connectionId → account name * @returns {Object} Analytics data */ -export async function computeAnalytics(history, range = "30d", connectionMap = {}) { +export async function computeAnalytics(history: any[], range = "30d", connectionMap: Record = {}) { const { start, end } = getDateRange(range); // ---- Filtered entries ---- @@ -88,25 +88,25 @@ export async function computeAnalytics(history, range = "30d", connectionMap = { completionTokens: 0, totalCost: 0, totalRequests: entries.length, - uniqueModels: new Set(), - uniqueAccounts: new Set(), - uniqueApiKeys: new Set(), + uniqueModels: new Set(), + uniqueAccounts: new Set(), + uniqueApiKeys: new Set(), }; // ---- Daily trend ---- - const dailyMap = {}; // "YYYY-MM-DD" → { requests, promptTokens, completionTokens, cost } - const dailyByModelMap = {}; // "YYYY-MM-DD" → { modelShort → tokens } + const dailyMap: Record = {}; // "YYYY-MM-DD" → { requests, promptTokens, completionTokens, cost } + const dailyByModelMap: Record> = {}; // "YYYY-MM-DD" → { modelShort → tokens } // ---- Activity heatmap (always last 365 days, regardless of range filter) ---- const heatmapStart = new Date(); heatmapStart.setDate(heatmapStart.getDate() - 364); - const activityMap = {}; + const activityMap: Record = {}; // ---- By model / account / provider ---- - const byModelMap = {}; - const byAccountMap = {}; - const byProviderMap = {}; - const byApiKeyMap = {}; + const byModelMap: Record = {}; + const byAccountMap: Record = {}; + const byProviderMap: Record = {}; + const byApiKeyMap: Record = {}; // ---- Weekly pattern (0=Sun..6=Sat) ---- const weeklyTokens = [0, 0, 0, 0, 0, 0, 0]; @@ -251,7 +251,7 @@ export async function computeAnalytics(history, range = "30d", connectionMap = { const dailyTrend = Object.values(dailyMap).sort((a, b) => a.date.localeCompare(b.date)); // Daily by model — collect all unique model names - const allModels = new Set(); + const allModels = new Set(); for (const day of Object.values(dailyByModelMap)) { for (const m of Object.keys(day)) allModels.add(m); } diff --git a/src/shared/constants/errorCodes.ts b/src/shared/constants/errorCodes.ts index e0bb8294a9..914eeccab1 100644 --- a/src/shared/constants/errorCodes.ts +++ b/src/shared/constants/errorCodes.ts @@ -11,18 +11,20 @@ * @module shared/constants/errorCodes */ -// @ts-check +export interface ErrorCodeDef { + code: string; + message: string; + httpStatus: number; + category: string; +} -/** - * @typedef {Object} ErrorCodeDef - * @property {string} code - Error code (e.g. "AUTH_001") - * @property {string} message - Human-readable message - * @property {number} httpStatus - HTTP status code - * @property {string} category - Category (AUTH, PROXY, RATE_LIMIT, etc.) - */ +interface ErrorDetails { + detail?: string; + requestId?: string; + retryAfter?: number; +} -/** @type {Record} */ -export const ERROR_CODES = { +export const ERROR_CODES: Record = { // ── Auth ── AUTH_001: { code: "AUTH_001", message: "Authentication required", httpStatus: 401, category: "AUTH" }, AUTH_002: { code: "AUTH_002", message: "Invalid API key", httpStatus: 401, category: "AUTH" }, @@ -62,17 +64,7 @@ export const ERROR_CODES = { INTERNAL_003: { code: "INTERNAL_003", message: "Circuit breaker open", httpStatus: 503, category: "INTERNAL" }, }; -/** - * Create a standardized error response. - * - * @param {string} code - Error code from ERROR_CODES - * @param {Object} [details] - Additional error details - * @param {string} [details.detail] - Extra detail message - * @param {string} [details.requestId] - Correlation request ID - * @param {number} [details.retryAfter] - Retry-After seconds - * @returns {{ error: { code: string, message: string, category: string, detail?: string, requestId?: string }, status: number, retryAfter?: number }} - */ -export function createErrorResponse(code, details = {}) { +export function createErrorResponse(code: string, details: ErrorDetails = {}) { const def = ERROR_CODES[code]; if (!def) { return { @@ -85,7 +77,7 @@ export function createErrorResponse(code, details = {}) { }; } - const response = { + const response: any = { error: { code: def.code, message: def.message, @@ -103,12 +95,6 @@ export function createErrorResponse(code, details = {}) { return response; } -/** - * Get all error codes for a category. - * - * @param {string} category - * @returns {ErrorCodeDef[]} - */ -export function getErrorsByCategory(category) { +export function getErrorsByCategory(category: string): ErrorCodeDef[] { return Object.values(ERROR_CODES).filter((e) => e.category === category); }