refactor(types): Wave 3a — lib layer, db, compliance, domain typed

- 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 
This commit is contained in:
diegosouzapw
2026-02-17 03:35:11 -03:00
parent faedce9b4a
commit 859f6db36c
8 changed files with 166 additions and 187 deletions

View File

@@ -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<string, unknown>;
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<PolicyRequest, 'model'>} 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<PolicyRequest, "model">) {
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: [],

View File

@@ -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<string, string> = {}) {
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);
}

View File

@@ -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);
}

View File

@@ -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<string, unknown> = {};
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<string, any> = {
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<string, unknown> = {};
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;

View File

@@ -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<string, any> = { 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<string, unknown>) {
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<string, any> = {};
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<string, any> = {};
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<string, any>) {
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<string, any> = {};
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<string, string>;
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);
}

View File

@@ -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<ProxyLogEntry>) {
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);
}
}

View File

@@ -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<string, string> = {}) {
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<string>(),
uniqueAccounts: new Set<string>(),
uniqueApiKeys: new Set<string>(),
};
// ---- Daily trend ----
const dailyMap = {}; // "YYYY-MM-DD" → { requests, promptTokens, completionTokens, cost }
const dailyByModelMap = {}; // "YYYY-MM-DD" → { modelShort → tokens }
const dailyMap: Record<string, any> = {}; // "YYYY-MM-DD" → { requests, promptTokens, completionTokens, cost }
const dailyByModelMap: Record<string, Record<string, number>> = {}; // "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<string, number> = {};
// ---- By model / account / provider ----
const byModelMap = {};
const byAccountMap = {};
const byProviderMap = {};
const byApiKeyMap = {};
const byModelMap: Record<string, any> = {};
const byAccountMap: Record<string, any> = {};
const byProviderMap: Record<string, any> = {};
const byApiKeyMap: Record<string, any> = {};
// ---- 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<string>();
for (const day of Object.values(dailyByModelMap)) {
for (const m of Object.keys(day)) allModels.add(m);
}

View File

@@ -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<string, ErrorCodeDef>} */
export const ERROR_CODES = {
export const ERROR_CODES: Record<string, ErrorCodeDef> = {
// ── 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);
}