@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
@@ -75,6 +75,14 @@
75
75
  "maxLength": "ต้องไม่เกิน ${maxLength} ตัวอักษร",
76
76
  "numeric": "ต้องเป็น ID ตัวเลข",
77
77
  "uuid": "ต้องเป็น UUID ที่ถูกต้อง",
78
+ "uuidv1": "ต้องเป็น UUID v1 ที่ถูกต้อง",
79
+ "uuidv2": "ต้องเป็น UUID v2 ที่ถูกต้อง",
80
+ "uuidv3": "ต้องเป็น UUID v3 ที่ถูกต้อง",
81
+ "uuidv4": "ต้องเป็น UUID v4 ที่ถูกต้อง",
82
+ "uuidv5": "ต้องเป็น UUID v5 ที่ถูกต้อง",
83
+ "uuidv6": "ต้องเป็น UUID v6 ที่ถูกต้อง",
84
+ "uuidv7": "ต้องเป็น UUID v7 ที่ถูกต้อง",
85
+ "uuidv8": "ต้องเป็น UUID v8 ที่ถูกต้อง",
78
86
  "objectId": "ต้องเป็น MongoDB ObjectId ที่ถูกต้อง",
79
87
  "nanoid": "ต้องเป็น Nano ID ที่ถูกต้อง",
80
88
  "snowflake": "ต้องเป็น Snowflake ID ที่ถูกต้อง",
@@ -162,6 +170,31 @@
162
170
  "videoOnly": "อนุญาตเฉพาะไฟล์วิดีโอ",
163
171
  "audioOnly": "อนุญาตเฉพาะไฟล์เสียง",
164
172
  "archiveOnly": "อนุญาตเฉพาะไฟล์บีบอัด"
173
+ },
174
+ "creditCard": {
175
+ "required": "จำเป็นต้องกรอก",
176
+ "invalid": "หมายเลขบัตรเครดิตไม่ถูกต้อง",
177
+ "notInWhitelist": "หมายเลขบัตรเครดิตไม่อยู่ในรายการที่อนุญาต"
178
+ },
179
+ "ip": {
180
+ "required": "จำเป็นต้องกรอก",
181
+ "invalid": "ที่อยู่ IP ไม่ถูกต้อง",
182
+ "notIPv4": "ต้องเป็นที่อยู่ IPv4 ที่ถูกต้อง",
183
+ "notIPv6": "ต้องเป็นที่อยู่ IPv6 ที่ถูกต้อง",
184
+ "notInWhitelist": "ที่อยู่ IP ไม่อยู่ในรายการที่อนุญาต"
185
+ },
186
+ "color": {
187
+ "required": "จำเป็นต้องกรอก",
188
+ "invalid": "รูปแบบสีไม่ถูกต้อง",
189
+ "notHex": "ต้องเป็นสี hex ที่ถูกต้อง",
190
+ "notRgb": "ต้องเป็นสี RGB ที่ถูกต้อง",
191
+ "notHsl": "ต้องเป็นสี HSL ที่ถูกต้อง"
192
+ },
193
+ "coordinate": {
194
+ "required": "จำเป็นต้องกรอก",
195
+ "invalid": "พิกัดไม่ถูกต้อง",
196
+ "invalidLatitude": "ละติจูดต้องอยู่ระหว่าง -90 ถึง 90",
197
+ "invalidLongitude": "ลองจิจูดต้องอยู่ระหว่าง -180 ถึง 180"
165
198
  }
166
199
  },
167
200
  "taiwan": {
@@ -199,6 +232,24 @@
199
232
  "format6Only": "อนุญาตเฉพาะรหัสไปรษณีย์ 6 หลัก",
200
233
  "invalidSuffix": "ส่วนท้ายรหัสไปรษณีย์ไม่ถูกต้อง - 5 หลักต้องเป็น 01-99 หรือ 6 หลักต้องเป็น 001-999",
201
234
  "deprecated5Digit": "รหัสไปรษณีย์ 5 หลักถูกยกเลิกและไม่รองรับอีกต่อไป"
235
+ },
236
+ "invoice": {
237
+ "required": "จำเป็นต้องกรอก",
238
+ "invalid": "หมายเลขใบกำกับภาษีไต้หวันไม่ถูกต้อง"
239
+ },
240
+ "licensePlate": {
241
+ "required": "จำเป็นต้องกรอก",
242
+ "invalid": "หมายเลขทะเบียนรถไต้หวันไม่ถูกต้อง"
243
+ },
244
+ "bankAccount": {
245
+ "required": "จำเป็นต้องกรอก",
246
+ "invalid": "บัญชีธนาคารไต้หวันไม่ถูกต้อง",
247
+ "invalidBankCode": "รหัสธนาคารไม่ถูกต้อง",
248
+ "invalidAccountNumber": "หมายเลขบัญชีไม่ถูกต้อง"
249
+ },
250
+ "passport": {
251
+ "required": "จำเป็นต้องกรอก",
252
+ "invalid": "หมายเลขหนังสือเดินทางไต้หวันไม่ถูกต้อง"
202
253
  }
203
254
  }
204
255
  }
@@ -75,6 +75,14 @@
75
75
  "maxLength": "Phải có tối đa ${maxLength} ký tự",
76
76
  "numeric": "Phải là ID dạng số",
77
77
  "uuid": "Phải là UUID hợp lệ",
78
+ "uuidv1": "Phải là UUID v1 hợp lệ",
79
+ "uuidv2": "Phải là UUID v2 hợp lệ",
80
+ "uuidv3": "Phải là UUID v3 hợp lệ",
81
+ "uuidv4": "Phải là UUID v4 hợp lệ",
82
+ "uuidv5": "Phải là UUID v5 hợp lệ",
83
+ "uuidv6": "Phải là UUID v6 hợp lệ",
84
+ "uuidv7": "Phải là UUID v7 hợp lệ",
85
+ "uuidv8": "Phải là UUID v8 hợp lệ",
78
86
  "objectId": "Phải là MongoDB ObjectId hợp lệ",
79
87
  "nanoid": "Phải là Nano ID hợp lệ",
80
88
  "snowflake": "Phải là Snowflake ID hợp lệ",
@@ -162,6 +170,31 @@
162
170
  "videoOnly": "Chỉ cho phép tệp video",
163
171
  "audioOnly": "Chỉ cho phép tệp âm thanh",
164
172
  "archiveOnly": "Chỉ cho phép tệp nén"
173
+ },
174
+ "creditCard": {
175
+ "required": "Bắt buộc",
176
+ "invalid": "Số thẻ tín dụng không hợp lệ",
177
+ "notInWhitelist": "Số thẻ tín dụng không nằm trong danh sách cho phép"
178
+ },
179
+ "ip": {
180
+ "required": "Bắt buộc",
181
+ "invalid": "Địa chỉ IP không hợp lệ",
182
+ "notIPv4": "Phải là địa chỉ IPv4 hợp lệ",
183
+ "notIPv6": "Phải là địa chỉ IPv6 hợp lệ",
184
+ "notInWhitelist": "Địa chỉ IP không nằm trong danh sách cho phép"
185
+ },
186
+ "color": {
187
+ "required": "Bắt buộc",
188
+ "invalid": "Định dạng màu không hợp lệ",
189
+ "notHex": "Phải là màu hex hợp lệ",
190
+ "notRgb": "Phải là màu RGB hợp lệ",
191
+ "notHsl": "Phải là màu HSL hợp lệ"
192
+ },
193
+ "coordinate": {
194
+ "required": "Bắt buộc",
195
+ "invalid": "Tọa độ không hợp lệ",
196
+ "invalidLatitude": "Vĩ độ phải từ -90 đến 90",
197
+ "invalidLongitude": "Kinh độ phải từ -180 đến 180"
165
198
  }
166
199
  },
167
200
  "taiwan": {
@@ -199,6 +232,24 @@
199
232
  "format6Only": "Chỉ cho phép mã bưu chính 6 chữ số",
200
233
  "invalidSuffix": "Hậu tố mã bưu chính không hợp lệ - 5 chữ số phải là 01-99 hoặc 6 chữ số phải là 001-999",
201
234
  "deprecated5Digit": "Mã bưu chính 5 chữ số đã ngừng sử dụng và không còn được hỗ trợ"
235
+ },
236
+ "invoice": {
237
+ "required": "Bắt buộc",
238
+ "invalid": "Số hóa đơn thống nhất Đài Loan không hợp lệ"
239
+ },
240
+ "licensePlate": {
241
+ "required": "Bắt buộc",
242
+ "invalid": "Số biển số xe Đài Loan không hợp lệ"
243
+ },
244
+ "bankAccount": {
245
+ "required": "Bắt buộc",
246
+ "invalid": "Tài khoản ngân hàng Đài Loan không hợp lệ",
247
+ "invalidBankCode": "Mã ngân hàng không hợp lệ",
248
+ "invalidAccountNumber": "Số tài khoản không hợp lệ"
249
+ },
250
+ "passport": {
251
+ "required": "Bắt buộc",
252
+ "invalid": "Số hộ chiếu Đài Loan không hợp lệ"
202
253
  }
203
254
  }
204
255
  }
@@ -75,6 +75,14 @@
75
75
  "maxLength": "长度最多 ${maxLength} 个字符",
76
76
  "numeric": "必须为数字 ID",
77
77
  "uuid": "必须为有效的 UUID",
78
+ "uuidv1": "必须为有效的 UUID v1",
79
+ "uuidv2": "必须为有效的 UUID v2",
80
+ "uuidv3": "必须为有效的 UUID v3",
81
+ "uuidv4": "必须为有效的 UUID v4",
82
+ "uuidv5": "必须为有效的 UUID v5",
83
+ "uuidv6": "必须为有效的 UUID v6",
84
+ "uuidv7": "必须为有效的 UUID v7",
85
+ "uuidv8": "必须为有效的 UUID v8",
78
86
  "objectId": "必须为有效的 MongoDB ObjectId",
79
87
  "nanoid": "必须为有效的 Nano ID",
80
88
  "snowflake": "必须为有效的 Snowflake ID",
@@ -162,6 +170,31 @@
162
170
  "videoOnly": "仅允许视频文件",
163
171
  "audioOnly": "仅允许音频文件",
164
172
  "archiveOnly": "仅允许压缩文件"
173
+ },
174
+ "creditCard": {
175
+ "required": "必填",
176
+ "invalid": "无效的信用卡号码",
177
+ "notInWhitelist": "信用卡号码不在允许的列表中"
178
+ },
179
+ "ip": {
180
+ "required": "必填",
181
+ "invalid": "无效的 IP 地址",
182
+ "notIPv4": "必须为有效的 IPv4 地址",
183
+ "notIPv6": "必须为有效的 IPv6 地址",
184
+ "notInWhitelist": "IP 地址不在允许的列表中"
185
+ },
186
+ "color": {
187
+ "required": "必填",
188
+ "invalid": "无效的颜色格式",
189
+ "notHex": "必须为有效的十六进制颜色",
190
+ "notRgb": "必须为有效的 RGB 颜色",
191
+ "notHsl": "必须为有效的 HSL 颜色"
192
+ },
193
+ "coordinate": {
194
+ "required": "必填",
195
+ "invalid": "无效的坐标",
196
+ "invalidLatitude": "纬度必须介于 -90 与 90 之间",
197
+ "invalidLongitude": "经度必须介于 -180 与 180 之间"
165
198
  }
166
199
  },
167
200
  "taiwan": {
@@ -199,6 +232,24 @@
199
232
  "format6Only": "仅允许 6 位邮政编码",
200
233
  "invalidSuffix": "无效的邮政编码后缀 - 5 位格式须为 01-99,6 位格式须为 001-999",
201
234
  "deprecated5Digit": "5 位邮政编码已弃用且不再支持"
235
+ },
236
+ "invoice": {
237
+ "required": "必填",
238
+ "invalid": "无效的统一发票号码"
239
+ },
240
+ "licensePlate": {
241
+ "required": "必填",
242
+ "invalid": "无效的车牌号码"
243
+ },
244
+ "bankAccount": {
245
+ "required": "必填",
246
+ "invalid": "无效的银行账号",
247
+ "invalidBankCode": "无效的银行代码",
248
+ "invalidAccountNumber": "无效的账号号码"
249
+ },
250
+ "passport": {
251
+ "required": "必填",
252
+ "invalid": "无效的护照号码"
202
253
  }
203
254
  }
204
255
  }
@@ -75,6 +75,14 @@
75
75
  "maxLength": "長度最多 ${maxLength} 字元",
76
76
  "numeric": "必須為數字 ID",
77
77
  "uuid": "必須為有效的 UUID",
78
+ "uuidv1": "必須為有效的 UUID v1",
79
+ "uuidv2": "必須為有效的 UUID v2",
80
+ "uuidv3": "必須為有效的 UUID v3",
81
+ "uuidv4": "必須為有效的 UUID v4",
82
+ "uuidv5": "必須為有效的 UUID v5",
83
+ "uuidv6": "必須為有效的 UUID v6",
84
+ "uuidv7": "必須為有效的 UUID v7",
85
+ "uuidv8": "必須為有效的 UUID v8",
78
86
  "objectId": "必須為有效的 MongoDB ObjectId",
79
87
  "nanoid": "必須為有效的 Nano ID",
80
88
  "snowflake": "必須為有效的 Snowflake ID",
@@ -162,6 +170,31 @@
162
170
  "videoOnly": "僅允許影片檔案",
163
171
  "audioOnly": "僅允許音訊檔案",
164
172
  "archiveOnly": "僅允許壓縮檔案"
173
+ },
174
+ "creditCard": {
175
+ "required": "必填",
176
+ "invalid": "無效的信用卡號碼",
177
+ "notInWhitelist": "信用卡號碼不在允許的清單中"
178
+ },
179
+ "ip": {
180
+ "required": "必填",
181
+ "invalid": "無效的 IP 位址",
182
+ "notIPv4": "必須為有效的 IPv4 位址",
183
+ "notIPv6": "必須為有效的 IPv6 位址",
184
+ "notInWhitelist": "IP 位址不在允許的清單中"
185
+ },
186
+ "color": {
187
+ "required": "必填",
188
+ "invalid": "無效的顏色格式",
189
+ "notHex": "必須為有效的十六進位顏色",
190
+ "notRgb": "必須為有效的 RGB 顏色",
191
+ "notHsl": "必須為有效的 HSL 顏色"
192
+ },
193
+ "coordinate": {
194
+ "required": "必填",
195
+ "invalid": "無效的座標",
196
+ "invalidLatitude": "緯度必須介於 -90 與 90 之間",
197
+ "invalidLongitude": "經度必須介於 -180 與 180 之間"
165
198
  }
166
199
  },
167
200
  "taiwan": {
@@ -199,6 +232,24 @@
199
232
  "format6Only": "僅允許 6 碼郵遞區號",
200
233
  "invalidSuffix": "無效的郵遞區號後碼 - 5 碼格式須為 01-99,6 碼格式須為 001-999",
201
234
  "deprecated5Digit": "5 碼郵遞區號已棄用且不再支援"
235
+ },
236
+ "invoice": {
237
+ "required": "必填",
238
+ "invalid": "無效的統一發票號碼"
239
+ },
240
+ "licensePlate": {
241
+ "required": "必填",
242
+ "invalid": "無效的車牌號碼"
243
+ },
244
+ "bankAccount": {
245
+ "required": "必填",
246
+ "invalid": "無效的銀行帳號",
247
+ "invalidBankCode": "無效的銀行代碼",
248
+ "invalidAccountNumber": "無效的帳號號碼"
249
+ },
250
+ "passport": {
251
+ "required": "必填",
252
+ "invalid": "無效的護照號碼"
202
253
  }
203
254
  }
204
255
  }
package/src/index.ts CHANGED
@@ -1,18 +1,26 @@
1
1
  export * from "./validators/common/boolean"
2
+ export * from "./validators/common/color"
3
+ export * from "./validators/common/coordinate"
4
+ export * from "./validators/common/credit-card"
2
5
  export * from "./validators/common/date"
3
6
  export * from "./validators/common/datetime"
4
7
  export * from "./validators/common/email"
5
8
  export * from "./validators/common/file"
6
9
  export * from "./validators/common/id"
10
+ export * from "./validators/common/ip"
7
11
  export * from "./validators/common/number"
8
12
  export * from "./validators/common/password"
9
13
  export * from "./validators/common/text"
10
14
  export * from "./validators/common/time"
11
15
  export * from "./validators/common/url"
16
+ export * from "./validators/taiwan/bank-account"
12
17
  export * from "./validators/taiwan/business-id"
13
- export * from "./validators/taiwan/national-id"
18
+ export * from "./validators/taiwan/fax"
19
+ export * from "./validators/taiwan/invoice"
20
+ export * from "./validators/taiwan/license-plate"
14
21
  export * from "./validators/taiwan/mobile"
22
+ export * from "./validators/taiwan/national-id"
23
+ export * from "./validators/taiwan/passport"
15
24
  export * from "./validators/taiwan/postal-code"
16
25
  export * from "./validators/taiwan/tel"
17
- export * from "./validators/taiwan/fax"
18
26
  export * from "./config"
@@ -0,0 +1,192 @@
1
+ import { z, ZodNullable, ZodString } from "zod"
2
+ import { t } from "../../i18n"
3
+ import { getLocale, type Locale } from "../../config"
4
+
5
+ export type ColorFormat = "hex" | "rgb" | "hsl" | "any"
6
+
7
+ export type ColorMessages = {
8
+ required?: string
9
+ invalid?: string
10
+ notHex?: string
11
+ notRgb?: string
12
+ notHsl?: string
13
+ }
14
+
15
+ export type ColorOptions<IsRequired extends boolean = true> = {
16
+ format?: ColorFormat | ColorFormat[]
17
+ allowAlpha?: boolean
18
+ transform?: (value: string) => string
19
+ defaultValue?: IsRequired extends true ? string : string | null
20
+ i18n?: Partial<Record<Locale, Partial<ColorMessages>>>
21
+ }
22
+
23
+ export type ColorSchema<IsRequired extends boolean> = IsRequired extends true ? ZodString : ZodNullable<ZodString>
24
+
25
+ const HEX_RGB = /^#[0-9a-f]{3}$/i
26
+ const HEX_RRGGBB = /^#[0-9a-f]{6}$/i
27
+ const HEX_RRGGBBAA = /^#[0-9a-f]{8}$/i
28
+
29
+ function isValidByte(n: number): boolean {
30
+ return Number.isInteger(n) && n >= 0 && n <= 255
31
+ }
32
+
33
+ function isValidAlpha(n: number): boolean {
34
+ return n >= 0 && n <= 1
35
+ }
36
+
37
+ function isValidHue(n: number): boolean {
38
+ return n >= 0 && n <= 360
39
+ }
40
+
41
+ function isValidPercentage(n: number): boolean {
42
+ return n >= 0 && n <= 100
43
+ }
44
+
45
+ function validateHex(value: string, allowAlpha: boolean): boolean {
46
+ if (HEX_RGB.test(value) || HEX_RRGGBB.test(value)) return true
47
+ if (allowAlpha && HEX_RRGGBBAA.test(value)) return true
48
+ return false
49
+ }
50
+
51
+ function validateRgb(value: string, allowAlpha: boolean): boolean {
52
+ const rgbaMatch = value.match(/^rgba?\(\s*(-?\d+(?:\.\d+)?)\s*,\s*(-?\d+(?:\.\d+)?)\s*,\s*(-?\d+(?:\.\d+)?)\s*(?:,\s*(-?\d+(?:\.\d+)?))?\s*\)$/)
53
+ if (!rgbaMatch) return false
54
+
55
+ const r = Number(rgbaMatch[1])
56
+ const g = Number(rgbaMatch[2])
57
+ const b = Number(rgbaMatch[3])
58
+ const a = rgbaMatch[4] !== undefined ? Number(rgbaMatch[4]) : undefined
59
+
60
+ if (!isValidByte(r) || !isValidByte(g) || !isValidByte(b)) return false
61
+
62
+ if (a !== undefined) {
63
+ if (!allowAlpha) return false
64
+ if (!isValidAlpha(a)) return false
65
+ }
66
+
67
+ // If function name is "rgba", alpha must be present
68
+ if (value.startsWith("rgba(") && a === undefined) return false
69
+ // If function name is "rgb", alpha must not be present
70
+ if (value.startsWith("rgb(") && a !== undefined) return false
71
+
72
+ return true
73
+ }
74
+
75
+ function validateHsl(value: string, allowAlpha: boolean): boolean {
76
+ const hslaMatch = value.match(/^hsla?\(\s*(-?\d+(?:\.\d+)?)\s*,\s*(-?\d+(?:\.\d+)?)%\s*,\s*(-?\d+(?:\.\d+)?)%\s*(?:,\s*(-?\d+(?:\.\d+)?))?\s*\)$/)
77
+ if (!hslaMatch) return false
78
+
79
+ const h = Number(hslaMatch[1])
80
+ const s = Number(hslaMatch[2])
81
+ const l = Number(hslaMatch[3])
82
+ const a = hslaMatch[4] !== undefined ? Number(hslaMatch[4]) : undefined
83
+
84
+ if (!isValidHue(h) || !isValidPercentage(s) || !isValidPercentage(l)) return false
85
+
86
+ if (a !== undefined) {
87
+ if (!allowAlpha) return false
88
+ if (!isValidAlpha(a)) return false
89
+ }
90
+
91
+ // If function name is "hsla", alpha must be present
92
+ if (value.startsWith("hsla(") && a === undefined) return false
93
+ // If function name is "hsl", alpha must not be present
94
+ if (value.startsWith("hsl(") && a !== undefined) return false
95
+
96
+ return true
97
+ }
98
+
99
+ function validateColor(value: string, formats: ColorFormat[], allowAlpha: boolean): { valid: boolean; failedFormat?: ColorFormat } {
100
+ const hasAny = formats.includes("any")
101
+
102
+ if (hasAny) {
103
+ if (validateHex(value, allowAlpha)) return { valid: true }
104
+ if (validateRgb(value, allowAlpha)) return { valid: true }
105
+ if (validateHsl(value, allowAlpha)) return { valid: true }
106
+ return { valid: false }
107
+ }
108
+
109
+ for (const format of formats) {
110
+ if (format === "hex" && validateHex(value, allowAlpha)) return { valid: true }
111
+ if (format === "rgb" && validateRgb(value, allowAlpha)) return { valid: true }
112
+ if (format === "hsl" && validateHsl(value, allowAlpha)) return { valid: true }
113
+ }
114
+
115
+ if (formats.length === 1) {
116
+ return { valid: false, failedFormat: formats[0] }
117
+ }
118
+
119
+ return { valid: false }
120
+ }
121
+
122
+ export function color<IsRequired extends boolean = false>(required?: IsRequired, options?: Omit<ColorOptions<IsRequired>, "required">): ColorSchema<IsRequired> {
123
+ const { format = "any", allowAlpha = true, transform, defaultValue, i18n } = options ?? {}
124
+
125
+ const isRequired = required ?? (false as IsRequired)
126
+
127
+ const actualDefaultValue = defaultValue ?? (isRequired ? "" : null)
128
+
129
+ const formats: ColorFormat[] = Array.isArray(format) ? format : [format]
130
+
131
+ const getMessage = (key: keyof ColorMessages, params?: Record<string, any>) => {
132
+ if (i18n) {
133
+ const currentLocale = getLocale()
134
+ const customMessages = i18n[currentLocale]
135
+ if (customMessages && customMessages[key]) {
136
+ const template = customMessages[key]!
137
+ return template.replace(/\$\{(\w+)}/g, (_, k) => params?.[k] ?? "")
138
+ }
139
+ }
140
+ return t(`common.color.${key}`, params)
141
+ }
142
+
143
+ const preprocessFn = (val: unknown) => {
144
+ if (val === "" || val === null || val === undefined) {
145
+ return actualDefaultValue
146
+ }
147
+
148
+ let processed = String(val).trim()
149
+
150
+ if (processed === "" && !required) {
151
+ return null
152
+ }
153
+
154
+ if (transform) {
155
+ processed = transform(processed)
156
+ }
157
+
158
+ return processed
159
+ }
160
+
161
+ const baseSchema = isRequired ? z.preprocess(preprocessFn, z.string()) : z.preprocess(preprocessFn, z.string().nullable())
162
+
163
+ const schema = baseSchema.superRefine((val, ctx) => {
164
+ if (val === null) return
165
+
166
+ if (isRequired && (val === "" || val === "null" || val === "undefined")) {
167
+ ctx.addIssue({ code: "custom", message: getMessage("required") })
168
+ return
169
+ }
170
+
171
+ if (!isRequired && val === "") return
172
+
173
+ const result = validateColor(val, formats, allowAlpha)
174
+
175
+ if (!result.valid) {
176
+ const formatMessageMap: Record<ColorFormat, keyof ColorMessages> = {
177
+ hex: "notHex",
178
+ rgb: "notRgb",
179
+ hsl: "notHsl",
180
+ any: "invalid",
181
+ }
182
+
183
+ const messageKey = result.failedFormat ? formatMessageMap[result.failedFormat] : "invalid"
184
+ ctx.addIssue({ code: "custom", message: getMessage(messageKey) })
185
+ return
186
+ }
187
+ })
188
+
189
+ return schema as unknown as ColorSchema<IsRequired>
190
+ }
191
+
192
+ export { validateColor }
@@ -0,0 +1,159 @@
1
+ import { z, ZodNullable, ZodString } from "zod"
2
+ import { t } from "../../i18n"
3
+ import { getLocale, type Locale } from "../../config"
4
+
5
+ export type CoordinateType = "latitude" | "longitude" | "pair"
6
+
7
+ export type CoordinateMessages = {
8
+ required?: string
9
+ invalid?: string
10
+ invalidLatitude?: string
11
+ invalidLongitude?: string
12
+ }
13
+
14
+ export type CoordinateOptions<IsRequired extends boolean = true> = {
15
+ type?: CoordinateType
16
+ precision?: number
17
+ transform?: (value: string) => string
18
+ defaultValue?: IsRequired extends true ? string : string | null
19
+ i18n?: Partial<Record<Locale, Partial<CoordinateMessages>>>
20
+ }
21
+
22
+ export type CoordinateSchema<IsRequired extends boolean> = IsRequired extends true ? ZodString : ZodNullable<ZodString>
23
+
24
+ export function validateLatitude(value: number): boolean {
25
+ return Number.isFinite(value) && value >= -90 && value <= 90
26
+ }
27
+
28
+ export function validateLongitude(value: number): boolean {
29
+ return Number.isFinite(value) && value >= -180 && value <= 180
30
+ }
31
+
32
+ function validatePrecision(value: number, precision: number): boolean {
33
+ const decimalPart = value.toString().split(".")[1]
34
+ if (!decimalPart) return true
35
+ return decimalPart.length <= precision
36
+ }
37
+
38
+ export function coordinate<IsRequired extends boolean = false>(
39
+ required?: IsRequired,
40
+ options?: Omit<CoordinateOptions<IsRequired>, "required">,
41
+ ): CoordinateSchema<IsRequired> {
42
+ const { type = "pair", precision, transform, defaultValue, i18n } = options ?? {}
43
+
44
+ const isRequired = required ?? (false as IsRequired)
45
+
46
+ const actualDefaultValue = defaultValue ?? (isRequired ? "" : null)
47
+
48
+ const getMessage = (key: keyof CoordinateMessages, params?: Record<string, any>) => {
49
+ if (i18n) {
50
+ const currentLocale = getLocale()
51
+ const customMessages = i18n[currentLocale]
52
+ if (customMessages && customMessages[key]) {
53
+ const template = customMessages[key]!
54
+ return template.replace(/\$\{(\w+)}/g, (_, k) => params?.[k] ?? "")
55
+ }
56
+ }
57
+ return t(`common.coordinate.${key}`, params)
58
+ }
59
+
60
+ const preprocessFn = (val: unknown) => {
61
+ if (val === "" || val === null || val === undefined) {
62
+ return actualDefaultValue
63
+ }
64
+
65
+ let processed = String(val).trim()
66
+
67
+ if (processed === "" && !required) {
68
+ return null
69
+ }
70
+
71
+ if (transform) {
72
+ processed = transform(processed)
73
+ }
74
+
75
+ return processed
76
+ }
77
+
78
+ const baseSchema = isRequired ? z.preprocess(preprocessFn, z.string()) : z.preprocess(preprocessFn, z.string().nullable())
79
+
80
+ const schema = baseSchema.superRefine((val, ctx) => {
81
+ if (val === null) return
82
+
83
+ if (isRequired && (val === "" || val === "null" || val === "undefined")) {
84
+ ctx.addIssue({ code: "custom", message: getMessage("required") })
85
+ return
86
+ }
87
+
88
+ if (!isRequired && val === "") return
89
+
90
+ if (type === "pair") {
91
+ const parts = val.split(",")
92
+ if (parts.length !== 2) {
93
+ ctx.addIssue({ code: "custom", message: getMessage("invalid") })
94
+ return
95
+ }
96
+
97
+ const lat = Number(parts[0].trim())
98
+ const lng = Number(parts[1].trim())
99
+
100
+ if (isNaN(lat) || isNaN(lng)) {
101
+ ctx.addIssue({ code: "custom", message: getMessage("invalid") })
102
+ return
103
+ }
104
+
105
+ if (!validateLatitude(lat)) {
106
+ ctx.addIssue({ code: "custom", message: getMessage("invalidLatitude") })
107
+ return
108
+ }
109
+
110
+ if (!validateLongitude(lng)) {
111
+ ctx.addIssue({ code: "custom", message: getMessage("invalidLongitude") })
112
+ return
113
+ }
114
+
115
+ if (precision !== undefined) {
116
+ if (!validatePrecision(lat, precision) || !validatePrecision(lng, precision)) {
117
+ ctx.addIssue({ code: "custom", message: getMessage("invalid") })
118
+ return
119
+ }
120
+ }
121
+ } else if (type === "latitude") {
122
+ const num = Number(val)
123
+
124
+ if (isNaN(num)) {
125
+ ctx.addIssue({ code: "custom", message: getMessage("invalidLatitude") })
126
+ return
127
+ }
128
+
129
+ if (!validateLatitude(num)) {
130
+ ctx.addIssue({ code: "custom", message: getMessage("invalidLatitude") })
131
+ return
132
+ }
133
+
134
+ if (precision !== undefined && !validatePrecision(num, precision)) {
135
+ ctx.addIssue({ code: "custom", message: getMessage("invalid") })
136
+ return
137
+ }
138
+ } else if (type === "longitude") {
139
+ const num = Number(val)
140
+
141
+ if (isNaN(num)) {
142
+ ctx.addIssue({ code: "custom", message: getMessage("invalidLongitude") })
143
+ return
144
+ }
145
+
146
+ if (!validateLongitude(num)) {
147
+ ctx.addIssue({ code: "custom", message: getMessage("invalidLongitude") })
148
+ return
149
+ }
150
+
151
+ if (precision !== undefined && !validatePrecision(num, precision)) {
152
+ ctx.addIssue({ code: "custom", message: getMessage("invalid") })
153
+ return
154
+ }
155
+ }
156
+ })
157
+
158
+ return schema as unknown as CoordinateSchema<IsRequired>
159
+ }