mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-22 07:02:16 +03:00
T-19 — Domain Layer: - modelAvailability.js: Model availability tracking with TTL cooldowns - costRules.js: Per-API-key budget management with daily/monthly limits - fallbackPolicy.js: Declarative fallback chain routing T-22 — Error Codes Catalog: - errorCodes.js: 24 codes in 6 categories + createErrorResponse helper T-23 — Correlation ID: - requestId.js: AsyncLocalStorage-based x-request-id propagation T-25 — Fetch Timeout: - fetchTimeout.js: AbortController wrapper with FETCH_TIMEOUT_MS env var T-27 — JSDoc + @ts-check: - Added @ts-check to 8 critical files TASKS.md updated: 37/46 tasks Concluído, 9 remaining Tests: 119/119 pass (88 existing + 31 new)
115 lines
4.9 KiB
JavaScript
115 lines
4.9 KiB
JavaScript
/**
|
|
* Error Codes Catalog — T-22
|
|
*
|
|
* Centralized error code registry for consistent API error responses.
|
|
* Each code has a category prefix, numeric ID, message, and HTTP status.
|
|
*
|
|
* Usage:
|
|
* import { ERROR_CODES, createErrorResponse } from "@/shared/constants/errorCodes";
|
|
* return createErrorResponse("AUTH_001", { detail: "Token expired" });
|
|
*
|
|
* @module shared/constants/errorCodes
|
|
*/
|
|
|
|
// @ts-check
|
|
|
|
/**
|
|
* @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.)
|
|
*/
|
|
|
|
/** @type {Record<string, ErrorCodeDef>} */
|
|
export const ERROR_CODES = {
|
|
// ── 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" },
|
|
AUTH_003: { code: "AUTH_003", message: "API key expired", httpStatus: 401, category: "AUTH" },
|
|
AUTH_004: { code: "AUTH_004", message: "Insufficient permissions", httpStatus: 403, category: "AUTH" },
|
|
AUTH_005: { code: "AUTH_005", message: "Account locked", httpStatus: 423, category: "AUTH" },
|
|
AUTH_006: { code: "AUTH_006", message: "No credentials for provider", httpStatus: 400, category: "AUTH" },
|
|
|
|
// ── Proxy ──
|
|
PROXY_001: { code: "PROXY_001", message: "Proxy connection failed", httpStatus: 502, category: "PROXY" },
|
|
PROXY_002: { code: "PROXY_002", message: "Proxy timeout", httpStatus: 504, category: "PROXY" },
|
|
PROXY_003: { code: "PROXY_003", message: "All proxies exhausted", httpStatus: 503, category: "PROXY" },
|
|
|
|
// ── Rate Limiting ──
|
|
RATE_001: { code: "RATE_001", message: "Rate limit exceeded", httpStatus: 429, category: "RATE_LIMIT" },
|
|
RATE_002: { code: "RATE_002", message: "Daily budget exceeded", httpStatus: 429, category: "RATE_LIMIT" },
|
|
RATE_003: { code: "RATE_003", message: "All accounts rate-limited", httpStatus: 503, category: "RATE_LIMIT" },
|
|
|
|
// ── Model / Routing ──
|
|
MODEL_001: { code: "MODEL_001", message: "Model not found", httpStatus: 404, category: "MODEL" },
|
|
MODEL_002: { code: "MODEL_002", message: "Ambiguous model identifier", httpStatus: 400, category: "MODEL" },
|
|
MODEL_003: { code: "MODEL_003", message: "Model temporarily unavailable", httpStatus: 503, category: "MODEL" },
|
|
|
|
// ── Provider ──
|
|
PROVIDER_001: { code: "PROVIDER_001", message: "Provider error", httpStatus: 502, category: "PROVIDER" },
|
|
PROVIDER_002: { code: "PROVIDER_002", message: "Provider timeout", httpStatus: 504, category: "PROVIDER" },
|
|
PROVIDER_003: { code: "PROVIDER_003", message: "Provider not configured", httpStatus: 400, category: "PROVIDER" },
|
|
|
|
// ── Validation ──
|
|
VALID_001: { code: "VALID_001", message: "Invalid request body", httpStatus: 400, category: "VALIDATION" },
|
|
VALID_002: { code: "VALID_002", message: "Missing required field", httpStatus: 400, category: "VALIDATION" },
|
|
VALID_003: { code: "VALID_003", message: "Input sanitization blocked", httpStatus: 400, category: "VALIDATION" },
|
|
|
|
// ── Internal ──
|
|
INTERNAL_001: { code: "INTERNAL_001", message: "Internal server error", httpStatus: 500, category: "INTERNAL" },
|
|
INTERNAL_002: { code: "INTERNAL_002", message: "Database error", httpStatus: 500, category: "INTERNAL" },
|
|
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 = {}) {
|
|
const def = ERROR_CODES[code];
|
|
if (!def) {
|
|
return {
|
|
error: {
|
|
code: "INTERNAL_001",
|
|
message: `Unknown error code: ${code}`,
|
|
category: "INTERNAL",
|
|
},
|
|
status: 500,
|
|
};
|
|
}
|
|
|
|
const response = {
|
|
error: {
|
|
code: def.code,
|
|
message: def.message,
|
|
category: def.category,
|
|
...(details.detail ? { detail: details.detail } : {}),
|
|
...(details.requestId ? { requestId: details.requestId } : {}),
|
|
},
|
|
status: def.httpStatus,
|
|
};
|
|
|
|
if (details.retryAfter) {
|
|
response.retryAfter = details.retryAfter;
|
|
}
|
|
|
|
return response;
|
|
}
|
|
|
|
/**
|
|
* Get all error codes for a category.
|
|
*
|
|
* @param {string} category
|
|
* @returns {ErrorCodeDef[]}
|
|
*/
|
|
export function getErrorsByCategory(category) {
|
|
return Object.values(ERROR_CODES).filter((e) => e.category === category);
|
|
}
|