@hy_ong/zod-kit 0.1.0 → 0.1.2
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 +134 -68
- package/dist/index.cjs +40 -21
- package/dist/index.d.cts +24 -11
- package/dist/index.d.ts +24 -11
- package/dist/index.js +40 -21
- package/package.json +14 -5
- package/src/validators/common/id.ts +108 -41
- package/tests/common/id.test.ts +62 -4
package/README.md
CHANGED
|
@@ -39,12 +39,16 @@ pnpm add @hy_ong/zod-kit zod
|
|
|
39
39
|
```typescript
|
|
40
40
|
import { email, password, text, mobile, datetime, time, postalCode } from '@hy_ong/zod-kit'
|
|
41
41
|
|
|
42
|
-
// Simple email validation
|
|
43
|
-
const emailSchema = email()
|
|
42
|
+
// Simple email validation (required by default)
|
|
43
|
+
const emailSchema = email(true)
|
|
44
44
|
emailSchema.parse('user@example.com') // ✅ "user@example.com"
|
|
45
45
|
|
|
46
|
+
// Optional email
|
|
47
|
+
const optionalEmail = email(false)
|
|
48
|
+
optionalEmail.parse(null) // ✅ null
|
|
49
|
+
|
|
46
50
|
// Password with complexity requirements
|
|
47
|
-
const passwordSchema = password({
|
|
51
|
+
const passwordSchema = password(true, {
|
|
48
52
|
minLength: 8,
|
|
49
53
|
requireUppercase: true,
|
|
50
54
|
requireDigits: true,
|
|
@@ -52,19 +56,19 @@ const passwordSchema = password({
|
|
|
52
56
|
})
|
|
53
57
|
|
|
54
58
|
// Taiwan mobile phone validation
|
|
55
|
-
const phoneSchema = mobile()
|
|
59
|
+
const phoneSchema = mobile(true)
|
|
56
60
|
phoneSchema.parse('0912345678') // ✅ "0912345678"
|
|
57
61
|
|
|
58
62
|
// DateTime validation
|
|
59
|
-
const datetimeSchema = datetime()
|
|
63
|
+
const datetimeSchema = datetime(true)
|
|
60
64
|
datetimeSchema.parse('2024-03-15 14:30') // ✅ "2024-03-15 14:30"
|
|
61
65
|
|
|
62
66
|
// Time validation
|
|
63
|
-
const timeSchema = time()
|
|
67
|
+
const timeSchema = time(true)
|
|
64
68
|
timeSchema.parse('14:30') // ✅ "14:30"
|
|
65
69
|
|
|
66
70
|
// Taiwan postal code validation
|
|
67
|
-
const postalSchema = postalCode()
|
|
71
|
+
const postalSchema = postalCode(true)
|
|
68
72
|
postalSchema.parse('100001') // ✅ "100001"
|
|
69
73
|
```
|
|
70
74
|
|
|
@@ -72,37 +76,51 @@ postalSchema.parse('100001') // ✅ "100001"
|
|
|
72
76
|
|
|
73
77
|
### Common Validators
|
|
74
78
|
|
|
75
|
-
#### `email(options?)`
|
|
79
|
+
#### `email(required?, options?)`
|
|
76
80
|
|
|
77
81
|
Validates email addresses with comprehensive format checking.
|
|
78
82
|
|
|
83
|
+
**Parameters:**
|
|
84
|
+
- `required` (boolean, optional): Whether the field is required. Default: `false`
|
|
85
|
+
- `options` (object, optional): Configuration options
|
|
86
|
+
|
|
79
87
|
```typescript
|
|
80
88
|
import { email } from '@hy_ong/zod-kit'
|
|
81
89
|
|
|
82
|
-
//
|
|
83
|
-
const
|
|
90
|
+
// Required email (recommended)
|
|
91
|
+
const requiredEmail = email(true)
|
|
92
|
+
|
|
93
|
+
// Optional email
|
|
94
|
+
const optionalEmail = email(false)
|
|
95
|
+
optionalEmail.parse(null) // ✅ null
|
|
84
96
|
|
|
85
97
|
// With options
|
|
86
|
-
const advancedEmail = email({
|
|
87
|
-
required: true, // Default: true
|
|
98
|
+
const advancedEmail = email(true, {
|
|
88
99
|
allowedDomains: ['gmail.com', 'company.com'],
|
|
89
100
|
minLength: 5,
|
|
90
101
|
maxLength: 100,
|
|
91
102
|
transform: (val) => val.toLowerCase(),
|
|
103
|
+
defaultValue: 'default@example.com',
|
|
92
104
|
i18n: {
|
|
93
|
-
en: { invalid: 'Please enter a valid email' }
|
|
105
|
+
en: { invalid: 'Please enter a valid email' },
|
|
106
|
+
'zh-TW': { invalid: '請輸入有效的電子郵件' }
|
|
94
107
|
}
|
|
95
108
|
})
|
|
96
109
|
```
|
|
97
110
|
|
|
98
|
-
#### `password(options?)`
|
|
111
|
+
#### `password(required?, options?)`
|
|
99
112
|
|
|
100
113
|
Validates passwords with customizable complexity requirements.
|
|
101
114
|
|
|
115
|
+
**Parameters:**
|
|
116
|
+
- `required` (boolean, optional): Whether the field is required. Default: `false`
|
|
117
|
+
- `options` (object, optional): Configuration options
|
|
118
|
+
|
|
102
119
|
```typescript
|
|
103
120
|
import { password } from '@hy_ong/zod-kit'
|
|
104
121
|
|
|
105
|
-
|
|
122
|
+
// Required password with complexity rules
|
|
123
|
+
const passwordSchema = password(true, {
|
|
106
124
|
minLength: 8, // Minimum length
|
|
107
125
|
maxLength: 128, // Maximum length
|
|
108
126
|
requireUppercase: true, // Require A-Z
|
|
@@ -113,67 +131,77 @@ const passwordSchema = password({
|
|
|
113
131
|
{ pattern: /[A-Z]/, message: 'Need uppercase' }
|
|
114
132
|
]
|
|
115
133
|
})
|
|
134
|
+
|
|
135
|
+
// Optional password
|
|
136
|
+
const optionalPassword = password(false)
|
|
116
137
|
```
|
|
117
138
|
|
|
118
|
-
#### `text(options?)`
|
|
139
|
+
#### `text(required?, options?)`
|
|
119
140
|
|
|
120
141
|
General text validation with length and pattern constraints.
|
|
121
142
|
|
|
122
143
|
```typescript
|
|
123
144
|
import { text } from '@hy_ong/zod-kit'
|
|
124
145
|
|
|
125
|
-
const nameSchema = text({
|
|
146
|
+
const nameSchema = text(true, {
|
|
126
147
|
minLength: 2,
|
|
127
148
|
maxLength: 50,
|
|
128
149
|
pattern: /^[a-zA-Z\s]+$/,
|
|
129
150
|
transform: (val) => val.trim()
|
|
130
151
|
})
|
|
152
|
+
|
|
153
|
+
const optionalText = text(false)
|
|
131
154
|
```
|
|
132
155
|
|
|
133
|
-
#### `number(options?)`
|
|
156
|
+
#### `number(required?, options?)`
|
|
134
157
|
|
|
135
158
|
Validates numeric values with range and type constraints.
|
|
136
159
|
|
|
137
160
|
```typescript
|
|
138
161
|
import { number } from '@hy_ong/zod-kit'
|
|
139
162
|
|
|
140
|
-
const ageSchema = number({
|
|
163
|
+
const ageSchema = number(true, {
|
|
141
164
|
min: 0,
|
|
142
165
|
max: 150,
|
|
143
166
|
integer: true,
|
|
144
167
|
positive: true
|
|
145
168
|
})
|
|
169
|
+
|
|
170
|
+
const optionalNumber = number(false)
|
|
146
171
|
```
|
|
147
172
|
|
|
148
|
-
#### `url(options?)`
|
|
173
|
+
#### `url(required?, options?)`
|
|
149
174
|
|
|
150
175
|
URL validation with protocol and domain restrictions.
|
|
151
176
|
|
|
152
177
|
```typescript
|
|
153
178
|
import { url } from '@hy_ong/zod-kit'
|
|
154
179
|
|
|
155
|
-
const urlSchema = url({
|
|
180
|
+
const urlSchema = url(true, {
|
|
156
181
|
protocols: ['https'], // Only HTTPS allowed
|
|
157
182
|
allowedDomains: ['safe.com'], // Domain whitelist
|
|
158
183
|
requireTLD: true // Require top-level domain
|
|
159
184
|
})
|
|
185
|
+
|
|
186
|
+
const optionalUrl = url(false)
|
|
160
187
|
```
|
|
161
188
|
|
|
162
|
-
#### `boolean(options?)`
|
|
189
|
+
#### `boolean(required?, options?)`
|
|
163
190
|
|
|
164
191
|
Boolean validation with flexible input handling.
|
|
165
192
|
|
|
166
193
|
```typescript
|
|
167
194
|
import { boolean } from '@hy_ong/zod-kit'
|
|
168
195
|
|
|
169
|
-
const consentSchema = boolean({
|
|
170
|
-
required: true,
|
|
196
|
+
const consentSchema = boolean(true, {
|
|
171
197
|
trueValues: ['yes', '1', 'true'], // Custom truthy values
|
|
172
198
|
falseValues: ['no', '0', 'false'] // Custom falsy values
|
|
173
199
|
})
|
|
200
|
+
|
|
201
|
+
const optionalBoolean = boolean(false)
|
|
174
202
|
```
|
|
175
203
|
|
|
176
|
-
#### `datetime(options?)`
|
|
204
|
+
#### `datetime(required?, options?)`
|
|
177
205
|
|
|
178
206
|
Validates datetime with comprehensive format support and timezone handling.
|
|
179
207
|
|
|
@@ -181,11 +209,11 @@ Validates datetime with comprehensive format support and timezone handling.
|
|
|
181
209
|
import { datetime } from '@hy_ong/zod-kit'
|
|
182
210
|
|
|
183
211
|
// Basic datetime validation
|
|
184
|
-
const basicSchema = datetime()
|
|
212
|
+
const basicSchema = datetime(true)
|
|
185
213
|
basicSchema.parse('2024-03-15 14:30') // ✓ Valid
|
|
186
214
|
|
|
187
215
|
// Business hours validation
|
|
188
|
-
const businessHours = datetime({
|
|
216
|
+
const businessHours = datetime(true, {
|
|
189
217
|
format: 'YYYY-MM-DD HH:mm',
|
|
190
218
|
minHour: 9,
|
|
191
219
|
maxHour: 17,
|
|
@@ -193,19 +221,22 @@ const businessHours = datetime({
|
|
|
193
221
|
})
|
|
194
222
|
|
|
195
223
|
// Timezone-aware validation
|
|
196
|
-
const timezoneSchema = datetime({
|
|
224
|
+
const timezoneSchema = datetime(true, {
|
|
197
225
|
timezone: 'Asia/Taipei',
|
|
198
226
|
mustBeFuture: true
|
|
199
227
|
})
|
|
200
228
|
|
|
201
229
|
// Multiple format support
|
|
202
|
-
const flexibleSchema = datetime({
|
|
230
|
+
const flexibleSchema = datetime(true, {
|
|
203
231
|
format: 'DD/MM/YYYY HH:mm'
|
|
204
232
|
})
|
|
205
233
|
flexibleSchema.parse('15/03/2024 14:30') // ✓ Valid
|
|
234
|
+
|
|
235
|
+
// Optional datetime
|
|
236
|
+
const optionalDatetime = datetime(false)
|
|
206
237
|
```
|
|
207
238
|
|
|
208
|
-
#### `time(options?)`
|
|
239
|
+
#### `time(required?, options?)`
|
|
209
240
|
|
|
210
241
|
Time validation with multiple formats and constraints.
|
|
211
242
|
|
|
@@ -213,15 +244,15 @@ Time validation with multiple formats and constraints.
|
|
|
213
244
|
import { time } from '@hy_ong/zod-kit'
|
|
214
245
|
|
|
215
246
|
// Basic time validation (24-hour format)
|
|
216
|
-
const basicSchema = time()
|
|
247
|
+
const basicSchema = time(true)
|
|
217
248
|
basicSchema.parse('14:30') // ✓ Valid
|
|
218
249
|
|
|
219
250
|
// 12-hour format with AM/PM
|
|
220
|
-
const ampmSchema = time({ format: 'hh:mm A' })
|
|
251
|
+
const ampmSchema = time(true, { format: 'hh:mm A' })
|
|
221
252
|
ampmSchema.parse('02:30 PM') // ✓ Valid
|
|
222
253
|
|
|
223
254
|
// Business hours validation
|
|
224
|
-
const businessHours = time({
|
|
255
|
+
const businessHours = time(true, {
|
|
225
256
|
format: 'HH:mm',
|
|
226
257
|
minHour: 9,
|
|
227
258
|
maxHour: 17,
|
|
@@ -229,28 +260,33 @@ const businessHours = time({
|
|
|
229
260
|
})
|
|
230
261
|
|
|
231
262
|
// Time range validation
|
|
232
|
-
const timeRangeSchema = time({
|
|
263
|
+
const timeRangeSchema = time(true, {
|
|
233
264
|
min: '09:00',
|
|
234
265
|
max: '17:00'
|
|
235
266
|
})
|
|
267
|
+
|
|
268
|
+
// Optional time
|
|
269
|
+
const optionalTime = time(false)
|
|
236
270
|
```
|
|
237
271
|
|
|
238
|
-
#### `date(options?)`
|
|
272
|
+
#### `date(required?, options?)`
|
|
239
273
|
|
|
240
274
|
Date validation with range and format constraints.
|
|
241
275
|
|
|
242
276
|
```typescript
|
|
243
277
|
import { date } from '@hy_ong/zod-kit'
|
|
244
278
|
|
|
245
|
-
const birthdateSchema = date({
|
|
279
|
+
const birthdateSchema = date(true, {
|
|
246
280
|
format: 'YYYY-MM-DD',
|
|
247
281
|
minDate: '1900-01-01',
|
|
248
282
|
maxDate: new Date(),
|
|
249
283
|
timezone: 'Asia/Taipei'
|
|
250
284
|
})
|
|
285
|
+
|
|
286
|
+
const optionalDate = date(false)
|
|
251
287
|
```
|
|
252
288
|
|
|
253
|
-
#### `file(options?)`
|
|
289
|
+
#### `file(required?, options?)`
|
|
254
290
|
|
|
255
291
|
File validation with MIME type filtering and size constraints.
|
|
256
292
|
|
|
@@ -258,113 +294,127 @@ File validation with MIME type filtering and size constraints.
|
|
|
258
294
|
import { file } from '@hy_ong/zod-kit'
|
|
259
295
|
|
|
260
296
|
// Basic file validation
|
|
261
|
-
const basicSchema = file()
|
|
297
|
+
const basicSchema = file(true)
|
|
262
298
|
basicSchema.parse(new File(['content'], 'test.txt'))
|
|
263
299
|
|
|
264
300
|
// Size restrictions
|
|
265
|
-
const sizeSchema = file({
|
|
301
|
+
const sizeSchema = file(true, {
|
|
266
302
|
maxSize: 1024 * 1024, // 1MB
|
|
267
303
|
minSize: 1024 // 1KB
|
|
268
304
|
})
|
|
269
305
|
|
|
270
306
|
// Extension restrictions
|
|
271
|
-
const imageSchema = file({
|
|
307
|
+
const imageSchema = file(true, {
|
|
272
308
|
extension: ['.jpg', '.png', '.gif'],
|
|
273
309
|
maxSize: 5 * 1024 * 1024 // 5MB
|
|
274
310
|
})
|
|
275
311
|
|
|
276
312
|
// MIME type restrictions
|
|
277
|
-
const documentSchema = file({
|
|
313
|
+
const documentSchema = file(true, {
|
|
278
314
|
type: ['application/pdf', 'application/msword'],
|
|
279
315
|
maxSize: 10 * 1024 * 1024 // 10MB
|
|
280
316
|
})
|
|
281
317
|
|
|
282
318
|
// Image files only
|
|
283
|
-
const imageOnlySchema = file({ imageOnly: true })
|
|
319
|
+
const imageOnlySchema = file(true, { imageOnly: true })
|
|
320
|
+
|
|
321
|
+
// Optional file
|
|
322
|
+
const optionalFile = file(false)
|
|
284
323
|
```
|
|
285
324
|
|
|
286
|
-
#### `id(options?)`
|
|
325
|
+
#### `id(required?, options?)`
|
|
287
326
|
|
|
288
327
|
Flexible ID validation supporting multiple formats.
|
|
289
328
|
|
|
290
329
|
```typescript
|
|
291
330
|
import { id } from '@hy_ong/zod-kit'
|
|
292
331
|
|
|
293
|
-
const userIdSchema = id({
|
|
332
|
+
const userIdSchema = id(true, {
|
|
294
333
|
type: 'uuid', // 'uuid', 'nanoid', 'objectId', 'auto', etc.
|
|
295
334
|
allowedTypes: ['uuid', 'nanoid'], // Multiple allowed types
|
|
296
335
|
customRegex: /^USR_[A-Z0-9]+$/ // Custom pattern
|
|
297
336
|
})
|
|
337
|
+
|
|
338
|
+
const optionalId = id(false)
|
|
298
339
|
```
|
|
299
340
|
|
|
300
341
|
### Taiwan-Specific Validators
|
|
301
342
|
|
|
302
|
-
#### `nationalId(options?)`
|
|
343
|
+
#### `nationalId(required?, options?)`
|
|
303
344
|
|
|
304
345
|
Validates Taiwan National ID (身份證字號).
|
|
305
346
|
|
|
306
347
|
```typescript
|
|
307
348
|
import { nationalId } from '@hy_ong/zod-kit'
|
|
308
349
|
|
|
309
|
-
const idSchema = nationalId({
|
|
310
|
-
required: true,
|
|
350
|
+
const idSchema = nationalId(true, {
|
|
311
351
|
normalize: true, // Convert to uppercase
|
|
312
352
|
whitelist: ['A123456789'] // Allow specific IDs
|
|
313
353
|
})
|
|
314
354
|
|
|
315
355
|
idSchema.parse('A123456789') // ✅ Valid Taiwan National ID
|
|
356
|
+
|
|
357
|
+
const optionalId = nationalId(false)
|
|
316
358
|
```
|
|
317
359
|
|
|
318
|
-
#### `businessId(options?)`
|
|
360
|
+
#### `businessId(required?, options?)`
|
|
319
361
|
|
|
320
362
|
Validates Taiwan Business ID (統一編號).
|
|
321
363
|
|
|
322
364
|
```typescript
|
|
323
365
|
import { businessId } from '@hy_ong/zod-kit'
|
|
324
366
|
|
|
325
|
-
const bizSchema = businessId()
|
|
367
|
+
const bizSchema = businessId(true)
|
|
326
368
|
bizSchema.parse('12345675') // ✅ Valid business ID with checksum
|
|
369
|
+
|
|
370
|
+
const optionalBizId = businessId(false)
|
|
327
371
|
```
|
|
328
372
|
|
|
329
|
-
#### `mobile(options?)`
|
|
373
|
+
#### `mobile(required?, options?)`
|
|
330
374
|
|
|
331
375
|
Validates Taiwan mobile phone numbers.
|
|
332
376
|
|
|
333
377
|
```typescript
|
|
334
378
|
import { mobile } from '@hy_ong/zod-kit'
|
|
335
379
|
|
|
336
|
-
const phoneSchema = mobile({
|
|
380
|
+
const phoneSchema = mobile(true, {
|
|
337
381
|
allowInternational: true, // Allow +886 prefix
|
|
338
382
|
allowSeparators: true, // Allow 0912-345-678
|
|
339
383
|
operators: ['09'] // Restrict to specific operators
|
|
340
384
|
})
|
|
385
|
+
|
|
386
|
+
const optionalMobile = mobile(false)
|
|
341
387
|
```
|
|
342
388
|
|
|
343
|
-
#### `tel(options?)`
|
|
389
|
+
#### `tel(required?, options?)`
|
|
344
390
|
|
|
345
391
|
Validates Taiwan landline telephone numbers.
|
|
346
392
|
|
|
347
393
|
```typescript
|
|
348
394
|
import { tel } from '@hy_ong/zod-kit'
|
|
349
395
|
|
|
350
|
-
const landlineSchema = tel({
|
|
396
|
+
const landlineSchema = tel(true, {
|
|
351
397
|
allowSeparators: true, // Allow 02-1234-5678
|
|
352
398
|
areaCodes: ['02', '03'] // Restrict to specific areas
|
|
353
399
|
})
|
|
400
|
+
|
|
401
|
+
const optionalTel = tel(false)
|
|
354
402
|
```
|
|
355
403
|
|
|
356
|
-
#### `fax(options?)`
|
|
404
|
+
#### `fax(required?, options?)`
|
|
357
405
|
|
|
358
406
|
Validates Taiwan fax numbers (same format as landline).
|
|
359
407
|
|
|
360
408
|
```typescript
|
|
361
409
|
import { fax } from '@hy_ong/zod-kit'
|
|
362
410
|
|
|
363
|
-
const faxSchema = fax()
|
|
411
|
+
const faxSchema = fax(true)
|
|
364
412
|
faxSchema.parse('02-2345-6789') // ✅ Valid fax number
|
|
413
|
+
|
|
414
|
+
const optionalFax = fax(false)
|
|
365
415
|
```
|
|
366
416
|
|
|
367
|
-
#### `postalCode(options?)`
|
|
417
|
+
#### `postalCode(required?, options?)`
|
|
368
418
|
|
|
369
419
|
Validates Taiwan postal codes with support for 3-digit, 5-digit, and 6-digit formats.
|
|
370
420
|
|
|
@@ -372,31 +422,34 @@ Validates Taiwan postal codes with support for 3-digit, 5-digit, and 6-digit for
|
|
|
372
422
|
import { postalCode } from '@hy_ong/zod-kit'
|
|
373
423
|
|
|
374
424
|
// Accept 3-digit or 6-digit formats (recommended)
|
|
375
|
-
const modernSchema = postalCode()
|
|
425
|
+
const modernSchema = postalCode(true)
|
|
376
426
|
modernSchema.parse('100') // ✅ Valid 3-digit
|
|
377
427
|
modernSchema.parse('100001') // ✅ Valid 6-digit
|
|
378
428
|
|
|
379
429
|
// Accept all formats
|
|
380
|
-
const flexibleSchema = postalCode({ format: 'all' })
|
|
430
|
+
const flexibleSchema = postalCode(true, { format: 'all' })
|
|
381
431
|
flexibleSchema.parse('100') // ✅ Valid
|
|
382
432
|
flexibleSchema.parse('10001') // ✅ Valid (5-digit legacy)
|
|
383
433
|
flexibleSchema.parse('100001') // ✅ Valid
|
|
384
434
|
|
|
385
435
|
// Only 6-digit format (current standard)
|
|
386
|
-
const modernOnlySchema = postalCode({ format: '6' })
|
|
436
|
+
const modernOnlySchema = postalCode(true, { format: '6' })
|
|
387
437
|
modernOnlySchema.parse('100001') // ✅ Valid
|
|
388
438
|
modernOnlySchema.parse('100') // ❌ Invalid
|
|
389
439
|
|
|
390
440
|
// With dashes allowed
|
|
391
|
-
const dashSchema = postalCode({ allowDashes: true })
|
|
441
|
+
const dashSchema = postalCode(true, { allowDashes: true })
|
|
392
442
|
dashSchema.parse('100-001') // ✅ Valid (normalized to '100001')
|
|
393
443
|
|
|
394
444
|
// Specific areas only
|
|
395
|
-
const taipeiSchema = postalCode({
|
|
445
|
+
const taipeiSchema = postalCode(true, {
|
|
396
446
|
allowedPrefixes: ['100', '103', '104', '105', '106']
|
|
397
447
|
})
|
|
398
448
|
taipeiSchema.parse('100001') // ✅ Valid (Taipei area)
|
|
399
449
|
taipeiSchema.parse('200001') // ❌ Invalid (not in allowlist)
|
|
450
|
+
|
|
451
|
+
// Optional postal code
|
|
452
|
+
const optionalPostal = postalCode(false)
|
|
400
453
|
```
|
|
401
454
|
|
|
402
455
|
## 🌐 Internationalization
|
|
@@ -411,7 +464,7 @@ setLocale('zh-TW') // Traditional Chinese
|
|
|
411
464
|
setLocale('en') // English (default)
|
|
412
465
|
|
|
413
466
|
// Or use custom messages per validator
|
|
414
|
-
const emailSchema = email({
|
|
467
|
+
const emailSchema = email(true, {
|
|
415
468
|
i18n: {
|
|
416
469
|
en: {
|
|
417
470
|
required: 'Email is required',
|
|
@@ -429,10 +482,10 @@ const emailSchema = email({
|
|
|
429
482
|
|
|
430
483
|
### Optional Fields
|
|
431
484
|
|
|
432
|
-
Make any field optional by
|
|
485
|
+
Make any field optional by passing `false` as the first argument:
|
|
433
486
|
|
|
434
487
|
```typescript
|
|
435
|
-
const optionalEmail = email(
|
|
488
|
+
const optionalEmail = email(false)
|
|
436
489
|
|
|
437
490
|
optionalEmail.parse(null) // ✅ null
|
|
438
491
|
optionalEmail.parse('') // ✅ null
|
|
@@ -444,7 +497,7 @@ optionalEmail.parse('test@example.com') // ✅ "test@example.com"
|
|
|
444
497
|
Transform values during validation:
|
|
445
498
|
|
|
446
499
|
```typescript
|
|
447
|
-
const trimmedText = text({
|
|
500
|
+
const trimmedText = text(true, {
|
|
448
501
|
transform: (val) => val.trim().toLowerCase(),
|
|
449
502
|
minLength: 1
|
|
450
503
|
})
|
|
@@ -452,12 +505,25 @@ const trimmedText = text({
|
|
|
452
505
|
trimmedText.parse(' HELLO ') // ✅ "hello"
|
|
453
506
|
```
|
|
454
507
|
|
|
508
|
+
### Default Values
|
|
509
|
+
|
|
510
|
+
Provide default values for empty inputs:
|
|
511
|
+
|
|
512
|
+
```typescript
|
|
513
|
+
const emailWithDefault = email(true, {
|
|
514
|
+
defaultValue: 'default@example.com'
|
|
515
|
+
})
|
|
516
|
+
|
|
517
|
+
emailWithDefault.parse('') // ✅ "default@example.com"
|
|
518
|
+
emailWithDefault.parse(null) // ✅ "default@example.com"
|
|
519
|
+
```
|
|
520
|
+
|
|
455
521
|
### Whitelist Validation
|
|
456
522
|
|
|
457
523
|
Allow specific values regardless of format:
|
|
458
524
|
|
|
459
525
|
```typescript
|
|
460
|
-
const flexibleId = id({
|
|
526
|
+
const flexibleId = id(true, {
|
|
461
527
|
type: 'uuid',
|
|
462
528
|
whitelist: ['admin', 'system', 'test-user']
|
|
463
529
|
})
|
|
@@ -475,8 +541,8 @@ import { z } from 'zod'
|
|
|
475
541
|
import { email, password } from '@hy_ong/zod-kit'
|
|
476
542
|
|
|
477
543
|
const userSchema = z.object({
|
|
478
|
-
email: email(),
|
|
479
|
-
password: password({ minLength: 8 }),
|
|
544
|
+
email: email(true),
|
|
545
|
+
password: password(true, { minLength: 8 }),
|
|
480
546
|
confirmPassword: z.string()
|
|
481
547
|
}).refine(data => data.password === data.confirmPassword, {
|
|
482
548
|
message: "Passwords don't match"
|
package/dist/index.cjs
CHANGED
|
@@ -1291,35 +1291,31 @@ var validateIdType = (value, type) => {
|
|
|
1291
1291
|
return pattern ? pattern.test(value) : false;
|
|
1292
1292
|
};
|
|
1293
1293
|
function id(required, options) {
|
|
1294
|
-
const {
|
|
1295
|
-
type = "auto",
|
|
1296
|
-
minLength,
|
|
1297
|
-
maxLength,
|
|
1298
|
-
allowedTypes,
|
|
1299
|
-
customRegex,
|
|
1300
|
-
includes,
|
|
1301
|
-
excludes,
|
|
1302
|
-
startsWith,
|
|
1303
|
-
endsWith,
|
|
1304
|
-
caseSensitive = true,
|
|
1305
|
-
transform,
|
|
1306
|
-
defaultValue,
|
|
1307
|
-
i18n
|
|
1308
|
-
} = options ?? {};
|
|
1294
|
+
const { type = "auto", minLength, maxLength, allowedTypes, customRegex, includes, excludes, startsWith, endsWith, caseSensitive = true, transform, defaultValue, i18n } = options ?? {};
|
|
1309
1295
|
const isRequired = required ?? false;
|
|
1310
|
-
const
|
|
1296
|
+
const isNumericType = type === "numeric";
|
|
1297
|
+
const actualDefaultValue = defaultValue !== void 0 ? defaultValue : isRequired ? isNumericType ? NaN : "" : null;
|
|
1311
1298
|
const getMessage = (key, params) => {
|
|
1312
1299
|
if (i18n) {
|
|
1313
1300
|
const currentLocale2 = getLocale();
|
|
1314
1301
|
const customMessages = i18n[currentLocale2];
|
|
1315
1302
|
if (customMessages && customMessages[key]) {
|
|
1316
1303
|
const template = customMessages[key];
|
|
1317
|
-
return template.replace(/\$\{(\w+)}/g, (
|
|
1304
|
+
return template.replace(/\$\{(\w+)}/g, (_match, k) => params?.[k] ?? "");
|
|
1318
1305
|
}
|
|
1319
1306
|
}
|
|
1320
1307
|
return t(`common.id.${key}`, params);
|
|
1321
1308
|
};
|
|
1322
|
-
const
|
|
1309
|
+
const preprocessNumericFn = (val) => {
|
|
1310
|
+
if (val === "" || val === null || val === void 0) {
|
|
1311
|
+
if (isRequired && defaultValue === void 0) {
|
|
1312
|
+
return void 0;
|
|
1313
|
+
}
|
|
1314
|
+
return actualDefaultValue;
|
|
1315
|
+
}
|
|
1316
|
+
return Number(val);
|
|
1317
|
+
};
|
|
1318
|
+
const preprocessStringFn = (val) => {
|
|
1323
1319
|
if (val === "" || val === null || val === void 0) {
|
|
1324
1320
|
return actualDefaultValue;
|
|
1325
1321
|
}
|
|
@@ -1329,7 +1325,30 @@ function id(required, options) {
|
|
|
1329
1325
|
}
|
|
1330
1326
|
return processed;
|
|
1331
1327
|
};
|
|
1332
|
-
|
|
1328
|
+
if (isNumericType) {
|
|
1329
|
+
const numericSchema = import_zod6.z.preprocess(preprocessNumericFn, import_zod6.z.any()).refine((val) => {
|
|
1330
|
+
if (!isRequired && val === null) return true;
|
|
1331
|
+
if (val === void 0 || isRequired && val === null) {
|
|
1332
|
+
throw new import_zod6.z.ZodError([{ code: "custom", message: getMessage("required"), path: [] }]);
|
|
1333
|
+
}
|
|
1334
|
+
if (typeof val !== "number" || isNaN(val)) {
|
|
1335
|
+
throw new import_zod6.z.ZodError([{ code: "custom", message: getMessage("numeric"), path: [] }]);
|
|
1336
|
+
}
|
|
1337
|
+
const strVal = String(val);
|
|
1338
|
+
if (!ID_PATTERNS.numeric.test(strVal)) {
|
|
1339
|
+
throw new import_zod6.z.ZodError([{ code: "custom", message: getMessage("numeric"), path: [] }]);
|
|
1340
|
+
}
|
|
1341
|
+
if (minLength !== void 0 && strVal.length < minLength) {
|
|
1342
|
+
throw new import_zod6.z.ZodError([{ code: "custom", message: getMessage("minLength", { minLength }), path: [] }]);
|
|
1343
|
+
}
|
|
1344
|
+
if (maxLength !== void 0 && strVal.length > maxLength) {
|
|
1345
|
+
throw new import_zod6.z.ZodError([{ code: "custom", message: getMessage("maxLength", { maxLength }), path: [] }]);
|
|
1346
|
+
}
|
|
1347
|
+
return true;
|
|
1348
|
+
});
|
|
1349
|
+
return numericSchema;
|
|
1350
|
+
}
|
|
1351
|
+
const baseSchema = isRequired ? import_zod6.z.preprocess(preprocessStringFn, import_zod6.z.string()) : import_zod6.z.preprocess(preprocessStringFn, import_zod6.z.string().nullable());
|
|
1333
1352
|
const schema = baseSchema.refine((val) => {
|
|
1334
1353
|
if (val === null) return true;
|
|
1335
1354
|
if (isRequired && (val === "" || val === "null" || val === "undefined")) {
|
|
@@ -1355,7 +1374,7 @@ function id(required, options) {
|
|
|
1355
1374
|
const typeNames = allowedTypes.join(", ");
|
|
1356
1375
|
throw new import_zod6.z.ZodError([{ code: "custom", message: getMessage("invalid") + ` (allowed types: ${typeNames})`, path: [] }]);
|
|
1357
1376
|
}
|
|
1358
|
-
} else if (type !== "auto") {
|
|
1377
|
+
} else if (type && type !== "auto") {
|
|
1359
1378
|
isValidId = validateIdType(val, type);
|
|
1360
1379
|
if (!isValidId) {
|
|
1361
1380
|
throw new import_zod6.z.ZodError([{ code: "custom", message: getMessage(type) || getMessage("invalid"), path: [] }]);
|
|
@@ -1366,7 +1385,7 @@ function id(required, options) {
|
|
|
1366
1385
|
throw new import_zod6.z.ZodError([{ code: "custom", message: getMessage("invalid"), path: [] }]);
|
|
1367
1386
|
}
|
|
1368
1387
|
}
|
|
1369
|
-
} else if (val !== null && hasContentValidations && type !== "auto" && !customRegex) {
|
|
1388
|
+
} else if (val !== null && hasContentValidations && type && type !== "auto" && !customRegex) {
|
|
1370
1389
|
if (allowedTypes && allowedTypes.length > 0) {
|
|
1371
1390
|
const isValidType = allowedTypes.some((allowedType) => validateIdType(val, allowedType));
|
|
1372
1391
|
if (!isValidType) {
|
package/dist/index.d.cts
CHANGED
|
@@ -978,10 +978,11 @@ type IdType = "numeric" | "uuid" | "objectId" | "nanoid" | "snowflake" | "cuid"
|
|
|
978
978
|
* Configuration options for ID validation
|
|
979
979
|
*
|
|
980
980
|
* @template IsRequired - Whether the field is required (affects return type)
|
|
981
|
+
* @template Type - The ID type being validated
|
|
981
982
|
*
|
|
982
983
|
* @interface IdOptions
|
|
983
984
|
* @property {IsRequired} [required=true] - Whether the field is required
|
|
984
|
-
* @property {
|
|
985
|
+
* @property {Type} [type="auto"] - Expected ID type or auto-detection
|
|
985
986
|
* @property {number} [minLength] - Minimum length of ID
|
|
986
987
|
* @property {number} [maxLength] - Maximum length of ID
|
|
987
988
|
* @property {IdType[]} [allowedTypes] - Multiple allowed ID types (overrides type)
|
|
@@ -992,11 +993,11 @@ type IdType = "numeric" | "uuid" | "objectId" | "nanoid" | "snowflake" | "cuid"
|
|
|
992
993
|
* @property {string} [endsWith] - String that ID must end with
|
|
993
994
|
* @property {boolean} [caseSensitive=true] - Whether validation is case-sensitive
|
|
994
995
|
* @property {Function} [transform] - Custom transformation function for ID
|
|
995
|
-
* @property {
|
|
996
|
+
* @property {any} [defaultValue] - Default value when input is empty (string for string types, number for numeric)
|
|
996
997
|
* @property {Record<Locale, IdMessages>} [i18n] - Custom error messages for different locales
|
|
997
998
|
*/
|
|
998
|
-
type IdOptions<
|
|
999
|
-
type?:
|
|
999
|
+
type IdOptions<Type extends IdType | undefined = undefined> = {
|
|
1000
|
+
type?: Type;
|
|
1000
1001
|
minLength?: number;
|
|
1001
1002
|
maxLength?: number;
|
|
1002
1003
|
allowedTypes?: IdType[];
|
|
@@ -1007,17 +1008,20 @@ type IdOptions<IsRequired extends boolean = true> = {
|
|
|
1007
1008
|
endsWith?: string;
|
|
1008
1009
|
caseSensitive?: boolean;
|
|
1009
1010
|
transform?: (value: string) => string;
|
|
1010
|
-
defaultValue?:
|
|
1011
|
+
defaultValue?: any;
|
|
1011
1012
|
i18n?: Record<Locale, IdMessages>;
|
|
1012
1013
|
};
|
|
1013
1014
|
/**
|
|
1014
|
-
* Type alias for ID validation schema based on required flag
|
|
1015
|
+
* Type alias for ID validation schema based on required flag and ID type
|
|
1015
1016
|
*
|
|
1016
1017
|
* @template IsRequired - Whether the field is required
|
|
1018
|
+
* @template IdType - The ID type being validated
|
|
1017
1019
|
* @typedef IdSchema
|
|
1018
|
-
* @description Returns
|
|
1020
|
+
* @description Returns appropriate Zod type based on required flag and ID type:
|
|
1021
|
+
* - numeric type: ZodNumber or ZodNullable<ZodNumber>
|
|
1022
|
+
* - other types: ZodString or ZodNullable<ZodString>
|
|
1019
1023
|
*/
|
|
1020
|
-
type IdSchema<IsRequired extends boolean> = IsRequired extends true ? ZodString : ZodNullable<ZodString>;
|
|
1024
|
+
type IdSchema<IsRequired extends boolean, Type extends IdType | undefined = undefined> = Type extends "numeric" ? IsRequired extends true ? ZodNumber : ZodNullable<ZodNumber> : IsRequired extends true ? ZodString : ZodNullable<ZodString>;
|
|
1021
1025
|
/**
|
|
1022
1026
|
* Regular expression patterns for different ID formats
|
|
1023
1027
|
*
|
|
@@ -1079,9 +1083,9 @@ declare const validateIdType: (value: string, type: IdType) => boolean;
|
|
|
1079
1083
|
* Creates a Zod schema for ID validation with comprehensive format support
|
|
1080
1084
|
*
|
|
1081
1085
|
* @template IsRequired - Whether the field is required (affects return type)
|
|
1086
|
+
* @template Type - The ID type being validated (affects return type for numeric)
|
|
1082
1087
|
* @param {IsRequired} [required=false] - Whether the field is required
|
|
1083
|
-
* @
|
|
1084
|
-
* @returns {IdSchema<IsRequired>} Zod schema for ID validation
|
|
1088
|
+
* @returns {IdSchema<IsRequired, Type>} Zod schema for ID validation
|
|
1085
1089
|
*
|
|
1086
1090
|
* @description
|
|
1087
1091
|
* Creates a comprehensive ID validator with support for multiple ID formats,
|
|
@@ -1149,7 +1153,16 @@ declare const validateIdType: (value: string, type: IdType) => boolean;
|
|
|
1149
1153
|
* @see {@link detectIdType} for auto-detection logic
|
|
1150
1154
|
* @see {@link validateIdType} for type-specific validation
|
|
1151
1155
|
*/
|
|
1152
|
-
declare function id<IsRequired extends boolean = false>(required?: IsRequired
|
|
1156
|
+
declare function id<IsRequired extends boolean = false>(required?: IsRequired): IdSchema<IsRequired, undefined>;
|
|
1157
|
+
declare function id<IsRequired extends boolean = false>(required: IsRequired, options: Omit<IdOptions<"numeric">, "required"> & {
|
|
1158
|
+
type: "numeric";
|
|
1159
|
+
}): IdSchema<IsRequired, "numeric">;
|
|
1160
|
+
declare function id<IsRequired extends boolean = false, Type extends Exclude<IdType, "numeric"> = Exclude<IdType, "numeric">>(required: IsRequired, options: Omit<IdOptions<Type>, "required"> & {
|
|
1161
|
+
type: Type;
|
|
1162
|
+
}): IdSchema<IsRequired, Type>;
|
|
1163
|
+
declare function id<IsRequired extends boolean = false>(required: IsRequired, options: Omit<IdOptions, "required"> & {
|
|
1164
|
+
type?: never;
|
|
1165
|
+
}): IdSchema<IsRequired, undefined>;
|
|
1153
1166
|
|
|
1154
1167
|
/**
|
|
1155
1168
|
* @fileoverview Number validator for Zod Kit
|
package/dist/index.d.ts
CHANGED
|
@@ -978,10 +978,11 @@ type IdType = "numeric" | "uuid" | "objectId" | "nanoid" | "snowflake" | "cuid"
|
|
|
978
978
|
* Configuration options for ID validation
|
|
979
979
|
*
|
|
980
980
|
* @template IsRequired - Whether the field is required (affects return type)
|
|
981
|
+
* @template Type - The ID type being validated
|
|
981
982
|
*
|
|
982
983
|
* @interface IdOptions
|
|
983
984
|
* @property {IsRequired} [required=true] - Whether the field is required
|
|
984
|
-
* @property {
|
|
985
|
+
* @property {Type} [type="auto"] - Expected ID type or auto-detection
|
|
985
986
|
* @property {number} [minLength] - Minimum length of ID
|
|
986
987
|
* @property {number} [maxLength] - Maximum length of ID
|
|
987
988
|
* @property {IdType[]} [allowedTypes] - Multiple allowed ID types (overrides type)
|
|
@@ -992,11 +993,11 @@ type IdType = "numeric" | "uuid" | "objectId" | "nanoid" | "snowflake" | "cuid"
|
|
|
992
993
|
* @property {string} [endsWith] - String that ID must end with
|
|
993
994
|
* @property {boolean} [caseSensitive=true] - Whether validation is case-sensitive
|
|
994
995
|
* @property {Function} [transform] - Custom transformation function for ID
|
|
995
|
-
* @property {
|
|
996
|
+
* @property {any} [defaultValue] - Default value when input is empty (string for string types, number for numeric)
|
|
996
997
|
* @property {Record<Locale, IdMessages>} [i18n] - Custom error messages for different locales
|
|
997
998
|
*/
|
|
998
|
-
type IdOptions<
|
|
999
|
-
type?:
|
|
999
|
+
type IdOptions<Type extends IdType | undefined = undefined> = {
|
|
1000
|
+
type?: Type;
|
|
1000
1001
|
minLength?: number;
|
|
1001
1002
|
maxLength?: number;
|
|
1002
1003
|
allowedTypes?: IdType[];
|
|
@@ -1007,17 +1008,20 @@ type IdOptions<IsRequired extends boolean = true> = {
|
|
|
1007
1008
|
endsWith?: string;
|
|
1008
1009
|
caseSensitive?: boolean;
|
|
1009
1010
|
transform?: (value: string) => string;
|
|
1010
|
-
defaultValue?:
|
|
1011
|
+
defaultValue?: any;
|
|
1011
1012
|
i18n?: Record<Locale, IdMessages>;
|
|
1012
1013
|
};
|
|
1013
1014
|
/**
|
|
1014
|
-
* Type alias for ID validation schema based on required flag
|
|
1015
|
+
* Type alias for ID validation schema based on required flag and ID type
|
|
1015
1016
|
*
|
|
1016
1017
|
* @template IsRequired - Whether the field is required
|
|
1018
|
+
* @template IdType - The ID type being validated
|
|
1017
1019
|
* @typedef IdSchema
|
|
1018
|
-
* @description Returns
|
|
1020
|
+
* @description Returns appropriate Zod type based on required flag and ID type:
|
|
1021
|
+
* - numeric type: ZodNumber or ZodNullable<ZodNumber>
|
|
1022
|
+
* - other types: ZodString or ZodNullable<ZodString>
|
|
1019
1023
|
*/
|
|
1020
|
-
type IdSchema<IsRequired extends boolean> = IsRequired extends true ? ZodString : ZodNullable<ZodString>;
|
|
1024
|
+
type IdSchema<IsRequired extends boolean, Type extends IdType | undefined = undefined> = Type extends "numeric" ? IsRequired extends true ? ZodNumber : ZodNullable<ZodNumber> : IsRequired extends true ? ZodString : ZodNullable<ZodString>;
|
|
1021
1025
|
/**
|
|
1022
1026
|
* Regular expression patterns for different ID formats
|
|
1023
1027
|
*
|
|
@@ -1079,9 +1083,9 @@ declare const validateIdType: (value: string, type: IdType) => boolean;
|
|
|
1079
1083
|
* Creates a Zod schema for ID validation with comprehensive format support
|
|
1080
1084
|
*
|
|
1081
1085
|
* @template IsRequired - Whether the field is required (affects return type)
|
|
1086
|
+
* @template Type - The ID type being validated (affects return type for numeric)
|
|
1082
1087
|
* @param {IsRequired} [required=false] - Whether the field is required
|
|
1083
|
-
* @
|
|
1084
|
-
* @returns {IdSchema<IsRequired>} Zod schema for ID validation
|
|
1088
|
+
* @returns {IdSchema<IsRequired, Type>} Zod schema for ID validation
|
|
1085
1089
|
*
|
|
1086
1090
|
* @description
|
|
1087
1091
|
* Creates a comprehensive ID validator with support for multiple ID formats,
|
|
@@ -1149,7 +1153,16 @@ declare const validateIdType: (value: string, type: IdType) => boolean;
|
|
|
1149
1153
|
* @see {@link detectIdType} for auto-detection logic
|
|
1150
1154
|
* @see {@link validateIdType} for type-specific validation
|
|
1151
1155
|
*/
|
|
1152
|
-
declare function id<IsRequired extends boolean = false>(required?: IsRequired
|
|
1156
|
+
declare function id<IsRequired extends boolean = false>(required?: IsRequired): IdSchema<IsRequired, undefined>;
|
|
1157
|
+
declare function id<IsRequired extends boolean = false>(required: IsRequired, options: Omit<IdOptions<"numeric">, "required"> & {
|
|
1158
|
+
type: "numeric";
|
|
1159
|
+
}): IdSchema<IsRequired, "numeric">;
|
|
1160
|
+
declare function id<IsRequired extends boolean = false, Type extends Exclude<IdType, "numeric"> = Exclude<IdType, "numeric">>(required: IsRequired, options: Omit<IdOptions<Type>, "required"> & {
|
|
1161
|
+
type: Type;
|
|
1162
|
+
}): IdSchema<IsRequired, Type>;
|
|
1163
|
+
declare function id<IsRequired extends boolean = false>(required: IsRequired, options: Omit<IdOptions, "required"> & {
|
|
1164
|
+
type?: never;
|
|
1165
|
+
}): IdSchema<IsRequired, undefined>;
|
|
1153
1166
|
|
|
1154
1167
|
/**
|
|
1155
1168
|
* @fileoverview Number validator for Zod Kit
|
package/dist/index.js
CHANGED
|
@@ -1213,35 +1213,31 @@ var validateIdType = (value, type) => {
|
|
|
1213
1213
|
return pattern ? pattern.test(value) : false;
|
|
1214
1214
|
};
|
|
1215
1215
|
function id(required, options) {
|
|
1216
|
-
const {
|
|
1217
|
-
type = "auto",
|
|
1218
|
-
minLength,
|
|
1219
|
-
maxLength,
|
|
1220
|
-
allowedTypes,
|
|
1221
|
-
customRegex,
|
|
1222
|
-
includes,
|
|
1223
|
-
excludes,
|
|
1224
|
-
startsWith,
|
|
1225
|
-
endsWith,
|
|
1226
|
-
caseSensitive = true,
|
|
1227
|
-
transform,
|
|
1228
|
-
defaultValue,
|
|
1229
|
-
i18n
|
|
1230
|
-
} = options ?? {};
|
|
1216
|
+
const { type = "auto", minLength, maxLength, allowedTypes, customRegex, includes, excludes, startsWith, endsWith, caseSensitive = true, transform, defaultValue, i18n } = options ?? {};
|
|
1231
1217
|
const isRequired = required ?? false;
|
|
1232
|
-
const
|
|
1218
|
+
const isNumericType = type === "numeric";
|
|
1219
|
+
const actualDefaultValue = defaultValue !== void 0 ? defaultValue : isRequired ? isNumericType ? NaN : "" : null;
|
|
1233
1220
|
const getMessage = (key, params) => {
|
|
1234
1221
|
if (i18n) {
|
|
1235
1222
|
const currentLocale2 = getLocale();
|
|
1236
1223
|
const customMessages = i18n[currentLocale2];
|
|
1237
1224
|
if (customMessages && customMessages[key]) {
|
|
1238
1225
|
const template = customMessages[key];
|
|
1239
|
-
return template.replace(/\$\{(\w+)}/g, (
|
|
1226
|
+
return template.replace(/\$\{(\w+)}/g, (_match, k) => params?.[k] ?? "");
|
|
1240
1227
|
}
|
|
1241
1228
|
}
|
|
1242
1229
|
return t(`common.id.${key}`, params);
|
|
1243
1230
|
};
|
|
1244
|
-
const
|
|
1231
|
+
const preprocessNumericFn = (val) => {
|
|
1232
|
+
if (val === "" || val === null || val === void 0) {
|
|
1233
|
+
if (isRequired && defaultValue === void 0) {
|
|
1234
|
+
return void 0;
|
|
1235
|
+
}
|
|
1236
|
+
return actualDefaultValue;
|
|
1237
|
+
}
|
|
1238
|
+
return Number(val);
|
|
1239
|
+
};
|
|
1240
|
+
const preprocessStringFn = (val) => {
|
|
1245
1241
|
if (val === "" || val === null || val === void 0) {
|
|
1246
1242
|
return actualDefaultValue;
|
|
1247
1243
|
}
|
|
@@ -1251,7 +1247,30 @@ function id(required, options) {
|
|
|
1251
1247
|
}
|
|
1252
1248
|
return processed;
|
|
1253
1249
|
};
|
|
1254
|
-
|
|
1250
|
+
if (isNumericType) {
|
|
1251
|
+
const numericSchema = z6.preprocess(preprocessNumericFn, z6.any()).refine((val) => {
|
|
1252
|
+
if (!isRequired && val === null) return true;
|
|
1253
|
+
if (val === void 0 || isRequired && val === null) {
|
|
1254
|
+
throw new z6.ZodError([{ code: "custom", message: getMessage("required"), path: [] }]);
|
|
1255
|
+
}
|
|
1256
|
+
if (typeof val !== "number" || isNaN(val)) {
|
|
1257
|
+
throw new z6.ZodError([{ code: "custom", message: getMessage("numeric"), path: [] }]);
|
|
1258
|
+
}
|
|
1259
|
+
const strVal = String(val);
|
|
1260
|
+
if (!ID_PATTERNS.numeric.test(strVal)) {
|
|
1261
|
+
throw new z6.ZodError([{ code: "custom", message: getMessage("numeric"), path: [] }]);
|
|
1262
|
+
}
|
|
1263
|
+
if (minLength !== void 0 && strVal.length < minLength) {
|
|
1264
|
+
throw new z6.ZodError([{ code: "custom", message: getMessage("minLength", { minLength }), path: [] }]);
|
|
1265
|
+
}
|
|
1266
|
+
if (maxLength !== void 0 && strVal.length > maxLength) {
|
|
1267
|
+
throw new z6.ZodError([{ code: "custom", message: getMessage("maxLength", { maxLength }), path: [] }]);
|
|
1268
|
+
}
|
|
1269
|
+
return true;
|
|
1270
|
+
});
|
|
1271
|
+
return numericSchema;
|
|
1272
|
+
}
|
|
1273
|
+
const baseSchema = isRequired ? z6.preprocess(preprocessStringFn, z6.string()) : z6.preprocess(preprocessStringFn, z6.string().nullable());
|
|
1255
1274
|
const schema = baseSchema.refine((val) => {
|
|
1256
1275
|
if (val === null) return true;
|
|
1257
1276
|
if (isRequired && (val === "" || val === "null" || val === "undefined")) {
|
|
@@ -1277,7 +1296,7 @@ function id(required, options) {
|
|
|
1277
1296
|
const typeNames = allowedTypes.join(", ");
|
|
1278
1297
|
throw new z6.ZodError([{ code: "custom", message: getMessage("invalid") + ` (allowed types: ${typeNames})`, path: [] }]);
|
|
1279
1298
|
}
|
|
1280
|
-
} else if (type !== "auto") {
|
|
1299
|
+
} else if (type && type !== "auto") {
|
|
1281
1300
|
isValidId = validateIdType(val, type);
|
|
1282
1301
|
if (!isValidId) {
|
|
1283
1302
|
throw new z6.ZodError([{ code: "custom", message: getMessage(type) || getMessage("invalid"), path: [] }]);
|
|
@@ -1288,7 +1307,7 @@ function id(required, options) {
|
|
|
1288
1307
|
throw new z6.ZodError([{ code: "custom", message: getMessage("invalid"), path: [] }]);
|
|
1289
1308
|
}
|
|
1290
1309
|
}
|
|
1291
|
-
} else if (val !== null && hasContentValidations && type !== "auto" && !customRegex) {
|
|
1310
|
+
} else if (val !== null && hasContentValidations && type && type !== "auto" && !customRegex) {
|
|
1292
1311
|
if (allowedTypes && allowedTypes.length > 0) {
|
|
1293
1312
|
const isValidType = allowedTypes.some((allowedType) => validateIdType(val, allowedType));
|
|
1294
1313
|
if (!isValidType) {
|
package/package.json
CHANGED
|
@@ -1,12 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hy_ong/zod-kit",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "Zod
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"description": "A comprehensive TypeScript library providing pre-built Zod validation schemas with full internationalization support for common data types and Taiwan-specific formats",
|
|
5
5
|
"keywords": [
|
|
6
|
-
"hy_ong",
|
|
7
6
|
"zod",
|
|
8
|
-
"
|
|
9
|
-
"
|
|
7
|
+
"validation",
|
|
8
|
+
"schema",
|
|
9
|
+
"typescript",
|
|
10
|
+
"taiwan",
|
|
11
|
+
"i18n",
|
|
12
|
+
"form-validation",
|
|
13
|
+
"email",
|
|
14
|
+
"password",
|
|
15
|
+
"phone",
|
|
16
|
+
"postal-code",
|
|
17
|
+
"national-id",
|
|
18
|
+
"business-id"
|
|
10
19
|
],
|
|
11
20
|
"homepage": "https://github.com/hy-ong/zod-kit#readme",
|
|
12
21
|
"bugs": {
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* @version 0.0.5
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import { z, ZodNullable, ZodString } from "zod"
|
|
11
|
+
import { z, ZodNullable, ZodString, ZodNumber } from "zod"
|
|
12
12
|
import { t } from "../../i18n"
|
|
13
13
|
import { getLocale, type Locale } from "../../config"
|
|
14
14
|
|
|
@@ -85,10 +85,11 @@ export type IdType =
|
|
|
85
85
|
* Configuration options for ID validation
|
|
86
86
|
*
|
|
87
87
|
* @template IsRequired - Whether the field is required (affects return type)
|
|
88
|
+
* @template Type - The ID type being validated
|
|
88
89
|
*
|
|
89
90
|
* @interface IdOptions
|
|
90
91
|
* @property {IsRequired} [required=true] - Whether the field is required
|
|
91
|
-
* @property {
|
|
92
|
+
* @property {Type} [type="auto"] - Expected ID type or auto-detection
|
|
92
93
|
* @property {number} [minLength] - Minimum length of ID
|
|
93
94
|
* @property {number} [maxLength] - Maximum length of ID
|
|
94
95
|
* @property {IdType[]} [allowedTypes] - Multiple allowed ID types (overrides type)
|
|
@@ -99,11 +100,11 @@ export type IdType =
|
|
|
99
100
|
* @property {string} [endsWith] - String that ID must end with
|
|
100
101
|
* @property {boolean} [caseSensitive=true] - Whether validation is case-sensitive
|
|
101
102
|
* @property {Function} [transform] - Custom transformation function for ID
|
|
102
|
-
* @property {
|
|
103
|
+
* @property {any} [defaultValue] - Default value when input is empty (string for string types, number for numeric)
|
|
103
104
|
* @property {Record<Locale, IdMessages>} [i18n] - Custom error messages for different locales
|
|
104
105
|
*/
|
|
105
|
-
export type IdOptions<
|
|
106
|
-
type?:
|
|
106
|
+
export type IdOptions<Type extends IdType | undefined = undefined> = {
|
|
107
|
+
type?: Type
|
|
107
108
|
minLength?: number
|
|
108
109
|
maxLength?: number
|
|
109
110
|
allowedTypes?: IdType[]
|
|
@@ -114,18 +115,27 @@ export type IdOptions<IsRequired extends boolean = true> = {
|
|
|
114
115
|
endsWith?: string
|
|
115
116
|
caseSensitive?: boolean
|
|
116
117
|
transform?: (value: string) => string
|
|
117
|
-
defaultValue?:
|
|
118
|
+
defaultValue?: any // Simplified to avoid complex conditional types
|
|
118
119
|
i18n?: Record<Locale, IdMessages>
|
|
119
120
|
}
|
|
120
121
|
|
|
121
122
|
/**
|
|
122
|
-
* Type alias for ID validation schema based on required flag
|
|
123
|
+
* Type alias for ID validation schema based on required flag and ID type
|
|
123
124
|
*
|
|
124
125
|
* @template IsRequired - Whether the field is required
|
|
126
|
+
* @template IdType - The ID type being validated
|
|
125
127
|
* @typedef IdSchema
|
|
126
|
-
* @description Returns
|
|
128
|
+
* @description Returns appropriate Zod type based on required flag and ID type:
|
|
129
|
+
* - numeric type: ZodNumber or ZodNullable<ZodNumber>
|
|
130
|
+
* - other types: ZodString or ZodNullable<ZodString>
|
|
127
131
|
*/
|
|
128
|
-
export type IdSchema<IsRequired extends boolean
|
|
132
|
+
export type IdSchema<IsRequired extends boolean, Type extends IdType | undefined = undefined> = Type extends "numeric"
|
|
133
|
+
? IsRequired extends true
|
|
134
|
+
? ZodNumber
|
|
135
|
+
: ZodNullable<ZodNumber>
|
|
136
|
+
: IsRequired extends true
|
|
137
|
+
? ZodString
|
|
138
|
+
: ZodNullable<ZodString>
|
|
129
139
|
|
|
130
140
|
/**
|
|
131
141
|
* Regular expression patterns for different ID formats
|
|
@@ -216,9 +226,9 @@ const validateIdType = (value: string, type: IdType): boolean => {
|
|
|
216
226
|
* Creates a Zod schema for ID validation with comprehensive format support
|
|
217
227
|
*
|
|
218
228
|
* @template IsRequired - Whether the field is required (affects return type)
|
|
229
|
+
* @template Type - The ID type being validated (affects return type for numeric)
|
|
219
230
|
* @param {IsRequired} [required=false] - Whether the field is required
|
|
220
|
-
* @
|
|
221
|
-
* @returns {IdSchema<IsRequired>} Zod schema for ID validation
|
|
231
|
+
* @returns {IdSchema<IsRequired, Type>} Zod schema for ID validation
|
|
222
232
|
*
|
|
223
233
|
* @description
|
|
224
234
|
* Creates a comprehensive ID validator with support for multiple ID formats,
|
|
@@ -286,27 +296,31 @@ const validateIdType = (value: string, type: IdType): boolean => {
|
|
|
286
296
|
* @see {@link detectIdType} for auto-detection logic
|
|
287
297
|
* @see {@link validateIdType} for type-specific validation
|
|
288
298
|
*/
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
const
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
const
|
|
299
|
+
// Overload: no options provided
|
|
300
|
+
export function id<IsRequired extends boolean = false>(required?: IsRequired): IdSchema<IsRequired, undefined>
|
|
301
|
+
|
|
302
|
+
// Overload: options with numeric type
|
|
303
|
+
export function id<IsRequired extends boolean = false>(required: IsRequired, options: Omit<IdOptions<"numeric">, "required"> & { type: "numeric" }): IdSchema<IsRequired, "numeric">
|
|
304
|
+
|
|
305
|
+
// Overload: options with other specific type
|
|
306
|
+
export function id<IsRequired extends boolean = false, Type extends Exclude<IdType, "numeric"> = Exclude<IdType, "numeric">>(
|
|
307
|
+
required: IsRequired,
|
|
308
|
+
options: Omit<IdOptions<Type>, "required"> & { type: Type }
|
|
309
|
+
): IdSchema<IsRequired, Type>
|
|
310
|
+
|
|
311
|
+
// Overload: options without type specified
|
|
312
|
+
export function id<IsRequired extends boolean = false>(required: IsRequired, options: Omit<IdOptions, "required"> & { type?: never }): IdSchema<IsRequired, undefined>
|
|
313
|
+
|
|
314
|
+
// Implementation
|
|
315
|
+
export function id<IsRequired extends boolean = false, Type extends IdType | undefined = undefined>(required?: IsRequired, options?: any): any {
|
|
316
|
+
const { type = "auto" as Type, minLength, maxLength, allowedTypes, customRegex, includes, excludes, startsWith, endsWith, caseSensitive = true, transform, defaultValue, i18n } = options ?? {}
|
|
317
|
+
|
|
318
|
+
const isRequired = (required ?? false) as IsRequired
|
|
319
|
+
const isNumericType = type === "numeric"
|
|
320
|
+
|
|
321
|
+
// Set appropriate default value based on required flag and type
|
|
322
|
+
// For required fields, we don't set a default unless explicitly provided
|
|
323
|
+
const actualDefaultValue = defaultValue !== undefined ? defaultValue : isRequired ? (isNumericType ? NaN : "") : null
|
|
310
324
|
|
|
311
325
|
// Helper function to get custom message or fallback to default i18n
|
|
312
326
|
const getMessage = (key: keyof IdMessages, params?: Record<string, any>) => {
|
|
@@ -315,14 +329,30 @@ export function id<IsRequired extends boolean = false>(required?: IsRequired, op
|
|
|
315
329
|
const customMessages = i18n[currentLocale]
|
|
316
330
|
if (customMessages && customMessages[key]) {
|
|
317
331
|
const template = customMessages[key]!
|
|
318
|
-
return template.replace(/\$\{(\w+)}/g, (
|
|
332
|
+
return template.replace(/\$\{(\w+)}/g, (_match: string, k: string) => params?.[k] ?? "")
|
|
319
333
|
}
|
|
320
334
|
}
|
|
321
335
|
return t(`common.id.${key}`, params)
|
|
322
336
|
}
|
|
323
337
|
|
|
324
|
-
// Preprocessing function
|
|
325
|
-
const
|
|
338
|
+
// Preprocessing function for numeric type
|
|
339
|
+
const preprocessNumericFn = (val: unknown) => {
|
|
340
|
+
// Handle empty/null values
|
|
341
|
+
if (val === "" || val === null || val === undefined) {
|
|
342
|
+
// If required and no default, return a special marker that will fail validation
|
|
343
|
+
if (isRequired && defaultValue === undefined) {
|
|
344
|
+
// Return undefined to trigger required error in refine
|
|
345
|
+
return undefined as any
|
|
346
|
+
}
|
|
347
|
+
return actualDefaultValue
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// Try to convert to number and return (even if NaN) so it can be validated by the schema
|
|
351
|
+
return Number(val)
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// Preprocessing function for string type
|
|
355
|
+
const preprocessStringFn = (val: unknown) => {
|
|
326
356
|
if (val === "" || val === null || val === undefined) {
|
|
327
357
|
return actualDefaultValue
|
|
328
358
|
}
|
|
@@ -336,7 +366,44 @@ export function id<IsRequired extends boolean = false>(required?: IsRequired, op
|
|
|
336
366
|
return processed
|
|
337
367
|
}
|
|
338
368
|
|
|
339
|
-
|
|
369
|
+
// Create base schema based on type
|
|
370
|
+
if (isNumericType) {
|
|
371
|
+
// Use z.any() to avoid Zod's built-in type checking, then validate manually
|
|
372
|
+
const numericSchema = z.preprocess(preprocessNumericFn, z.any()).refine((val) => {
|
|
373
|
+
// Allow null for optional fields
|
|
374
|
+
if (!isRequired && val === null) return true
|
|
375
|
+
|
|
376
|
+
// Required check for undefined/null/empty (empty string when required)
|
|
377
|
+
if (val === undefined || (isRequired && val === null)) {
|
|
378
|
+
throw new z.ZodError([{ code: "custom", message: getMessage("required"), path: [] }])
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// Numeric validation - check if it's an actual number (not NaN)
|
|
382
|
+
if (typeof val !== "number" || isNaN(val)) {
|
|
383
|
+
throw new z.ZodError([{ code: "custom", message: getMessage("numeric"), path: [] }])
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// Length checks on string representation
|
|
387
|
+
const strVal = String(val)
|
|
388
|
+
if (!ID_PATTERNS.numeric.test(strVal)) {
|
|
389
|
+
throw new z.ZodError([{ code: "custom", message: getMessage("numeric"), path: [] }])
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
if (minLength !== undefined && strVal.length < minLength) {
|
|
393
|
+
throw new z.ZodError([{ code: "custom", message: getMessage("minLength", { minLength }), path: [] }])
|
|
394
|
+
}
|
|
395
|
+
if (maxLength !== undefined && strVal.length > maxLength) {
|
|
396
|
+
throw new z.ZodError([{ code: "custom", message: getMessage("maxLength", { maxLength }), path: [] }])
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
return true
|
|
400
|
+
})
|
|
401
|
+
|
|
402
|
+
return numericSchema as unknown as IdSchema<IsRequired, Type>
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// String-based ID validation
|
|
406
|
+
const baseSchema = isRequired ? z.preprocess(preprocessStringFn, z.string()) : z.preprocess(preprocessStringFn, z.string().nullable())
|
|
340
407
|
|
|
341
408
|
const schema = baseSchema
|
|
342
409
|
.refine((val) => {
|
|
@@ -372,12 +439,12 @@ export function id<IsRequired extends boolean = false>(required?: IsRequired, op
|
|
|
372
439
|
|
|
373
440
|
if (allowedTypes && allowedTypes.length > 0) {
|
|
374
441
|
// Check if ID matches any of the allowed types
|
|
375
|
-
isValidId = allowedTypes.some((allowedType) => validateIdType(val, allowedType))
|
|
442
|
+
isValidId = allowedTypes.some((allowedType: IdType) => validateIdType(val, allowedType))
|
|
376
443
|
if (!isValidId) {
|
|
377
444
|
const typeNames = allowedTypes.join(", ")
|
|
378
445
|
throw new z.ZodError([{ code: "custom", message: getMessage("invalid") + ` (allowed types: ${typeNames})`, path: [] }])
|
|
379
446
|
}
|
|
380
|
-
} else if (type !== "auto") {
|
|
447
|
+
} else if (type && type !== "auto") {
|
|
381
448
|
// Validate specific type
|
|
382
449
|
isValidId = validateIdType(val, type)
|
|
383
450
|
if (!isValidId) {
|
|
@@ -390,10 +457,10 @@ export function id<IsRequired extends boolean = false>(required?: IsRequired, op
|
|
|
390
457
|
throw new z.ZodError([{ code: "custom", message: getMessage("invalid"), path: [] }])
|
|
391
458
|
}
|
|
392
459
|
}
|
|
393
|
-
} else if (val !== null && hasContentValidations && type !== "auto" && !customRegex) {
|
|
460
|
+
} else if (val !== null && hasContentValidations && type && type !== "auto" && !customRegex) {
|
|
394
461
|
// Still validate specific types even with content validations (but not auto)
|
|
395
462
|
if (allowedTypes && allowedTypes.length > 0) {
|
|
396
|
-
const isValidType = allowedTypes.some((allowedType) => validateIdType(val, allowedType))
|
|
463
|
+
const isValidType = allowedTypes.some((allowedType: IdType) => validateIdType(val, allowedType))
|
|
397
464
|
if (!isValidType) {
|
|
398
465
|
const typeNames = allowedTypes.join(", ")
|
|
399
466
|
throw new z.ZodError([{ code: "custom", message: getMessage("invalid") + ` (allowed types: ${typeNames})`, path: [] }])
|
|
@@ -444,7 +511,7 @@ export function id<IsRequired extends boolean = false>(required?: IsRequired, op
|
|
|
444
511
|
return val // preserve the original case for UUID/ObjectId or when case-sensitive
|
|
445
512
|
})
|
|
446
513
|
|
|
447
|
-
return schema as unknown as IdSchema<IsRequired>
|
|
514
|
+
return schema as unknown as IdSchema<IsRequired, Type>
|
|
448
515
|
}
|
|
449
516
|
|
|
450
517
|
/**
|
package/tests/common/id.test.ts
CHANGED
|
@@ -135,8 +135,8 @@ describe.each(locales)("id(true) locale: $locale", ({ locale, messages }) => {
|
|
|
135
135
|
})
|
|
136
136
|
|
|
137
137
|
it("should apply transform function", () => {
|
|
138
|
-
const schema = id(true, { type: "
|
|
139
|
-
expect(schema.parse("
|
|
138
|
+
const schema = id(true, { type: "uuid", transform: (val) => val.toUpperCase() })
|
|
139
|
+
expect(schema.parse("550e8400-e29b-41d4-a716-446655440000")).toBe("550E8400-E29B-41D4-A716-446655440000")
|
|
140
140
|
})
|
|
141
141
|
})
|
|
142
142
|
|
|
@@ -162,7 +162,9 @@ describe.each(locales)("id(true) locale: $locale", ({ locale, messages }) => {
|
|
|
162
162
|
it("should accept valid numeric IDs", () => {
|
|
163
163
|
const schema = id(true, { type: "numeric" })
|
|
164
164
|
validIds.numeric.forEach((validId) => {
|
|
165
|
-
|
|
165
|
+
const result = schema.parse(validId)
|
|
166
|
+
expect(typeof result).toBe("number")
|
|
167
|
+
expect(result).toBe(Number(validId))
|
|
166
168
|
})
|
|
167
169
|
})
|
|
168
170
|
|
|
@@ -461,7 +463,8 @@ describe.each(locales)("id(true) locale: $locale", ({ locale, messages }) => {
|
|
|
461
463
|
expect(schema.parse("")).toBe(null)
|
|
462
464
|
expect(() => schema.parse("ab")).toThrow() // not numeric
|
|
463
465
|
expect(() => schema.parse("12")).toThrow() // too short
|
|
464
|
-
expect(schema.parse("12345")).toBe(
|
|
466
|
+
expect(schema.parse("12345")).toBe(12345) // Returns number for numeric type
|
|
467
|
+
expect(typeof schema.parse("12345")).toBe("number")
|
|
465
468
|
})
|
|
466
469
|
|
|
467
470
|
it("should handle multiple allowed types with constraints", () => {
|
|
@@ -499,4 +502,59 @@ describe.each(locales)("id(true) locale: $locale", ({ locale, messages }) => {
|
|
|
499
502
|
expect(ID_PATTERNS.objectId.test("507f1f77bcf86cd799439011")).toBe(true)
|
|
500
503
|
})
|
|
501
504
|
})
|
|
505
|
+
|
|
506
|
+
describe("numeric type returns number", () => {
|
|
507
|
+
it("should return number type when type is numeric and required is true", () => {
|
|
508
|
+
const schema = id(true, { type: "numeric" })
|
|
509
|
+
const result = schema.parse("123")
|
|
510
|
+
expect(result).toBe(123)
|
|
511
|
+
expect(typeof result).toBe("number")
|
|
512
|
+
})
|
|
513
|
+
|
|
514
|
+
it("should return number | null when type is numeric and required is false", () => {
|
|
515
|
+
const schema = id(false, { type: "numeric" })
|
|
516
|
+
const result = schema.parse("456")
|
|
517
|
+
expect(result).toBe(456)
|
|
518
|
+
expect(typeof result).toBe("number")
|
|
519
|
+
|
|
520
|
+
const nullResult = schema.parse(null)
|
|
521
|
+
expect(nullResult).toBe(null)
|
|
522
|
+
|
|
523
|
+
const emptyResult = schema.parse("")
|
|
524
|
+
expect(emptyResult).toBe(null)
|
|
525
|
+
})
|
|
526
|
+
|
|
527
|
+
it("should use numeric default value for numeric type", () => {
|
|
528
|
+
const schema = id(true, { type: "numeric", defaultValue: 999 })
|
|
529
|
+
const result = schema.parse("")
|
|
530
|
+
expect(result).toBe(999)
|
|
531
|
+
expect(typeof result).toBe("number")
|
|
532
|
+
})
|
|
533
|
+
|
|
534
|
+
it("should validate numeric ID constraints", () => {
|
|
535
|
+
const schema = id(true, { type: "numeric", minLength: 3, maxLength: 5 })
|
|
536
|
+
|
|
537
|
+
expect(schema.parse("123")).toBe(123)
|
|
538
|
+
expect(schema.parse("12345")).toBe(12345)
|
|
539
|
+
|
|
540
|
+
// Too short
|
|
541
|
+
expect(() => schema.parse("12")).toThrow()
|
|
542
|
+
|
|
543
|
+
// Too long
|
|
544
|
+
expect(() => schema.parse("123456")).toThrow()
|
|
545
|
+
|
|
546
|
+
// Not numeric
|
|
547
|
+
expect(() => schema.parse("abc")).toThrow()
|
|
548
|
+
})
|
|
549
|
+
|
|
550
|
+
it("should convert string numbers to number type", () => {
|
|
551
|
+
const schema = id(true, { type: "numeric" })
|
|
552
|
+
|
|
553
|
+
expect(schema.parse("0")).toBe(0)
|
|
554
|
+
expect(schema.parse("999999")).toBe(999999)
|
|
555
|
+
expect(schema.parse(123)).toBe(123)
|
|
556
|
+
|
|
557
|
+
expect(typeof schema.parse("123")).toBe("number")
|
|
558
|
+
})
|
|
559
|
+
})
|
|
502
560
|
})
|