@zipbul/baker 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.ko.md +448 -0
- package/README.md +448 -0
- package/dist/index-6pbm9cq6.js +15 -0
- package/dist/index-6pbm9cq6.js.map +17 -0
- package/dist/index-fww37qs9.js +5 -0
- package/dist/index-fww37qs9.js.map +20 -0
- package/dist/index-w36xamck.js +6 -0
- package/dist/index-w36xamck.js.map +10 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +145 -0
- package/dist/index.js.map +18 -0
- package/dist/src/aot/array.d.ts +7 -0
- package/dist/src/aot/common.d.ts +15 -0
- package/dist/src/aot/date.d.ts +3 -0
- package/dist/src/aot/index.d.ts +9 -0
- package/dist/src/aot/index.js +5 -0
- package/dist/src/aot/index.js.map +18 -0
- package/dist/src/aot/locales.d.ts +5 -0
- package/dist/src/aot/number.d.ts +6 -0
- package/dist/src/aot/object.d.ts +6 -0
- package/dist/src/aot/string.d.ts +72 -0
- package/dist/src/aot/transform.d.ts +25 -0
- package/dist/src/aot/typechecker.d.ts +10 -0
- package/dist/src/collect.d.ts +12 -0
- package/dist/src/create-rule.d.ts +31 -0
- package/dist/src/decorators/array.d.ts +13 -0
- package/dist/src/decorators/common.d.ts +33 -0
- package/dist/src/decorators/date.d.ts +5 -0
- package/dist/src/decorators/index.d.ts +11 -0
- package/dist/src/decorators/index.js +5 -0
- package/dist/src/decorators/index.js.map +9 -0
- package/dist/src/decorators/locales.d.ts +9 -0
- package/dist/src/decorators/number.d.ts +11 -0
- package/dist/src/decorators/object.d.ts +9 -0
- package/dist/src/decorators/string.d.ts +72 -0
- package/dist/src/decorators/transform.d.ts +68 -0
- package/dist/src/decorators/typechecker.d.ts +18 -0
- package/dist/src/errors.d.ts +37 -0
- package/dist/src/functions/deserialize.d.ts +8 -0
- package/dist/src/functions/index.d.ts +2 -0
- package/dist/src/functions/serialize.d.ts +8 -0
- package/dist/src/interfaces.d.ts +47 -0
- package/dist/src/registry.d.ts +16 -0
- package/dist/src/rules/array.d.ts +7 -0
- package/dist/src/rules/common.d.ts +7 -0
- package/dist/src/rules/date.d.ts +3 -0
- package/dist/src/rules/index.d.ts +11 -0
- package/dist/src/rules/index.js +5 -0
- package/dist/src/rules/index.js.map +9 -0
- package/dist/src/rules/locales.d.ts +5 -0
- package/dist/src/rules/number.d.ts +6 -0
- package/dist/src/rules/object.d.ts +7 -0
- package/dist/src/rules/string.d.ts +145 -0
- package/dist/src/rules/typechecker.d.ts +14 -0
- package/dist/src/seal/circular-analyzer.d.ts +13 -0
- package/dist/src/seal/deserialize-builder.d.ts +5 -0
- package/dist/src/seal/expose-validator.d.ts +11 -0
- package/dist/src/seal/index.d.ts +5 -0
- package/dist/src/seal/seal.d.ts +27 -0
- package/dist/src/seal/serialize-builder.d.ts +7 -0
- package/dist/src/symbols.d.ts +8 -0
- package/dist/src/symbols.js +5 -0
- package/dist/src/symbols.js.map +9 -0
- package/dist/src/types.d.ts +119 -0
- package/package.json +83 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Junhyung Park
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.ko.md
ADDED
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<h1 align="center">@zipbul/baker</h1>
|
|
3
|
+
<p align="center">
|
|
4
|
+
<strong>데코레이터 기반 validate + transform — 인라인 코드 생성</strong>
|
|
5
|
+
</p>
|
|
6
|
+
<p align="center">
|
|
7
|
+
class-validator DX · AOT급 성능 · reflect-metadata 불필요
|
|
8
|
+
</p>
|
|
9
|
+
<p align="center">
|
|
10
|
+
<a href="https://github.com/zipbul/baker/actions"><img src="https://github.com/zipbul/baker/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
|
|
11
|
+
<a href="https://www.npmjs.com/package/@zipbul/baker"><img src="https://img.shields.io/npm/v/@zipbul/baker.svg" alt="npm version"></a>
|
|
12
|
+
<a href="https://www.npmjs.com/package/@zipbul/baker"><img src="https://img.shields.io/npm/dm/@zipbul/baker.svg" alt="npm downloads"></a>
|
|
13
|
+
<a href="https://github.com/zipbul/baker/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/@zipbul/baker.svg" alt="license"></a>
|
|
14
|
+
</p>
|
|
15
|
+
</p>
|
|
16
|
+
|
|
17
|
+
<p align="center">
|
|
18
|
+
<a href="./README.md">English</a>
|
|
19
|
+
</p>
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## 🤔 왜 Baker인가?
|
|
24
|
+
|
|
25
|
+
| | class-validator | Zod | TypeBox | **Baker** |
|
|
26
|
+
|---|---|---|---|---|
|
|
27
|
+
| 스키마 방식 | 데코레이터 | 함수 체이닝 | JSON Schema 빌더 | **데코레이터** |
|
|
28
|
+
| 성능 | 런타임 인터프리터 | 런타임 인터프리터 | JIT 컴파일 | **`new Function()` 인라인 코드생성** |
|
|
29
|
+
| Transform 내장 | 별도 패키지 | `.transform()` | ✗ | **통합** |
|
|
30
|
+
| reflect-metadata | 필수 | N/A | N/A | **불필요** |
|
|
31
|
+
| class-validator 마이그레이션 | — | 전면 재작성 | 전면 재작성 | **거의 그대로** |
|
|
32
|
+
|
|
33
|
+
Baker는 class-validator의 **익숙한 데코레이터 DX**를 유지하면서, `seal()` 시점에 `new Function()`으로 최적화된 검증+변환 함수를 생성합니다. **컴파일러 플러그인 없이 AOT 수준의 성능**을 제공합니다.
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## ✨ 주요 기능
|
|
38
|
+
|
|
39
|
+
- 🎯 **데코레이터 우선** — `@IsString()`, `@Min()`, `@IsEmail()` 등 80개 이상의 내장 검증기
|
|
40
|
+
- ⚡ **인라인 코드 생성** — `seal()`이 검증기를 최적화된 함수로 컴파일, 런타임 해석 없음
|
|
41
|
+
- 🔄 **검증 + 변환 통합** — `deserialize()`와 `serialize()`를 하나의 async 호출로
|
|
42
|
+
- 🪶 **reflect-metadata 불필요** — `reflect-metadata` import 없이 동작
|
|
43
|
+
- 🔁 **순환 참조 감지** — seal 시점에 자동 정적 분석
|
|
44
|
+
- 🏷️ **그룹 기반 검증** — `groups` 옵션으로 요청별 다른 규칙 적용
|
|
45
|
+
- 🧩 **커스텀 규칙** — `createRule()`로 코드생성을 지원하는 사용자 정의 검증기 작성
|
|
46
|
+
- 🚀 **AOT 모드** — zipbul CLI로 빌드 시점에 코드 생성, 런타임 `seal()` 비용 제거
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## 📦 설치
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
bun add @zipbul/baker
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
> **요구사항:** Bun ≥ 1.0, tsconfig.json에 `experimentalDecorators: true` 설정
|
|
57
|
+
|
|
58
|
+
```jsonc
|
|
59
|
+
// tsconfig.json
|
|
60
|
+
{
|
|
61
|
+
"compilerOptions": {
|
|
62
|
+
"experimentalDecorators": true
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## 🚀 빠른 시작
|
|
70
|
+
|
|
71
|
+
### 1. DTO 정의
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
import { IsString, IsInt, IsEmail, Min, Max } from '@zipbul/baker/decorators';
|
|
75
|
+
|
|
76
|
+
class CreateUserDto {
|
|
77
|
+
@IsString()
|
|
78
|
+
name!: string;
|
|
79
|
+
|
|
80
|
+
@IsInt()
|
|
81
|
+
@Min(0)
|
|
82
|
+
@Max(120)
|
|
83
|
+
age!: number;
|
|
84
|
+
|
|
85
|
+
@IsEmail()
|
|
86
|
+
email!: string;
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### 2. 앱 시작 시 seal()
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
import { seal } from '@zipbul/baker';
|
|
94
|
+
|
|
95
|
+
// 등록된 모든 DTO를 최적화된 검증 함수로 컴파일
|
|
96
|
+
seal();
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### 3. 요청마다 deserialize()
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
import { deserialize, BakerValidationError } from '@zipbul/baker';
|
|
103
|
+
|
|
104
|
+
try {
|
|
105
|
+
const user = await deserialize(CreateUserDto, requestBody);
|
|
106
|
+
// user는 검증 완료된 CreateUserDto 인스턴스
|
|
107
|
+
} catch (e) {
|
|
108
|
+
if (e instanceof BakerValidationError) {
|
|
109
|
+
console.log(e.errors); // BakerError[]
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### 4. serialize()
|
|
115
|
+
|
|
116
|
+
```typescript
|
|
117
|
+
import { serialize } from '@zipbul/baker';
|
|
118
|
+
|
|
119
|
+
const plain = await serialize(userInstance);
|
|
120
|
+
// plain: Record<string, unknown>
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## 🏗️ 데코레이터
|
|
126
|
+
|
|
127
|
+
### 타입 검사
|
|
128
|
+
|
|
129
|
+
| 데코레이터 | 설명 |
|
|
130
|
+
|---|---|
|
|
131
|
+
| `@IsString()` | `typeof === 'string'` |
|
|
132
|
+
| `@IsNumber(opts?)` | `typeof === 'number'` + NaN/Infinity 검사 |
|
|
133
|
+
| `@IsInt()` | 정수 검사 |
|
|
134
|
+
| `@IsBoolean()` | `typeof === 'boolean'` |
|
|
135
|
+
| `@IsDate()` | `instanceof Date && !isNaN` |
|
|
136
|
+
| `@IsEnum(enumObj)` | 열거형 값 검사 |
|
|
137
|
+
| `@IsArray()` | `Array.isArray()` |
|
|
138
|
+
| `@IsObject()` | `typeof === 'object'`, null/Array 제외 |
|
|
139
|
+
|
|
140
|
+
### 공통
|
|
141
|
+
|
|
142
|
+
| 데코레이터 | 설명 |
|
|
143
|
+
|---|---|
|
|
144
|
+
| `@IsDefined()` | `!== undefined && !== null` |
|
|
145
|
+
| `@IsOptional()` | 값이 없으면 이후 규칙 건너뜀 |
|
|
146
|
+
| `@IsNotEmpty()` | `!== undefined && !== null && !== ''` |
|
|
147
|
+
| `@IsEmpty()` | `=== undefined \|\| === null \|\| === ''` |
|
|
148
|
+
| `@Equals(val)` | `=== val` |
|
|
149
|
+
| `@NotEquals(val)` | `!== val` |
|
|
150
|
+
| `@IsIn(values)` | 주어진 배열에 포함 |
|
|
151
|
+
| `@IsNotIn(values)` | 주어진 배열에 미포함 |
|
|
152
|
+
| `@ValidateNested()` | 중첩 DTO 검증 |
|
|
153
|
+
| `@ValidateIf(fn)` | 조건부 검증 |
|
|
154
|
+
|
|
155
|
+
### 숫자
|
|
156
|
+
|
|
157
|
+
| 데코레이터 | 설명 |
|
|
158
|
+
|---|---|
|
|
159
|
+
| `@Min(n)` | `value >= n` |
|
|
160
|
+
| `@Max(n)` | `value <= n` |
|
|
161
|
+
| `@IsPositive()` | `value > 0` |
|
|
162
|
+
| `@IsNegative()` | `value < 0` |
|
|
163
|
+
| `@IsInRange(min, max)` | `min <= value <= max` |
|
|
164
|
+
| `@IsDivisibleBy(n)` | `value % n === 0` |
|
|
165
|
+
|
|
166
|
+
### 문자열
|
|
167
|
+
|
|
168
|
+
<details>
|
|
169
|
+
<summary>50개 이상의 문자열 검증기 — 클릭하여 펼치기</summary>
|
|
170
|
+
|
|
171
|
+
| 데코레이터 | 설명 |
|
|
172
|
+
|---|---|
|
|
173
|
+
| `@MinLength(n)` | 최소 길이 |
|
|
174
|
+
| `@MaxLength(n)` | 최대 길이 |
|
|
175
|
+
| `@Length(min, max)` | 길이 범위 |
|
|
176
|
+
| `@Contains(seed)` | 부분 문자열 포함 |
|
|
177
|
+
| `@NotContains(seed)` | 부분 문자열 미포함 |
|
|
178
|
+
| `@Matches(pattern)` | 정규식 매치 |
|
|
179
|
+
| `@IsAlpha()` | 알파벳만 |
|
|
180
|
+
| `@IsAlphanumeric()` | 알파벳/숫자만 |
|
|
181
|
+
| `@IsNumeric()` | 숫자 문자열 |
|
|
182
|
+
| `@IsEmail(opts?)` | 이메일 형식 |
|
|
183
|
+
| `@IsURL(opts?)` | URL 형식 |
|
|
184
|
+
| `@IsUUID(version?)` | UUID v1–v5 |
|
|
185
|
+
| `@IsIP(version?)` | IPv4 / IPv6 |
|
|
186
|
+
| `@IsMACAddress()` | MAC 주소 |
|
|
187
|
+
| `@IsISBN(version?)` | ISBN-10 / ISBN-13 |
|
|
188
|
+
| `@IsISIN()` | ISIN |
|
|
189
|
+
| `@IsIBAN()` | IBAN |
|
|
190
|
+
| `@IsJSON()` | JSON 파싱 가능 문자열 |
|
|
191
|
+
| `@IsBase64()` | Base64 인코딩 |
|
|
192
|
+
| `@IsBase32()` | Base32 인코딩 |
|
|
193
|
+
| `@IsBase58()` | Base58 인코딩 |
|
|
194
|
+
| `@IsHexColor()` | 16진수 색상 코드 |
|
|
195
|
+
| `@IsHSL()` | HSL 색상 |
|
|
196
|
+
| `@IsRgbColor()` | RGB 색상 |
|
|
197
|
+
| `@IsHexadecimal()` | 16진수 문자열 |
|
|
198
|
+
| `@IsBIC()` | BIC/SWIFT 코드 |
|
|
199
|
+
| `@IsISRC()` | ISRC 코드 |
|
|
200
|
+
| `@IsEAN()` | EAN 바코드 |
|
|
201
|
+
| `@IsMimeType()` | MIME 타입 |
|
|
202
|
+
| `@IsMagnetURI()` | Magnet URI |
|
|
203
|
+
| `@IsCreditCard()` | 신용카드 번호 |
|
|
204
|
+
| `@IsHash(algorithm)` | 해시 (`md5 \| sha1 \| sha256 \| sha512` 등) |
|
|
205
|
+
| `@IsRFC3339()` | RFC 3339 날짜 |
|
|
206
|
+
| `@IsMilitaryTime()` | 24시간 형식 (`HH:MM`) |
|
|
207
|
+
| `@IsLatitude()` | 위도 (-90 ~ 90) |
|
|
208
|
+
| `@IsLongitude()` | 경도 (-180 ~ 180) |
|
|
209
|
+
| `@IsEthereumAddress()` | 이더리움 주소 |
|
|
210
|
+
| `@IsBtcAddress()` | 비트코인 주소 (P2PKH/P2SH/bech32) |
|
|
211
|
+
| `@IsISO4217CurrencyCode()` | ISO 4217 통화 코드 |
|
|
212
|
+
| `@IsPhoneNumber()` | E.164 국제 전화번호 |
|
|
213
|
+
| `@IsStrongPassword(opts?)` | 강력한 비밀번호 |
|
|
214
|
+
| `@IsSemVer()` | 시맨틱 버전 |
|
|
215
|
+
| `@IsISO8601()` | ISO 8601 날짜 문자열 |
|
|
216
|
+
| `@IsMongoId()` | MongoDB ObjectId |
|
|
217
|
+
| `@IsTaxId(locale)` | 국가별 납세자 번호 |
|
|
218
|
+
|
|
219
|
+
</details>
|
|
220
|
+
|
|
221
|
+
### 날짜
|
|
222
|
+
|
|
223
|
+
| 데코레이터 | 설명 |
|
|
224
|
+
|---|---|
|
|
225
|
+
| `@MinDate(date)` | 최소 날짜 |
|
|
226
|
+
| `@MaxDate(date)` | 최대 날짜 |
|
|
227
|
+
|
|
228
|
+
### 배열
|
|
229
|
+
|
|
230
|
+
| 데코레이터 | 설명 |
|
|
231
|
+
|---|---|
|
|
232
|
+
| `@ArrayContains(values)` | 주어진 요소를 모두 포함 |
|
|
233
|
+
| `@ArrayNotContains(values)` | 주어진 요소를 포함하지 않음 |
|
|
234
|
+
| `@ArrayMinSize(n)` | 배열 최소 길이 |
|
|
235
|
+
| `@ArrayMaxSize(n)` | 배열 최대 길이 |
|
|
236
|
+
| `@ArrayUnique()` | 중복 없음 |
|
|
237
|
+
| `@ArrayNotEmpty()` | 빈 배열이 아님 |
|
|
238
|
+
|
|
239
|
+
### 로케일
|
|
240
|
+
|
|
241
|
+
| 데코레이터 | 설명 |
|
|
242
|
+
|---|---|
|
|
243
|
+
| `@IsMobilePhone(locale)` | 국가별 이동전화 번호 |
|
|
244
|
+
| `@IsPostalCode(locale)` | 국가별 우편번호 |
|
|
245
|
+
| `@IsIdentityCard(locale)` | 국가별 신분증 번호 |
|
|
246
|
+
| `@IsPassportNumber(locale)` | 국가별 여권 번호 |
|
|
247
|
+
|
|
248
|
+
### Transform & Type
|
|
249
|
+
|
|
250
|
+
| 데코레이터 | 설명 |
|
|
251
|
+
|---|---|
|
|
252
|
+
| `@Transform(fn, opts?)` | 커스텀 변환 함수 |
|
|
253
|
+
| `@Type(fn)` | 중첩 DTO 타입 지정 + 암묵적 변환 |
|
|
254
|
+
| `@Expose(opts?)` | 프로퍼티 노출 제어 |
|
|
255
|
+
| `@Exclude(opts?)` | 직렬화에서 프로퍼티 제외 |
|
|
256
|
+
|
|
257
|
+
---
|
|
258
|
+
|
|
259
|
+
## ⚙️ Validation Options
|
|
260
|
+
|
|
261
|
+
모든 검증 데코레이터는 마지막 인자로 `ValidationOptions`를 받습니다:
|
|
262
|
+
|
|
263
|
+
```typescript
|
|
264
|
+
interface ValidationOptions {
|
|
265
|
+
each?: boolean; // 배열의 각 원소에 규칙 적용
|
|
266
|
+
groups?: string[]; // 이 규칙이 속하는 그룹
|
|
267
|
+
message?: string | ((args: {
|
|
268
|
+
property: string;
|
|
269
|
+
value: unknown;
|
|
270
|
+
constraints: unknown[];
|
|
271
|
+
}) => string); // 커스텀 에러 메시지
|
|
272
|
+
context?: unknown; // 에러에 첨부할 임의 컨텍스트
|
|
273
|
+
}
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
**예시:**
|
|
277
|
+
|
|
278
|
+
```typescript
|
|
279
|
+
class UserDto {
|
|
280
|
+
@IsString({ message: '이름은 문자열이어야 합니다' })
|
|
281
|
+
name!: string;
|
|
282
|
+
|
|
283
|
+
@IsInt({
|
|
284
|
+
message: ({ property }) => `${property}는 정수여야 합니다`,
|
|
285
|
+
context: { httpStatus: 400 },
|
|
286
|
+
})
|
|
287
|
+
age!: number;
|
|
288
|
+
}
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
---
|
|
292
|
+
|
|
293
|
+
## 🚨 에러 처리
|
|
294
|
+
|
|
295
|
+
검증 실패 시 `deserialize()`는 `BakerValidationError`를 throw합니다:
|
|
296
|
+
|
|
297
|
+
```typescript
|
|
298
|
+
class BakerValidationError extends Error {
|
|
299
|
+
readonly errors: BakerError[];
|
|
300
|
+
readonly className: string;
|
|
301
|
+
}
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
각 에러는 `BakerError` 인터페이스를 따릅니다:
|
|
305
|
+
|
|
306
|
+
```typescript
|
|
307
|
+
interface BakerError {
|
|
308
|
+
readonly path: string; // 필드 경로 ('user.address.city')
|
|
309
|
+
readonly code: string; // 에러 코드 ('isString', 'min', 'isEmail')
|
|
310
|
+
readonly message?: string; // 커스텀 메시지 (message 옵션 설정 시)
|
|
311
|
+
readonly context?: unknown; // 커스텀 컨텍스트 (context 옵션 설정 시)
|
|
312
|
+
}
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
---
|
|
316
|
+
|
|
317
|
+
## 📋 배열 검증
|
|
318
|
+
|
|
319
|
+
`each: true` 옵션으로 Array, Set, Map의 각 원소에 규칙을 적용합니다:
|
|
320
|
+
|
|
321
|
+
```typescript
|
|
322
|
+
class TagsDto {
|
|
323
|
+
@IsString({ each: true })
|
|
324
|
+
tags!: string[];
|
|
325
|
+
}
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
---
|
|
329
|
+
|
|
330
|
+
## 🏷️ 그룹 기반 검증
|
|
331
|
+
|
|
332
|
+
용도에 따라 다른 규칙을 적용할 수 있습니다:
|
|
333
|
+
|
|
334
|
+
```typescript
|
|
335
|
+
class UserDto {
|
|
336
|
+
@IsString({ groups: ['create'] })
|
|
337
|
+
name!: string;
|
|
338
|
+
|
|
339
|
+
@IsEmail({ groups: ['create', 'update'] })
|
|
340
|
+
email!: string;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// 'create' 그룹의 규칙만 검증
|
|
344
|
+
const user = await deserialize(UserDto, body, { groups: ['create'] });
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
---
|
|
348
|
+
|
|
349
|
+
## 🪆 중첩 객체
|
|
350
|
+
|
|
351
|
+
```typescript
|
|
352
|
+
import { ValidateNested, Type } from '@zipbul/baker/decorators';
|
|
353
|
+
|
|
354
|
+
class AddressDto {
|
|
355
|
+
@IsString()
|
|
356
|
+
city!: string;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
class UserDto {
|
|
360
|
+
@ValidateNested()
|
|
361
|
+
@Type(() => AddressDto)
|
|
362
|
+
address!: AddressDto;
|
|
363
|
+
}
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
---
|
|
367
|
+
|
|
368
|
+
## 🧩 커스텀 규칙
|
|
369
|
+
|
|
370
|
+
코드생성을 지원하는 사용자 정의 검증 규칙을 만들 수 있습니다:
|
|
371
|
+
|
|
372
|
+
```typescript
|
|
373
|
+
import { createRule } from '@zipbul/baker';
|
|
374
|
+
|
|
375
|
+
const isPositiveInt = createRule({
|
|
376
|
+
name: 'isPositiveInt',
|
|
377
|
+
validate: (value) => Number.isInteger(value) && (value as number) > 0,
|
|
378
|
+
emit: (varName, ctx) =>
|
|
379
|
+
`if (!Number.isInteger(${varName}) || ${varName} <= 0) ${ctx.fail('isPositiveInt')};`,
|
|
380
|
+
});
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
---
|
|
384
|
+
|
|
385
|
+
## ⚙️ Seal 옵션
|
|
386
|
+
|
|
387
|
+
```typescript
|
|
388
|
+
seal({
|
|
389
|
+
enableImplicitConversion: false, // 데코레이터 기반 자동 타입 변환
|
|
390
|
+
enableCircularCheck: 'auto', // 순환 참조 감지 ('auto' | true | false)
|
|
391
|
+
exposeDefaultValues: false, // 누락된 키에 클래스 기본값 사용
|
|
392
|
+
stopAtFirstError: false, // 첫 에러에서 중단 또는 전체 수집
|
|
393
|
+
debug: false, // 생성된 소스를 검사용으로 저장
|
|
394
|
+
});
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
---
|
|
398
|
+
|
|
399
|
+
## 🔧 AOT 모드
|
|
400
|
+
|
|
401
|
+
**zipbul CLI**를 사용하면 빌드 시점에 검증 코드를 생성하여, 런타임 `seal()` 비용을 완전히 제거할 수 있습니다.
|
|
402
|
+
|
|
403
|
+
AOT 모드에서는 `/aot` 임포트(빈 스텁 데코레이터)를 사용합니다:
|
|
404
|
+
|
|
405
|
+
```typescript
|
|
406
|
+
import { IsString } from '@zipbul/baker/aot';
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
CLI가 빌드 단계에서 이 스텁들을 사전 생성된 검증 코드로 대체합니다.
|
|
410
|
+
|
|
411
|
+
---
|
|
412
|
+
|
|
413
|
+
## 📂 서브패스 익스포트
|
|
414
|
+
|
|
415
|
+
| 임포트 경로 | 용도 |
|
|
416
|
+
|---|---|
|
|
417
|
+
| `@zipbul/baker` | 메인 API: `seal`, `deserialize`, `serialize`, 모든 데코레이터 |
|
|
418
|
+
| `@zipbul/baker/decorators` | 데코레이터만 |
|
|
419
|
+
| `@zipbul/baker/aot` | AOT 모드용 빈 스텁 데코레이터 |
|
|
420
|
+
| `@zipbul/baker/rules` | 원시 규칙 객체 |
|
|
421
|
+
| `@zipbul/baker/symbols` | 내부 심볼 |
|
|
422
|
+
|
|
423
|
+
---
|
|
424
|
+
|
|
425
|
+
## 🔍 동작 원리
|
|
426
|
+
|
|
427
|
+
```
|
|
428
|
+
┌─────────────┐ ┌──────────────┐ ┌─────────────────────┐
|
|
429
|
+
│ 데코레이터 │ ──▶ │ seal() │ ──▶ │ new Function() 코드 │
|
|
430
|
+
│ (메타데이터) │ │ 앱 시작 시 │ │ (인라인 코드생성) │
|
|
431
|
+
└─────────────┘ └──────────────┘ └──────────┬──────────┘
|
|
432
|
+
│
|
|
433
|
+
┌──────────▼──────────┐
|
|
434
|
+
│ deserialize() / │
|
|
435
|
+
│ serialize() │
|
|
436
|
+
│ (생성된 코드 실행) │
|
|
437
|
+
└─────────────────────┘
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
1. **데코레이터**가 클래스 프로퍼티에 검증 메타데이터를 부착합니다
|
|
441
|
+
2. **`seal()`**이 모든 메타데이터를 읽고, 순환 참조를 분석하고, `new Function()`으로 인라인 JavaScript 함수를 생성합니다
|
|
442
|
+
3. **`deserialize()` / `serialize()`**가 생성된 함수를 실행합니다 — 해석 루프 없이, 직선적인 최적화 코드만 실행
|
|
443
|
+
|
|
444
|
+
---
|
|
445
|
+
|
|
446
|
+
## 📄 라이선스
|
|
447
|
+
|
|
448
|
+
[MIT](./LICENSE) © [Junhyung Park](https://github.com/parkrevil)
|