import * as next_server from 'next/server'; import { NextResponse, NextRequest } from 'next/server'; /** * createHttpErrors — 프로젝트별 ErrorCatalog를 주입해 에러 유틸을 생성하는 팩토리 * * 프로젝트마다 ErrorCatalog가 다르기 때문에 팩토리 패턴으로 분리한다. * 반환된 함수들은 카탈로그 키에 대한 타입 자동완성을 지원한다. * * @example * // shared/errors.ts (프로젝트 내 1회 설정) * import { createHttpErrors } from "@linkup/http"; * import { ErrorCatalog } from "./errorCatalog"; * * export const { throwHttpError, createHttpError, errorHandler } = * createHttpErrors(ErrorCatalog, { * onMysqlDupEntry: (sqlMessage) => { * if (sqlMessage?.includes("product_domae_tb_unique")) return "이미 등록된 도매사 상품입니다."; * }, * }); * * @example * // route 에서 사용 * import { throwHttpError } from "@/shared/errors"; * throwHttpError("NOT_FOUND", "회원을 찾을 수 없습니다."); */ /** ErrorCatalog 각 항목의 형태 */ interface ErrorEntry { status: number; message: string; } /** AppError — throwHttpError / createHttpError 가 만드는 에러 객체 */ interface AppError extends Error { status: number; code: string; detail?: string; } interface HttpErrorsOptions { /** * MySQL ER_DUP_ENTRY 발생 시 호출된다. * 프로젝트 고유 유니크 키 이름으로 detail 메시지를 분기할 때 사용. * undefined를 반환하면 기본 메시지("이미 등록된 데이터입니다.")를 사용한다. */ onMysqlDupEntry?: (sqlMessage?: string) => string | undefined; /** * 에러 로깅 핸들러. * 기본: development 환경에서만 console.error. */ onLog?: (err: unknown) => void; } declare function createHttpErrors>(catalog: TCatalog, options?: HttpErrorsOptions): { throwHttpError: (code: keyof TCatalog & string, detail?: string) => never; createHttpError: (code: keyof TCatalog & string, detail?: string) => AppError; errorHandler: (err: unknown) => NextResponse; }; /** * createApiWrapper — Next.js Route Handler try/catch 래퍼 생성 팩토리 * * createHttpErrors()로 만든 errorHandler를 주입해 apiWrapper를 생성한다. * 모든 Route Handler에서 try/catch를 제거하고 에러 처리를 한 곳으로 모을 수 있다. * * @example * // shared/errors.ts * import { createHttpErrors, createApiWrapper } from "@linkup/http"; * import { ErrorCatalog } from "./errorCatalog"; * * export const { throwHttpError, errorHandler } = createHttpErrors(ErrorCatalog); * export const apiWrapper = createApiWrapper(errorHandler); * * @example * // app/api/members/route.ts * import { apiWrapper, throwHttpError } from "@/shared/errors"; * * export const GET = apiWrapper(async (req) => { * const user = await queryOne("SELECT ...", []); * if (!user) throwHttpError("NOT_FOUND"); * return NextResponse.json({ data: user }); * }); * * @example * // params가 있는 동적 라우트 * export const DELETE = apiWrapper<{ params: Promise<{ id: string }> }>( * async (req, { params }) => { * const { id } = await params; * await remove("users", { id }); * return NextResponse.json({ success: true }); * } * ); */ type RouteHandler = (req: NextRequest, context: TContext) => Promise; declare function createApiWrapper(errorHandler: (err: unknown) => Response): (handler: RouteHandler) => (req: NextRequest, context: TContext) => Promise; /** * createAuthWrapper — 인증 체크가 포함된 Route Handler 래퍼 생성 팩토리 * * getUser 함수를 주입받아 authWrapper를 생성한다. * getUser는 인증 실패 시 throwHttpError("UNAUTHORIZED")를 throw해야 한다. * (throw된 에러는 errorHandler가 처리하므로 route에서 별도 try/catch 불필요) * * @example * // shared/errors.ts (프로젝트 내 1회 설정) * import { createHttpErrors, createApiWrapper, createAuthWrapper } from "@linkup/http"; * import { ErrorCatalog } from "./errorCatalog"; * import { getUserSession } from "./auth"; * * export const { throwHttpError, errorHandler } = createHttpErrors(ErrorCatalog); * export const apiWrapper = createApiWrapper(errorHandler); * export const authWrapper = createAuthWrapper(errorHandler, getUserSession); * * @example * // shared/auth.ts (프로젝트에서 직접 구현) * import { getToken } from "next-auth/jwt"; * import { throwHttpError } from "./errors"; * * export async function getUserSession(req: NextRequest) { * const token = await getToken({ req, secret: process.env.NEXTAUTH_SECRET }); * if (!token?.user?.userKey) throwHttpError("UNAUTHORIZED", "다시 로그인 해주세요"); * return token.user; * } * * @example * // route 에서 사용 — user가 3번째 인자로 자동 주입됨 * import { authWrapper } from "@/shared/errors"; * * export const GET = authWrapper(async (req, _ctx, user) => { * return NextResponse.json({ userKey: user.userKey }); * }); * * // 동적 라우트 params + 인증 * export const DELETE = authWrapper<{ params: Promise<{ id: string }> }>( * async (req, { params }, user) => { * const { id } = await params; * return NextResponse.json({ id, deletedBy: user.userKey }); * } * ); */ type AuthRouteHandler = (req: NextRequest, context: TContext, user: TUser) => Promise; declare function createAuthWrapper(errorHandler: (err: unknown) => Response, getUser: (req: NextRequest) => Promise): (handler: AuthRouteHandler) => (req: NextRequest, context: TContext) => Promise; /** * 공통 HTTP 에러 카탈로그 * * 프로젝트 고유 에러 코드가 필요하면 spread로 확장한다. * * @example * // 그대로 사용 * import { throwHttpError } from "@sunkim4638/admin-modules/http"; * throwHttpError("NOT_FOUND", "회원을 찾을 수 없습니다."); * * @example * // 확장해서 사용 * import { createHttpErrors, createApiWrapper, ErrorCatalog } from "@sunkim4638/admin-modules/http"; * * const MyErrorCatalog = { * ...ErrorCatalog, * PAYMENT_FAILED: { status: 402, message: "결제에 실패했습니다." }, * } as const; * * export const { throwHttpError, errorHandler } = createHttpErrors(MyErrorCatalog); * export const apiWrapper = createApiWrapper(errorHandler); */ declare const ErrorCatalog: { readonly BAD_REQUEST: { readonly status: 400; readonly message: "잘못된 요청입니다."; }; readonly UNAUTHORIZED: { readonly status: 401; readonly message: "인증이 필요합니다."; }; readonly FORBIDDEN: { readonly status: 403; readonly message: "권한이 없습니다."; }; readonly NOT_FOUND: { readonly status: 404; readonly message: "데이터를 찾을 수 없습니다."; }; readonly CONFLICT: { readonly status: 409; readonly message: "이미 존재하는 데이터입니다."; }; readonly UNPROCESSABLE: { readonly status: 422; readonly message: "처리할 수 없는 요청입니다."; }; readonly INTERNAL_ERROR: { readonly status: 500; readonly message: "서버 오류가 발생했습니다."; }; readonly SERVICE_UNAVAILABLE: { readonly status: 503; readonly message: "서비스를 일시적으로 사용할 수 없습니다."; }; }; type ErrorKey = keyof typeof ErrorCatalog; declare const throwHttpError: (code: "BAD_REQUEST" | "UNAUTHORIZED" | "FORBIDDEN" | "NOT_FOUND" | "CONFLICT" | "UNPROCESSABLE" | "INTERNAL_ERROR" | "SERVICE_UNAVAILABLE", detail?: string) => never; declare const createHttpError: (code: "BAD_REQUEST" | "UNAUTHORIZED" | "FORBIDDEN" | "NOT_FOUND" | "CONFLICT" | "UNPROCESSABLE" | "INTERNAL_ERROR" | "SERVICE_UNAVAILABLE", detail?: string) => AppError; declare const errorHandler: (err: unknown) => next_server.NextResponse; declare const apiWrapper: (handler: (req: next_server.NextRequest, context: TContext) => Promise) => (req: next_server.NextRequest, context: TContext) => Promise; export { type AppError, ErrorCatalog, type ErrorEntry, type ErrorKey, type HttpErrorsOptions, apiWrapper, createApiWrapper, createAuthWrapper, createHttpError, createHttpErrors, errorHandler, throwHttpError };