From 135b9e8c58f5a12abeccf1c19a2b1d04a39f0694 Mon Sep 17 00:00:00 2001 From: arsam Date: Fri, 18 Sep 2026 23:09:17 +0330 Subject: [PATCH] Import source: backend/src/lib/envelope Import the existing local project snapshot. This commit organizes existing files; it does not represent newly developed functionality. --- backend/src/lib/envelope.ts | 105 ++++++++++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 backend/src/lib/envelope.ts diff --git a/backend/src/lib/envelope.ts b/backend/src/lib/envelope.ts new file mode 100644 index 0000000..5ae9f03 --- /dev/null +++ b/backend/src/lib/envelope.ts @@ -0,0 +1,105 @@ +import type { FastifyReply, FastifyRequest } from "fastify"; + +export type ApiErrorCode = + | "VALIDATION_ERROR" + | "PERMISSION_DENIED" + | "NETWORK_ERROR" + | "MAINTENANCE" + | "NOT_FOUND" + | "PARTIAL_FAILURE" + | "RATE_LIMIT_ERROR" + | "METHOD_NOT_ALLOWED"; + +export type ApiError = { + code: ApiErrorCode; + message: string; + fields?: Record; +}; + +export type ApiMeta = { + requestId: string; + latencyMs?: number; + pagination?: { + page: number; + pageSize: number; + total: number; + pageCount: number; + }; + rateLimit?: { + limit: number; + remaining: number; + reset: number; + }; +}; + +export function requestId(request: FastifyRequest) { + return (request.headers["x-request-id"] as string | undefined) || request.id; +} + +export function ok(request: FastifyRequest, data: T, meta: Partial = {}) { + return { + data, + meta: { + requestId: requestId(request), + ...meta, + }, + error: null, + }; +} + +export function fail( + request: FastifyRequest, + reply: FastifyReply, + statusCode: number, + error: ApiError, + meta: Partial = {}, +) { + return reply.status(statusCode).send({ + data: null, + meta: { + requestId: requestId(request), + ...meta, + }, + error, + }); +} + +export class ApiException extends Error { + statusCode: number; + code: ApiErrorCode; + fields?: Record; + meta?: Partial; + + constructor( + statusCode: number, + code: ApiErrorCode, + message: string, + options: { fields?: Record; meta?: Partial } = {}, + ) { + super(message); + this.statusCode = statusCode; + this.code = code; + this.fields = options.fields; + this.meta = options.meta; + } +} + +export function validationError(message: string, fields: Record) { + return new ApiException(400, "VALIDATION_ERROR", message, { fields }); +} + +export function permissionDenied(message = "Authentication required to access this resource.") { + return new ApiException(401, "PERMISSION_DENIED", message); +} + +export function forbidden(message = "This account is not authorized for this operation.") { + return new ApiException(403, "PERMISSION_DENIED", message); +} + +export function notFound(message: string) { + return new ApiException(404, "NOT_FOUND", message); +} + +export function conflict(message: string, fields: Record) { + return new ApiException(409, "VALIDATION_ERROR", message, { fields }); +}