@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.
Files changed (191) hide show
  1. package/.github/workflows/ci.yml +24 -0
  2. package/CLAUDE.md +64 -22
  3. package/dist/chunk-2SWEVDFZ.js +134 -0
  4. package/dist/chunk-32JI34CV.cjs +146 -0
  5. package/dist/chunk-42C5OHRK.js +71 -0
  6. package/dist/chunk-46VAH2BJ.js +160 -0
  7. package/dist/chunk-5JGTDL3Y.js +87 -0
  8. package/dist/chunk-5LEXCVLX.js +257 -0
  9. package/dist/chunk-6AAP4LPF.js +2606 -0
  10. package/dist/chunk-B4EZYZOK.cjs +215 -0
  11. package/dist/chunk-COYKBWTI.js +161 -0
  12. package/dist/chunk-DFJZ3NS2.cjs +151 -0
  13. package/dist/chunk-EDHT4LPO.js +118 -0
  14. package/dist/chunk-EGHL277K.cjs +165 -0
  15. package/dist/chunk-ERH4NIMU.cjs +69 -0
  16. package/dist/chunk-FM3EZ72O.js +165 -0
  17. package/dist/chunk-GJIRDBZJ.cjs +90 -0
  18. package/dist/chunk-H2XTEM4M.js +696 -0
  19. package/dist/chunk-HMSM6FFA.cjs +181 -0
  20. package/dist/chunk-HTEHINI7.cjs +177 -0
  21. package/dist/chunk-JOLSGZGN.cjs +696 -0
  22. package/dist/chunk-JXY7APBU.js +69 -0
  23. package/dist/chunk-K2UOY6TB.js +136 -0
  24. package/dist/chunk-KFOHKTFD.js +61 -0
  25. package/dist/chunk-L4HSIKTU.cjs +135 -0
  26. package/dist/chunk-LH7ZB4BK.js +124 -0
  27. package/dist/chunk-LL4ZWLGO.js +90 -0
  28. package/dist/chunk-M6MTP3NY.cjs +99 -0
  29. package/dist/chunk-MHJFYYGV.js +215 -0
  30. package/dist/chunk-MINMXGW3.js +135 -0
  31. package/dist/chunk-MM7IL2RG.js +181 -0
  32. package/dist/chunk-OPQJWHXN.cjs +301 -0
  33. package/dist/chunk-ORFHDJII.cjs +136 -0
  34. package/dist/chunk-ORVV4MCF.cjs +87 -0
  35. package/dist/chunk-QICQ6YEY.js +75 -0
  36. package/dist/chunk-RKUQREMW.js +127 -0
  37. package/dist/chunk-RO47DKQG.js +146 -0
  38. package/dist/chunk-RRPXIRTQ.cjs +257 -0
  39. package/dist/chunk-RYFG2GKM.cjs +118 -0
  40. package/dist/chunk-STNHTRG7.cjs +124 -0
  41. package/dist/chunk-TFGS34VD.cjs +71 -0
  42. package/dist/chunk-TQXDUMML.cjs +61 -0
  43. package/dist/chunk-UBK3VCVH.cjs +134 -0
  44. package/dist/chunk-UCOXAZJF.cjs +2606 -0
  45. package/dist/chunk-UQZKFAFX.js +130 -0
  46. package/dist/chunk-VB2KV2ZM.cjs +130 -0
  47. package/dist/chunk-WABKPFPK.js +151 -0
  48. package/dist/chunk-WDI4QJMQ.js +177 -0
  49. package/dist/chunk-YDH3L27K.cjs +127 -0
  50. package/dist/chunk-YIM3D2AD.js +99 -0
  51. package/dist/chunk-YPSEIDUR.cjs +160 -0
  52. package/dist/chunk-ZNJLWJX3.cjs +75 -0
  53. package/dist/chunk-ZTFCJCPO.cjs +161 -0
  54. package/dist/chunk-ZXUMK2RR.js +301 -0
  55. package/dist/common/boolean.cjs +7 -0
  56. package/dist/common/boolean.d.cts +119 -0
  57. package/dist/common/boolean.d.ts +119 -0
  58. package/dist/common/boolean.js +7 -0
  59. package/dist/common/color.cjs +9 -0
  60. package/dist/common/color.d.cts +26 -0
  61. package/dist/common/color.d.ts +26 -0
  62. package/dist/common/color.js +9 -0
  63. package/dist/common/coordinate.cjs +11 -0
  64. package/dist/common/coordinate.d.cts +23 -0
  65. package/dist/common/coordinate.d.ts +23 -0
  66. package/dist/common/coordinate.js +11 -0
  67. package/dist/common/credit-card.cjs +11 -0
  68. package/dist/common/credit-card.d.cts +22 -0
  69. package/dist/common/credit-card.d.ts +22 -0
  70. package/dist/common/credit-card.js +11 -0
  71. package/dist/common/date.cjs +7 -0
  72. package/dist/common/date.d.cts +174 -0
  73. package/dist/common/date.d.ts +174 -0
  74. package/dist/common/date.js +7 -0
  75. package/dist/common/datetime.cjs +15 -0
  76. package/dist/common/datetime.d.cts +301 -0
  77. package/dist/common/datetime.d.ts +301 -0
  78. package/dist/common/datetime.js +15 -0
  79. package/dist/common/email.cjs +7 -0
  80. package/dist/common/email.d.cts +149 -0
  81. package/dist/common/email.d.ts +149 -0
  82. package/dist/common/email.js +7 -0
  83. package/dist/common/file.cjs +7 -0
  84. package/dist/common/file.d.cts +178 -0
  85. package/dist/common/file.d.ts +178 -0
  86. package/dist/common/file.js +7 -0
  87. package/dist/common/id.cjs +13 -0
  88. package/dist/common/id.d.cts +288 -0
  89. package/dist/common/id.d.ts +288 -0
  90. package/dist/common/id.js +13 -0
  91. package/dist/common/ip.cjs +11 -0
  92. package/dist/common/ip.d.cts +25 -0
  93. package/dist/common/ip.d.ts +25 -0
  94. package/dist/common/ip.js +11 -0
  95. package/dist/common/number.cjs +7 -0
  96. package/dist/common/number.d.cts +167 -0
  97. package/dist/common/number.d.ts +167 -0
  98. package/dist/common/number.js +7 -0
  99. package/dist/common/password.cjs +7 -0
  100. package/dist/common/password.d.cts +192 -0
  101. package/dist/common/password.d.ts +192 -0
  102. package/dist/common/password.js +7 -0
  103. package/dist/common/text.cjs +7 -0
  104. package/dist/common/text.d.cts +156 -0
  105. package/dist/common/text.d.ts +156 -0
  106. package/dist/common/text.js +7 -0
  107. package/dist/common/time.cjs +15 -0
  108. package/dist/common/time.d.cts +268 -0
  109. package/dist/common/time.d.ts +268 -0
  110. package/dist/common/time.js +15 -0
  111. package/dist/common/url.cjs +7 -0
  112. package/dist/common/url.d.cts +196 -0
  113. package/dist/common/url.d.ts +196 -0
  114. package/dist/common/url.js +7 -0
  115. package/dist/config-CABSSvAp.d.cts +5 -0
  116. package/dist/config-CABSSvAp.d.ts +5 -0
  117. package/dist/index.cjs +180 -5255
  118. package/dist/index.d.cts +28 -3150
  119. package/dist/index.d.ts +28 -3150
  120. package/dist/index.js +135 -5131
  121. package/dist/taiwan/bank-account.cjs +11 -0
  122. package/dist/taiwan/bank-account.d.cts +22 -0
  123. package/dist/taiwan/bank-account.d.ts +22 -0
  124. package/dist/taiwan/bank-account.js +11 -0
  125. package/dist/taiwan/business-id.cjs +9 -0
  126. package/dist/taiwan/business-id.d.cts +133 -0
  127. package/dist/taiwan/business-id.d.ts +133 -0
  128. package/dist/taiwan/business-id.js +9 -0
  129. package/dist/taiwan/fax.cjs +9 -0
  130. package/dist/taiwan/fax.d.cts +157 -0
  131. package/dist/taiwan/fax.d.ts +157 -0
  132. package/dist/taiwan/fax.js +9 -0
  133. package/dist/taiwan/invoice.cjs +9 -0
  134. package/dist/taiwan/invoice.d.cts +17 -0
  135. package/dist/taiwan/invoice.d.ts +17 -0
  136. package/dist/taiwan/invoice.js +9 -0
  137. package/dist/taiwan/license-plate.cjs +9 -0
  138. package/dist/taiwan/license-plate.d.cts +19 -0
  139. package/dist/taiwan/license-plate.d.ts +19 -0
  140. package/dist/taiwan/license-plate.js +9 -0
  141. package/dist/taiwan/mobile.cjs +9 -0
  142. package/dist/taiwan/mobile.d.cts +146 -0
  143. package/dist/taiwan/mobile.d.ts +146 -0
  144. package/dist/taiwan/mobile.js +9 -0
  145. package/dist/taiwan/national-id.cjs +15 -0
  146. package/dist/taiwan/national-id.d.cts +214 -0
  147. package/dist/taiwan/national-id.d.ts +214 -0
  148. package/dist/taiwan/national-id.js +15 -0
  149. package/dist/taiwan/passport.cjs +9 -0
  150. package/dist/taiwan/passport.d.cts +19 -0
  151. package/dist/taiwan/passport.d.ts +19 -0
  152. package/dist/taiwan/passport.js +9 -0
  153. package/dist/taiwan/postal-code.cjs +17 -0
  154. package/dist/taiwan/postal-code.d.cts +237 -0
  155. package/dist/taiwan/postal-code.d.ts +237 -0
  156. package/dist/taiwan/postal-code.js +17 -0
  157. package/dist/taiwan/tel.cjs +9 -0
  158. package/dist/taiwan/tel.d.cts +162 -0
  159. package/dist/taiwan/tel.d.ts +162 -0
  160. package/dist/taiwan/tel.js +9 -0
  161. package/package.json +132 -6
  162. package/src/i18n/locales/en-GB.json +51 -0
  163. package/src/i18n/locales/en-US.json +52 -1
  164. package/src/i18n/locales/id-ID.json +51 -0
  165. package/src/i18n/locales/ja-JP.json +51 -0
  166. package/src/i18n/locales/ko-KR.json +51 -0
  167. package/src/i18n/locales/ms-MY.json +51 -0
  168. package/src/i18n/locales/th-TH.json +51 -0
  169. package/src/i18n/locales/vi-VN.json +51 -0
  170. package/src/i18n/locales/zh-CN.json +51 -0
  171. package/src/i18n/locales/zh-TW.json +51 -0
  172. package/src/index.ts +10 -2
  173. package/src/validators/common/color.ts +192 -0
  174. package/src/validators/common/coordinate.ts +159 -0
  175. package/src/validators/common/credit-card.ts +134 -0
  176. package/src/validators/common/id.ts +45 -3
  177. package/src/validators/common/ip.ts +210 -0
  178. package/src/validators/taiwan/bank-account.ts +176 -0
  179. package/src/validators/taiwan/invoice.ts +84 -0
  180. package/src/validators/taiwan/license-plate.ts +110 -0
  181. package/src/validators/taiwan/passport.ts +103 -0
  182. package/tests/common/color.test.ts +587 -0
  183. package/tests/common/coordinate.test.ts +345 -0
  184. package/tests/common/credit-card.test.ts +378 -0
  185. package/tests/common/id.test.ts +68 -3
  186. package/tests/common/ip.test.ts +419 -0
  187. package/tests/taiwan/bank-account.test.ts +286 -0
  188. package/tests/taiwan/invoice.test.ts +227 -0
  189. package/tests/taiwan/license-plate.test.ts +280 -0
  190. package/tests/taiwan/passport.test.ts +277 -0
  191. 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 }