@hy_ong/zod-kit 0.1.1 → 0.1.3
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/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 +1 -1
- package/src/validators/common/date.ts +26 -16
- package/src/validators/common/datetime.ts +46 -28
- package/src/validators/common/email.ts +57 -15
- package/src/validators/common/id.ts +139 -58
- package/src/validators/common/number.ts +80 -43
- package/src/validators/common/password.ts +30 -18
- package/src/validators/common/text.ts +48 -14
- package/src/validators/common/time.ts +34 -22
- package/src/validators/common/url.ts +40 -23
- package/src/validators/taiwan/business-id.ts +19 -19
- package/src/validators/taiwan/fax.ts +29 -28
- package/src/validators/taiwan/mobile.ts +29 -28
- package/src/validators/taiwan/national-id.ts +27 -27
- package/src/validators/taiwan/postal-code.ts +51 -45
- package/src/validators/taiwan/tel.ts +29 -28
- package/tests/common/id.test.ts +62 -4
- package/tests/taiwan/business-id.test.ts +23 -23
- package/tests/taiwan/fax.test.ts +34 -34
- package/tests/taiwan/mobile.test.ts +33 -33
- package/tests/taiwan/national-id.test.ts +32 -32
- package/tests/taiwan/postal-code.test.ts +62 -62
- package/tests/taiwan/tel.test.ts +34 -34
|
@@ -223,52 +223,86 @@ export function text<IsRequired extends boolean = false>(required?: IsRequired,
|
|
|
223
223
|
|
|
224
224
|
const baseSchema = isRequired ? z.preprocess(preprocessFn, z.string()) : z.preprocess(preprocessFn, z.string().nullable())
|
|
225
225
|
|
|
226
|
-
// Single
|
|
227
|
-
const schema = baseSchema.
|
|
228
|
-
if (val === null) return
|
|
226
|
+
// Single superRefine with all validations for better performance
|
|
227
|
+
const schema = baseSchema.superRefine((val, ctx) => {
|
|
228
|
+
if (val === null) return
|
|
229
229
|
|
|
230
230
|
// Required check
|
|
231
231
|
if (isRequired && (val === "" || val === "null" || val === "undefined")) {
|
|
232
|
-
|
|
232
|
+
ctx.addIssue({
|
|
233
|
+
code: "custom",
|
|
234
|
+
message: getMessage("required")
|
|
235
|
+
})
|
|
236
|
+
return
|
|
233
237
|
}
|
|
234
238
|
|
|
235
239
|
// Not empty check (different from required - checks whitespace)
|
|
236
240
|
// For notEmpty, we need to check if the original string (before processing) was only whitespace
|
|
237
241
|
if (notEmpty && val !== null && val.trim() === "") {
|
|
238
|
-
|
|
242
|
+
ctx.addIssue({
|
|
243
|
+
code: "custom",
|
|
244
|
+
message: getMessage("notEmpty")
|
|
245
|
+
})
|
|
246
|
+
return
|
|
239
247
|
}
|
|
240
248
|
|
|
241
249
|
// Length checks
|
|
242
250
|
if (val !== null && minLength !== undefined && val.length < minLength) {
|
|
243
|
-
|
|
251
|
+
ctx.addIssue({
|
|
252
|
+
code: "custom",
|
|
253
|
+
message: getMessage("minLength", { minLength })
|
|
254
|
+
})
|
|
255
|
+
return
|
|
244
256
|
}
|
|
245
257
|
if (val !== null && maxLength !== undefined && val.length > maxLength) {
|
|
246
|
-
|
|
258
|
+
ctx.addIssue({
|
|
259
|
+
code: "custom",
|
|
260
|
+
message: getMessage("maxLength", { maxLength })
|
|
261
|
+
})
|
|
262
|
+
return
|
|
247
263
|
}
|
|
248
264
|
|
|
249
265
|
// String content checks
|
|
250
266
|
if (val !== null && startsWith !== undefined && !val.startsWith(startsWith)) {
|
|
251
|
-
|
|
267
|
+
ctx.addIssue({
|
|
268
|
+
code: "custom",
|
|
269
|
+
message: getMessage("startsWith", { startsWith })
|
|
270
|
+
})
|
|
271
|
+
return
|
|
252
272
|
}
|
|
253
273
|
if (val !== null && endsWith !== undefined && !val.endsWith(endsWith)) {
|
|
254
|
-
|
|
274
|
+
ctx.addIssue({
|
|
275
|
+
code: "custom",
|
|
276
|
+
message: getMessage("endsWith", { endsWith })
|
|
277
|
+
})
|
|
278
|
+
return
|
|
255
279
|
}
|
|
256
280
|
if (val !== null && includes !== undefined && !val.includes(includes)) {
|
|
257
|
-
|
|
281
|
+
ctx.addIssue({
|
|
282
|
+
code: "custom",
|
|
283
|
+
message: getMessage("includes", { includes })
|
|
284
|
+
})
|
|
285
|
+
return
|
|
258
286
|
}
|
|
259
287
|
if (val !== null && excludes !== undefined) {
|
|
260
288
|
const excludeList = Array.isArray(excludes) ? excludes : [excludes]
|
|
261
289
|
for (const exclude of excludeList) {
|
|
262
290
|
if (val.includes(exclude)) {
|
|
263
|
-
|
|
291
|
+
ctx.addIssue({
|
|
292
|
+
code: "custom",
|
|
293
|
+
message: getMessage("excludes", { excludes: exclude })
|
|
294
|
+
})
|
|
295
|
+
return
|
|
264
296
|
}
|
|
265
297
|
}
|
|
266
298
|
}
|
|
267
299
|
if (val !== null && regex !== undefined && !regex.test(val)) {
|
|
268
|
-
|
|
300
|
+
ctx.addIssue({
|
|
301
|
+
code: "custom",
|
|
302
|
+
message: getMessage("invalid", { regex })
|
|
303
|
+
})
|
|
304
|
+
return
|
|
269
305
|
}
|
|
270
|
-
|
|
271
|
-
return true
|
|
272
306
|
})
|
|
273
307
|
|
|
274
308
|
return schema as unknown as TextSchema<IsRequired>
|
|
@@ -465,25 +465,27 @@ export function time<IsRequired extends boolean = false>(required?: IsRequired,
|
|
|
465
465
|
|
|
466
466
|
const baseSchema = isRequired ? z.preprocess(preprocessFn, z.string()) : z.preprocess(preprocessFn, z.string().nullable())
|
|
467
467
|
|
|
468
|
-
const schema = baseSchema.
|
|
469
|
-
if (val === null) return
|
|
468
|
+
const schema = baseSchema.superRefine((val, ctx) => {
|
|
469
|
+
if (val === null) return
|
|
470
470
|
|
|
471
471
|
// Required check
|
|
472
472
|
if (isRequired && (val === "" || val === "null" || val === "undefined")) {
|
|
473
|
-
|
|
473
|
+
ctx.addIssue({ code: "custom", message: getMessage("required") })
|
|
474
|
+
return
|
|
474
475
|
}
|
|
475
476
|
|
|
476
|
-
if (val === null) return
|
|
477
|
-
if (!isRequired && val === "") return
|
|
477
|
+
if (val === null) return
|
|
478
|
+
if (!isRequired && val === "") return
|
|
478
479
|
|
|
479
480
|
// Whitelist check
|
|
480
481
|
if (whitelist && whitelist.length > 0) {
|
|
481
482
|
if (whitelist.includes(val)) {
|
|
482
|
-
return
|
|
483
|
+
return
|
|
483
484
|
}
|
|
484
485
|
// If whitelistOnly is true, reject values not in whitelist
|
|
485
486
|
if (whitelistOnly) {
|
|
486
|
-
|
|
487
|
+
ctx.addIssue({ code: "custom", message: getMessage("notInWhitelist") })
|
|
488
|
+
return
|
|
487
489
|
}
|
|
488
490
|
// Otherwise, continue with normal validation
|
|
489
491
|
}
|
|
@@ -491,60 +493,69 @@ export function time<IsRequired extends boolean = false>(required?: IsRequired,
|
|
|
491
493
|
// Custom regex validation (overrides format validation)
|
|
492
494
|
if (regex) {
|
|
493
495
|
if (!regex.test(val)) {
|
|
494
|
-
|
|
496
|
+
ctx.addIssue({ code: "custom", message: getMessage("customRegex") })
|
|
497
|
+
return
|
|
495
498
|
}
|
|
496
499
|
} else {
|
|
497
500
|
// Time format validation (only if no regex is provided)
|
|
498
501
|
if (!validateTimeFormat(val, format)) {
|
|
499
|
-
|
|
502
|
+
ctx.addIssue({ code: "custom", message: getMessage("format", { format }) })
|
|
503
|
+
return
|
|
500
504
|
}
|
|
501
505
|
}
|
|
502
506
|
|
|
503
507
|
// String content checks
|
|
504
508
|
if (includes && !val.includes(includes)) {
|
|
505
|
-
|
|
509
|
+
ctx.addIssue({ code: "custom", message: getMessage("includes", { includes }) })
|
|
510
|
+
return
|
|
506
511
|
}
|
|
507
512
|
|
|
508
513
|
if (excludes) {
|
|
509
514
|
const excludeList = Array.isArray(excludes) ? excludes : [excludes]
|
|
510
515
|
for (const exclude of excludeList) {
|
|
511
516
|
if (val.includes(exclude)) {
|
|
512
|
-
|
|
517
|
+
ctx.addIssue({ code: "custom", message: getMessage("excludes", { excludes: exclude }) })
|
|
518
|
+
return
|
|
513
519
|
}
|
|
514
520
|
}
|
|
515
521
|
}
|
|
516
522
|
|
|
517
523
|
// Skip time parsing and range validation if using custom regex
|
|
518
524
|
if (regex) {
|
|
519
|
-
return
|
|
525
|
+
return
|
|
520
526
|
}
|
|
521
527
|
|
|
522
528
|
// Parse time for range validation
|
|
523
529
|
const timeMinutes = parseTimeToMinutes(val, format)
|
|
524
530
|
if (timeMinutes === null) {
|
|
525
|
-
|
|
531
|
+
ctx.addIssue({ code: "custom", message: getMessage("invalid") })
|
|
532
|
+
return
|
|
526
533
|
}
|
|
527
534
|
|
|
528
535
|
// Hour validation
|
|
529
536
|
const hour = Math.floor(timeMinutes / 60)
|
|
530
537
|
if (minHour !== undefined && hour < minHour) {
|
|
531
|
-
|
|
538
|
+
ctx.addIssue({ code: "custom", message: getMessage("hour", { minHour, maxHour: maxHour ?? 23 }) })
|
|
539
|
+
return
|
|
532
540
|
}
|
|
533
541
|
if (maxHour !== undefined && hour > maxHour) {
|
|
534
|
-
|
|
542
|
+
ctx.addIssue({ code: "custom", message: getMessage("hour", { minHour: minHour ?? 0, maxHour }) })
|
|
543
|
+
return
|
|
535
544
|
}
|
|
536
545
|
|
|
537
546
|
// Allowed hours check
|
|
538
547
|
if (allowedHours && allowedHours.length > 0) {
|
|
539
548
|
if (!allowedHours.includes(hour)) {
|
|
540
|
-
|
|
549
|
+
ctx.addIssue({ code: "custom", message: getMessage("hour", { allowedHours: allowedHours.join(", ") }) })
|
|
550
|
+
return
|
|
541
551
|
}
|
|
542
552
|
}
|
|
543
553
|
|
|
544
554
|
// Minute step validation
|
|
545
555
|
const minute = timeMinutes % 60
|
|
546
556
|
if (minuteStep !== undefined && minute % minuteStep !== 0) {
|
|
547
|
-
|
|
557
|
+
ctx.addIssue({ code: "custom", message: getMessage("minute", { minuteStep }) })
|
|
558
|
+
return
|
|
548
559
|
}
|
|
549
560
|
|
|
550
561
|
// Second step validation (only for formats with seconds)
|
|
@@ -554,7 +565,8 @@ export function time<IsRequired extends boolean = false>(required?: IsRequired,
|
|
|
554
565
|
if (secondMatch) {
|
|
555
566
|
const seconds = parseInt(secondMatch[1], 10)
|
|
556
567
|
if (seconds % secondStep !== 0) {
|
|
557
|
-
|
|
568
|
+
ctx.addIssue({ code: "custom", message: getMessage("second", { secondStep }) })
|
|
569
|
+
return
|
|
558
570
|
}
|
|
559
571
|
}
|
|
560
572
|
}
|
|
@@ -563,18 +575,18 @@ export function time<IsRequired extends boolean = false>(required?: IsRequired,
|
|
|
563
575
|
if (min) {
|
|
564
576
|
const minMinutes = parseTimeToMinutes(min, format)
|
|
565
577
|
if (minMinutes !== null && timeMinutes < minMinutes) {
|
|
566
|
-
|
|
578
|
+
ctx.addIssue({ code: "custom", message: getMessage("min", { min }) })
|
|
579
|
+
return
|
|
567
580
|
}
|
|
568
581
|
}
|
|
569
582
|
|
|
570
583
|
if (max) {
|
|
571
584
|
const maxMinutes = parseTimeToMinutes(max, format)
|
|
572
585
|
if (maxMinutes !== null && timeMinutes > maxMinutes) {
|
|
573
|
-
|
|
586
|
+
ctx.addIssue({ code: "custom", message: getMessage("max", { max }) })
|
|
587
|
+
return
|
|
574
588
|
}
|
|
575
589
|
}
|
|
576
|
-
|
|
577
|
-
return true
|
|
578
590
|
})
|
|
579
591
|
|
|
580
592
|
return schema as unknown as TimeSchema<IsRequired>
|
|
@@ -253,12 +253,13 @@ export function url<IsRequired extends boolean = false>(required?: IsRequired, o
|
|
|
253
253
|
|
|
254
254
|
const baseSchema = isRequired ? z.preprocess(preprocessFn, z.string()) : z.preprocess(preprocessFn, z.string().nullable())
|
|
255
255
|
|
|
256
|
-
const schema = baseSchema.
|
|
257
|
-
if (val === null) return
|
|
256
|
+
const schema = baseSchema.superRefine((val, ctx) => {
|
|
257
|
+
if (val === null) return
|
|
258
258
|
|
|
259
259
|
// Required check
|
|
260
260
|
if (isRequired && (val === "" || val === "null" || val === "undefined")) {
|
|
261
|
-
|
|
261
|
+
ctx.addIssue({ code: "custom", message: getMessage("required") })
|
|
262
|
+
return
|
|
262
263
|
}
|
|
263
264
|
|
|
264
265
|
// URL format validation
|
|
@@ -266,88 +267,104 @@ export function url<IsRequired extends boolean = false>(required?: IsRequired, o
|
|
|
266
267
|
try {
|
|
267
268
|
urlObj = new URL(val)
|
|
268
269
|
} catch {
|
|
269
|
-
|
|
270
|
+
ctx.addIssue({ code: "custom", message: getMessage("invalid") })
|
|
271
|
+
return
|
|
270
272
|
}
|
|
271
273
|
|
|
272
274
|
// Length checks
|
|
273
275
|
if (val !== null && min !== undefined && val.length < min) {
|
|
274
|
-
|
|
276
|
+
ctx.addIssue({ code: "custom", message: getMessage("min", { min }) })
|
|
277
|
+
return
|
|
275
278
|
}
|
|
276
279
|
if (val !== null && max !== undefined && val.length > max) {
|
|
277
|
-
|
|
280
|
+
ctx.addIssue({ code: "custom", message: getMessage("max", { max }) })
|
|
281
|
+
return
|
|
278
282
|
}
|
|
279
283
|
|
|
280
284
|
// String content checks
|
|
281
285
|
if (val !== null && includes !== undefined && !val.includes(includes)) {
|
|
282
|
-
|
|
286
|
+
ctx.addIssue({ code: "custom", message: getMessage("includes", { includes }) })
|
|
287
|
+
return
|
|
283
288
|
}
|
|
284
289
|
if (val !== null && excludes !== undefined) {
|
|
285
290
|
const excludeList = Array.isArray(excludes) ? excludes : [excludes]
|
|
286
291
|
for (const exclude of excludeList) {
|
|
287
292
|
if (val.includes(exclude)) {
|
|
288
|
-
|
|
293
|
+
ctx.addIssue({ code: "custom", message: getMessage("excludes", { excludes: exclude }) })
|
|
294
|
+
return
|
|
289
295
|
}
|
|
290
296
|
}
|
|
291
297
|
}
|
|
292
298
|
|
|
293
299
|
// Protocol validation
|
|
294
300
|
if (protocols && !protocols.includes(urlObj.protocol.slice(0, -1))) {
|
|
295
|
-
|
|
301
|
+
ctx.addIssue({ code: "custom", message: getMessage("protocol", { protocols: protocols.join(", ") }) })
|
|
302
|
+
return
|
|
296
303
|
}
|
|
297
304
|
|
|
298
305
|
// Domain validation
|
|
299
306
|
const hostname = urlObj.hostname.toLowerCase()
|
|
300
307
|
if (allowedDomains && !allowedDomains.some((domain) => hostname === domain || hostname.endsWith(`.${domain}`))) {
|
|
301
|
-
|
|
308
|
+
ctx.addIssue({ code: "custom", message: getMessage("domain", { domains: allowedDomains.join(", ") }) })
|
|
309
|
+
return
|
|
302
310
|
}
|
|
303
311
|
if (blockedDomains && blockedDomains.some((domain) => hostname === domain || hostname.endsWith(`.${domain}`))) {
|
|
304
312
|
const blockedDomain = blockedDomains.find((domain) => hostname === domain || hostname.endsWith(`.${domain}`))
|
|
305
|
-
|
|
313
|
+
ctx.addIssue({ code: "custom", message: getMessage("domainBlacklist", { domain: blockedDomain }) })
|
|
314
|
+
return
|
|
306
315
|
}
|
|
307
316
|
|
|
308
317
|
// Port validation
|
|
309
318
|
const port = urlObj.port ? parseInt(urlObj.port) : urlObj.protocol === "https:" ? 443 : 80
|
|
310
319
|
if (allowedPorts && !allowedPorts.includes(port)) {
|
|
311
|
-
|
|
320
|
+
ctx.addIssue({ code: "custom", message: getMessage("port", { ports: allowedPorts.join(", ") }) })
|
|
321
|
+
return
|
|
312
322
|
}
|
|
313
323
|
if (blockedPorts && blockedPorts.includes(port)) {
|
|
314
|
-
|
|
324
|
+
ctx.addIssue({ code: "custom", message: getMessage("port", { port }) })
|
|
325
|
+
return
|
|
315
326
|
}
|
|
316
327
|
|
|
317
328
|
// Path validation
|
|
318
329
|
if (pathStartsWith && !urlObj.pathname.startsWith(pathStartsWith)) {
|
|
319
|
-
|
|
330
|
+
ctx.addIssue({ code: "custom", message: getMessage("pathStartsWith", { path: pathStartsWith }) })
|
|
331
|
+
return
|
|
320
332
|
}
|
|
321
333
|
if (pathEndsWith && !urlObj.pathname.endsWith(pathEndsWith)) {
|
|
322
|
-
|
|
334
|
+
ctx.addIssue({ code: "custom", message: getMessage("pathEndsWith", { path: pathEndsWith }) })
|
|
335
|
+
return
|
|
323
336
|
}
|
|
324
337
|
|
|
325
338
|
// Query validation
|
|
326
339
|
if (mustHaveQuery && !urlObj.search) {
|
|
327
|
-
|
|
340
|
+
ctx.addIssue({ code: "custom", message: getMessage("hasQuery") })
|
|
341
|
+
return
|
|
328
342
|
}
|
|
329
343
|
if (mustNotHaveQuery && urlObj.search) {
|
|
330
|
-
|
|
344
|
+
ctx.addIssue({ code: "custom", message: getMessage("noQuery") })
|
|
345
|
+
return
|
|
331
346
|
}
|
|
332
347
|
|
|
333
348
|
// Fragment validation
|
|
334
349
|
if (mustHaveFragment && !urlObj.hash) {
|
|
335
|
-
|
|
350
|
+
ctx.addIssue({ code: "custom", message: getMessage("hasFragment") })
|
|
351
|
+
return
|
|
336
352
|
}
|
|
337
353
|
if (mustNotHaveFragment && urlObj.hash) {
|
|
338
|
-
|
|
354
|
+
ctx.addIssue({ code: "custom", message: getMessage("noFragment") })
|
|
355
|
+
return
|
|
339
356
|
}
|
|
340
357
|
|
|
341
358
|
// Localhost validation
|
|
342
359
|
const isLocalhost = hostname === "localhost" || hostname === "127.0.0.1" || hostname.startsWith("192.168.") || hostname.startsWith("10.") || hostname.match(/^172\.(1[6-9]|2[0-9]|3[0-1])\./)
|
|
343
360
|
if (blockLocalhost && isLocalhost) {
|
|
344
|
-
|
|
361
|
+
ctx.addIssue({ code: "custom", message: getMessage("noLocalhost") })
|
|
362
|
+
return
|
|
345
363
|
}
|
|
346
364
|
if (!allowLocalhost && isLocalhost) {
|
|
347
|
-
|
|
365
|
+
ctx.addIssue({ code: "custom", message: getMessage("localhost") })
|
|
366
|
+
return
|
|
348
367
|
}
|
|
349
|
-
|
|
350
|
-
return true
|
|
351
368
|
})
|
|
352
369
|
|
|
353
370
|
return schema as unknown as UrlSchema<IsRequired>
|
|
@@ -15,11 +15,11 @@ import { getLocale, type Locale } from "../../config"
|
|
|
15
15
|
/**
|
|
16
16
|
* Type definition for business ID validation error messages
|
|
17
17
|
*
|
|
18
|
-
* @interface
|
|
18
|
+
* @interface TwBusinessIdMessages
|
|
19
19
|
* @property {string} [required] - Message when field is required but empty
|
|
20
20
|
* @property {string} [invalid] - Message when business ID format or checksum is invalid
|
|
21
21
|
*/
|
|
22
|
-
export type
|
|
22
|
+
export type TwBusinessIdMessages = {
|
|
23
23
|
required?: string
|
|
24
24
|
invalid?: string
|
|
25
25
|
}
|
|
@@ -29,26 +29,26 @@ export type BusinessIdMessages = {
|
|
|
29
29
|
*
|
|
30
30
|
* @template IsRequired - Whether the field is required (affects return type)
|
|
31
31
|
*
|
|
32
|
-
* @interface
|
|
32
|
+
* @interface TwBusinessIdOptions
|
|
33
33
|
* @property {IsRequired} [required=true] - Whether the field is required
|
|
34
34
|
* @property {Function} [transform] - Custom transformation function for business ID
|
|
35
35
|
* @property {string | null} [defaultValue] - Default value when input is empty
|
|
36
|
-
* @property {Record<Locale,
|
|
36
|
+
* @property {Record<Locale, TwBusinessIdMessages>} [i18n] - Custom error messages for different locales
|
|
37
37
|
*/
|
|
38
|
-
export type
|
|
38
|
+
export type TwBusinessIdOptions<IsRequired extends boolean = true> = {
|
|
39
39
|
transform?: (value: string) => string
|
|
40
40
|
defaultValue?: IsRequired extends true ? string : string | null
|
|
41
|
-
i18n?: Record<Locale,
|
|
41
|
+
i18n?: Record<Locale, TwBusinessIdMessages>
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
/**
|
|
45
45
|
* Type alias for business ID validation schema based on required flag
|
|
46
46
|
*
|
|
47
47
|
* @template IsRequired - Whether the field is required
|
|
48
|
-
* @typedef
|
|
48
|
+
* @typedef TwBusinessIdSchema
|
|
49
49
|
* @description Returns ZodString if required, ZodNullable<ZodString> if optional
|
|
50
50
|
*/
|
|
51
|
-
export type
|
|
51
|
+
export type TwBusinessIdSchema<IsRequired extends boolean> = IsRequired extends true ? ZodString : ZodNullable<ZodString>
|
|
52
52
|
|
|
53
53
|
/**
|
|
54
54
|
* Validates Taiwan Business Identification Number (統一編號)
|
|
@@ -179,10 +179,10 @@ const validateTaiwanBusinessId = (value: string): boolean => {
|
|
|
179
179
|
* ```
|
|
180
180
|
*
|
|
181
181
|
* @throws {z.ZodError} When validation fails with specific error messages
|
|
182
|
-
* @see {@link
|
|
182
|
+
* @see {@link TwBusinessIdOptions} for all available configuration options
|
|
183
183
|
* @see {@link validateTaiwanBusinessId} for validation logic details
|
|
184
184
|
*/
|
|
185
|
-
export function
|
|
185
|
+
export function twBusinessId<IsRequired extends boolean = false>(required?: IsRequired, options?: Omit<TwBusinessIdOptions<IsRequired>, 'required'>): TwBusinessIdSchema<IsRequired> {
|
|
186
186
|
const {
|
|
187
187
|
transform,
|
|
188
188
|
defaultValue,
|
|
@@ -195,7 +195,7 @@ export function businessId<IsRequired extends boolean = false>(required?: IsRequ
|
|
|
195
195
|
const actualDefaultValue = defaultValue ?? (isRequired ? "" : null)
|
|
196
196
|
|
|
197
197
|
// Helper function to get custom message or fallback to default i18n
|
|
198
|
-
const getMessage = (key: keyof
|
|
198
|
+
const getMessage = (key: keyof TwBusinessIdMessages, params?: Record<string, any>) => {
|
|
199
199
|
if (i18n) {
|
|
200
200
|
const currentLocale = getLocale()
|
|
201
201
|
const customMessages = i18n[currentLocale]
|
|
@@ -229,23 +229,23 @@ export function businessId<IsRequired extends boolean = false>(required?: IsRequ
|
|
|
229
229
|
|
|
230
230
|
const baseSchema = isRequired ? z.preprocess(preprocessFn, z.string()) : z.preprocess(preprocessFn, z.string().nullable())
|
|
231
231
|
|
|
232
|
-
const schema = baseSchema.
|
|
233
|
-
if (val === null) return
|
|
232
|
+
const schema = baseSchema.superRefine((val, ctx) => {
|
|
233
|
+
if (val === null) return
|
|
234
234
|
|
|
235
235
|
// Required check
|
|
236
236
|
if (isRequired && (val === "" || val === "null" || val === "undefined")) {
|
|
237
|
-
|
|
237
|
+
ctx.addIssue({ code: "custom", message: getMessage("required") })
|
|
238
|
+
return
|
|
238
239
|
}
|
|
239
240
|
|
|
240
|
-
if (val === null) return
|
|
241
|
-
if (!isRequired && val === "") return
|
|
241
|
+
if (val === null) return
|
|
242
|
+
if (!isRequired && val === "") return
|
|
242
243
|
|
|
243
244
|
// Taiwan Business ID format validation (8 digits + checksum)
|
|
244
245
|
if (!validateTaiwanBusinessId(val)) {
|
|
245
|
-
|
|
246
|
+
ctx.addIssue({ code: "custom", message: getMessage("invalid") })
|
|
247
|
+
return
|
|
246
248
|
}
|
|
247
|
-
|
|
248
|
-
return true
|
|
249
249
|
})
|
|
250
250
|
|
|
251
251
|
return schema as unknown as BusinessIdSchema<IsRequired>
|