@asapjs/error
v1.0.0-alpha.48
Published
Error handling utilities for ASAP.js
Readme
@asapjs/error
TypeScript 기반 ASAP.js 프레임워크를 위한 에러 처리 패키지
제거됨(alpha.42):
wrapWithEffect,effectErrorHandler,errorToResponse,causeToError,runEffectAsPromise등 Effect 라이브러리 통합 API는 alpha.42에서 제거되었습니다. 에러 정규화는 프레임워크 독립 함수인resolveErrorBody로 일원화되었고(각 어댑터의 Wrapper가 catch 블록에서 직접 호출), express 의존성도 이 패키지에서 제거되었습니다. 아래 문서는 현행 API만 다룹니다.
주요 기능
- TypeIs 기반 에러 정의: 간단하고 타입 안전한 에러 생성
- 자동 Swagger 문서화: TypeIs 스키마로부터 OpenAPI 스펙 자동 생성
- 프레임워크 독립 정규화:
resolveErrorBody로 어떤 에러든 통일된HttpErrorBody로 변환 - 레거시 호환성: 기존
HttpException지원
설치
yarn add @asapjs/error기본 사용법
1. 에러 정의 (새로운 방식)
import { error } from '@asapjs/error';
import { TypeIs } from '@asapjs/schema'; // TypeIs는 @asapjs/schema 소속 — @asapjs/error는 export하지 않음
export class UserErrors {
static NOT_FOUND = error(404, "USER_NOT_FOUND", "사용자를 찾을 수 없습니다. ID: {userId}", {
userId: TypeIs.INT({ comment: "사용자 ID" }),
});
static EMAIL_DUPLICATE = error(409, "USER_EMAIL_DUPLICATE", "이미 사용 중인 이메일입니다: {email}", {
email: TypeIs.STRING({ comment: "중복된 이메일" }),
existingUserId: TypeIs.INT({ comment: "기존 사용자 ID", optional: true }),
});
static INVALID_DATA = error(400, "USER_INVALID_DATA", "잘못된 사용자 데이터입니다", {
invalidFields: TypeIs.ARRAY({
type: () => TypeIs.STRING(),
comment: "유효하지 않은 필드 목록"
}),
details: TypeIs.JSON({
comment: "상세 오류 정보",
optional: true
}),
});
}2. 컨트롤러에서 사용
import { Get, Post, RouterController } from '@asapjs/router';
import { UserErrors } from './errors/UserErrors';
export default class UserController extends RouterController {
@Get('/:id', {
title: '사용자 조회',
errors: [UserErrors.NOT_FOUND], // Swagger에 자동으로 문서화
})
public getUser = async ({ path }) => {
const user = await findUser(path.id);
if (!user) {
throw UserErrors.NOT_FOUND({ userId: path.id });
}
return { result: user };
};
@Post('/', {
title: '사용자 생성',
body: CreateUserDto,
errors: [UserErrors.EMAIL_DUPLICATE, UserErrors.INVALID_DATA],
})
public createUser = async ({ body }) => {
// 이메일 검증
if (!isValidEmail(body.email)) {
throw UserErrors.INVALID_DATA({
invalidFields: ['email'],
details: { email: 'Invalid email format' }
});
}
// 중복 체크
const existing = await findUserByEmail(body.email);
if (existing) {
throw UserErrors.EMAIL_DUPLICATE({
email: body.email,
existingUserId: existing.id
});
}
const user = await createUser(body);
return { result: user };
};
}3. 에러 정규화는 각 어댑터의 Wrapper가 처리
컨트롤러 핸들러가 error()로 만든 HttpError(혹은 임의의 에러)를 던지면, @asapjs/router의
Wrapper(express)나 @asapjs/fastify의 FastifyWrapper가 catch 블록에서 resolveErrorBody를
호출해 통일된 JSON 응답으로 변환합니다. 별도의 에러 미들웨어를 등록할 필요가 없습니다.
import { resolveErrorBody } from '@asapjs/error';
try {
// ...
} catch (err) {
const body = resolveErrorBody(err); // { status, errorCode, message, data? }
res.status(body.status).json(body);
}HTTP 에러 응답 포맷
모든 에러는 다음과 같은 통일된 포맷으로 응답됩니다:
{
"status": 404,
"errorCode": "USER_NOT_FOUND",
"message": "사용자를 찾을 수 없습니다. ID: 123",
"data": {
"userId": 123
}
}주요 특징
1. TypeIs를 활용한 타입 안전성
error()는 스키마의 각 TypeIs 필드의 값 타입을 추론해, 에러 생성 함수의 인자를
컴파일 타임에 검증합니다. 예를 들어 TypeIs.INT() 필드에는 number만 허용됩니다.
const NOT_FOUND = error(404, "USER_NOT_FOUND", "...{userId}", {
userId: TypeIs.INT(),
});
throw NOT_FOUND({ userId: "123" }); // ❌ 컴파일 에러: string은 number에 할당 불가
throw NOT_FOUND({ userId: 123 }); // ✅ OK추론되는 값 타입: INT/FLOAT/DECIMAL/DOUBLE → number, BIGINT/LONG → bigint,
STRING/TEXT/PASSWORD/ENUM/DATEONLY → string, BOOLEAN → boolean,
DATETIME → Date, ARRAY → any[], JSON/DTO/BINARY 및 값 타입을 명시하지 않은
커스텀 팩토리(extendTypeIs) → any(하위호환 폴백).
참고 (한계)
- 이 검증은 순수 컴파일 타임 타입 체크입니다. 런타임에서는 여전히 스키마 매핑만 수행하며(예:
"1"→1코어션) 유효성 예외를 던지지 않습니다.optional: true는 런타임 옵션이라 값 타입에 실리지 않으므로, 이번 릴리스에서는 모든 필드가 required로 추론됩니다. optional 필드를 생략하면 컴파일 에러가 날 수 있습니다 (v1.0.0-alpha.42BREAKING — CHANGELOG 참조).
2. 메시지 템플릿
에러 메시지에 {fieldName} 형식으로 데이터를 삽입할 수 있습니다:
static NOT_FOUND = error(404, "USER_NOT_FOUND", "사용자 {userId}를 찾을 수 없습니다", {
userId: TypeIs.INT(),
});
// 사용 시: "사용자 123를 찾을 수 없습니다"
throw UserErrors.NOT_FOUND({ userId: 123 });3. 자동 Swagger 문서화
TypeIs 스키마가 자동으로 OpenAPI 스펙으로 변환됩니다:
components:
schemas:
USER_NOT_FOUND:
type: object
properties:
status:
type: number
example: 404
errorCode:
type: string
example: USER_NOT_FOUND
message:
type: string
data:
type: object
properties:
userId:
type: integer
description: 사용자 IDTypeIs 지원 타입
TypeIs.INT() // 정수
TypeIs.STRING() // 문자열
TypeIs.BOOLEAN() // 불린
TypeIs.FLOAT() // 실수
TypeIs.JSON() // JSON 객체
TypeIs.ARRAY() // 배열
TypeIs.DATETIME() // 날짜/시간
TypeIs.ENUM() // 열거형모든 타입은 optional: true 옵션을 지원합니다.
고급 기능
레거시 호환성
기존 HttpException을 사용하는 코드도 자동으로 처리됩니다:
throw new HttpException(400, '잘못된 요청입니다');
// resolveErrorBody가 자동으로 { status: 400, errorCode: 'HTTP_EXCEPTION', message: '...' } 로 변환마이그레이션 가이드
기존 방식
export class UserNotFoundError extends NotFoundError {
constructor(userId: number) {
super('USER_NOT_FOUND', `사용자를 찾을 수 없습니다. ID: ${userId}`, { userId });
}
}
throw new UserNotFoundError(123);새로운 방식
export class UserErrors {
static NOT_FOUND = error(404, "USER_NOT_FOUND", "사용자를 찾을 수 없습니다. ID: {userId}", {
userId: TypeIs.INT(),
});
}
throw UserErrors.NOT_FOUND({ userId: 123 });새로운 방식은 더 간결하고, 타입 안전하며, 자동 문서화를 지원합니다.
