TypeScript에서 스키마 검증을 위한 프로퍼티 디스크립터: 타입 안전 오류 처리
JavaScript 프로젝트에서 복잡한 중첩 객체를 검증하는 일은 개발자들에게 늘 골칫거리입니다. 문자열 경로를 사용해 오류를 추출하는 전통적인 방법은 리팩토링 시 런타임 버그를 유발하죠. 해결책은 바로 프로퍼티 디스크립터입니다. 프로퍼티 디스크립터의 구조화된 트리를 통해 타입 안전한 선택자와 완전한 IDE 자동 완성을 제공합니다.
핵심 아이디어는 각 스키마가 노드 트리를 구성하며, 모든 디스크립터가 자신의 프로퍼티 경로, 연결된 스키마, 부모, 접근 메서드를 알고 있다는 점입니다. 이를 통해 취약한 문자열 대신 람다로 선택자를 컴파일할 수 있습니다.
import { object, string, number, InferType } from '@cleverbrush/schema';
const OrderSchema = object({
id: string(),
customer: object({
name: string().minLength(2),
email: string().email(),
address: object({
city: string().required('City is required'),
zip: number()
})
}),
total: number().min(0)
});
const result = OrderSchema.validate({
id: '123',
customer: {
name: 'A',
email: 'not-an-email',
address: {
zip: -1
}
},
total: -5
});
const nameErrors = result.getErrorsFor(t => t.customer.name);
const cityErrors = result.getErrorsFor(t => t.customer.address.city);
필드를 이름 변경해도 TypeScript가 컴파일 타임에 잡아줍니다. IDE는 완벽한 자동 완성을 제공하죠.
불변 플루언트 API와 빌더
모든 스키마 메서드 호출은 새로운 인스턴스를 반환해 변이를 방지합니다. 조합과 재사용이 매우 간편해집니다.
라이브러리는 14가지 빌더 타입을 제공합니다:
string()— minLength, maxLength, matches, email, url, uuidnumber()— min, max, positive, negative, finite, multipleOfboolean()— 기본 검증date()— minDate, maxDateobject()— 명명된 프로퍼티array()— 타입화된 요소, 비어있지 않음union()— 판별된 유니온tuple()— 고정 길이record()— 동적 키func()— 함수 타입화any()— hasType 포함nul()— nulllazy()— 재귀 스키마extern()— Standard Schema v1 래퍼
기본 SchemaBuilder의 공통 메서드:
.optional()— undefined 허용.required('msg')— 필수 필드.nullable()— null 허용.default(value)— 기본값.catch(value)— 오류 시 대체값.readonly()— Readonly<T>.brand<'T'>()— 브랜디드 타입.describe('text')— JSDoc 보존.addValidator(fn)— 커스텀 로직.addPreprocessor(fn)— 전처리.introspect()— 메타데이터
런타임 스키마 인트로스펙션
.introspect() 메서드는 타입, 제약조건, 플래그, 설명, 확장을 포함한 완전한 스키마 설명을 반환합니다. 런타임에서 모든 도구와 통합에 활용 가능합니다.
const Name = string().minLength(2).maxLength(100).describe('User name').optional();
const info = Name.introspect();
// info.type === 'string'
// info.minLength === 2
// info.description === 'User name'
각 빌더에 대해 완전히 확장된 인트로스펙션을 제공합니다. JSON Schema 변환, 폼 생성, 커스텀 렌더러에 이상적입니다.
타입화된 확장 시스템
확장은 컴포저블하고 인트로스펙션 가능한 코어 추가 기능입니다. HEX 색상과 포트 예시:
const hexColorExt = defineExtension({
string: {
hexColor(this: StringSchemaBuilder) {
return this.addValidator((v) => {
const valid = /^#[0-9a-f]{6}$/i.test(v as string);
return {
valid,
errors: valid ? [] : [{ message: 'Must be a valid HEX color' }]
};
});
}
}
});
const s = withExtensions(hexColorExt, portExt);
const colorSchema = s.string().hexColor();
확장은 .introspect().extensions에 나타납니다. 내장 확장에는 email, url, uuid, positive, nonempty 등이 있습니다.
Standard Schema v1 호환성
스키마는 tRPC, TanStack Form, React Hook Form, Hono와 원활히 통합되는 Standard Schema v1을 구현합니다. extern()은 Zod/Valibot을 빌더로 래핑합니다.
const UserSchema = object({
name: string().minLength(2),
address: extern(zodAddress)
});
프로퍼티 디스크립터는 혼합 스키마에서도 타입 안전한 오류 접근을 제공합니다.
주요 요약
- 타입 안전 선택자: 프로퍼티 디스크립터로 문자열 경로와 런타임 버그 제거.
- 불변 플루언트 API: 부작용 없이 조합 간편.
- 런타임 인트로스펙션: 모든 도구를 위한 메타데이터 추출.
- 확장: 타입화된 컴포저블 코어 추가 기능.
- Standard Schema v1: 생태계 호환성 보장.
— Editorial Team
아직 댓글이 없습니다.