@hy_ong/zod-kit 0.2.0 → 0.2.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/.github/workflows/ci.yml +24 -0
- package/CLAUDE.md +64 -22
- package/dist/chunk-2SWEVDFZ.js +134 -0
- package/dist/chunk-32JI34CV.cjs +146 -0
- package/dist/chunk-42C5OHRK.js +71 -0
- package/dist/chunk-46VAH2BJ.js +160 -0
- package/dist/chunk-5JGTDL3Y.js +87 -0
- package/dist/chunk-5LEXCVLX.js +257 -0
- package/dist/chunk-6AAP4LPF.js +2606 -0
- package/dist/chunk-B4EZYZOK.cjs +215 -0
- package/dist/chunk-COYKBWTI.js +161 -0
- package/dist/chunk-DFJZ3NS2.cjs +151 -0
- package/dist/chunk-EDHT4LPO.js +118 -0
- package/dist/chunk-EGHL277K.cjs +165 -0
- package/dist/chunk-ERH4NIMU.cjs +69 -0
- package/dist/chunk-FM3EZ72O.js +165 -0
- package/dist/chunk-GJIRDBZJ.cjs +90 -0
- package/dist/chunk-H2XTEM4M.js +696 -0
- package/dist/chunk-HMSM6FFA.cjs +181 -0
- package/dist/chunk-HTEHINI7.cjs +177 -0
- package/dist/chunk-JOLSGZGN.cjs +696 -0
- package/dist/chunk-JXY7APBU.js +69 -0
- package/dist/chunk-K2UOY6TB.js +136 -0
- package/dist/chunk-KFOHKTFD.js +61 -0
- package/dist/chunk-L4HSIKTU.cjs +135 -0
- package/dist/chunk-LH7ZB4BK.js +124 -0
- package/dist/chunk-LL4ZWLGO.js +90 -0
- package/dist/chunk-M6MTP3NY.cjs +99 -0
- package/dist/chunk-MHJFYYGV.js +215 -0
- package/dist/chunk-MINMXGW3.js +135 -0
- package/dist/chunk-MM7IL2RG.js +181 -0
- package/dist/chunk-OPQJWHXN.cjs +301 -0
- package/dist/chunk-ORFHDJII.cjs +136 -0
- package/dist/chunk-ORVV4MCF.cjs +87 -0
- package/dist/chunk-QICQ6YEY.js +75 -0
- package/dist/chunk-RKUQREMW.js +127 -0
- package/dist/chunk-RO47DKQG.js +146 -0
- package/dist/chunk-RRPXIRTQ.cjs +257 -0
- package/dist/chunk-RYFG2GKM.cjs +118 -0
- package/dist/chunk-STNHTRG7.cjs +124 -0
- package/dist/chunk-TFGS34VD.cjs +71 -0
- package/dist/chunk-TQXDUMML.cjs +61 -0
- package/dist/chunk-UBK3VCVH.cjs +134 -0
- package/dist/chunk-UCOXAZJF.cjs +2606 -0
- package/dist/chunk-UQZKFAFX.js +130 -0
- package/dist/chunk-VB2KV2ZM.cjs +130 -0
- package/dist/chunk-WABKPFPK.js +151 -0
- package/dist/chunk-WDI4QJMQ.js +177 -0
- package/dist/chunk-YDH3L27K.cjs +127 -0
- package/dist/chunk-YIM3D2AD.js +99 -0
- package/dist/chunk-YPSEIDUR.cjs +160 -0
- package/dist/chunk-ZNJLWJX3.cjs +75 -0
- package/dist/chunk-ZTFCJCPO.cjs +161 -0
- package/dist/chunk-ZXUMK2RR.js +301 -0
- package/dist/common/boolean.cjs +7 -0
- package/dist/common/boolean.d.cts +119 -0
- package/dist/common/boolean.d.ts +119 -0
- package/dist/common/boolean.js +7 -0
- package/dist/common/color.cjs +9 -0
- package/dist/common/color.d.cts +26 -0
- package/dist/common/color.d.ts +26 -0
- package/dist/common/color.js +9 -0
- package/dist/common/coordinate.cjs +11 -0
- package/dist/common/coordinate.d.cts +23 -0
- package/dist/common/coordinate.d.ts +23 -0
- package/dist/common/coordinate.js +11 -0
- package/dist/common/credit-card.cjs +11 -0
- package/dist/common/credit-card.d.cts +22 -0
- package/dist/common/credit-card.d.ts +22 -0
- package/dist/common/credit-card.js +11 -0
- package/dist/common/date.cjs +7 -0
- package/dist/common/date.d.cts +174 -0
- package/dist/common/date.d.ts +174 -0
- package/dist/common/date.js +7 -0
- package/dist/common/datetime.cjs +15 -0
- package/dist/common/datetime.d.cts +301 -0
- package/dist/common/datetime.d.ts +301 -0
- package/dist/common/datetime.js +15 -0
- package/dist/common/email.cjs +7 -0
- package/dist/common/email.d.cts +149 -0
- package/dist/common/email.d.ts +149 -0
- package/dist/common/email.js +7 -0
- package/dist/common/file.cjs +7 -0
- package/dist/common/file.d.cts +178 -0
- package/dist/common/file.d.ts +178 -0
- package/dist/common/file.js +7 -0
- package/dist/common/id.cjs +13 -0
- package/dist/common/id.d.cts +288 -0
- package/dist/common/id.d.ts +288 -0
- package/dist/common/id.js +13 -0
- package/dist/common/ip.cjs +11 -0
- package/dist/common/ip.d.cts +25 -0
- package/dist/common/ip.d.ts +25 -0
- package/dist/common/ip.js +11 -0
- package/dist/common/number.cjs +7 -0
- package/dist/common/number.d.cts +167 -0
- package/dist/common/number.d.ts +167 -0
- package/dist/common/number.js +7 -0
- package/dist/common/password.cjs +7 -0
- package/dist/common/password.d.cts +192 -0
- package/dist/common/password.d.ts +192 -0
- package/dist/common/password.js +7 -0
- package/dist/common/text.cjs +7 -0
- package/dist/common/text.d.cts +156 -0
- package/dist/common/text.d.ts +156 -0
- package/dist/common/text.js +7 -0
- package/dist/common/time.cjs +15 -0
- package/dist/common/time.d.cts +268 -0
- package/dist/common/time.d.ts +268 -0
- package/dist/common/time.js +15 -0
- package/dist/common/url.cjs +7 -0
- package/dist/common/url.d.cts +196 -0
- package/dist/common/url.d.ts +196 -0
- package/dist/common/url.js +7 -0
- package/dist/config-CABSSvAp.d.cts +5 -0
- package/dist/config-CABSSvAp.d.ts +5 -0
- package/dist/index.cjs +180 -5255
- package/dist/index.d.cts +28 -3150
- package/dist/index.d.ts +28 -3150
- package/dist/index.js +135 -5131
- package/dist/taiwan/bank-account.cjs +11 -0
- package/dist/taiwan/bank-account.d.cts +22 -0
- package/dist/taiwan/bank-account.d.ts +22 -0
- package/dist/taiwan/bank-account.js +11 -0
- package/dist/taiwan/business-id.cjs +9 -0
- package/dist/taiwan/business-id.d.cts +133 -0
- package/dist/taiwan/business-id.d.ts +133 -0
- package/dist/taiwan/business-id.js +9 -0
- package/dist/taiwan/fax.cjs +9 -0
- package/dist/taiwan/fax.d.cts +157 -0
- package/dist/taiwan/fax.d.ts +157 -0
- package/dist/taiwan/fax.js +9 -0
- package/dist/taiwan/invoice.cjs +9 -0
- package/dist/taiwan/invoice.d.cts +17 -0
- package/dist/taiwan/invoice.d.ts +17 -0
- package/dist/taiwan/invoice.js +9 -0
- package/dist/taiwan/license-plate.cjs +9 -0
- package/dist/taiwan/license-plate.d.cts +19 -0
- package/dist/taiwan/license-plate.d.ts +19 -0
- package/dist/taiwan/license-plate.js +9 -0
- package/dist/taiwan/mobile.cjs +9 -0
- package/dist/taiwan/mobile.d.cts +146 -0
- package/dist/taiwan/mobile.d.ts +146 -0
- package/dist/taiwan/mobile.js +9 -0
- package/dist/taiwan/national-id.cjs +15 -0
- package/dist/taiwan/national-id.d.cts +214 -0
- package/dist/taiwan/national-id.d.ts +214 -0
- package/dist/taiwan/national-id.js +15 -0
- package/dist/taiwan/passport.cjs +9 -0
- package/dist/taiwan/passport.d.cts +19 -0
- package/dist/taiwan/passport.d.ts +19 -0
- package/dist/taiwan/passport.js +9 -0
- package/dist/taiwan/postal-code.cjs +17 -0
- package/dist/taiwan/postal-code.d.cts +237 -0
- package/dist/taiwan/postal-code.d.ts +237 -0
- package/dist/taiwan/postal-code.js +17 -0
- package/dist/taiwan/tel.cjs +9 -0
- package/dist/taiwan/tel.d.cts +162 -0
- package/dist/taiwan/tel.d.ts +162 -0
- package/dist/taiwan/tel.js +9 -0
- package/package.json +132 -6
- package/src/i18n/locales/en-GB.json +51 -0
- package/src/i18n/locales/en-US.json +52 -1
- package/src/i18n/locales/id-ID.json +51 -0
- package/src/i18n/locales/ja-JP.json +51 -0
- package/src/i18n/locales/ko-KR.json +51 -0
- package/src/i18n/locales/ms-MY.json +51 -0
- package/src/i18n/locales/th-TH.json +51 -0
- package/src/i18n/locales/vi-VN.json +51 -0
- package/src/i18n/locales/zh-CN.json +51 -0
- package/src/i18n/locales/zh-TW.json +51 -0
- package/src/index.ts +10 -2
- package/src/validators/common/color.ts +192 -0
- package/src/validators/common/coordinate.ts +159 -0
- package/src/validators/common/credit-card.ts +134 -0
- package/src/validators/common/id.ts +45 -3
- package/src/validators/common/ip.ts +210 -0
- package/src/validators/taiwan/bank-account.ts +176 -0
- package/src/validators/taiwan/invoice.ts +84 -0
- package/src/validators/taiwan/license-plate.ts +110 -0
- package/src/validators/taiwan/passport.ts +103 -0
- package/tests/common/color.test.ts +587 -0
- package/tests/common/coordinate.test.ts +345 -0
- package/tests/common/credit-card.test.ts +378 -0
- package/tests/common/id.test.ts +68 -3
- package/tests/common/ip.test.ts +419 -0
- package/tests/taiwan/bank-account.test.ts +286 -0
- package/tests/taiwan/invoice.test.ts +227 -0
- package/tests/taiwan/license-plate.test.ts +280 -0
- package/tests/taiwan/passport.test.ts +277 -0
- package/tsup.config.ts +36 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { z, ZodNullable, ZodString } from "zod"
|
|
2
|
+
import { t } from "../../i18n"
|
|
3
|
+
import { getLocale, type Locale } from "../../config"
|
|
4
|
+
|
|
5
|
+
export type PlateType = "car" | "motorcycle" | "any"
|
|
6
|
+
|
|
7
|
+
export type TwLicensePlateMessages = {
|
|
8
|
+
required?: string
|
|
9
|
+
invalid?: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export type TwLicensePlateOptions<IsRequired extends boolean = true> = {
|
|
13
|
+
plateType?: PlateType
|
|
14
|
+
transform?: (value: string) => string
|
|
15
|
+
defaultValue?: IsRequired extends true ? string : string | null
|
|
16
|
+
i18n?: Partial<Record<Locale, Partial<TwLicensePlateMessages>>>
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export type TwLicensePlateSchema<IsRequired extends boolean> = IsRequired extends true ? ZodString : ZodNullable<ZodString>
|
|
20
|
+
|
|
21
|
+
const CAR_PATTERNS = [
|
|
22
|
+
/^[A-Z]{3}\d{4}$/, // ABC-1234 (new)
|
|
23
|
+
/^\d{4}[A-Z]{2}$/, // 1234-AB (new/legacy)
|
|
24
|
+
/^[A-Z]{2}\d{4}$/, // AB-1234 (legacy)
|
|
25
|
+
/^[A-Z]\d{5}$/, // A1-2345 -> A12345 (legacy mixed)
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
const MOTORCYCLE_PATTERNS = [
|
|
29
|
+
/^[A-Z]{3}\d{4}$/, // ABC-1234
|
|
30
|
+
/^\d{3}[A-Z]{3}$/, // 123-ABC
|
|
31
|
+
/^[A-Z]{2}\d{4}$/, // AB-1234 (legacy)
|
|
32
|
+
/^\d{4}[A-Z]{2}$/, // 1234-AB (legacy)
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
export const validateTaiwanLicensePlate = (value: string, plateType: PlateType = "any"): boolean => {
|
|
36
|
+
const patterns: RegExp[] = []
|
|
37
|
+
|
|
38
|
+
if (plateType === "car" || plateType === "any") {
|
|
39
|
+
patterns.push(...CAR_PATTERNS)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (plateType === "motorcycle" || plateType === "any") {
|
|
43
|
+
for (const pattern of MOTORCYCLE_PATTERNS) {
|
|
44
|
+
if (!patterns.some((p) => p.source === pattern.source)) {
|
|
45
|
+
patterns.push(pattern)
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return patterns.some((pattern) => pattern.test(value))
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function twLicensePlate<IsRequired extends boolean = false>(required?: IsRequired, options?: Omit<TwLicensePlateOptions<IsRequired>, "required">): TwLicensePlateSchema<IsRequired> {
|
|
54
|
+
const { plateType = "any", transform, defaultValue, i18n } = options ?? {}
|
|
55
|
+
|
|
56
|
+
const isRequired = required ?? (false as IsRequired)
|
|
57
|
+
|
|
58
|
+
const actualDefaultValue = defaultValue ?? (isRequired ? "" : null)
|
|
59
|
+
|
|
60
|
+
const getMessage = (key: keyof TwLicensePlateMessages, params?: Record<string, any>) => {
|
|
61
|
+
if (i18n) {
|
|
62
|
+
const currentLocale = getLocale()
|
|
63
|
+
const customMessages = i18n[currentLocale]
|
|
64
|
+
if (customMessages && customMessages[key]) {
|
|
65
|
+
const template = customMessages[key]!
|
|
66
|
+
return template.replace(/\$\{(\w+)}/g, (_, k) => params?.[k] ?? "")
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return t(`taiwan.licensePlate.${key}`, params)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const preprocessFn = (val: unknown) => {
|
|
73
|
+
if (val === "" || val === null || val === undefined) {
|
|
74
|
+
return actualDefaultValue
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
let processed = String(val).trim().toUpperCase().replace(/[-\s]/g, "")
|
|
78
|
+
|
|
79
|
+
if (processed === "" && !required) {
|
|
80
|
+
return null
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (transform) {
|
|
84
|
+
processed = transform(processed)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return processed
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const baseSchema = isRequired ? z.preprocess(preprocessFn, z.string()) : z.preprocess(preprocessFn, z.string().nullable())
|
|
91
|
+
|
|
92
|
+
const schema = baseSchema.superRefine((val, ctx) => {
|
|
93
|
+
if (val === null) return
|
|
94
|
+
|
|
95
|
+
if (isRequired && (val === "" || val === "null" || val === "undefined")) {
|
|
96
|
+
ctx.addIssue({ code: "custom", message: getMessage("required") })
|
|
97
|
+
return
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (val === null) return
|
|
101
|
+
if (!isRequired && val === "") return
|
|
102
|
+
|
|
103
|
+
if (!validateTaiwanLicensePlate(val, plateType)) {
|
|
104
|
+
ctx.addIssue({ code: "custom", message: getMessage("invalid") })
|
|
105
|
+
return
|
|
106
|
+
}
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
return schema as unknown as TwLicensePlateSchema<IsRequired>
|
|
110
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { z, ZodNullable, ZodString } from "zod"
|
|
2
|
+
import { t } from "../../i18n"
|
|
3
|
+
import { getLocale, type Locale } from "../../config"
|
|
4
|
+
|
|
5
|
+
export type PassportType = "ordinary" | "diplomatic" | "official" | "travel" | "any"
|
|
6
|
+
|
|
7
|
+
export type TwPassportMessages = {
|
|
8
|
+
required?: string
|
|
9
|
+
invalid?: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export type TwPassportOptions<IsRequired extends boolean = true> = {
|
|
13
|
+
passportType?: PassportType
|
|
14
|
+
transform?: (value: string) => string
|
|
15
|
+
defaultValue?: IsRequired extends true ? string : string | null
|
|
16
|
+
i18n?: Partial<Record<Locale, Partial<TwPassportMessages>>>
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export type TwPassportSchema<IsRequired extends boolean> = IsRequired extends true ? ZodString : ZodNullable<ZodString>
|
|
20
|
+
|
|
21
|
+
const PASSPORT_TYPE_DIGIT: Record<Exclude<PassportType, "any">, string> = {
|
|
22
|
+
diplomatic: "0",
|
|
23
|
+
official: "1",
|
|
24
|
+
ordinary: "2",
|
|
25
|
+
travel: "3",
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const validateTaiwanPassport = (value: string): boolean => {
|
|
29
|
+
if (!/^\d{9}$/.test(value)) {
|
|
30
|
+
return false
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const firstDigit = parseInt(value[0], 10)
|
|
34
|
+
return firstDigit >= 0 && firstDigit <= 3
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function twPassport<IsRequired extends boolean = false>(required?: IsRequired, options?: Omit<TwPassportOptions<IsRequired>, "required">): TwPassportSchema<IsRequired> {
|
|
38
|
+
const { passportType = "any", transform, defaultValue, i18n } = options ?? {}
|
|
39
|
+
|
|
40
|
+
const isRequired = required ?? (false as IsRequired)
|
|
41
|
+
|
|
42
|
+
const actualDefaultValue = defaultValue ?? (isRequired ? "" : null)
|
|
43
|
+
|
|
44
|
+
const getMessage = (key: keyof TwPassportMessages, params?: Record<string, any>) => {
|
|
45
|
+
if (i18n) {
|
|
46
|
+
const currentLocale = getLocale()
|
|
47
|
+
const customMessages = i18n[currentLocale]
|
|
48
|
+
if (customMessages && customMessages[key]) {
|
|
49
|
+
const template = customMessages[key]!
|
|
50
|
+
return template.replace(/\$\{(\w+)}/g, (_, k) => params?.[k] ?? "")
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return t(`taiwan.passport.${key}`, params)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const preprocessFn = (val: unknown) => {
|
|
57
|
+
if (val === "" || val === null || val === undefined) {
|
|
58
|
+
return actualDefaultValue
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
let processed = String(val).trim()
|
|
62
|
+
|
|
63
|
+
if (processed === "" && !required) {
|
|
64
|
+
return null
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (transform) {
|
|
68
|
+
processed = transform(processed)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return processed
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const baseSchema = isRequired ? z.preprocess(preprocessFn, z.string()) : z.preprocess(preprocessFn, z.string().nullable())
|
|
75
|
+
|
|
76
|
+
const schema = baseSchema.superRefine((val, ctx) => {
|
|
77
|
+
if (val === null) return
|
|
78
|
+
|
|
79
|
+
if (isRequired && (val === "" || val === "null" || val === "undefined")) {
|
|
80
|
+
ctx.addIssue({ code: "custom", message: getMessage("required") })
|
|
81
|
+
return
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (!isRequired && val === "") return
|
|
85
|
+
|
|
86
|
+
if (!validateTaiwanPassport(val)) {
|
|
87
|
+
ctx.addIssue({ code: "custom", message: getMessage("invalid") })
|
|
88
|
+
return
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (passportType !== "any") {
|
|
92
|
+
const expectedDigit = PASSPORT_TYPE_DIGIT[passportType]
|
|
93
|
+
if (val[0] !== expectedDigit) {
|
|
94
|
+
ctx.addIssue({ code: "custom", message: getMessage("invalid") })
|
|
95
|
+
return
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
return schema as unknown as TwPassportSchema<IsRequired>
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export { validateTaiwanPassport }
|