Import source: backend/src/lib/envelope

Import the existing local project snapshot. This commit organizes existing files; it does not represent newly developed functionality.
This commit is contained in:
arsam
2026-09-18 23:09:17 +03:30
parent 3c133e7954
commit 135b9e8c58
+105
View File
@@ -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<string, string>;
};
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<T>(request: FastifyRequest, data: T, meta: Partial<ApiMeta> = {}) {
return {
data,
meta: {
requestId: requestId(request),
...meta,
},
error: null,
};
}
export function fail(
request: FastifyRequest,
reply: FastifyReply,
statusCode: number,
error: ApiError,
meta: Partial<ApiMeta> = {},
) {
return reply.status(statusCode).send({
data: null,
meta: {
requestId: requestId(request),
...meta,
},
error,
});
}
export class ApiException extends Error {
statusCode: number;
code: ApiErrorCode;
fields?: Record<string, string>;
meta?: Partial<ApiMeta>;
constructor(
statusCode: number,
code: ApiErrorCode,
message: string,
options: { fields?: Record<string, string>; meta?: Partial<ApiMeta> } = {},
) {
super(message);
this.statusCode = statusCode;
this.code = code;
this.fields = options.fields;
this.meta = options.meta;
}
}
export function validationError(message: string, fields: Record<string, string>) {
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<string, string>) {
return new ApiException(409, "VALIDATION_ERROR", message, { fields });
}