@zipbul/baker 1.1.0 → 2.0.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.md +148 -508
- package/dist/index-btgens0c.js +6 -0
- package/dist/index-k369bbht.js +6 -0
- package/dist/index.d.ts +5 -6
- package/dist/index.js +205 -202
- package/dist/src/collect.d.ts +0 -2
- package/dist/src/configure.d.ts +0 -6
- package/dist/src/create-rule.d.ts +1 -1
- package/dist/src/decorators/field.d.ts +0 -9
- package/dist/src/decorators/index.d.ts +1 -1
- package/dist/src/decorators/index.js +1 -1
- package/dist/src/errors.d.ts +19 -8
- package/dist/src/functions/_run-sealed.d.ts +7 -0
- package/dist/src/functions/deserialize.d.ts +5 -4
- package/dist/src/functions/index.d.ts +1 -2
- package/dist/src/functions/serialize.d.ts +2 -2
- package/dist/src/functions/validate.d.ts +13 -0
- package/dist/src/rules/index.d.ts +1 -1
- package/dist/src/rules/index.js +9 -9
- package/dist/src/rules/string.d.ts +6 -48
- package/dist/src/symbols.d.ts +0 -2
- package/dist/src/symbols.js +2 -2
- package/dist/src/types.d.ts +2 -58
- package/package.json +13 -3
- package/README.ko.md +0 -588
- package/dist/index-70ggmxsa.js +0 -6
- package/dist/index-gcptd79v.js +0 -6
- package/dist/src/functions/to-json-schema.d.ts +0 -20
package/README.md
CHANGED
|
@@ -1,627 +1,267 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
<strong>Decorator-based validate + transform with inline code generation</strong>
|
|
5
|
-
</p>
|
|
6
|
-
<p align="center">
|
|
7
|
-
Single <code>@Field()</code> decorator · AOT-level performance · zero 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.ko.md">한국어</a>
|
|
19
|
-
</p>
|
|
20
|
-
|
|
21
|
-
---
|
|
22
|
-
|
|
23
|
-
## Why Baker?
|
|
24
|
-
|
|
25
|
-
| | class-validator | Zod | TypeBox | **Baker** |
|
|
26
|
-
|---|---|---|---|---|
|
|
27
|
-
| Schema style | Decorators | Function chaining | JSON Schema builder | **Single `@Field()` decorator** |
|
|
28
|
-
| Performance | Runtime interpreter | Runtime interpreter | JIT compile | **`new Function()` inline codegen** |
|
|
29
|
-
| Transform built-in | Separate package | `.transform()` | N/A | **Unified** |
|
|
30
|
-
| reflect-metadata | Required | N/A | N/A | **Not needed** |
|
|
31
|
-
| class-validator migration | — | Full rewrite | Full rewrite | **Near drop-in** |
|
|
32
|
-
|
|
33
|
-
Baker gives you a **single `@Field()` decorator** that combines validation, transformation, exposure control, and type hints. At first use, it auto-seals all DTOs by generating optimized functions via `new Function()` — delivering **AOT-equivalent performance without a compiler plugin**.
|
|
34
|
-
|
|
35
|
-
---
|
|
36
|
-
|
|
37
|
-
## Features
|
|
38
|
-
|
|
39
|
-
- **Single decorator** — `@Field()` replaces 30+ individual decorators
|
|
40
|
-
- **80+ built-in rules** — `isString`, `min()`, `isEmail()` and more, composed as arguments
|
|
41
|
-
- **Inline code generation** — auto-seal compiles validators at first `deserialize()`/`serialize()` call
|
|
42
|
-
- **Unified validate + transform** — `deserialize()` and `serialize()` in one call (sync DTOs skip async overhead)
|
|
43
|
-
- **Zero reflect-metadata** — no `reflect-metadata` import needed
|
|
44
|
-
- **Circular reference detection** — automatic static analysis at seal time
|
|
45
|
-
- **Group-based validation** — apply different rules per request with `groups`
|
|
46
|
-
- **Custom rules** — `createRule()` for user-defined validators with codegen support
|
|
47
|
-
- **JSON Schema output** — `toJsonSchema()` generates JSON Schema Draft 2020-12 from your DTOs
|
|
48
|
-
- **Polymorphic discriminator** — `@Field({ discriminator })` for union types
|
|
49
|
-
- **Whitelist mode** — reject undeclared fields with `configure({ forbidUnknown: true })`
|
|
50
|
-
- **Class inheritance** — child DTOs inherit parent `@Field()` decorators automatically
|
|
51
|
-
- **Async transforms** — transform functions can be async
|
|
52
|
-
- **Map/Set support** — auto-convert between `Map`/`Set` and JSON-compatible types
|
|
53
|
-
- **Per-field error messages** — `message` and `context` options on `@Field()` for custom errors
|
|
54
|
-
|
|
55
|
-
---
|
|
56
|
-
|
|
57
|
-
## Installation
|
|
1
|
+
# @zipbul/baker
|
|
2
|
+
|
|
3
|
+
Decorator-based validation + transformation with inline code generation. Zero `reflect-metadata`.
|
|
58
4
|
|
|
59
5
|
```bash
|
|
60
6
|
bun add @zipbul/baker
|
|
61
7
|
```
|
|
62
8
|
|
|
63
|
-
|
|
9
|
+
Requires `"experimentalDecorators": true` in `tsconfig.json`.
|
|
64
10
|
|
|
65
|
-
|
|
66
|
-
// tsconfig.json
|
|
67
|
-
{
|
|
68
|
-
"compilerOptions": {
|
|
69
|
-
"experimentalDecorators": true
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
```
|
|
11
|
+
## API
|
|
73
12
|
|
|
74
|
-
|
|
13
|
+
### `deserialize<T>(Class, input, options?): T | BakerErrors | Promise<T | BakerErrors>`
|
|
75
14
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
### 1. Define a DTO
|
|
15
|
+
Validates input and creates a class instance. Sync DTOs return directly. Async DTOs (async transform/rules) return Promise. Always safe to `await`.
|
|
79
16
|
|
|
80
17
|
```typescript
|
|
81
|
-
import { Field } from '@zipbul/baker';
|
|
82
|
-
import { isString,
|
|
18
|
+
import { deserialize, isBakerError, Field } from '@zipbul/baker';
|
|
19
|
+
import { isString, isNumber, isEmail, min, minLength } from '@zipbul/baker/rules';
|
|
83
20
|
|
|
84
|
-
class
|
|
85
|
-
@Field(isString)
|
|
86
|
-
|
|
21
|
+
class UserDto {
|
|
22
|
+
@Field(isString, minLength(2)) name!: string;
|
|
23
|
+
@Field(isNumber(), min(0)) age!: number;
|
|
24
|
+
@Field(isString, isEmail()) email!: string;
|
|
25
|
+
}
|
|
87
26
|
|
|
88
|
-
|
|
89
|
-
age!: number;
|
|
27
|
+
const result = await deserialize(UserDto, { name: 'Alice', age: 30, email: 'alice@test.com' });
|
|
90
28
|
|
|
91
|
-
|
|
92
|
-
|
|
29
|
+
if (isBakerError(result)) {
|
|
30
|
+
console.log(result.errors); // { path: string, code: string }[]
|
|
31
|
+
} else {
|
|
32
|
+
console.log(result.name); // 'Alice'
|
|
93
33
|
}
|
|
94
34
|
```
|
|
95
35
|
|
|
96
|
-
|
|
36
|
+
Never throws on validation failure. Throws `SealError` only for programming errors (no `@Field` decorators, banned field names).
|
|
97
37
|
|
|
98
|
-
|
|
99
|
-
import { deserialize, BakerValidationError } from '@zipbul/baker';
|
|
100
|
-
|
|
101
|
-
try {
|
|
102
|
-
const user = await deserialize(CreateUserDto, requestBody);
|
|
103
|
-
// user is a validated CreateUserDto instance
|
|
104
|
-
} catch (e) {
|
|
105
|
-
if (e instanceof BakerValidationError) {
|
|
106
|
-
console.log(e.errors); // BakerError[]
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
```
|
|
38
|
+
### `validate(Class, input, options?): true | BakerErrors | Promise<true | BakerErrors>`
|
|
110
39
|
|
|
111
|
-
|
|
40
|
+
Same validation as `deserialize` without instance creation.
|
|
112
41
|
|
|
113
42
|
```typescript
|
|
114
|
-
import {
|
|
43
|
+
import { validate, isBakerError } from '@zipbul/baker';
|
|
115
44
|
|
|
116
|
-
const
|
|
117
|
-
|
|
45
|
+
const result = await validate(UserDto, input);
|
|
46
|
+
if (isBakerError(result)) { /* errors */ }
|
|
118
47
|
```
|
|
119
48
|
|
|
120
|
-
|
|
49
|
+
### `validate(input, ...rules): true | BakerErrors | Promise<true | BakerErrors>`
|
|
121
50
|
|
|
122
|
-
|
|
51
|
+
Ad-hoc single value validation. No DTO needed.
|
|
123
52
|
|
|
124
|
-
|
|
53
|
+
```typescript
|
|
54
|
+
const result = await validate('hello@test.com', isString, isEmail());
|
|
55
|
+
// result === true
|
|
56
|
+
```
|
|
125
57
|
|
|
126
|
-
|
|
58
|
+
### `serialize<T>(instance, options?): Record<string, unknown> | Promise<Record<string, unknown>>`
|
|
127
59
|
|
|
128
|
-
|
|
60
|
+
Converts a class instance to a plain object. No validation. Sync DTOs return directly.
|
|
129
61
|
|
|
130
62
|
```typescript
|
|
131
|
-
|
|
132
|
-
@Field(isString, minLength(3), maxLength(100))
|
|
133
|
-
|
|
134
|
-
// Options only
|
|
135
|
-
@Field({ optional: true, nullable: true })
|
|
136
|
-
|
|
137
|
-
// Rules + options
|
|
138
|
-
@Field(isString, { name: 'user_name', groups: ['create'] })
|
|
63
|
+
import { serialize } from '@zipbul/baker';
|
|
139
64
|
|
|
140
|
-
|
|
141
|
-
@Field()
|
|
65
|
+
const plain = await serialize(userInstance);
|
|
142
66
|
```
|
|
143
67
|
|
|
144
|
-
###
|
|
68
|
+
### `isBakerError(value): value is BakerErrors`
|
|
69
|
+
|
|
70
|
+
Type guard. Narrows `deserialize`/`validate` result to error type.
|
|
145
71
|
|
|
146
72
|
```typescript
|
|
147
|
-
interface
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
};
|
|
153
|
-
keepDiscriminatorProperty?: boolean; // Keep discriminator key in output
|
|
154
|
-
rules?: (EmittableRule | ArrayOfMarker)[]; // Validation rules (alternative to positional args)
|
|
155
|
-
optional?: boolean; // Allow undefined
|
|
156
|
-
nullable?: boolean; // Allow null
|
|
157
|
-
name?: string; // JSON key mapping (bidirectional)
|
|
158
|
-
deserializeName?: string; // Deserialize-only key mapping
|
|
159
|
-
serializeName?: string; // Serialize-only key mapping
|
|
160
|
-
exclude?: boolean | 'deserializeOnly' | 'serializeOnly';
|
|
161
|
-
groups?: string[]; // Visibility + conditional validation
|
|
162
|
-
when?: (obj: any) => boolean; // Conditional validation
|
|
163
|
-
schema?: JsonSchemaOverride; // JSON Schema metadata
|
|
164
|
-
transform?: (params: FieldTransformParams) => unknown;
|
|
165
|
-
transformDirection?: 'deserializeOnly' | 'serializeOnly';
|
|
166
|
-
message?: string | ((args: MessageArgs) => string); // Error message for all rules
|
|
167
|
-
context?: unknown; // Error context for all rules
|
|
168
|
-
mapValue?: () => Constructor; // Map value DTO type
|
|
169
|
-
setValue?: () => Constructor; // Set element DTO type
|
|
73
|
+
interface BakerError {
|
|
74
|
+
readonly path: string; // 'name', 'address.city', 'items[0].value'
|
|
75
|
+
readonly code: string; // 'isString', 'minLength', 'invalidInput'
|
|
76
|
+
readonly message?: string; // custom message if set
|
|
77
|
+
readonly context?: unknown; // custom context if set
|
|
170
78
|
}
|
|
171
79
|
```
|
|
172
80
|
|
|
173
|
-
###
|
|
81
|
+
### `configure(config): ConfigureResult`
|
|
174
82
|
|
|
175
|
-
|
|
83
|
+
Global configuration. Call before first `deserialize`/`serialize`/`validate`.
|
|
176
84
|
|
|
177
85
|
```typescript
|
|
178
|
-
|
|
179
|
-
name!: string;
|
|
180
|
-
|
|
181
|
-
@Field(isEmail(), {
|
|
182
|
-
message: ({ property, value }) => `${property} got bad value: ${value}`,
|
|
183
|
-
context: { severity: 'error' },
|
|
184
|
-
})
|
|
185
|
-
email!: string;
|
|
186
|
-
```
|
|
86
|
+
import { configure } from '@zipbul/baker';
|
|
187
87
|
|
|
188
|
-
|
|
88
|
+
configure({
|
|
89
|
+
autoConvert: true, // "123" → 123. Default: false
|
|
90
|
+
allowClassDefaults: true, // use class field initializers for missing keys. Default: false
|
|
91
|
+
stopAtFirstError: true, // return on first validation failure. Default: false
|
|
92
|
+
forbidUnknown: true, // reject undeclared fields. Default: false
|
|
93
|
+
});
|
|
94
|
+
```
|
|
189
95
|
|
|
190
|
-
### `
|
|
96
|
+
### `createRule(name, validate): EmittableRule`
|
|
191
97
|
|
|
192
|
-
|
|
98
|
+
Creates a custom validation rule.
|
|
193
99
|
|
|
194
100
|
```typescript
|
|
195
|
-
import {
|
|
196
|
-
import { isString, minLength } from '@zipbul/baker/rules';
|
|
101
|
+
import { createRule } from '@zipbul/baker';
|
|
197
102
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
103
|
+
const isEven = createRule('isEven', (v) => typeof v === 'number' && v % 2 === 0);
|
|
104
|
+
|
|
105
|
+
const isUnique = createRule({
|
|
106
|
+
name: 'isUnique',
|
|
107
|
+
validate: async (v) => await db.checkUnique(v),
|
|
108
|
+
constraints: { table: 'users' },
|
|
109
|
+
});
|
|
202
110
|
```
|
|
203
111
|
|
|
204
|
-
|
|
112
|
+
## `@Field` Decorator
|
|
205
113
|
|
|
206
114
|
```typescript
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
@Field(arrayMinSize(1), arrayMaxSize(10), arrayOf(isInt, min(0), max(100)))
|
|
211
|
-
scores!: number[];
|
|
212
|
-
}
|
|
115
|
+
@Field(...rules)
|
|
116
|
+
@Field(...rules, options)
|
|
117
|
+
@Field(options)
|
|
213
118
|
```
|
|
214
119
|
|
|
215
|
-
|
|
120
|
+
### Options
|
|
216
121
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
122
|
+
```typescript
|
|
123
|
+
interface FieldOptions {
|
|
124
|
+
type?: () => DtoClass | [DtoClass]; // nested DTO. [Dto] for arrays
|
|
125
|
+
discriminator?: { // polymorphic dispatch
|
|
126
|
+
property: string;
|
|
127
|
+
subTypes: { value: Function; name: string }[];
|
|
128
|
+
};
|
|
129
|
+
keepDiscriminatorProperty?: boolean; // preserve discriminator in result. Default: false
|
|
130
|
+
rules?: EmittableRule[]; // rules as array (alternative to variadic)
|
|
131
|
+
optional?: boolean; // allow undefined. Default: false
|
|
132
|
+
nullable?: boolean; // allow null. Default: false
|
|
133
|
+
name?: string; // bidirectional key mapping
|
|
134
|
+
deserializeName?: string; // input key mapping
|
|
135
|
+
serializeName?: string; // output key mapping
|
|
136
|
+
exclude?: boolean | 'deserializeOnly' | 'serializeOnly'; // field exclusion
|
|
137
|
+
groups?: string[]; // conditional visibility
|
|
138
|
+
when?: (obj: any) => boolean; // conditional validation
|
|
139
|
+
transform?: (params: FieldTransformParams) => unknown; // value transform
|
|
140
|
+
transformDirection?: 'deserializeOnly' | 'serializeOnly'; // transform direction
|
|
141
|
+
message?: string | ((args) => string); // error message override
|
|
142
|
+
context?: unknown; // error context
|
|
143
|
+
mapValue?: () => DtoClass; // Map value DTO
|
|
144
|
+
setValue?: () => DtoClass; // Set element DTO
|
|
145
|
+
}
|
|
146
|
+
```
|
|
220
147
|
|
|
221
|
-
|
|
148
|
+
## Rules
|
|
222
149
|
|
|
223
150
|
### Type Checkers
|
|
224
151
|
|
|
225
|
-
|
|
226
|
-
|---|---|
|
|
227
|
-
| `isString` | `typeof === 'string'` |
|
|
228
|
-
| `isNumber(opts?)` | `typeof === 'number'` with NaN/Infinity/maxDecimalPlaces checks |
|
|
229
|
-
| `isInt` | Integer check |
|
|
230
|
-
| `isBoolean` | `typeof === 'boolean'` |
|
|
231
|
-
| `isDate` | `instanceof Date && !isNaN` |
|
|
232
|
-
| `isEnum(enumObj)` | Enum value check |
|
|
233
|
-
| `isArray` | `Array.isArray()` |
|
|
234
|
-
| `isObject` | `typeof === 'object'`, excludes null/Array |
|
|
235
|
-
|
|
236
|
-
> `isString`, `isInt`, `isBoolean`, `isDate`, `isArray`, `isObject` are constants (no parentheses needed). `isNumber(opts?)` and `isEnum(enumObj)` are factory functions.
|
|
237
|
-
|
|
238
|
-
### Common
|
|
239
|
-
|
|
240
|
-
| Rule | Description |
|
|
241
|
-
|---|---|
|
|
242
|
-
| `equals(val)` | Strict equality (`===`) |
|
|
243
|
-
| `notEquals(val)` | Strict inequality (`!==`) |
|
|
244
|
-
| `isEmpty` | `undefined`, `null`, or `''` |
|
|
245
|
-
| `isNotEmpty` | Not `undefined`, `null`, or `''` |
|
|
246
|
-
| `isIn(arr)` | Value is in the given array |
|
|
247
|
-
| `isNotIn(arr)` | Value is not in the given array |
|
|
248
|
-
|
|
249
|
-
> `isEmpty` and `isNotEmpty` are constants. The rest are factory functions.
|
|
250
|
-
|
|
251
|
-
### Number
|
|
252
|
-
|
|
253
|
-
| Rule | Description |
|
|
254
|
-
|---|---|
|
|
255
|
-
| `min(n, opts?)` | `value >= n` (supports `{ exclusive: true }`) |
|
|
256
|
-
| `max(n, opts?)` | `value <= n` (supports `{ exclusive: true }`) |
|
|
257
|
-
| `isPositive` | `value > 0` |
|
|
258
|
-
| `isNegative` | `value < 0` |
|
|
259
|
-
| `isDivisibleBy(n)` | `value % n === 0` |
|
|
260
|
-
|
|
261
|
-
> `isPositive` and `isNegative` are constants (no parentheses). `min()`, `max()`, and `isDivisibleBy()` are factory functions.
|
|
262
|
-
|
|
263
|
-
### String
|
|
264
|
-
|
|
265
|
-
All string rules require the value to be a `string` type.
|
|
266
|
-
|
|
267
|
-
| Rule | Kind | Description |
|
|
268
|
-
|---|---|---|
|
|
269
|
-
| `minLength(n)` | factory | Minimum length |
|
|
270
|
-
| `maxLength(n)` | factory | Maximum length |
|
|
271
|
-
| `length(min, max)` | factory | Length range |
|
|
272
|
-
| `contains(seed)` | factory | Contains substring |
|
|
273
|
-
| `notContains(seed)` | factory | Does not contain substring |
|
|
274
|
-
| `matches(pattern, modifiers?)` | factory | Regex match |
|
|
275
|
-
| `isLowercase` | constant | All lowercase |
|
|
276
|
-
| `isUppercase` | constant | All uppercase |
|
|
277
|
-
| `isAscii` | constant | ASCII only |
|
|
278
|
-
| `isAlpha` | constant | Alphabetic only (en-US) |
|
|
279
|
-
| `isAlphanumeric` | constant | Alphanumeric only (en-US) |
|
|
280
|
-
| `isBooleanString` | constant | `'true'`, `'false'`, `'1'`, or `'0'` |
|
|
281
|
-
| `isNumberString(opts?)` | factory | Numeric string |
|
|
282
|
-
| `isDecimal(opts?)` | factory | Decimal string |
|
|
283
|
-
| `isFullWidth` | constant | Full-width characters |
|
|
284
|
-
| `isHalfWidth` | constant | Half-width characters |
|
|
285
|
-
| `isVariableWidth` | constant | Mix of full-width and half-width |
|
|
286
|
-
| `isMultibyte` | constant | Multibyte characters |
|
|
287
|
-
| `isSurrogatePair` | constant | Surrogate pair characters |
|
|
288
|
-
| `isHexadecimal` | constant | Hexadecimal string |
|
|
289
|
-
| `isOctal` | constant | Octal string |
|
|
290
|
-
| `isEmail(opts?)` | factory | Email format |
|
|
291
|
-
| `isURL(opts?)` | factory | URL format (port range validated) |
|
|
292
|
-
| `isUUID(version?)` | factory | UUID v1-v5 |
|
|
293
|
-
| `isIP(version?)` | factory | IPv4 / IPv6 |
|
|
294
|
-
| `isHexColor` | constant | Hex color (`#fff`, `#ffffff`) |
|
|
295
|
-
| `isRgbColor(includePercent?)` | factory | RGB color string |
|
|
296
|
-
| `isHSL` | constant | HSL color string |
|
|
297
|
-
| `isMACAddress(opts?)` | factory | MAC address |
|
|
298
|
-
| `isISBN(version?)` | factory | ISBN-10 / ISBN-13 |
|
|
299
|
-
| `isISIN` | constant | ISIN (International Securities Identification Number) |
|
|
300
|
-
| `isISO8601(opts?)` | factory | ISO 8601 date string |
|
|
301
|
-
| `isISRC` | constant | ISRC (International Standard Recording Code) |
|
|
302
|
-
| `isISSN(opts?)` | factory | ISSN (International Standard Serial Number) |
|
|
303
|
-
| `isJWT` | constant | JSON Web Token |
|
|
304
|
-
| `isLatLong(opts?)` | factory | Latitude/longitude string |
|
|
305
|
-
| `isLocale` | constant | Locale string (e.g. `en_US`) |
|
|
306
|
-
| `isDataURI` | constant | Data URI |
|
|
307
|
-
| `isFQDN(opts?)` | factory | Fully qualified domain name |
|
|
308
|
-
| `isPort` | constant | Port number string (0-65535) |
|
|
309
|
-
| `isEAN` | constant | EAN (European Article Number) |
|
|
310
|
-
| `isISO31661Alpha2` | constant | ISO 3166-1 alpha-2 country code |
|
|
311
|
-
| `isISO31661Alpha3` | constant | ISO 3166-1 alpha-3 country code |
|
|
312
|
-
| `isBIC` | constant | BIC (Bank Identification Code) / SWIFT code |
|
|
313
|
-
| `isFirebasePushId` | constant | Firebase Push ID |
|
|
314
|
-
| `isSemVer` | constant | Semantic version string |
|
|
315
|
-
| `isMongoId` | constant | MongoDB ObjectId (24-char hex) |
|
|
316
|
-
| `isJSON` | constant | Parseable JSON string |
|
|
317
|
-
| `isBase32(opts?)` | factory | Base32 encoded |
|
|
318
|
-
| `isBase58` | constant | Base58 encoded |
|
|
319
|
-
| `isBase64(opts?)` | factory | Base64 encoded |
|
|
320
|
-
| `isDateString(opts?)` | factory | Date string (configurable strict mode) |
|
|
321
|
-
| `isMimeType` | constant | MIME type string |
|
|
322
|
-
| `isCurrency(opts?)` | factory | Currency string |
|
|
323
|
-
| `isMagnetURI` | constant | Magnet URI |
|
|
324
|
-
| `isCreditCard` | constant | Credit card number (Luhn) |
|
|
325
|
-
| `isIBAN(opts?)` | factory | IBAN |
|
|
326
|
-
| `isByteLength(min, max?)` | factory | Byte length range |
|
|
327
|
-
| `isHash(algorithm)` | factory | Hash string (md4, md5, sha1, sha256, sha384, sha512, etc.) |
|
|
328
|
-
| `isRFC3339` | constant | RFC 3339 date-time string |
|
|
329
|
-
| `isMilitaryTime` | constant | Military time (HH:MM) |
|
|
330
|
-
| `isLatitude` | constant | Latitude string |
|
|
331
|
-
| `isLongitude` | constant | Longitude string |
|
|
332
|
-
| `isEthereumAddress` | constant | Ethereum address |
|
|
333
|
-
| `isBtcAddress` | constant | Bitcoin address |
|
|
334
|
-
| `isISO4217CurrencyCode` | constant | ISO 4217 currency code |
|
|
335
|
-
| `isPhoneNumber` | constant | E.164 international phone number |
|
|
336
|
-
| `isStrongPassword(opts?)` | factory | Strong password (configurable min length, uppercase, lowercase, numbers, symbols) |
|
|
337
|
-
| `isTaxId(locale)` | factory | Tax ID for given locale |
|
|
338
|
-
|
|
339
|
-
### Array
|
|
340
|
-
|
|
341
|
-
| Rule | Description |
|
|
342
|
-
|---|---|
|
|
343
|
-
| `arrayContains(values)` | Contains all given elements |
|
|
344
|
-
| `arrayNotContains(values)` | Contains none of the given elements |
|
|
345
|
-
| `arrayMinSize(n)` | Minimum array length |
|
|
346
|
-
| `arrayMaxSize(n)` | Maximum array length |
|
|
347
|
-
| `arrayUnique()` | No duplicates |
|
|
348
|
-
| `arrayNotEmpty()` | Not empty |
|
|
152
|
+
`isString`, `isInt`, `isBoolean`, `isDate`, `isArray`, `isObject` — constants, no `()`.
|
|
349
153
|
|
|
350
|
-
|
|
154
|
+
`isNumber(options?)`, `isEnum(entity)` — factories, need `()`.
|
|
351
155
|
|
|
352
|
-
|
|
353
|
-
|---|---|
|
|
354
|
-
| `minDate(date)` | Minimum date |
|
|
355
|
-
| `maxDate(date)` | Maximum date |
|
|
156
|
+
### Numbers
|
|
356
157
|
|
|
357
|
-
|
|
158
|
+
`min(n)`, `max(n)`, `min(n, { exclusive: true })`, `isPositive`, `isNegative`, `isDivisibleBy(n)`
|
|
358
159
|
|
|
359
|
-
|
|
360
|
-
|---|---|
|
|
361
|
-
| `isNotEmptyObject(opts?)` | At least one key (supports `{ nullable: true }` to ignore null-valued keys) |
|
|
362
|
-
| `isInstance(Class)` | `instanceof` check against given class |
|
|
160
|
+
### Strings
|
|
363
161
|
|
|
364
|
-
|
|
162
|
+
`minLength(n)`, `maxLength(n)`, `length(min, max)`, `contains(seed)`, `notContains(seed)`, `matches(regex)`
|
|
365
163
|
|
|
366
|
-
|
|
164
|
+
### Formats
|
|
367
165
|
|
|
368
|
-
|
|
369
|
-
|---|---|
|
|
370
|
-
| `isMobilePhone(locale)` | Mobile phone number for the given locale (e.g. `'ko-KR'`, `'en-US'`, `'ja-JP'`) |
|
|
371
|
-
| `isPostalCode(locale)` | Postal code for the given locale/country code (e.g. `'US'`, `'KR'`, `'GB'`) |
|
|
372
|
-
| `isIdentityCard(locale)` | National identity card number for the given locale (e.g. `'KR'`, `'US'`, `'CN'`) |
|
|
373
|
-
| `isPassportNumber(locale)` | Passport number for the given locale (e.g. `'US'`, `'KR'`, `'GB'`) |
|
|
166
|
+
`isEmail()`, `isURL()`, `isUUID(version?)`, `isIP(version?)`, `isISO8601()`, `isJSON`, `isJWT`, `isCreditCard`, `isIBAN()`, `isFQDN()`, `isMACAddress()`, `isBase64()`, `isHexColor`, `isSemVer`, `isMongoId`, `isPhoneNumber`, `isStrongPassword()`
|
|
374
167
|
|
|
375
|
-
|
|
168
|
+
### Arrays
|
|
376
169
|
|
|
377
|
-
|
|
170
|
+
`arrayMinSize(n)`, `arrayMaxSize(n)`, `arrayUnique()`, `arrayNotEmpty`, `arrayContains(values)`, `arrayOf(...rules)`
|
|
378
171
|
|
|
379
|
-
|
|
172
|
+
### Common
|
|
380
173
|
|
|
381
|
-
|
|
382
|
-
import { configure } from '@zipbul/baker';
|
|
174
|
+
`equals(val)`, `notEquals(val)`, `isIn(values)`, `isNotIn(values)`, `isEmpty`, `isNotEmpty`
|
|
383
175
|
|
|
384
|
-
|
|
385
|
-
autoConvert: false, // Implicit type conversion ("123" -> 123)
|
|
386
|
-
allowClassDefaults: false, // Use class default values for missing keys
|
|
387
|
-
stopAtFirstError: false, // Stop at first error or collect all
|
|
388
|
-
forbidUnknown: false, // Reject undeclared fields
|
|
389
|
-
debug: false, // Emit field exclusion comments in generated code
|
|
390
|
-
});
|
|
391
|
-
```
|
|
176
|
+
### Date
|
|
392
177
|
|
|
393
|
-
`
|
|
178
|
+
`minDate(date)`, `maxDate(date)`
|
|
394
179
|
|
|
395
|
-
|
|
180
|
+
### Locale
|
|
396
181
|
|
|
397
|
-
|
|
182
|
+
`isMobilePhone(locale)`, `isPostalCode(locale)`, `isIdentityCard(locale)`, `isPassportNumber(locale)`
|
|
398
183
|
|
|
399
|
-
|
|
184
|
+
## Nested DTOs
|
|
400
185
|
|
|
401
186
|
```typescript
|
|
402
|
-
class
|
|
403
|
-
|
|
404
|
-
readonly className: string;
|
|
187
|
+
class AddressDto {
|
|
188
|
+
@Field(isString) city!: string;
|
|
405
189
|
}
|
|
406
190
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
readonly message?: string; // Custom message
|
|
411
|
-
readonly context?: unknown; // Custom context
|
|
191
|
+
class UserDto {
|
|
192
|
+
@Field({ type: () => AddressDto }) address!: AddressDto;
|
|
193
|
+
@Field({ type: () => [AddressDto] }) addresses!: AddressDto[];
|
|
412
194
|
}
|
|
413
195
|
```
|
|
414
196
|
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
## Nested Objects
|
|
418
|
-
|
|
419
|
-
Use `type` option for nested DTO validation:
|
|
197
|
+
## Collections
|
|
420
198
|
|
|
421
199
|
```typescript
|
|
422
|
-
class AddressDto {
|
|
423
|
-
@Field(isString)
|
|
424
|
-
city!: string;
|
|
425
|
-
}
|
|
426
|
-
|
|
427
200
|
class UserDto {
|
|
428
|
-
@Field({ type: () =>
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
// Array of nested DTOs
|
|
432
|
-
@Field({ type: () => [AddressDto] })
|
|
433
|
-
addresses!: AddressDto[];
|
|
201
|
+
@Field({ type: () => Set as any, setValue: () => TagDto }) tags!: Set<TagDto>;
|
|
202
|
+
@Field({ type: () => Map as any, mapValue: () => TagDto }) tagMap!: Map<string, TagDto>;
|
|
434
203
|
}
|
|
435
204
|
```
|
|
436
205
|
|
|
437
|
-
|
|
206
|
+
## Discriminator
|
|
438
207
|
|
|
439
208
|
```typescript
|
|
440
|
-
class
|
|
441
|
-
@Field(isString) breed!: string;
|
|
442
|
-
}
|
|
443
|
-
class CatDto {
|
|
444
|
-
@Field(isBoolean) indoor!: boolean;
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
class PetOwnerDto {
|
|
209
|
+
class PetOwner {
|
|
448
210
|
@Field({
|
|
449
|
-
type: () =>
|
|
211
|
+
type: () => CatDto,
|
|
450
212
|
discriminator: {
|
|
451
|
-
property: '
|
|
213
|
+
property: 'kind',
|
|
452
214
|
subTypes: [
|
|
453
|
-
{ value: DogDto, name: 'dog' },
|
|
454
215
|
{ value: CatDto, name: 'cat' },
|
|
216
|
+
{ value: DogDto, name: 'dog' },
|
|
455
217
|
],
|
|
456
218
|
},
|
|
457
|
-
})
|
|
458
|
-
pet!: DogDto | CatDto;
|
|
219
|
+
}) pet!: CatDto | DogDto;
|
|
459
220
|
}
|
|
460
221
|
```
|
|
461
222
|
|
|
462
|
-
Discriminator works in both directions — `deserialize()` switches on the property value, `serialize()` dispatches via `instanceof`.
|
|
463
|
-
|
|
464
|
-
### Map / Set Collections
|
|
465
|
-
|
|
466
|
-
Baker auto-converts between `Map`/`Set` and JSON-compatible types:
|
|
467
|
-
|
|
468
|
-
```typescript
|
|
469
|
-
// Set<primitive>: JSON array ↔ Set
|
|
470
|
-
@Field({ type: () => Set })
|
|
471
|
-
tags!: Set<string>;
|
|
472
|
-
|
|
473
|
-
// Set<DTO>: JSON array of objects ↔ Set of DTO instances
|
|
474
|
-
@Field({ type: () => Set, setValue: () => TagDto })
|
|
475
|
-
tags!: Set<TagDto>;
|
|
476
|
-
|
|
477
|
-
// Map<string, primitive>: JSON object ↔ Map
|
|
478
|
-
@Field({ type: () => Map })
|
|
479
|
-
config!: Map<string, unknown>;
|
|
480
|
-
|
|
481
|
-
// Map<string, DTO>: JSON object ↔ Map of DTO instances
|
|
482
|
-
@Field({ type: () => Map, mapValue: () => PriceDto })
|
|
483
|
-
prices!: Map<string, PriceDto>;
|
|
484
|
-
```
|
|
485
|
-
|
|
486
|
-
Map keys are always strings (JSON constraint). JSON Schema maps `Set` to `{ type: 'array', uniqueItems: true }` and `Map` to `{ type: 'object', additionalProperties: ... }`.
|
|
487
|
-
|
|
488
|
-
---
|
|
489
|
-
|
|
490
223
|
## Inheritance
|
|
491
224
|
|
|
492
|
-
Baker supports class inheritance. Child DTOs automatically inherit all `@Field()` decorators from parent classes. You can override or extend fields in child classes:
|
|
493
|
-
|
|
494
225
|
```typescript
|
|
495
226
|
class BaseDto {
|
|
496
|
-
@Field(isString)
|
|
497
|
-
name!: string;
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
class ExtendedDto extends BaseDto {
|
|
501
|
-
@Field(isInt, min(0))
|
|
502
|
-
age!: number;
|
|
503
|
-
// `name` is inherited from BaseDto
|
|
504
|
-
}
|
|
505
|
-
```
|
|
506
|
-
|
|
507
|
-
---
|
|
508
|
-
|
|
509
|
-
## Transform
|
|
510
|
-
|
|
511
|
-
The `transform` option in `FieldOptions` lets you transform values during deserialization and/or serialization. Transform functions can be **async**.
|
|
512
|
-
|
|
513
|
-
```typescript
|
|
514
|
-
class UserDto {
|
|
515
|
-
@Field(isString, {
|
|
516
|
-
transform: ({ value, direction }) => {
|
|
517
|
-
return direction === 'deserialize'
|
|
518
|
-
? (value as string).trim().toLowerCase()
|
|
519
|
-
: value;
|
|
520
|
-
},
|
|
521
|
-
})
|
|
522
|
-
email!: string;
|
|
523
|
-
|
|
524
|
-
@Field(isString, {
|
|
525
|
-
transform: async ({ value }) => {
|
|
526
|
-
return await someAsyncOperation(value);
|
|
527
|
-
},
|
|
528
|
-
transformDirection: 'deserializeOnly',
|
|
529
|
-
})
|
|
530
|
-
data!: string;
|
|
531
|
-
}
|
|
532
|
-
```
|
|
533
|
-
|
|
534
|
-
---
|
|
535
|
-
|
|
536
|
-
## Custom Rules
|
|
537
|
-
|
|
538
|
-
```typescript
|
|
539
|
-
import { createRule } from '@zipbul/baker';
|
|
540
|
-
|
|
541
|
-
const isPositiveInt = createRule({
|
|
542
|
-
name: 'isPositiveInt',
|
|
543
|
-
validate: (value) => Number.isInteger(value) && (value as number) > 0,
|
|
544
|
-
});
|
|
545
|
-
|
|
546
|
-
class Dto {
|
|
547
|
-
@Field(isPositiveInt)
|
|
548
|
-
count!: number;
|
|
227
|
+
@Field(isString) id!: string;
|
|
549
228
|
}
|
|
550
|
-
```
|
|
551
|
-
|
|
552
|
-
---
|
|
553
|
-
|
|
554
|
-
## Class-level JSON Schema Metadata
|
|
555
|
-
|
|
556
|
-
Use `collectClassSchema()` to attach class-level JSON Schema metadata (title, description, etc.) to a DTO. This metadata is merged into the output of `toJsonSchema()`.
|
|
557
|
-
|
|
558
|
-
> `collectClassSchema` is a low-level API exported from `src/collect.ts`. It is not available as a subpath export and must be imported directly.
|
|
559
|
-
|
|
560
|
-
```typescript
|
|
561
|
-
import { collectClassSchema } from '@zipbul/baker/src/collect';
|
|
562
229
|
|
|
563
|
-
class
|
|
230
|
+
class UserDto extends BaseDto {
|
|
564
231
|
@Field(isString) name!: string;
|
|
565
|
-
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
collectClassSchema(CreateUserDto, {
|
|
569
|
-
title: 'CreateUserRequest',
|
|
570
|
-
description: 'Payload for creating a new user',
|
|
571
|
-
});
|
|
572
|
-
```
|
|
573
|
-
|
|
574
|
-
For property-level schema overrides, use the `schema` option in `@Field()`:
|
|
575
|
-
|
|
576
|
-
```typescript
|
|
577
|
-
class Dto {
|
|
578
|
-
@Field(isString, minLength(1), {
|
|
579
|
-
schema: { description: 'User display name', minLength: 5 },
|
|
580
|
-
})
|
|
581
|
-
name!: string;
|
|
232
|
+
// inherits 'id' field with isString rule
|
|
582
233
|
}
|
|
583
234
|
```
|
|
584
235
|
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
## JSON Schema
|
|
588
|
-
|
|
589
|
-
Generate JSON Schema Draft 2020-12 from your DTOs:
|
|
236
|
+
## Exports
|
|
590
237
|
|
|
591
238
|
```typescript
|
|
592
|
-
|
|
239
|
+
// Functions
|
|
240
|
+
import { deserialize, validate, serialize, configure, createRule } from '@zipbul/baker';
|
|
593
241
|
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
groups: ['create'], // Filter by group
|
|
597
|
-
onUnmappedRule: (name) => { /* custom rules without schema mapping */ },
|
|
598
|
-
});
|
|
599
|
-
```
|
|
242
|
+
// Decorators
|
|
243
|
+
import { Field, arrayOf } from '@zipbul/baker';
|
|
600
244
|
|
|
601
|
-
|
|
245
|
+
// Error handling
|
|
246
|
+
import { isBakerError, SealError } from '@zipbul/baker';
|
|
602
247
|
|
|
603
|
-
|
|
248
|
+
// Types
|
|
249
|
+
import type {
|
|
250
|
+
BakerError, BakerErrors, FieldOptions, FieldTransformParams,
|
|
251
|
+
ArrayOfMarker, EmittableRule, BakerConfig, ConfigureResult, RuntimeOptions,
|
|
252
|
+
} from '@zipbul/baker';
|
|
604
253
|
|
|
254
|
+
// Rules (subpath)
|
|
255
|
+
import { isString, isNumber, ... } from '@zipbul/baker/rules';
|
|
605
256
|
```
|
|
606
|
-
Decorators (@Field) auto-seal (first call) deserialize() / serialize()
|
|
607
|
-
metadata -> new Function() codegen -> execute generated code
|
|
608
|
-
```
|
|
609
|
-
|
|
610
|
-
1. `@Field()` attaches validation metadata to class properties at definition time
|
|
611
|
-
2. First `deserialize()`/`serialize()` call triggers **auto-seal** — reads all metadata, analyzes circular references, generates optimized JavaScript functions via `new Function()`
|
|
612
|
-
3. Subsequent calls execute the generated function directly — no interpretation loops
|
|
613
|
-
|
|
614
|
-
---
|
|
615
|
-
|
|
616
|
-
## Subpath Exports
|
|
617
257
|
|
|
618
|
-
|
|
619
|
-
|---|---|
|
|
620
|
-
| `@zipbul/baker` | Main API: `deserialize`, `serialize`, `configure`, `toJsonSchema`, `Field`, `arrayOf`, `createRule` |
|
|
621
|
-
| `@zipbul/baker/rules` | Rule functions and constants: `isString`, `min()`, `isEmail()`, `arrayOf()`, etc. |
|
|
258
|
+
## What Baker Does Not Do
|
|
622
259
|
|
|
623
|
-
|
|
260
|
+
- JSON Schema / OpenAPI generation
|
|
261
|
+
- GraphQL schema generation
|
|
262
|
+
- Runtime type inference from schemas
|
|
263
|
+
- `reflect-metadata` dependency
|
|
624
264
|
|
|
625
265
|
## License
|
|
626
266
|
|
|
627
|
-
|
|
267
|
+
MIT
|