@hy_ong/zod-kit 0.0.2 → 0.0.5
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/.claude/settings.local.json +23 -0
- package/LICENSE +21 -0
- package/README.md +7 -7
- package/debug.js +21 -0
- package/debug.ts +16 -0
- package/dist/index.cjs +1663 -189
- package/dist/index.d.cts +324 -32
- package/dist/index.d.ts +324 -32
- package/dist/index.js +1634 -187
- package/eslint.config.mts +8 -0
- package/package.json +10 -9
- package/src/config.ts +1 -1
- package/src/i18n/locales/en.json +123 -49
- package/src/i18n/locales/zh-TW.json +123 -46
- package/src/index.ts +13 -7
- package/src/validators/common/boolean.ts +97 -0
- package/src/validators/common/date.ts +171 -0
- package/src/validators/common/email.ts +200 -0
- package/src/validators/common/id.ts +259 -0
- package/src/validators/common/number.ts +194 -0
- package/src/validators/common/password.ts +214 -0
- package/src/validators/common/text.ts +151 -0
- package/src/validators/common/url.ts +207 -0
- package/src/validators/taiwan/business-id.ts +140 -0
- package/src/validators/taiwan/fax.ts +182 -0
- package/src/validators/taiwan/mobile.ts +110 -0
- package/src/validators/taiwan/national-id.ts +208 -0
- package/src/validators/taiwan/tel.ts +182 -0
- package/tests/common/boolean.test.ts +340 -92
- package/tests/common/date.test.ts +458 -0
- package/tests/common/email.test.ts +232 -60
- package/tests/common/id.test.ts +535 -0
- package/tests/common/number.test.ts +230 -60
- package/tests/common/password.test.ts +281 -54
- package/tests/common/text.test.ts +227 -30
- package/tests/common/url.test.ts +492 -67
- package/tests/taiwan/business-id.test.ts +240 -0
- package/tests/taiwan/fax.test.ts +463 -0
- package/tests/taiwan/mobile.test.ts +373 -0
- package/tests/taiwan/national-id.test.ts +435 -0
- package/tests/taiwan/tel.test.ts +467 -0
- package/eslint.config.mjs +0 -10
- package/src/common/boolean.ts +0 -37
- package/src/common/date.ts +0 -44
- package/src/common/email.ts +0 -45
- package/src/common/integer.ts +0 -47
- package/src/common/number.ts +0 -38
- package/src/common/password.ts +0 -34
- package/src/common/text.ts +0 -35
- package/src/common/url.ts +0 -38
- package/tests/common/integer.test.ts +0 -90
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { z, ZodNullable, ZodString } from "zod"
|
|
2
|
+
import { t } from "../../i18n"
|
|
3
|
+
import { getLocale, type Locale } from "../../config"
|
|
4
|
+
|
|
5
|
+
export type NationalIdMessages = {
|
|
6
|
+
required?: string
|
|
7
|
+
invalid?: string
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export type NationalIdType =
|
|
11
|
+
| "citizen" // 身分證字號 (國民身分證)
|
|
12
|
+
| "resident" // 居留證號 (外籍人士統一證號)
|
|
13
|
+
| "both" // 身分證/居留證皆可
|
|
14
|
+
|
|
15
|
+
export type NationalIdOptions<IsRequired extends boolean = true> = {
|
|
16
|
+
required?: IsRequired
|
|
17
|
+
type?: NationalIdType
|
|
18
|
+
allowOldResident?: boolean
|
|
19
|
+
transform?: (value: string) => string
|
|
20
|
+
defaultValue?: IsRequired extends true ? string : string | null
|
|
21
|
+
i18n?: Record<Locale, NationalIdMessages>
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export type NationalIdSchema<IsRequired extends boolean> = IsRequired extends true ? ZodString : ZodNullable<ZodString>
|
|
25
|
+
|
|
26
|
+
// 縣市代碼對應表
|
|
27
|
+
const CITY_CODES: Record<string, number> = {
|
|
28
|
+
'A': 10, 'B': 11, 'C': 12, 'D': 13, 'E': 14, 'F': 15, 'G': 16, 'H': 17,
|
|
29
|
+
'I': 34, 'J': 18, 'K': 19, 'L': 20, 'M': 21, 'N': 22, 'O': 35, 'P': 23,
|
|
30
|
+
'Q': 24, 'R': 25, 'S': 26, 'T': 27, 'U': 28, 'V': 29, 'W': 32, 'X': 30,
|
|
31
|
+
'Y': 31, 'Z': 33
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// 驗證國民身分證字號
|
|
35
|
+
const validateCitizenId = (value: string): boolean => {
|
|
36
|
+
// 格式檢查:1個英文字母 + 9個數字
|
|
37
|
+
if (!/^[A-Z][1-2]\d{8}$/.test(value)) {
|
|
38
|
+
return false
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const letter = value[0]
|
|
42
|
+
const digits = value.slice(1).split('').map(Number)
|
|
43
|
+
|
|
44
|
+
// 獲取縣市代碼
|
|
45
|
+
const cityCode = CITY_CODES[letter]
|
|
46
|
+
if (!cityCode) return false
|
|
47
|
+
|
|
48
|
+
// 計算校驗碼
|
|
49
|
+
const cityDigits = [Math.floor(cityCode / 10), cityCode % 10]
|
|
50
|
+
const coefficients = [1, 9, 8, 7, 6, 5, 4, 3, 2, 1]
|
|
51
|
+
|
|
52
|
+
let sum = cityDigits[0] * coefficients[0] + cityDigits[1] * coefficients[1]
|
|
53
|
+
for (let i = 0; i < 8; i++) {
|
|
54
|
+
sum += digits[i] * coefficients[i + 2]
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const checksum = (10 - (sum % 10)) % 10
|
|
58
|
+
return checksum === digits[8]
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// 驗證舊式居留證號
|
|
62
|
+
const validateOldResidentId = (value: string): boolean => {
|
|
63
|
+
// 格式檢查:1個英文字母 + [AB或CD] + 8個數字
|
|
64
|
+
if (!/^[A-Z][ABCD]\d{8}$/.test(value)) {
|
|
65
|
+
return false
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const letter = value[0]
|
|
69
|
+
const genderCode = value[1]
|
|
70
|
+
const digits = value.slice(2).split('').map(Number)
|
|
71
|
+
|
|
72
|
+
// 獲取縣市代碼
|
|
73
|
+
const cityCode = CITY_CODES[letter]
|
|
74
|
+
if (!cityCode) return false
|
|
75
|
+
|
|
76
|
+
// 性別代碼轉換
|
|
77
|
+
const genderValue = genderCode === 'A' || genderCode === 'C' ? 1 : 0
|
|
78
|
+
|
|
79
|
+
// 計算校驗碼
|
|
80
|
+
const cityDigits = [Math.floor(cityCode / 10), cityCode % 10]
|
|
81
|
+
const coefficients = [1, 9, 8, 7, 6, 5, 4, 3, 2, 1]
|
|
82
|
+
|
|
83
|
+
let sum = cityDigits[0] * coefficients[0] + cityDigits[1] * coefficients[1] + genderValue * coefficients[2]
|
|
84
|
+
for (let i = 0; i < 7; i++) {
|
|
85
|
+
sum += digits[i] * coefficients[i + 3]
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const checksum = (10 - (sum % 10)) % 10
|
|
89
|
+
return checksum === digits[7]
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// 驗證新式居留證號
|
|
93
|
+
const validateNewResidentId = (value: string): boolean => {
|
|
94
|
+
// 格式檢查:1個英文字母 + [89] + 1個數字[0-9] + 7個數字
|
|
95
|
+
if (!/^[A-Z][89]\d{8}$/.test(value)) {
|
|
96
|
+
return false
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const letter = value[0]
|
|
100
|
+
const digits = value.slice(1).split('').map(Number)
|
|
101
|
+
|
|
102
|
+
// 獲取縣市代碼
|
|
103
|
+
const cityCode = CITY_CODES[letter]
|
|
104
|
+
if (!cityCode) return false
|
|
105
|
+
|
|
106
|
+
// 計算校驗碼 (與身分證字號相同邏輯)
|
|
107
|
+
const cityDigits = [Math.floor(cityCode / 10), cityCode % 10]
|
|
108
|
+
const coefficients = [1, 9, 8, 7, 6, 5, 4, 3, 2, 1]
|
|
109
|
+
|
|
110
|
+
let sum = cityDigits[0] * coefficients[0] + cityDigits[1] * coefficients[1]
|
|
111
|
+
for (let i = 0; i < 8; i++) {
|
|
112
|
+
sum += digits[i] * coefficients[i + 2]
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const checksum = (10 - (sum % 10)) % 10
|
|
116
|
+
return checksum === digits[8]
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// 主要驗證函數
|
|
120
|
+
const validateTaiwanNationalId = (value: string, type: NationalIdType = "both", allowOldResident: boolean = true): boolean => {
|
|
121
|
+
if (!/^[A-Z].{9}$/.test(value)) {
|
|
122
|
+
return false
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
switch (type) {
|
|
126
|
+
case "citizen":
|
|
127
|
+
return validateCitizenId(value)
|
|
128
|
+
case "resident":
|
|
129
|
+
return (allowOldResident ? validateOldResidentId(value) : false) || validateNewResidentId(value)
|
|
130
|
+
case "both":
|
|
131
|
+
return validateCitizenId(value) || (allowOldResident ? validateOldResidentId(value) : false) || validateNewResidentId(value)
|
|
132
|
+
default:
|
|
133
|
+
return false
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function nationalId<IsRequired extends boolean = true>(options?: NationalIdOptions<IsRequired>): NationalIdSchema<IsRequired> {
|
|
138
|
+
const {
|
|
139
|
+
required = true,
|
|
140
|
+
type = "both",
|
|
141
|
+
allowOldResident = true,
|
|
142
|
+
transform,
|
|
143
|
+
defaultValue,
|
|
144
|
+
i18n
|
|
145
|
+
} = options ?? {}
|
|
146
|
+
|
|
147
|
+
// Set appropriate default value based on required flag
|
|
148
|
+
const actualDefaultValue = defaultValue ?? (required ? "" : null)
|
|
149
|
+
|
|
150
|
+
// Helper function to get custom message or fallback to default i18n
|
|
151
|
+
const getMessage = (key: keyof NationalIdMessages, params?: Record<string, any>) => {
|
|
152
|
+
if (i18n) {
|
|
153
|
+
const currentLocale = getLocale()
|
|
154
|
+
const customMessages = i18n[currentLocale]
|
|
155
|
+
if (customMessages && customMessages[key]) {
|
|
156
|
+
const template = customMessages[key]!
|
|
157
|
+
return template.replace(/\$\{(\w+)}/g, (_, k) => params?.[k] ?? "")
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return t(`taiwan.nationalId.${key}`, params)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Preprocessing function
|
|
164
|
+
const preprocessFn = (val: unknown) => {
|
|
165
|
+
if (val === "" || val === null || val === undefined) {
|
|
166
|
+
return actualDefaultValue
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
let processed = String(val).trim().toUpperCase()
|
|
170
|
+
|
|
171
|
+
// If after trimming we have an empty string and the field is optional, return null
|
|
172
|
+
if (processed === "" && !required) {
|
|
173
|
+
return null
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (transform) {
|
|
177
|
+
processed = transform(processed)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return processed
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const baseSchema = required ? z.preprocess(preprocessFn, z.string()) : z.preprocess(preprocessFn, z.string().nullable())
|
|
184
|
+
|
|
185
|
+
const schema = baseSchema.refine((val) => {
|
|
186
|
+
if (val === null) return true
|
|
187
|
+
|
|
188
|
+
// Required check
|
|
189
|
+
if (required && (val === "" || val === "null" || val === "undefined")) {
|
|
190
|
+
throw new z.ZodError([{ code: "custom", message: getMessage("required"), path: [] }])
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (val === null) return true
|
|
194
|
+
if (!required && val === "") return true
|
|
195
|
+
|
|
196
|
+
// Taiwan National ID validation
|
|
197
|
+
if (!validateTaiwanNationalId(val, type, allowOldResident)) {
|
|
198
|
+
throw new z.ZodError([{ code: "custom", message: getMessage("invalid"), path: [] }])
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return true
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
return schema as unknown as NationalIdSchema<IsRequired>
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Export utility functions for external use
|
|
208
|
+
export { validateTaiwanNationalId, validateCitizenId, validateOldResidentId, validateNewResidentId }
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { z, ZodNullable, ZodString } from "zod"
|
|
2
|
+
import { t } from "../../i18n"
|
|
3
|
+
import { getLocale, type Locale } from "../../config"
|
|
4
|
+
|
|
5
|
+
export type TelMessages = {
|
|
6
|
+
required?: string
|
|
7
|
+
invalid?: string
|
|
8
|
+
notInWhitelist?: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export type TelOptions<IsRequired extends boolean = true> = {
|
|
12
|
+
required?: IsRequired
|
|
13
|
+
whitelist?: string[]
|
|
14
|
+
transform?: (value: string) => string
|
|
15
|
+
defaultValue?: IsRequired extends true ? string : string | null
|
|
16
|
+
i18n?: Record<Locale, TelMessages>
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export type TelSchema<IsRequired extends boolean> = IsRequired extends true ? ZodString : ZodNullable<ZodString>
|
|
20
|
+
|
|
21
|
+
// Taiwan landline telephone validation (Official 2024 rules)
|
|
22
|
+
const validateTaiwanTel = (value: string): boolean => {
|
|
23
|
+
// Official Taiwan landline formats according to telecom numbering plan:
|
|
24
|
+
// 02: Taipei, New Taipei, Keelung - 8 digits (2&3&5~8+7D)
|
|
25
|
+
// 03: Taoyuan, Hsinchu, Yilan, Hualien - 7 digits
|
|
26
|
+
// 037: Miaoli - 6 digits (2~9+5D)
|
|
27
|
+
// 04: Taichung, Changhua - 7 digits
|
|
28
|
+
// 049: Nantou - 7 digits (2~9+6D)
|
|
29
|
+
// 05: Yunlin, Chiayi - 7 digits
|
|
30
|
+
// 06: Tainan - 7 digits
|
|
31
|
+
// 07: Kaohsiung - 7 digits (2~9+6D)
|
|
32
|
+
// 08: Pingtung - 7 digits (4&7&8+6D)
|
|
33
|
+
// 082: Kinmen - 6 digits (2~5&7~9+5D)
|
|
34
|
+
// 0826: Wuqiu - 5 digits (6+4D)
|
|
35
|
+
// 0836: Matsu - 5 digits (2~9+4D)
|
|
36
|
+
// 089: Taitung - 6 digits (2~9+5D)
|
|
37
|
+
|
|
38
|
+
// Remove common separators for validation
|
|
39
|
+
const cleanValue = value.replace(/[-\s]/g, "")
|
|
40
|
+
|
|
41
|
+
// Basic format: starts with 0, then area code, then number
|
|
42
|
+
if (!/^0\d{7,10}$/.test(cleanValue)) {
|
|
43
|
+
return false
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Check 4-digit area codes first
|
|
47
|
+
const areaCode4 = cleanValue.substring(0, 4)
|
|
48
|
+
if (areaCode4 === "0826") {
|
|
49
|
+
// Wuqiu: 0826 + 5 digits (6+4D), total 9 digits
|
|
50
|
+
return cleanValue.length === 9 && /^0826[6]\d{4}$/.test(cleanValue)
|
|
51
|
+
}
|
|
52
|
+
if (areaCode4 === "0836") {
|
|
53
|
+
// Matsu: 0836 + 5 digits (2~9+4D), total 9 digits
|
|
54
|
+
return cleanValue.length === 9 && /^0836[2-9]\d{4}$/.test(cleanValue)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Check 3-digit area codes
|
|
58
|
+
const areaCode3 = cleanValue.substring(0, 3)
|
|
59
|
+
if (areaCode3 === "037") {
|
|
60
|
+
// Miaoli: 037 + 6 digits (2~9+5D), total 9 digits
|
|
61
|
+
return cleanValue.length === 9 && /^037[2-9]\d{5}$/.test(cleanValue)
|
|
62
|
+
}
|
|
63
|
+
if (areaCode3 === "049") {
|
|
64
|
+
// Nantou: 049 + 7 digits (2~9+6D), total 10 digits
|
|
65
|
+
return cleanValue.length === 10 && /^049[2-9]\d{6}$/.test(cleanValue)
|
|
66
|
+
}
|
|
67
|
+
if (areaCode3 === "082") {
|
|
68
|
+
// Kinmen: 082 + 6 digits (2~5&7~9+5D), total 9 digits
|
|
69
|
+
return cleanValue.length === 9 && /^082[2-57-9]\d{5}$/.test(cleanValue)
|
|
70
|
+
}
|
|
71
|
+
if (areaCode3 === "089") {
|
|
72
|
+
// Taitung: 089 + 6 digits (2~9+5D), total 9 digits
|
|
73
|
+
return cleanValue.length === 9 && /^089[2-9]\d{5}$/.test(cleanValue)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Check 2-digit area codes
|
|
77
|
+
const areaCode2 = cleanValue.substring(0, 2)
|
|
78
|
+
|
|
79
|
+
if (areaCode2 === "02") {
|
|
80
|
+
// Taipei, New Taipei, Keelung: 02 + 8 digits (2&3&5~8+7D), total 10 digits
|
|
81
|
+
return cleanValue.length === 10 && /^02[235-8]\d{7}$/.test(cleanValue)
|
|
82
|
+
}
|
|
83
|
+
if (["03", "04", "05", "06"].includes(areaCode2)) {
|
|
84
|
+
// Taoyuan/Hsinchu/Yilan/Hualien (03), Taichung/Changhua (04),
|
|
85
|
+
// Yunlin/Chiayi (05), Tainan (06): 7 digits, total 9 digits
|
|
86
|
+
return cleanValue.length === 9
|
|
87
|
+
}
|
|
88
|
+
if (areaCode2 === "07") {
|
|
89
|
+
// Kaohsiung: 07 + 7 digits (2~9+6D), total 9 digits
|
|
90
|
+
return cleanValue.length === 9 && /^07[2-9]\d{6}$/.test(cleanValue)
|
|
91
|
+
}
|
|
92
|
+
if (areaCode2 === "08") {
|
|
93
|
+
// Pingtung: 08 + 7 digits (4&7&8+6D), total 9 digits
|
|
94
|
+
return cleanValue.length === 9 && /^08[478]\d{6}$/.test(cleanValue)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return false
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function tel<IsRequired extends boolean = true>(options?: TelOptions<IsRequired>): TelSchema<IsRequired> {
|
|
101
|
+
const { required = true, whitelist, transform, defaultValue, i18n } = options ?? {}
|
|
102
|
+
|
|
103
|
+
// Set appropriate default value based on required flag
|
|
104
|
+
const actualDefaultValue = defaultValue ?? (required ? "" : null)
|
|
105
|
+
|
|
106
|
+
// Helper function to get custom message or fallback to default i18n
|
|
107
|
+
const getMessage = (key: keyof TelMessages, params?: Record<string, any>) => {
|
|
108
|
+
if (i18n) {
|
|
109
|
+
const currentLocale = getLocale()
|
|
110
|
+
const customMessages = i18n[currentLocale]
|
|
111
|
+
if (customMessages && customMessages[key]) {
|
|
112
|
+
const template = customMessages[key]!
|
|
113
|
+
return template.replace(/\$\{(\w+)}/g, (_, k) => params?.[k] ?? "")
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return t(`taiwan.tel.${key}`, params)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Preprocessing function
|
|
120
|
+
const preprocessFn = (val: unknown) => {
|
|
121
|
+
if (val === null || val === undefined) {
|
|
122
|
+
return actualDefaultValue
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
let processed = String(val).trim()
|
|
126
|
+
|
|
127
|
+
// If after trimming we have an empty string
|
|
128
|
+
if (processed === "") {
|
|
129
|
+
// If empty string is in allowlist, return it as is
|
|
130
|
+
if (whitelist && whitelist.includes("")) {
|
|
131
|
+
return ""
|
|
132
|
+
}
|
|
133
|
+
// If the field is optional and empty string not in allowlist, return default value
|
|
134
|
+
if (!required) {
|
|
135
|
+
return actualDefaultValue
|
|
136
|
+
}
|
|
137
|
+
// If a field is required, return the default value (will be validated later)
|
|
138
|
+
return actualDefaultValue
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (transform) {
|
|
142
|
+
processed = transform(processed)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return processed
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const baseSchema = required ? z.preprocess(preprocessFn, z.string()) : z.preprocess(preprocessFn, z.string().nullable())
|
|
149
|
+
|
|
150
|
+
const schema = baseSchema.refine((val) => {
|
|
151
|
+
if (val === null) return true
|
|
152
|
+
|
|
153
|
+
// Required check
|
|
154
|
+
if (required && (val === "" || val === "null" || val === "undefined")) {
|
|
155
|
+
throw new z.ZodError([{ code: "custom", message: getMessage("required"), path: [] }])
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (val === null) return true
|
|
159
|
+
if (!required && val === "") return true
|
|
160
|
+
|
|
161
|
+
// Allowlist check (if an allowlist is provided, only allow values in the allowlist)
|
|
162
|
+
if (whitelist && whitelist.length > 0) {
|
|
163
|
+
if (whitelist.includes(val)) {
|
|
164
|
+
return true
|
|
165
|
+
}
|
|
166
|
+
// If not in the allowlist, reject regardless of format
|
|
167
|
+
throw new z.ZodError([{ code: "custom", message: getMessage("notInWhitelist"), path: [] }])
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Taiwan telephone format validation (only if no allowlist or allowlist is empty)
|
|
171
|
+
if (!validateTaiwanTel(val)) {
|
|
172
|
+
throw new z.ZodError([{ code: "custom", message: getMessage("invalid"), path: [] }])
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return true
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
return schema as unknown as TelSchema<IsRequired>
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Export utility function for external use
|
|
182
|
+
export { validateTaiwanTel }
|