@zipbul/baker 0.1.2 → 1.1.0
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/README.ko.md +368 -280
- package/README.md +407 -280
- package/dist/index-70ggmxsa.js +6 -0
- package/dist/index-gcptd79v.js +6 -0
- package/dist/index-xdn55cz3.js +4 -0
- package/dist/index.d.ts +6 -5
- package/dist/index.js +211 -155
- package/dist/src/collect.d.ts +3 -3
- package/dist/src/configure.d.ts +33 -0
- package/dist/src/create-rule.d.ts +10 -19
- package/dist/src/decorators/field.d.ts +86 -0
- package/dist/src/decorators/index.d.ts +2 -14
- package/dist/src/decorators/index.js +2 -2
- package/dist/src/errors.d.ts +17 -17
- package/dist/src/functions/deserialize.d.ts +6 -4
- package/dist/src/functions/serialize.d.ts +5 -4
- package/dist/src/functions/to-json-schema.d.ts +11 -5
- package/dist/src/interfaces.d.ts +9 -30
- package/dist/src/registry.d.ts +4 -12
- package/dist/src/rules/index.d.ts +2 -0
- package/dist/src/rules/index.js +11 -2
- package/dist/src/rules/object.d.ts +1 -1
- package/dist/src/seal/circular-analyzer.d.ts +5 -9
- package/dist/src/seal/expose-validator.d.ts +6 -6
- package/dist/src/seal/index.d.ts +1 -1
- package/dist/src/seal/seal.d.ts +30 -15
- package/dist/src/seal/serialize-builder.d.ts +2 -2
- package/dist/src/symbols.d.ts +5 -5
- package/dist/src/symbols.js +2 -2
- package/dist/src/types.d.ts +38 -32
- package/dist/src/utils.d.ts +2 -0
- package/package.json +1 -1
- package/dist/index-3gcf6hkv.js +0 -5
- package/dist/index-mx6gnk4h.js +0 -6
- package/dist/index-wy5sh2nx.js +0 -15
- package/dist/src/decorators/array.d.ts +0 -13
- package/dist/src/decorators/common.d.ts +0 -39
- package/dist/src/decorators/date.d.ts +0 -5
- package/dist/src/decorators/locales.d.ts +0 -9
- package/dist/src/decorators/nested.d.ts +0 -17
- package/dist/src/decorators/number.d.ts +0 -15
- package/dist/src/decorators/object.d.ts +0 -9
- package/dist/src/decorators/schema.d.ts +0 -13
- package/dist/src/decorators/string.d.ts +0 -72
- package/dist/src/decorators/transform.d.ts +0 -68
- package/dist/src/decorators/typechecker.d.ts +0 -18
package/README.ko.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<strong>데코레이터 기반 validate + transform — 인라인 코드 생성</strong>
|
|
5
5
|
</p>
|
|
6
6
|
<p align="center">
|
|
7
|
-
|
|
7
|
+
단일 <code>@Field()</code> 데코레이터 · AOT급 성능 · reflect-metadata 불필요
|
|
8
8
|
</p>
|
|
9
9
|
<p align="center">
|
|
10
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>
|
|
@@ -20,42 +20,45 @@
|
|
|
20
20
|
|
|
21
21
|
---
|
|
22
22
|
|
|
23
|
-
##
|
|
23
|
+
## 왜 Baker인가?
|
|
24
24
|
|
|
25
25
|
| | class-validator | Zod | TypeBox | **Baker** |
|
|
26
26
|
|---|---|---|---|---|
|
|
27
|
-
| 스키마 방식 | 데코레이터 | 함수 체이닝 | JSON Schema 빌더 |
|
|
27
|
+
| 스키마 방식 | 데코레이터 | 함수 체이닝 | JSON Schema 빌더 | **단일 `@Field()` 데코레이터** |
|
|
28
28
|
| 성능 | 런타임 인터프리터 | 런타임 인터프리터 | JIT 컴파일 | **`new Function()` 인라인 코드생성** |
|
|
29
|
-
| Transform 내장 | 별도 패키지 | `.transform()` |
|
|
29
|
+
| Transform 내장 | 별도 패키지 | `.transform()` | N/A | **통합** |
|
|
30
30
|
| reflect-metadata | 필수 | N/A | N/A | **불필요** |
|
|
31
31
|
| class-validator 마이그레이션 | — | 전면 재작성 | 전면 재작성 | **거의 그대로** |
|
|
32
32
|
|
|
33
|
-
Baker는
|
|
33
|
+
Baker는 검증, 변환, 노출 제어, 타입 힌트를 결합하는 **단일 `@Field()` 데코레이터**를 제공합니다. 첫 사용 시 `new Function()`으로 최적화된 함수를 생성하여 모든 DTO를 자동으로 seal합니다 — **컴파일러 플러그인 없이 AOT 수준의 성능**을 제공합니다.
|
|
34
34
|
|
|
35
35
|
---
|
|
36
36
|
|
|
37
|
-
##
|
|
38
|
-
|
|
39
|
-
-
|
|
40
|
-
-
|
|
41
|
-
-
|
|
42
|
-
-
|
|
43
|
-
-
|
|
44
|
-
-
|
|
45
|
-
-
|
|
46
|
-
-
|
|
47
|
-
-
|
|
48
|
-
-
|
|
37
|
+
## 주요 기능
|
|
38
|
+
|
|
39
|
+
- **단일 데코레이터** — `@Field()`가 30개 이상의 개별 데코레이터를 대체
|
|
40
|
+
- **80개 이상의 내장 규칙** — `isString`, `min()`, `isEmail()` 등을 인자로 조합
|
|
41
|
+
- **인라인 코드 생성** — 첫 `deserialize()`/`serialize()` 호출 시 auto-seal로 검증기를 컴파일
|
|
42
|
+
- **검증 + 변환 통합** — `deserialize()`와 `serialize()`를 하나의 async 호출로
|
|
43
|
+
- **reflect-metadata 불필요** — `reflect-metadata` import 없이 동작
|
|
44
|
+
- **순환 참조 감지** — seal 시점에 자동 정적 분석
|
|
45
|
+
- **그룹 기반 검증** — `groups` 옵션으로 요청별 다른 규칙 적용
|
|
46
|
+
- **커스텀 규칙** — `createRule()`로 코드생성을 지원하는 사용자 정의 검증기 작성
|
|
47
|
+
- **JSON Schema 출력** — `toJsonSchema()`로 DTO에서 JSON Schema Draft 2020-12 생성
|
|
48
|
+
- **다형성 discriminator** — `@Field({ discriminator })`로 유니온 타입 지원
|
|
49
|
+
- **Whitelist 모드** — `configure({ forbidUnknown: true })`로 미선언 필드 거부
|
|
50
|
+
- **클래스 상속** — 자식 DTO가 부모 `@Field()` 데코레이터를 자동으로 상속
|
|
51
|
+
- **비동기 transform** — transform 함수에 async 사용 가능
|
|
49
52
|
|
|
50
53
|
---
|
|
51
54
|
|
|
52
|
-
##
|
|
55
|
+
## 설치
|
|
53
56
|
|
|
54
57
|
```bash
|
|
55
58
|
bun add @zipbul/baker
|
|
56
59
|
```
|
|
57
60
|
|
|
58
|
-
> **요구사항:** Bun
|
|
61
|
+
> **요구사항:** Bun >= 1.0, tsconfig.json에 `experimentalDecorators: true` 설정
|
|
59
62
|
|
|
60
63
|
```jsonc
|
|
61
64
|
// tsconfig.json
|
|
@@ -68,37 +71,27 @@ bun add @zipbul/baker
|
|
|
68
71
|
|
|
69
72
|
---
|
|
70
73
|
|
|
71
|
-
##
|
|
74
|
+
## 빠른 시작
|
|
72
75
|
|
|
73
76
|
### 1. DTO 정의
|
|
74
77
|
|
|
75
78
|
```typescript
|
|
76
|
-
import {
|
|
79
|
+
import { Field } from '@zipbul/baker';
|
|
80
|
+
import { isString, isInt, isEmail, min, max } from '@zipbul/baker/rules';
|
|
77
81
|
|
|
78
82
|
class CreateUserDto {
|
|
79
|
-
@
|
|
83
|
+
@Field(isString)
|
|
80
84
|
name!: string;
|
|
81
85
|
|
|
82
|
-
@
|
|
83
|
-
@Min(0)
|
|
84
|
-
@Max(120)
|
|
86
|
+
@Field(isInt, min(0), max(120))
|
|
85
87
|
age!: number;
|
|
86
88
|
|
|
87
|
-
@
|
|
89
|
+
@Field(isEmail())
|
|
88
90
|
email!: string;
|
|
89
91
|
}
|
|
90
92
|
```
|
|
91
93
|
|
|
92
|
-
### 2.
|
|
93
|
-
|
|
94
|
-
```typescript
|
|
95
|
-
import { seal } from '@zipbul/baker';
|
|
96
|
-
|
|
97
|
-
// 등록된 모든 DTO를 최적화된 검증 함수로 컴파일
|
|
98
|
-
seal();
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
### 3. 요청마다 deserialize()
|
|
94
|
+
### 2. Deserialize (첫 호출 시 auto-seal)
|
|
102
95
|
|
|
103
96
|
```typescript
|
|
104
97
|
import { deserialize, BakerValidationError } from '@zipbul/baker';
|
|
@@ -113,7 +106,7 @@ try {
|
|
|
113
106
|
}
|
|
114
107
|
```
|
|
115
108
|
|
|
116
|
-
###
|
|
109
|
+
### 3. Serialize
|
|
117
110
|
|
|
118
111
|
```typescript
|
|
119
112
|
import { serialize } from '@zipbul/baker';
|
|
@@ -122,180 +115,271 @@ const plain = await serialize(userInstance);
|
|
|
122
115
|
// plain: Record<string, unknown>
|
|
123
116
|
```
|
|
124
117
|
|
|
118
|
+
> `seal()` 호출이 필요 없습니다 — baker는 첫 `deserialize()` 또는 `serialize()` 호출 시 등록된 모든 DTO를 자동으로 seal합니다.
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## `@Field()` 데코레이터
|
|
123
|
+
|
|
124
|
+
`@Field()`는 모든 개별 데코레이터를 대체하는 단일 데코레이터입니다. 검증 규칙을 위치 인자로, 고급 기능은 옵션 객체로 전달합니다.
|
|
125
|
+
|
|
126
|
+
### 시그니처
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
// 규칙만
|
|
130
|
+
@Field(isString, minLength(3), maxLength(100))
|
|
131
|
+
|
|
132
|
+
// 옵션만
|
|
133
|
+
@Field({ optional: true, nullable: true })
|
|
134
|
+
|
|
135
|
+
// 규칙 + 옵션
|
|
136
|
+
@Field(isString, { name: 'user_name', groups: ['create'] })
|
|
137
|
+
|
|
138
|
+
// 규칙 없이 (단순 필드)
|
|
139
|
+
@Field()
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### FieldOptions
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
interface FieldOptions {
|
|
146
|
+
type?: () => Constructor | [Constructor]; // 중첩 DTO 타입 (순환 참조를 위한 thunk)
|
|
147
|
+
discriminator?: { // 다형성 유니온
|
|
148
|
+
property: string;
|
|
149
|
+
subTypes: { value: Function; name: string }[];
|
|
150
|
+
};
|
|
151
|
+
keepDiscriminatorProperty?: boolean; // 출력에 discriminator 키 유지
|
|
152
|
+
rules?: (EmittableRule | ArrayOfMarker)[]; // 검증 규칙 (위치 인자 대안)
|
|
153
|
+
optional?: boolean; // undefined 허용
|
|
154
|
+
nullable?: boolean; // null 허용
|
|
155
|
+
name?: string; // JSON 키 매핑 (양방향)
|
|
156
|
+
deserializeName?: string; // 역직렬화 전용 키 매핑
|
|
157
|
+
serializeName?: string; // 직렬화 전용 키 매핑
|
|
158
|
+
exclude?: boolean | 'deserializeOnly' | 'serializeOnly';
|
|
159
|
+
groups?: string[]; // 가시성 + 조건부 검증
|
|
160
|
+
when?: (obj: any) => boolean; // 조건부 검증
|
|
161
|
+
schema?: JsonSchemaOverride; // JSON Schema 메타데이터
|
|
162
|
+
transform?: (params: FieldTransformParams) => unknown;
|
|
163
|
+
transformDirection?: 'deserializeOnly' | 'serializeOnly';
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### 규칙별 옵션 (message, groups)
|
|
168
|
+
|
|
169
|
+
개별 규칙 함수에 `message`, `groups`, `context`를 직접 전달하는 것은 **불가능**합니다. 대신 `@Field()` 레벨에서 제어합니다:
|
|
170
|
+
|
|
171
|
+
- **`groups`** — `FieldOptions.groups`로 설정 (해당 필드의 모든 규칙에 적용)
|
|
172
|
+
- **`message`** / **`context`** — `createRule()`로 커스텀 에러 메시지 설정하거나 `BakerError.code`로 처리
|
|
173
|
+
- **`each` (배열 요소 검증)** — `arrayOf()` 사용 (아래 참조)
|
|
174
|
+
|
|
175
|
+
### `arrayOf()` — 배열 요소 검증
|
|
176
|
+
|
|
177
|
+
`arrayOf()`는 배열의 각 요소에 규칙을 적용합니다. `@zipbul/baker/rules` 또는 `@zipbul/baker`에서 import합니다.
|
|
178
|
+
|
|
179
|
+
```typescript
|
|
180
|
+
import { Field, arrayOf } from '@zipbul/baker';
|
|
181
|
+
import { isString, minLength } from '@zipbul/baker/rules';
|
|
182
|
+
|
|
183
|
+
class TagsDto {
|
|
184
|
+
@Field(arrayOf(isString, minLength(1)))
|
|
185
|
+
tags!: string[];
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
`arrayOf()`를 최상위 배열 규칙과 함께 사용할 수 있습니다:
|
|
190
|
+
|
|
191
|
+
```typescript
|
|
192
|
+
import { arrayMinSize, arrayMaxSize } from '@zipbul/baker/rules';
|
|
193
|
+
|
|
194
|
+
class ScoresDto {
|
|
195
|
+
@Field(arrayMinSize(1), arrayMaxSize(10), arrayOf(isInt, min(0), max(100)))
|
|
196
|
+
scores!: number[];
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
125
200
|
---
|
|
126
201
|
|
|
127
|
-
##
|
|
202
|
+
## 내장 규칙
|
|
203
|
+
|
|
204
|
+
모든 규칙은 `@zipbul/baker/rules`에서 import하며 `@Field()`의 인자로 전달합니다.
|
|
205
|
+
|
|
206
|
+
> **상수 vs 팩토리 함수:** 일부 규칙은 미리 만들어진 상수로 `()` 없이 사용하고, 나머지는 매개변수를 받는 팩토리 함수로 `()`와 함께 사용합니다. 아래 표에서 상수는 별도로 표기합니다.
|
|
128
207
|
|
|
129
208
|
### 타입 검사
|
|
130
209
|
|
|
131
|
-
|
|
|
210
|
+
| 규칙 | 설명 |
|
|
132
211
|
|---|---|
|
|
133
|
-
|
|
|
134
|
-
|
|
|
135
|
-
|
|
|
136
|
-
|
|
|
137
|
-
|
|
|
138
|
-
|
|
|
139
|
-
|
|
|
140
|
-
|
|
|
212
|
+
| `isString` | `typeof === 'string'` |
|
|
213
|
+
| `isNumber(opts?)` | `typeof === 'number'` + NaN/Infinity/maxDecimalPlaces 검사 |
|
|
214
|
+
| `isInt` | 정수 검사 |
|
|
215
|
+
| `isBoolean` | `typeof === 'boolean'` |
|
|
216
|
+
| `isDate` | `instanceof Date && !isNaN` |
|
|
217
|
+
| `isEnum(enumObj)` | 열거형 값 검사 |
|
|
218
|
+
| `isArray` | `Array.isArray()` |
|
|
219
|
+
| `isObject` | `typeof === 'object'`, null/Array 제외 |
|
|
220
|
+
|
|
221
|
+
> `isString`, `isInt`, `isBoolean`, `isDate`, `isArray`, `isObject`는 상수(괄호 불필요). `isNumber(opts?)`와 `isEnum(enumObj)`는 팩토리 함수.
|
|
141
222
|
|
|
142
223
|
### 공통
|
|
143
224
|
|
|
144
|
-
|
|
|
225
|
+
| 규칙 | 설명 |
|
|
145
226
|
|---|---|
|
|
146
|
-
|
|
|
147
|
-
|
|
|
148
|
-
|
|
|
149
|
-
|
|
|
150
|
-
|
|
|
151
|
-
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
| `@IsNotIn(values)` | 주어진 배열에 미포함 |
|
|
155
|
-
| `@ValidateNested()` | 중첩 DTO 검증 |
|
|
156
|
-
| `@ValidateIf(fn)` | 조건부 검증 |
|
|
227
|
+
| `equals(val)` | 엄격 동등 비교 (`===`) |
|
|
228
|
+
| `notEquals(val)` | 엄격 비동등 비교 (`!==`) |
|
|
229
|
+
| `isEmpty` | `undefined`, `null`, 또는 `''` |
|
|
230
|
+
| `isNotEmpty` | `undefined`, `null`, `''`이 아님 |
|
|
231
|
+
| `isIn(arr)` | 주어진 배열에 포함 |
|
|
232
|
+
| `isNotIn(arr)` | 주어진 배열에 미포함 |
|
|
233
|
+
|
|
234
|
+
> `isEmpty`와 `isNotEmpty`는 상수. 나머지는 팩토리 함수.
|
|
157
235
|
|
|
158
236
|
### 숫자
|
|
159
237
|
|
|
160
|
-
|
|
|
238
|
+
| 규칙 | 설명 |
|
|
161
239
|
|---|---|
|
|
162
|
-
|
|
|
163
|
-
|
|
|
164
|
-
|
|
|
165
|
-
|
|
|
166
|
-
|
|
|
167
|
-
|
|
240
|
+
| `min(n, opts?)` | `value >= n` (`{ exclusive: true }` 지원) |
|
|
241
|
+
| `max(n, opts?)` | `value <= n` (`{ exclusive: true }` 지원) |
|
|
242
|
+
| `isPositive` | `value > 0` |
|
|
243
|
+
| `isNegative` | `value < 0` |
|
|
244
|
+
| `isDivisibleBy(n)` | `value % n === 0` |
|
|
245
|
+
|
|
246
|
+
> `isPositive`와 `isNegative`는 상수(괄호 없음). `min()`, `max()`, `isDivisibleBy()`는 팩토리 함수.
|
|
168
247
|
|
|
169
248
|
### 문자열
|
|
170
249
|
|
|
171
|
-
|
|
172
|
-
|
|
250
|
+
모든 문자열 규칙은 값이 `string` 타입이어야 합니다.
|
|
251
|
+
|
|
252
|
+
| 규칙 | 종류 | 설명 |
|
|
253
|
+
|---|---|---|
|
|
254
|
+
| `minLength(n)` | 팩토리 | 최소 길이 |
|
|
255
|
+
| `maxLength(n)` | 팩토리 | 최대 길이 |
|
|
256
|
+
| `length(min, max)` | 팩토리 | 길이 범위 |
|
|
257
|
+
| `contains(seed)` | 팩토리 | 부분 문자열 포함 |
|
|
258
|
+
| `notContains(seed)` | 팩토리 | 부분 문자열 미포함 |
|
|
259
|
+
| `matches(pattern, modifiers?)` | 팩토리 | 정규식 매치 |
|
|
260
|
+
| `isLowercase` | 상수 | 전체 소문자 |
|
|
261
|
+
| `isUppercase` | 상수 | 전체 대문자 |
|
|
262
|
+
| `isAscii` | 상수 | ASCII만 |
|
|
263
|
+
| `isAlpha` | 상수 | 알파벳만 (en-US) |
|
|
264
|
+
| `isAlphanumeric` | 상수 | 알파벳/숫자만 (en-US) |
|
|
265
|
+
| `isBooleanString` | 상수 | `'true'`, `'false'`, `'1'`, 또는 `'0'` |
|
|
266
|
+
| `isNumberString(opts?)` | 팩토리 | 숫자 문자열 |
|
|
267
|
+
| `isDecimal(opts?)` | 팩토리 | 소수 문자열 |
|
|
268
|
+
| `isFullWidth` | 상수 | 전각 문자 |
|
|
269
|
+
| `isHalfWidth` | 상수 | 반각 문자 |
|
|
270
|
+
| `isVariableWidth` | 상수 | 전각/반각 혼합 |
|
|
271
|
+
| `isMultibyte` | 상수 | 멀티바이트 문자 |
|
|
272
|
+
| `isSurrogatePair` | 상수 | 서로게이트 페어 문자 |
|
|
273
|
+
| `isHexadecimal` | 상수 | 16진수 문자열 |
|
|
274
|
+
| `isOctal` | 상수 | 8진수 문자열 |
|
|
275
|
+
| `isEmail(opts?)` | 팩토리 | 이메일 형식 |
|
|
276
|
+
| `isURL(opts?)` | 팩토리 | URL 형식 (포트 범위 검증) |
|
|
277
|
+
| `isUUID(version?)` | 팩토리 | UUID v1-v5 |
|
|
278
|
+
| `isIP(version?)` | 팩토리 | IPv4 / IPv6 |
|
|
279
|
+
| `isHexColor` | 상수 | Hex 색상 (`#fff`, `#ffffff`) |
|
|
280
|
+
| `isRgbColor(includePercent?)` | 팩토리 | RGB 색상 문자열 |
|
|
281
|
+
| `isHSL` | 상수 | HSL 색상 문자열 |
|
|
282
|
+
| `isMACAddress(opts?)` | 팩토리 | MAC 주소 |
|
|
283
|
+
| `isISBN(version?)` | 팩토리 | ISBN-10 / ISBN-13 |
|
|
284
|
+
| `isISIN` | 상수 | ISIN (국제증권식별번호) |
|
|
285
|
+
| `isISO8601(opts?)` | 팩토리 | ISO 8601 날짜 문자열 |
|
|
286
|
+
| `isISRC` | 상수 | ISRC (국제표준녹음코드) |
|
|
287
|
+
| `isISSN(opts?)` | 팩토리 | ISSN (국제표준일련번호) |
|
|
288
|
+
| `isJWT` | 상수 | JSON Web Token |
|
|
289
|
+
| `isLatLong(opts?)` | 팩토리 | 위도/경도 문자열 |
|
|
290
|
+
| `isLocale` | 상수 | 로케일 문자열 (예: `en_US`) |
|
|
291
|
+
| `isDataURI` | 상수 | Data URI |
|
|
292
|
+
| `isFQDN(opts?)` | 팩토리 | 정규화된 도메인 이름 |
|
|
293
|
+
| `isPort` | 상수 | 포트 번호 문자열 (0-65535) |
|
|
294
|
+
| `isEAN` | 상수 | EAN (유럽상품번호) |
|
|
295
|
+
| `isISO31661Alpha2` | 상수 | ISO 3166-1 alpha-2 국가 코드 |
|
|
296
|
+
| `isISO31661Alpha3` | 상수 | ISO 3166-1 alpha-3 국가 코드 |
|
|
297
|
+
| `isBIC` | 상수 | BIC (은행식별코드) / SWIFT 코드 |
|
|
298
|
+
| `isFirebasePushId` | 상수 | Firebase Push ID |
|
|
299
|
+
| `isSemVer` | 상수 | 시맨틱 버전 문자열 |
|
|
300
|
+
| `isMongoId` | 상수 | MongoDB ObjectId (24자 hex) |
|
|
301
|
+
| `isJSON` | 상수 | JSON 파싱 가능 문자열 |
|
|
302
|
+
| `isBase32(opts?)` | 팩토리 | Base32 인코딩 |
|
|
303
|
+
| `isBase58` | 상수 | Base58 인코딩 |
|
|
304
|
+
| `isBase64(opts?)` | 팩토리 | Base64 인코딩 |
|
|
305
|
+
| `isDateString(opts?)` | 팩토리 | 날짜 문자열 (strict 모드 설정 가능) |
|
|
306
|
+
| `isMimeType` | 상수 | MIME 타입 문자열 |
|
|
307
|
+
| `isCurrency(opts?)` | 팩토리 | 통화 문자열 |
|
|
308
|
+
| `isMagnetURI` | 상수 | Magnet URI |
|
|
309
|
+
| `isCreditCard` | 상수 | 신용카드 번호 (Luhn) |
|
|
310
|
+
| `isIBAN(opts?)` | 팩토리 | IBAN |
|
|
311
|
+
| `isByteLength(min, max?)` | 팩토리 | 바이트 길이 범위 |
|
|
312
|
+
| `isHash(algorithm)` | 팩토리 | 해시 문자열 (md4, md5, sha1, sha256, sha384, sha512 등) |
|
|
313
|
+
| `isRFC3339` | 상수 | RFC 3339 날짜시간 문자열 |
|
|
314
|
+
| `isMilitaryTime` | 상수 | 군사 시간 (HH:MM) |
|
|
315
|
+
| `isLatitude` | 상수 | 위도 문자열 |
|
|
316
|
+
| `isLongitude` | 상수 | 경도 문자열 |
|
|
317
|
+
| `isEthereumAddress` | 상수 | 이더리움 주소 |
|
|
318
|
+
| `isBtcAddress` | 상수 | 비트코인 주소 |
|
|
319
|
+
| `isISO4217CurrencyCode` | 상수 | ISO 4217 통화 코드 |
|
|
320
|
+
| `isPhoneNumber` | 상수 | E.164 국제 전화번호 |
|
|
321
|
+
| `isStrongPassword(opts?)` | 팩토리 | 강력한 비밀번호 (최소 길이, 대/소문자, 숫자, 특수문자 설정) |
|
|
322
|
+
| `isTaxId(locale)` | 팩토리 | 주어진 로케일의 세금 ID |
|
|
173
323
|
|
|
174
|
-
|
|
324
|
+
### 배열
|
|
325
|
+
|
|
326
|
+
| 규칙 | 설명 |
|
|
175
327
|
|---|---|
|
|
176
|
-
|
|
|
177
|
-
|
|
|
178
|
-
|
|
|
179
|
-
|
|
|
180
|
-
|
|
|
181
|
-
|
|
|
182
|
-
| `@IsAlpha()` | 알파벳만 |
|
|
183
|
-
| `@IsAlphanumeric()` | 알파벳/숫자만 |
|
|
184
|
-
| `@IsNumeric()` | 숫자 문자열 |
|
|
185
|
-
| `@IsEmail(opts?)` | 이메일 형식 |
|
|
186
|
-
| `@IsURL(opts?)` | URL 형식 |
|
|
187
|
-
| `@IsUUID(version?)` | UUID v1–v5 |
|
|
188
|
-
| `@IsIP(version?)` | IPv4 / IPv6 |
|
|
189
|
-
| `@IsMACAddress()` | MAC 주소 |
|
|
190
|
-
| `@IsISBN(version?)` | ISBN-10 / ISBN-13 |
|
|
191
|
-
| `@IsISIN()` | ISIN |
|
|
192
|
-
| `@IsIBAN()` | IBAN |
|
|
193
|
-
| `@IsJSON()` | JSON 파싱 가능 문자열 |
|
|
194
|
-
| `@IsBase64()` | Base64 인코딩 |
|
|
195
|
-
| `@IsBase32()` | Base32 인코딩 |
|
|
196
|
-
| `@IsBase58()` | Base58 인코딩 |
|
|
197
|
-
| `@IsHexColor()` | 16진수 색상 코드 |
|
|
198
|
-
| `@IsHSL()` | HSL 색상 |
|
|
199
|
-
| `@IsRgbColor()` | RGB 색상 |
|
|
200
|
-
| `@IsHexadecimal()` | 16진수 문자열 |
|
|
201
|
-
| `@IsBIC()` | BIC/SWIFT 코드 |
|
|
202
|
-
| `@IsISRC()` | ISRC 코드 |
|
|
203
|
-
| `@IsEAN()` | EAN 바코드 |
|
|
204
|
-
| `@IsMimeType()` | MIME 타입 |
|
|
205
|
-
| `@IsMagnetURI()` | Magnet URI |
|
|
206
|
-
| `@IsCreditCard()` | 신용카드 번호 |
|
|
207
|
-
| `@IsHash(algorithm)` | 해시 (`md5 \| sha1 \| sha256 \| sha512` 등) |
|
|
208
|
-
| `@IsRFC3339()` | RFC 3339 날짜 |
|
|
209
|
-
| `@IsMilitaryTime()` | 24시간 형식 (`HH:MM`) |
|
|
210
|
-
| `@IsLatitude()` | 위도 (-90 ~ 90) |
|
|
211
|
-
| `@IsLongitude()` | 경도 (-180 ~ 180) |
|
|
212
|
-
| `@IsEthereumAddress()` | 이더리움 주소 |
|
|
213
|
-
| `@IsBtcAddress()` | 비트코인 주소 (P2PKH/P2SH/bech32) |
|
|
214
|
-
| `@IsISO4217CurrencyCode()` | ISO 4217 통화 코드 |
|
|
215
|
-
| `@IsPhoneNumber()` | E.164 국제 전화번호 |
|
|
216
|
-
| `@IsStrongPassword(opts?)` | 강력한 비밀번호 |
|
|
217
|
-
| `@IsSemVer()` | 시맨틱 버전 |
|
|
218
|
-
| `@IsISO8601()` | ISO 8601 날짜 문자열 |
|
|
219
|
-
| `@IsMongoId()` | MongoDB ObjectId |
|
|
220
|
-
| `@IsTaxId(locale)` | 국가별 납세자 번호 |
|
|
221
|
-
|
|
222
|
-
</details>
|
|
328
|
+
| `arrayContains(values)` | 주어진 요소를 모두 포함 |
|
|
329
|
+
| `arrayNotContains(values)` | 주어진 요소를 포함하지 않음 |
|
|
330
|
+
| `arrayMinSize(n)` | 배열 최소 길이 |
|
|
331
|
+
| `arrayMaxSize(n)` | 배열 최대 길이 |
|
|
332
|
+
| `arrayUnique()` | 중복 없음 |
|
|
333
|
+
| `arrayNotEmpty()` | 빈 배열이 아님 |
|
|
223
334
|
|
|
224
335
|
### 날짜
|
|
225
336
|
|
|
226
|
-
|
|
|
337
|
+
| 규칙 | 설명 |
|
|
227
338
|
|---|---|
|
|
228
|
-
|
|
|
229
|
-
|
|
|
339
|
+
| `minDate(date)` | 최소 날짜 |
|
|
340
|
+
| `maxDate(date)` | 최대 날짜 |
|
|
230
341
|
|
|
231
|
-
###
|
|
342
|
+
### 객체
|
|
232
343
|
|
|
233
|
-
|
|
|
344
|
+
| 규칙 | 설명 |
|
|
234
345
|
|---|---|
|
|
235
|
-
|
|
|
236
|
-
|
|
|
237
|
-
| `@ArrayMinSize(n)` | 배열 최소 길이 |
|
|
238
|
-
| `@ArrayMaxSize(n)` | 배열 최대 길이 |
|
|
239
|
-
| `@ArrayUnique()` | 중복 없음 |
|
|
240
|
-
| `@ArrayNotEmpty()` | 빈 배열이 아님 |
|
|
346
|
+
| `isNotEmptyObject(opts?)` | 최소 1개의 키 보유 (`{ nullable: true }` 옵션으로 null 값 키 무시) |
|
|
347
|
+
| `isInstance(Class)` | 주어진 클래스에 대한 `instanceof` 검사 |
|
|
241
348
|
|
|
242
349
|
### 로케일
|
|
243
350
|
|
|
244
|
-
|
|
245
|
-
|---|---|
|
|
246
|
-
| `@IsMobilePhone(locale)` | 국가별 이동전화 번호 |
|
|
247
|
-
| `@IsPostalCode(locale)` | 국가별 우편번호 |
|
|
248
|
-
| `@IsIdentityCard(locale)` | 국가별 신분증 번호 |
|
|
249
|
-
| `@IsPassportNumber(locale)` | 국가별 여권 번호 |
|
|
250
|
-
|
|
251
|
-
### Transform & Type
|
|
351
|
+
로케일 문자열 매개변수를 받는 지역별 검증기입니다.
|
|
252
352
|
|
|
253
|
-
|
|
|
353
|
+
| 규칙 | 설명 |
|
|
254
354
|
|---|---|
|
|
255
|
-
|
|
|
256
|
-
|
|
|
257
|
-
|
|
|
258
|
-
|
|
|
259
|
-
| `@Exclude(opts?)` | 직렬화에서 프로퍼티 제외 |
|
|
260
|
-
| `@Schema(schema)` | JSON Schema 메타데이터 부착 (클래스 또는 프로퍼티 레벨) |
|
|
355
|
+
| `isMobilePhone(locale)` | 주어진 로케일의 휴대전화 번호 (예: `'ko-KR'`, `'en-US'`, `'ja-JP'`) |
|
|
356
|
+
| `isPostalCode(locale)` | 주어진 로케일/국가 코드의 우편번호 (예: `'US'`, `'KR'`, `'GB'`) |
|
|
357
|
+
| `isIdentityCard(locale)` | 주어진 로케일의 주민등록번호/신분증 번호 (예: `'KR'`, `'US'`, `'CN'`) |
|
|
358
|
+
| `isPassportNumber(locale)` | 주어진 로케일의 여권 번호 (예: `'US'`, `'KR'`, `'GB'`) |
|
|
261
359
|
|
|
262
360
|
---
|
|
263
361
|
|
|
264
|
-
##
|
|
362
|
+
## 설정
|
|
265
363
|
|
|
266
|
-
|
|
364
|
+
첫 `deserialize()`/`serialize()` 호출 **이전에** `configure()`를 호출하세요:
|
|
267
365
|
|
|
268
366
|
```typescript
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
}
|
|
367
|
+
import { configure } from '@zipbul/baker';
|
|
368
|
+
|
|
369
|
+
configure({
|
|
370
|
+
autoConvert: false, // 암묵적 타입 변환 ("123" -> 123)
|
|
371
|
+
allowClassDefaults: false, // 누락된 키에 클래스 기본값 사용
|
|
372
|
+
stopAtFirstError: false, // 첫 에러에서 중단 또는 전체 수집
|
|
373
|
+
forbidUnknown: false, // 미선언 필드 거부
|
|
374
|
+
debug: false, // 생성된 코드에 필드 제외 주석 포함
|
|
375
|
+
});
|
|
279
376
|
```
|
|
280
377
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
```typescript
|
|
284
|
-
class UserDto {
|
|
285
|
-
@IsString({ message: '이름은 문자열이어야 합니다' })
|
|
286
|
-
name!: string;
|
|
287
|
-
|
|
288
|
-
@IsInt({
|
|
289
|
-
message: ({ property }) => `${property}는 정수여야 합니다`,
|
|
290
|
-
context: { httpStatus: 400 },
|
|
291
|
-
})
|
|
292
|
-
age!: number;
|
|
293
|
-
}
|
|
294
|
-
```
|
|
378
|
+
`configure()`는 `{ warnings: string[] }`을 반환합니다 — auto-seal 이후에 호출된 경우, 영향을 받지 않는 클래스를 알려주는 경고가 포함됩니다.
|
|
295
379
|
|
|
296
380
|
---
|
|
297
381
|
|
|
298
|
-
##
|
|
382
|
+
## 에러 처리
|
|
299
383
|
|
|
300
384
|
검증 실패 시 `deserialize()`는 `BakerValidationError`를 throw합니다:
|
|
301
385
|
|
|
@@ -304,86 +388,50 @@ class BakerValidationError extends Error {
|
|
|
304
388
|
readonly errors: BakerError[];
|
|
305
389
|
readonly className: string;
|
|
306
390
|
}
|
|
307
|
-
```
|
|
308
391
|
|
|
309
|
-
각 에러는 `BakerError` 인터페이스를 따릅니다:
|
|
310
|
-
|
|
311
|
-
```typescript
|
|
312
392
|
interface BakerError {
|
|
313
|
-
readonly path: string; //
|
|
314
|
-
readonly code: string; //
|
|
315
|
-
readonly message?: string; // 커스텀 메시지
|
|
316
|
-
readonly context?: unknown; // 커스텀 컨텍스트
|
|
317
|
-
}
|
|
318
|
-
```
|
|
319
|
-
|
|
320
|
-
---
|
|
321
|
-
|
|
322
|
-
## 📋 배열 검증
|
|
323
|
-
|
|
324
|
-
`each: true` 옵션으로 Array, Set, Map의 각 원소에 규칙을 적용합니다:
|
|
325
|
-
|
|
326
|
-
```typescript
|
|
327
|
-
class TagsDto {
|
|
328
|
-
@IsString({ each: true })
|
|
329
|
-
tags!: string[];
|
|
393
|
+
readonly path: string; // 'user.address.city'
|
|
394
|
+
readonly code: string; // 'isString', 'min', 'isEmail'
|
|
395
|
+
readonly message?: string; // 커스텀 메시지
|
|
396
|
+
readonly context?: unknown; // 커스텀 컨텍스트
|
|
330
397
|
}
|
|
331
398
|
```
|
|
332
399
|
|
|
333
400
|
---
|
|
334
401
|
|
|
335
|
-
##
|
|
402
|
+
## 중첩 객체
|
|
336
403
|
|
|
337
|
-
|
|
404
|
+
`type` 옵션으로 중첩 DTO를 검증합니다:
|
|
338
405
|
|
|
339
406
|
```typescript
|
|
340
|
-
class UserDto {
|
|
341
|
-
@IsString({ groups: ['create'] })
|
|
342
|
-
name!: string;
|
|
343
|
-
|
|
344
|
-
@IsEmail({ groups: ['create', 'update'] })
|
|
345
|
-
email!: string;
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
// 'create' 그룹의 규칙만 검증
|
|
349
|
-
const user = await deserialize(UserDto, body, { groups: ['create'] });
|
|
350
|
-
```
|
|
351
|
-
|
|
352
|
-
---
|
|
353
|
-
|
|
354
|
-
## 🪆 중첩 객체
|
|
355
|
-
|
|
356
|
-
`@Nested`로 중첩 DTO를 검증합니다:
|
|
357
|
-
|
|
358
|
-
```typescript
|
|
359
|
-
import { Nested, IsString } from '@zipbul/baker';
|
|
360
|
-
|
|
361
407
|
class AddressDto {
|
|
362
|
-
@
|
|
408
|
+
@Field(isString)
|
|
363
409
|
city!: string;
|
|
364
410
|
}
|
|
365
411
|
|
|
366
412
|
class UserDto {
|
|
367
|
-
@
|
|
413
|
+
@Field({ type: () => AddressDto })
|
|
368
414
|
address!: AddressDto;
|
|
415
|
+
|
|
416
|
+
// 중첩 DTO 배열
|
|
417
|
+
@Field({ type: () => [AddressDto] })
|
|
418
|
+
addresses!: AddressDto[];
|
|
369
419
|
}
|
|
370
420
|
```
|
|
371
421
|
|
|
372
|
-
|
|
422
|
+
### Discriminator (다형성)
|
|
373
423
|
|
|
374
424
|
```typescript
|
|
375
|
-
class
|
|
376
|
-
@
|
|
377
|
-
label!: string;
|
|
425
|
+
class DogDto {
|
|
426
|
+
@Field(isString) breed!: string;
|
|
378
427
|
}
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
@Nested(() => ItemDto, { each: true })
|
|
382
|
-
items!: ItemDto[];
|
|
428
|
+
class CatDto {
|
|
429
|
+
@Field(isBoolean) indoor!: boolean;
|
|
383
430
|
}
|
|
384
431
|
|
|
385
432
|
class PetOwnerDto {
|
|
386
|
-
@
|
|
433
|
+
@Field({
|
|
434
|
+
type: () => DogDto,
|
|
387
435
|
discriminator: {
|
|
388
436
|
property: 'type',
|
|
389
437
|
subTypes: [
|
|
@@ -396,105 +444,145 @@ class PetOwnerDto {
|
|
|
396
444
|
}
|
|
397
445
|
```
|
|
398
446
|
|
|
447
|
+
Discriminator는 양방향으로 동작합니다 — `deserialize()`는 프로퍼티 값으로 분기하고, `serialize()`는 `instanceof`로 분기합니다.
|
|
448
|
+
|
|
399
449
|
---
|
|
400
450
|
|
|
401
|
-
##
|
|
451
|
+
## 상속
|
|
402
452
|
|
|
403
|
-
|
|
453
|
+
Baker는 클래스 상속을 지원합니다. 자식 DTO는 부모 클래스의 모든 `@Field()` 데코레이터를 자동으로 상속합니다. 자식 클래스에서 필드를 오버라이드하거나 확장할 수 있습니다:
|
|
404
454
|
|
|
405
455
|
```typescript
|
|
406
|
-
|
|
456
|
+
class BaseDto {
|
|
457
|
+
@Field(isString)
|
|
458
|
+
name!: string;
|
|
459
|
+
}
|
|
407
460
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
});
|
|
461
|
+
class ExtendedDto extends BaseDto {
|
|
462
|
+
@Field(isInt, min(0))
|
|
463
|
+
age!: number;
|
|
464
|
+
// `name`은 BaseDto에서 상속
|
|
465
|
+
}
|
|
414
466
|
```
|
|
415
467
|
|
|
416
468
|
---
|
|
417
469
|
|
|
418
|
-
##
|
|
470
|
+
## Transform
|
|
471
|
+
|
|
472
|
+
`FieldOptions`의 `transform` 옵션으로 역직렬화/직렬화 시 값을 변환할 수 있습니다. Transform 함수는 **비동기(async)**일 수 있습니다.
|
|
419
473
|
|
|
420
474
|
```typescript
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
})
|
|
475
|
+
class UserDto {
|
|
476
|
+
@Field(isString, {
|
|
477
|
+
transform: ({ value, direction }) => {
|
|
478
|
+
return direction === 'deserialize'
|
|
479
|
+
? (value as string).trim().toLowerCase()
|
|
480
|
+
: value;
|
|
481
|
+
},
|
|
482
|
+
})
|
|
483
|
+
email!: string;
|
|
484
|
+
|
|
485
|
+
@Field(isString, {
|
|
486
|
+
transform: async ({ value }) => {
|
|
487
|
+
return await someAsyncOperation(value);
|
|
488
|
+
},
|
|
489
|
+
transformDirection: 'deserializeOnly',
|
|
490
|
+
})
|
|
491
|
+
data!: string;
|
|
492
|
+
}
|
|
429
493
|
```
|
|
430
494
|
|
|
431
495
|
---
|
|
432
496
|
|
|
433
|
-
##
|
|
434
|
-
|
|
435
|
-
DTO에서 JSON Schema Draft 2020-12를 생성합니다:
|
|
497
|
+
## 커스텀 규칙
|
|
436
498
|
|
|
437
499
|
```typescript
|
|
438
|
-
import {
|
|
500
|
+
import { createRule } from '@zipbul/baker';
|
|
439
501
|
|
|
440
|
-
const
|
|
441
|
-
|
|
502
|
+
const isPositiveInt = createRule({
|
|
503
|
+
name: 'isPositiveInt',
|
|
504
|
+
validate: (value) => Number.isInteger(value) && (value as number) > 0,
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
class Dto {
|
|
508
|
+
@Field(isPositiveInt)
|
|
509
|
+
count!: number;
|
|
510
|
+
}
|
|
442
511
|
```
|
|
443
512
|
|
|
444
|
-
|
|
513
|
+
---
|
|
514
|
+
|
|
515
|
+
## 클래스 레벨 JSON Schema 메타데이터
|
|
516
|
+
|
|
517
|
+
`collectClassSchema()`를 사용하여 DTO에 클래스 레벨 JSON Schema 메타데이터(title, description 등)를 부착합니다. 이 메타데이터는 `toJsonSchema()` 출력에 병합됩니다.
|
|
518
|
+
|
|
519
|
+
> `collectClassSchema`는 `src/collect.ts`에서 제공하는 저수준 API입니다. 서브패스 익스포트로는 사용할 수 없으며 직접 import해야 합니다.
|
|
445
520
|
|
|
446
521
|
```typescript
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
522
|
+
import { collectClassSchema } from '@zipbul/baker/src/collect';
|
|
523
|
+
|
|
524
|
+
class CreateUserDto {
|
|
525
|
+
@Field(isString) name!: string;
|
|
526
|
+
@Field(isEmail()) email!: string;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
collectClassSchema(CreateUserDto, {
|
|
530
|
+
title: 'CreateUserRequest',
|
|
531
|
+
description: '새 사용자 생성을 위한 페이로드',
|
|
450
532
|
});
|
|
451
533
|
```
|
|
452
534
|
|
|
453
|
-
`@
|
|
535
|
+
프로퍼티 레벨 스키마 오버라이드는 `@Field()`의 `schema` 옵션을 사용합니다:
|
|
454
536
|
|
|
455
537
|
```typescript
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
538
|
+
class Dto {
|
|
539
|
+
@Field(isString, minLength(1), {
|
|
540
|
+
schema: { description: '사용자 표시 이름', minLength: 5 },
|
|
541
|
+
})
|
|
460
542
|
name!: string;
|
|
461
543
|
}
|
|
462
544
|
```
|
|
463
545
|
|
|
464
546
|
---
|
|
465
547
|
|
|
466
|
-
##
|
|
548
|
+
## JSON Schema
|
|
467
549
|
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
550
|
+
DTO에서 JSON Schema Draft 2020-12를 생성합니다:
|
|
551
|
+
|
|
552
|
+
```typescript
|
|
553
|
+
import { toJsonSchema } from '@zipbul/baker';
|
|
554
|
+
|
|
555
|
+
const schema = toJsonSchema(CreateUserDto, {
|
|
556
|
+
direction: 'deserialize', // 'deserialize' | 'serialize'
|
|
557
|
+
groups: ['create'], // 그룹별 필터링
|
|
558
|
+
onUnmappedRule: (name) => { /* 스키마 매핑이 없는 커스텀 규칙 */ },
|
|
559
|
+
});
|
|
560
|
+
```
|
|
474
561
|
|
|
475
562
|
---
|
|
476
563
|
|
|
477
|
-
##
|
|
564
|
+
## 동작 원리
|
|
478
565
|
|
|
479
566
|
```
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
│ (메타데이터) │ │ 앱 시작 시 │ │ (인라인 코드생성) │
|
|
483
|
-
└─────────────┘ └──────────────┘ └──────────┬──────────┘
|
|
484
|
-
│
|
|
485
|
-
┌──────────▼──────────┐
|
|
486
|
-
│ deserialize() / │
|
|
487
|
-
│ serialize() │
|
|
488
|
-
│ (생성된 코드 실행) │
|
|
489
|
-
└─────────────────────┘
|
|
567
|
+
Decorators (@Field) auto-seal (첫 호출) deserialize() / serialize()
|
|
568
|
+
메타데이터 -> new Function() 코드생성 -> 생성된 코드 실행
|
|
490
569
|
```
|
|
491
570
|
|
|
492
|
-
1.
|
|
493
|
-
2.
|
|
494
|
-
3.
|
|
571
|
+
1. `@Field()`가 정의 시점에 클래스 프로퍼티에 검증 메타데이터를 부착합니다
|
|
572
|
+
2. 첫 `deserialize()`/`serialize()` 호출이 **auto-seal**을 트리거합니다 — 모든 메타데이터를 읽고, 순환 참조를 분석하고, `new Function()`으로 최적화된 JavaScript 함수를 생성합니다
|
|
573
|
+
3. 이후 호출은 생성된 함수를 직접 실행합니다 — 해석 루프 없음
|
|
574
|
+
|
|
575
|
+
---
|
|
576
|
+
|
|
577
|
+
## 서브패스 익스포트
|
|
578
|
+
|
|
579
|
+
| 임포트 경로 | 용도 |
|
|
580
|
+
|---|---|
|
|
581
|
+
| `@zipbul/baker` | 메인 API: `deserialize`, `serialize`, `configure`, `toJsonSchema`, `Field`, `arrayOf`, `createRule` |
|
|
582
|
+
| `@zipbul/baker/rules` | 규칙 함수 및 상수: `isString`, `min()`, `isEmail()`, `arrayOf()` 등 |
|
|
495
583
|
|
|
496
584
|
---
|
|
497
585
|
|
|
498
|
-
##
|
|
586
|
+
## 라이선스
|
|
499
587
|
|
|
500
|
-
[MIT](./LICENSE)
|
|
588
|
+
[MIT](./LICENSE)
|