import { NextResponse } from "next/server"; import { ZodError, type z } from "zod"; /** Throwable API error with HTTP status code */ export class ApiError extends Error { status: number; constructor(message: string, status: number = 400) { super(message); this.name = "ApiError"; this.status = status; } } /** Standard success response: { ok: true, ...data } */ export function apiOk(data?: Record) { return NextResponse.json({ ok: true, ...data }); } /** Standard error response: { error: message } with given status */ export function apiError(message: string, status: number = 400) { return NextResponse.json({ error: message }, { status }); } /** Validation error from Zod: { error: fieldErrors } with 400 */ export function apiValidationError(zodError: z.ZodError) { return NextResponse.json( { error: zodError.flatten().fieldErrors }, { status: 400 }, ); } /** Centralized error handler for API routes */ export function handleApiError(error: unknown): Response { if (error instanceof ApiError) { return apiError(error.message, error.status); } if (error instanceof ZodError) { return apiValidationError(error); } // Prisma P2025 "Record not found" if ( error instanceof Error && (error.constructor.name === "PrismaClientKnownRequestError" || error.name === "PrismaClientKnownRequestError") && (error as Error & { code?: string }).code === "P2025" ) { return apiError("Not found", 404); } console.error( "[API error]", error instanceof Error ? { message: error.message, name: error.name } : error, ); return apiError("Internal server error", 500); }