import type { ContentfulStatusCode } from 'hono/utils/http-status' export class ApiError extends Error { constructor( public readonly statusCode: ContentfulStatusCode, public readonly errorCode: string, message: string, public readonly details?: unknown, ) { super(message) this.name = 'ApiError' }} /** * Creates an internal server error (500) */export function createInternalError(message = 'Internal Server Error', details?: unknown) { return new ApiError(500, 'INTERNAL_SERVER_ERROR', message, details)} /** * Creates a bad request error (400) */export function createBadRequestError(message: string, errorCode = 'BAD_REQUEST', details?: unknown) { return new ApiError(400, errorCode, message, details)} /** * Creates an unauthorized error (401) */export function createUnauthorizedError(message = 'Unauthorized', details?: unknown) { return new ApiError(401, 'UNAUTHORIZED', message, details)} /** * Creates a forbidden error (403) */export function createForbiddenError(message = 'Forbidden', details?: unknown) { return new ApiError(403, 'FORBIDDEN', message, details)} /** * Creates a not found error (404) */export function createNotFoundError(message = 'Not Found', details?: unknown) { return new ApiError(404, 'NOT_FOUND', message, details)} /** * Creates a payment required error (402) */export function createPaymentRequiredError(message: string, details?: unknown) { return new ApiError(402, 'PAYMENT_REQUIRED', message, details)} /** * Creates a conflict error (409) */export function createConflictError(message: string, details?: unknown) { return new ApiError(409, 'CONFLICT', message, details)} /** * Creates a service unavailable error (503) */export function createServiceUnavailableError(message = 'Service Unavailable', errorCode = 'SERVICE_UNAVAILABLE', details?: unknown) { return new ApiError(503, errorCode, message, details)} /** * Creates a bad gateway error (502). * * Use when: * - An upstream provider (LLM, TTS, third-party API) returned a fallback- * triggering response (401 / 402 / 403 / 5xx) and the gateway has exhausted * every retry/fallback path. The client must see a gateway-side error code, * not the upstream's status, because the client did nothing wrong. * * Expects: * - `details` is sanitized — never include raw upstream response bodies or * headers (they can leak provider-internal info like subscription IDs, * region identifiers, or rate-limit metadata). Use shape * `{ triedKeys?: number, triedUpstreams?: number, lastStatusCode?: number }`. */export function createBadGatewayError(message = 'Bad Gateway', details?: unknown) { return new ApiError(502, 'BAD_GATEWAY', message, details)} /** * Creates a gateway timeout error (504). * * Use when: * - The gateway aborted an upstream call (or the entire fallback chain) on a * timeout boundary. Distinct from 503: 504 tells clients "retry after a * delay" rather than "service is offline". */export function createGatewayTimeoutError(message = 'Gateway Timeout', details?: unknown) { return new ApiError(504, 'GATEWAY_TIMEOUT', message, details)}
import type { ContentfulStatusCode } from 'hono/utils/http-status'
export class ApiError extends Error {
constructor(
public readonly statusCode: ContentfulStatusCode,
public readonly errorCode: string,
message: string,
public readonly details?: unknown,
) {
super(message)
this.name = 'ApiError'
}
}
/**
* Creates an internal server error (500)
*/
export function createInternalError(message = 'Internal Server Error', details?: unknown) {
return new ApiError(500, 'INTERNAL_SERVER_ERROR', message, details)
}
/**
* Creates a bad request error (400)
*/
export function createBadRequestError(message: string, errorCode = 'BAD_REQUEST', details?: unknown) {
return new ApiError(400, errorCode, message, details)
}
/**
* Creates an unauthorized error (401)
*/
export function createUnauthorizedError(message = 'Unauthorized', details?: unknown) {
return new ApiError(401, 'UNAUTHORIZED', message, details)
}
/**
* Creates a forbidden error (403)
*/
export function createForbiddenError(message = 'Forbidden', details?: unknown) {
return new ApiError(403, 'FORBIDDEN', message, details)
}
/**
* Creates a not found error (404)
*/
export function createNotFoundError(message = 'Not Found', details?: unknown) {
return new ApiError(404, 'NOT_FOUND', message, details)
}
/**
* Creates a payment required error (402)
*/
export function createPaymentRequiredError(message: string, details?: unknown) {
return new ApiError(402, 'PAYMENT_REQUIRED', message, details)
}
/**
* Creates a conflict error (409)
*/
export function createConflictError(message: string, details?: unknown) {
return new ApiError(409, 'CONFLICT', message, details)
}
/**
* Creates a service unavailable error (503)
*/
export function createServiceUnavailableError(message = 'Service Unavailable', errorCode = 'SERVICE_UNAVAILABLE', details?: unknown) {
return new ApiError(503, errorCode, message, details)
}
/**
* Creates a bad gateway error (502).
*
* Use when:
* - An upstream provider (LLM, TTS, third-party API) returned a fallback-
* triggering response (401 / 402 / 403 / 5xx) and the gateway has exhausted
* every retry/fallback path. The client must see a gateway-side error code,
* not the upstream's status, because the client did nothing wrong.
*
* Expects:
* - `details` is sanitized — never include raw upstream response bodies or
* headers (they can leak provider-internal info like subscription IDs,
* region identifiers, or rate-limit metadata). Use shape
* `{ triedKeys?: number, triedUpstreams?: number, lastStatusCode?: number }`.
*/
export function createBadGatewayError(message = 'Bad Gateway', details?: unknown) {
return new ApiError(502, 'BAD_GATEWAY', message, details)
}
/**
* Creates a gateway timeout error (504).
*
* Use when:
* - The gateway aborted an upstream call (or the entire fallback chain) on a
* timeout boundary. Distinct from 503: 504 tells clients "retry after a
* delay" rather than "service is offline".
*/
export function createGatewayTimeoutError(message = 'Gateway Timeout', details?: unknown) {
return new ApiError(504, 'GATEWAY_TIMEOUT', message, details)
}