feat(api): port Fase 5 REST API — Bun.serve router + JWT middleware + auth/session/conversation/chat handlers + composition root
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* Typed API error with automatic HTTP response conversion.
|
||||
* Mirrors `apps/interfaces/api/src/error.rs`.
|
||||
*
|
||||
* Each kind maps to a status code:
|
||||
* - BadRequest → 400
|
||||
* - Unauthorized → 401
|
||||
* - NotFound → 404
|
||||
* - Conflict → 409
|
||||
* - TooManyRequests → 429
|
||||
* - Internal → 500
|
||||
* - ChatProxy → 502
|
||||
*/
|
||||
|
||||
export type ApiErrorKind =
|
||||
| "BadRequest"
|
||||
| "Unauthorized"
|
||||
| "NotFound"
|
||||
| "Conflict"
|
||||
| "TooManyRequests"
|
||||
| "Internal"
|
||||
| "ChatProxy";
|
||||
|
||||
export const HTTP_STATUS: Record<ApiErrorKind, number> = {
|
||||
BadRequest: 400,
|
||||
Unauthorized: 401,
|
||||
NotFound: 404,
|
||||
Conflict: 409,
|
||||
TooManyRequests: 429,
|
||||
Internal: 500,
|
||||
ChatProxy: 502,
|
||||
};
|
||||
|
||||
export class ApiError extends Error {
|
||||
constructor(public kind: ApiErrorKind, message: string) {
|
||||
super(message);
|
||||
this.name = "ApiError";
|
||||
}
|
||||
|
||||
get status(): number {
|
||||
return HTTP_STATUS[this.kind];
|
||||
}
|
||||
|
||||
static badRequest(msg: string): ApiError {
|
||||
return new ApiError("BadRequest", msg);
|
||||
}
|
||||
static unauthorized(msg: string): ApiError {
|
||||
return new ApiError("Unauthorized", msg);
|
||||
}
|
||||
static notFound(msg: string): ApiError {
|
||||
return new ApiError("NotFound", msg);
|
||||
}
|
||||
static conflict(msg: string): ApiError {
|
||||
return new ApiError("Conflict", msg);
|
||||
}
|
||||
static tooManyRequests(msg: string): ApiError {
|
||||
return new ApiError("TooManyRequests", msg);
|
||||
}
|
||||
static internal(msg: string): ApiError {
|
||||
return new ApiError("Internal", msg);
|
||||
}
|
||||
static chatProxy(msg: string): ApiError {
|
||||
return new ApiError("ChatProxy", msg);
|
||||
}
|
||||
|
||||
/** Whether an internal error's details should be hidden from the client. */
|
||||
get exposeDetails(): boolean {
|
||||
// Internal errors hide details; all others surface the message.
|
||||
return this.kind !== "Internal";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user