@hy_ong/zod-kit 0.2.1 → 0.2.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.
Files changed (139) hide show
  1. package/dist/{chunk-MCDESS3T.js → chunk-2JGRV3JO.js} +1 -1
  2. package/dist/{chunk-4LYZAO3P.js → chunk-3QLUXIY2.js} +1 -1
  3. package/dist/chunk-3ZF5JO3F.js +61 -0
  4. package/dist/{chunk-OP4KV3BY.cjs → chunk-4AQB4RSU.cjs} +3 -3
  5. package/dist/{chunk-UFNVCUPQ.cjs → chunk-53EEWALQ.cjs} +3 -3
  6. package/dist/{chunk-R5G4V7C6.cjs → chunk-5ZMTAI4G.cjs} +3 -3
  7. package/dist/{chunk-RKHX3DGH.js → chunk-6DFP7XY2.js} +1 -1
  8. package/dist/{chunk-P2NONIMS.js → chunk-6IAPM7BP.js} +1 -1
  9. package/dist/{chunk-AWV2IT66.js → chunk-7SKH66CM.js} +1 -1
  10. package/dist/chunk-7X3XPK6A.js +92 -0
  11. package/dist/{chunk-FVO4743A.cjs → chunk-A2GAEU4O.cjs} +3 -3
  12. package/dist/{chunk-P364KRO5.js → chunk-AI4U42JV.js} +1 -1
  13. package/dist/{chunk-5ZEKWPSE.cjs → chunk-AYCXAJRA.cjs} +3 -3
  14. package/dist/{chunk-5LS4DSRQ.cjs → chunk-BZSPJJYT.cjs} +3 -3
  15. package/dist/chunk-BZWQPSHO.cjs +92 -0
  16. package/dist/{chunk-KARFFIMP.js → chunk-D55YFP5R.js} +1 -1
  17. package/dist/{chunk-I2RJMDXN.js → chunk-E52GRXYY.js} +1 -1
  18. package/dist/{chunk-VDOAPLA6.cjs → chunk-EDTNS2XL.cjs} +3 -3
  19. package/dist/{chunk-YALLOVNO.cjs → chunk-FEL432I2.cjs} +3 -3
  20. package/dist/chunk-FMPHW7ID.cjs +61 -0
  21. package/dist/{chunk-OMFQ7Z63.cjs → chunk-FP4O2ICM.cjs} +3 -3
  22. package/dist/{chunk-AANSHH2O.cjs → chunk-G747FHUZ.cjs} +3 -3
  23. package/dist/{chunk-JZ2SHRGZ.js → chunk-G7FLGJYD.js} +1 -1
  24. package/dist/{chunk-LKPXHW5N.cjs → chunk-H25N5GP6.cjs} +3 -3
  25. package/dist/{chunk-FC6VDOC7.js → chunk-H6STFX4I.js} +11 -2
  26. package/dist/{chunk-MAQRXYE6.js → chunk-HQ4RAMSD.js} +1 -1
  27. package/dist/{chunk-6X22I6NQ.cjs → chunk-HZ2WESSL.cjs} +3 -3
  28. package/dist/{chunk-W2EWMV3A.cjs → chunk-IWR3H7IH.cjs} +3 -3
  29. package/dist/{chunk-5GAZQDVS.cjs → chunk-JZEF5Q3W.cjs} +13 -4
  30. package/dist/{chunk-77KZUPPN.cjs → chunk-KIUO2HIR.cjs} +3 -3
  31. package/dist/{chunk-OEK7QSQP.js → chunk-LBH5U2DZ.js} +1 -1
  32. package/dist/{chunk-PGSDXR2I.js → chunk-LC4RNKBM.js} +1 -1
  33. package/dist/{chunk-YAU6JCYL.cjs → chunk-LNWEJED7.cjs} +3 -3
  34. package/dist/{chunk-WWRFBLCR.cjs → chunk-LXFRQLH4.cjs} +3 -3
  35. package/dist/{chunk-B3U5G3AA.js → chunk-NWQSOSNF.js} +1 -1
  36. package/dist/{chunk-LIQSVJLS.js → chunk-OGU7AIZF.js} +1 -1
  37. package/dist/{chunk-NKCYXBGX.js → chunk-OKO6WO6M.js} +1 -1
  38. package/dist/{chunk-6OGDPSWT.js → chunk-OSUPJCBA.js} +1 -1
  39. package/dist/{chunk-ZBOQCXD4.js → chunk-POIDES2L.js} +190 -0
  40. package/dist/{chunk-MG25BEV4.cjs → chunk-Q24GYUTO.cjs} +3 -3
  41. package/dist/{chunk-YWV2BBXN.cjs → chunk-Q7TUNJD4.cjs} +190 -0
  42. package/dist/{chunk-DRXPGQM6.cjs → chunk-QQWX3ICK.cjs} +3 -3
  43. package/dist/{chunk-EAU42EVH.js → chunk-RFWCYULE.js} +1 -1
  44. package/dist/{chunk-VCRKYMJM.js → chunk-RHKBT3M2.js} +1 -1
  45. package/dist/{chunk-36NWHESN.js → chunk-RVGCMQ4J.js} +1 -1
  46. package/dist/{chunk-IJEEM3DI.js → chunk-T7PG4JDW.js} +1 -1
  47. package/dist/{chunk-JBNCMS42.cjs → chunk-TDEXEIHH.cjs} +3 -3
  48. package/dist/{chunk-VP5CCP5F.cjs → chunk-TPXRQT2H.cjs} +3 -3
  49. package/dist/{chunk-DPXRMSB2.js → chunk-TRQMRHFM.js} +1 -1
  50. package/dist/{chunk-ZFQQXWNB.js → chunk-U2PB6XEO.js} +1 -1
  51. package/dist/{chunk-G6DV7LX7.cjs → chunk-UCPKW43K.cjs} +3 -3
  52. package/dist/{chunk-PL2GERLG.cjs → chunk-V2KKGSKQ.cjs} +3 -3
  53. package/dist/{chunk-AI72FMOF.cjs → chunk-VKBNKPFO.cjs} +3 -3
  54. package/dist/{chunk-5OGW2ERW.js → chunk-XAN4CAVH.js} +1 -1
  55. package/dist/{chunk-CFFCBWYL.cjs → chunk-ZCX22PY4.cjs} +3 -3
  56. package/dist/{chunk-TSHL7ZO2.js → chunk-ZXPRRNZR.js} +1 -1
  57. package/dist/common/boolean.cjs +3 -3
  58. package/dist/common/boolean.js +2 -2
  59. package/dist/common/color.cjs +3 -3
  60. package/dist/common/color.js +2 -2
  61. package/dist/common/coordinate.cjs +3 -3
  62. package/dist/common/coordinate.js +2 -2
  63. package/dist/common/credit-card.cjs +3 -3
  64. package/dist/common/credit-card.js +2 -2
  65. package/dist/common/date.cjs +3 -3
  66. package/dist/common/date.js +2 -2
  67. package/dist/common/datetime.cjs +3 -3
  68. package/dist/common/datetime.js +2 -2
  69. package/dist/common/email.cjs +3 -3
  70. package/dist/common/email.js +2 -2
  71. package/dist/common/file.cjs +3 -3
  72. package/dist/common/file.js +2 -2
  73. package/dist/common/id.cjs +3 -3
  74. package/dist/common/id.d.cts +34 -8
  75. package/dist/common/id.d.ts +34 -8
  76. package/dist/common/id.js +2 -2
  77. package/dist/common/ip.cjs +3 -3
  78. package/dist/common/ip.js +2 -2
  79. package/dist/common/many-of.cjs +7 -0
  80. package/dist/common/many-of.d.cts +111 -0
  81. package/dist/common/many-of.d.ts +111 -0
  82. package/dist/common/many-of.js +7 -0
  83. package/dist/common/number.cjs +3 -3
  84. package/dist/common/number.js +2 -2
  85. package/dist/common/one-of.cjs +7 -0
  86. package/dist/common/one-of.d.cts +104 -0
  87. package/dist/common/one-of.d.ts +104 -0
  88. package/dist/common/one-of.js +7 -0
  89. package/dist/common/password.cjs +3 -3
  90. package/dist/common/password.js +2 -2
  91. package/dist/common/text.cjs +3 -3
  92. package/dist/common/text.js +2 -2
  93. package/dist/common/time.cjs +3 -3
  94. package/dist/common/time.js +2 -2
  95. package/dist/common/url.cjs +3 -3
  96. package/dist/common/url.js +2 -2
  97. package/dist/index.cjs +35 -27
  98. package/dist/index.d.cts +2 -0
  99. package/dist/index.d.ts +2 -0
  100. package/dist/index.js +46 -38
  101. package/dist/taiwan/bank-account.cjs +3 -3
  102. package/dist/taiwan/bank-account.js +2 -2
  103. package/dist/taiwan/business-id.cjs +3 -3
  104. package/dist/taiwan/business-id.js +2 -2
  105. package/dist/taiwan/fax.cjs +3 -3
  106. package/dist/taiwan/fax.js +2 -2
  107. package/dist/taiwan/invoice.cjs +3 -3
  108. package/dist/taiwan/invoice.js +2 -2
  109. package/dist/taiwan/license-plate.cjs +3 -3
  110. package/dist/taiwan/license-plate.js +2 -2
  111. package/dist/taiwan/mobile.cjs +3 -3
  112. package/dist/taiwan/mobile.js +2 -2
  113. package/dist/taiwan/national-id.cjs +3 -3
  114. package/dist/taiwan/national-id.js +2 -2
  115. package/dist/taiwan/passport.cjs +3 -3
  116. package/dist/taiwan/passport.js +2 -2
  117. package/dist/taiwan/postal-code.cjs +3 -3
  118. package/dist/taiwan/postal-code.js +2 -2
  119. package/dist/taiwan/tel.cjs +3 -3
  120. package/dist/taiwan/tel.js +2 -2
  121. package/package.json +15 -5
  122. package/src/i18n/locales/en-GB.json +19 -0
  123. package/src/i18n/locales/en-US.json +20 -1
  124. package/src/i18n/locales/id-ID.json +19 -0
  125. package/src/i18n/locales/ja-JP.json +19 -0
  126. package/src/i18n/locales/ko-KR.json +19 -0
  127. package/src/i18n/locales/ms-MY.json +19 -0
  128. package/src/i18n/locales/th-TH.json +19 -0
  129. package/src/i18n/locales/vi-VN.json +19 -0
  130. package/src/i18n/locales/zh-CN.json +19 -0
  131. package/src/i18n/locales/zh-TW.json +19 -0
  132. package/src/index.ts +2 -0
  133. package/src/validators/common/id.ts +45 -3
  134. package/src/validators/common/many-of.ts +219 -0
  135. package/src/validators/common/one-of.ts +172 -0
  136. package/tests/common/id.test.ts +68 -3
  137. package/tests/common/many-of.test.ts +198 -0
  138. package/tests/common/one-of.test.ts +136 -0
  139. package/tsup.config.ts +2 -0
@@ -17,6 +17,13 @@
17
17
  "businessOnly": "ビジネスメールアドレスのみ使用可能です",
18
18
  "noDisposable": "使い捨てメールアドレスは使用できません"
19
19
  },
20
+ "manyOf": {
21
+ "required": "必須項目です",
22
+ "invalid": "次のいずれかである必要があります:${values}",
23
+ "minSelect": "${min}個以上選択してください",
24
+ "maxSelect": "${max}個以下で選択してください",
25
+ "duplicate": "重複した値は許可されていません"
26
+ },
20
27
  "url": {
21
28
  "required": "必須項目です",
22
29
  "invalid": "無効なURL形式です",
@@ -68,6 +75,10 @@
68
75
  "finite": "有限の数である必要があります",
69
76
  "precision": "小数点以下は ${precision} 桁以内である必要があります"
70
77
  },
78
+ "oneOf": {
79
+ "required": "必須項目です",
80
+ "invalid": "次のいずれかである必要があります:${values}"
81
+ },
71
82
  "id": {
72
83
  "required": "必須項目です",
73
84
  "invalid": "無効なID形式です",
@@ -75,6 +86,14 @@
75
86
  "maxLength": "${maxLength}文字以下で入力してください",
76
87
  "numeric": "数値IDである必要があります",
77
88
  "uuid": "有効なUUIDである必要があります",
89
+ "uuidv1": "有効なUUID v1である必要があります",
90
+ "uuidv2": "有効なUUID v2である必要があります",
91
+ "uuidv3": "有効なUUID v3である必要があります",
92
+ "uuidv4": "有効なUUID v4である必要があります",
93
+ "uuidv5": "有効なUUID v5である必要があります",
94
+ "uuidv6": "有効なUUID v6である必要があります",
95
+ "uuidv7": "有効なUUID v7である必要があります",
96
+ "uuidv8": "有効なUUID v8である必要があります",
78
97
  "objectId": "有効なMongoDB ObjectIdである必要があります",
79
98
  "nanoid": "有効なNano IDである必要があります",
80
99
  "snowflake": "有効なSnowflake IDである必要があります",
@@ -17,6 +17,13 @@
17
17
  "businessOnly": "비즈니스 이메일 주소만 허용됩니다",
18
18
  "noDisposable": "일회용 이메일 주소는 허용되지 않습니다"
19
19
  },
20
+ "manyOf": {
21
+ "required": "필수 항목입니다",
22
+ "invalid": "다음 중 하나여야 합니다: ${values}",
23
+ "minSelect": "최소 ${min}개를 선택해야 합니다",
24
+ "maxSelect": "최대 ${max}개까지 선택할 수 있습니다",
25
+ "duplicate": "중복된 값은 허용되지 않습니다"
26
+ },
20
27
  "url": {
21
28
  "required": "필수 항목입니다",
22
29
  "invalid": "유효하지 않은 URL 형식입니다",
@@ -68,6 +75,10 @@
68
75
  "finite": "유한한 수여야 합니다",
69
76
  "precision": "소수점 이하 ${precision}자리 이내여야 합니다"
70
77
  },
78
+ "oneOf": {
79
+ "required": "필수 항목입니다",
80
+ "invalid": "다음 중 하나여야 합니다: ${values}"
81
+ },
71
82
  "id": {
72
83
  "required": "필수 항목입니다",
73
84
  "invalid": "유효하지 않은 ID 형식입니다",
@@ -75,6 +86,14 @@
75
86
  "maxLength": "최대 ${maxLength}자 이하여야 합니다",
76
87
  "numeric": "숫자 ID여야 합니다",
77
88
  "uuid": "유효한 UUID여야 합니다",
89
+ "uuidv1": "유효한 UUID v1이어야 합니다",
90
+ "uuidv2": "유효한 UUID v2이어야 합니다",
91
+ "uuidv3": "유효한 UUID v3이어야 합니다",
92
+ "uuidv4": "유효한 UUID v4이어야 합니다",
93
+ "uuidv5": "유효한 UUID v5이어야 합니다",
94
+ "uuidv6": "유효한 UUID v6이어야 합니다",
95
+ "uuidv7": "유효한 UUID v7이어야 합니다",
96
+ "uuidv8": "유효한 UUID v8이어야 합니다",
78
97
  "objectId": "유효한 MongoDB ObjectId여야 합니다",
79
98
  "nanoid": "유효한 Nano ID여야 합니다",
80
99
  "snowflake": "유효한 Snowflake ID여야 합니다",
@@ -17,6 +17,13 @@
17
17
  "businessOnly": "Hanya alamat e-mel perniagaan dibenarkan",
18
18
  "noDisposable": "Alamat e-mel pakai buang tidak dibenarkan"
19
19
  },
20
+ "manyOf": {
21
+ "required": "Diperlukan",
22
+ "invalid": "Mesti daripada: ${values}",
23
+ "minSelect": "Mesti pilih sekurang-kurangnya ${min} item",
24
+ "maxSelect": "Mesti pilih paling banyak ${max} item",
25
+ "duplicate": "Nilai pendua tidak dibenarkan"
26
+ },
20
27
  "url": {
21
28
  "required": "Wajib diisi",
22
29
  "invalid": "Format URL tidak sah",
@@ -68,6 +75,10 @@
68
75
  "finite": "Mesti nombor terhingga",
69
76
  "precision": "Mesti tidak melebihi ${precision} tempat perpuluhan"
70
77
  },
78
+ "oneOf": {
79
+ "required": "Diperlukan",
80
+ "invalid": "Mesti salah satu daripada: ${values}"
81
+ },
71
82
  "id": {
72
83
  "required": "Wajib diisi",
73
84
  "invalid": "Format ID tidak sah",
@@ -75,6 +86,14 @@
75
86
  "maxLength": "Mesti tidak melebihi ${maxLength} aksara",
76
87
  "numeric": "Mesti ID numerik",
77
88
  "uuid": "Mesti UUID yang sah",
89
+ "uuidv1": "Mesti UUID v1 yang sah",
90
+ "uuidv2": "Mesti UUID v2 yang sah",
91
+ "uuidv3": "Mesti UUID v3 yang sah",
92
+ "uuidv4": "Mesti UUID v4 yang sah",
93
+ "uuidv5": "Mesti UUID v5 yang sah",
94
+ "uuidv6": "Mesti UUID v6 yang sah",
95
+ "uuidv7": "Mesti UUID v7 yang sah",
96
+ "uuidv8": "Mesti UUID v8 yang sah",
78
97
  "objectId": "Mesti MongoDB ObjectId yang sah",
79
98
  "nanoid": "Mesti Nano ID yang sah",
80
99
  "snowflake": "Mesti Snowflake ID yang sah",
@@ -17,6 +17,13 @@
17
17
  "businessOnly": "อนุญาตเฉพาะอีเมลธุรกิจเท่านั้น",
18
18
  "noDisposable": "ไม่อนุญาตอีเมลแบบใช้แล้วทิ้ง"
19
19
  },
20
+ "manyOf": {
21
+ "required": "จำเป็น",
22
+ "invalid": "ต้องเป็นหนึ่งใน: ${values}",
23
+ "minSelect": "ต้องเลือกอย่างน้อย ${min} รายการ",
24
+ "maxSelect": "เลือกได้มากสุด ${max} รายการ",
25
+ "duplicate": "ไม่อนุญาตให้มีค่าซ้ำ"
26
+ },
20
27
  "url": {
21
28
  "required": "จำเป็นต้องกรอก",
22
29
  "invalid": "รูปแบบ URL ไม่ถูกต้อง",
@@ -68,6 +75,10 @@
68
75
  "finite": "ต้องเป็นจำนวนจำกัด",
69
76
  "precision": "ต้องมีทศนิยมไม่เกิน ${precision} ตำแหน่ง"
70
77
  },
78
+ "oneOf": {
79
+ "required": "จำเป็น",
80
+ "invalid": "ต้องเป็นหนึ่งใน: ${values}"
81
+ },
71
82
  "id": {
72
83
  "required": "จำเป็นต้องกรอก",
73
84
  "invalid": "รูปแบบ ID ไม่ถูกต้อง",
@@ -75,6 +86,14 @@
75
86
  "maxLength": "ต้องไม่เกิน ${maxLength} ตัวอักษร",
76
87
  "numeric": "ต้องเป็น ID ตัวเลข",
77
88
  "uuid": "ต้องเป็น UUID ที่ถูกต้อง",
89
+ "uuidv1": "ต้องเป็น UUID v1 ที่ถูกต้อง",
90
+ "uuidv2": "ต้องเป็น UUID v2 ที่ถูกต้อง",
91
+ "uuidv3": "ต้องเป็น UUID v3 ที่ถูกต้อง",
92
+ "uuidv4": "ต้องเป็น UUID v4 ที่ถูกต้อง",
93
+ "uuidv5": "ต้องเป็น UUID v5 ที่ถูกต้อง",
94
+ "uuidv6": "ต้องเป็น UUID v6 ที่ถูกต้อง",
95
+ "uuidv7": "ต้องเป็น UUID v7 ที่ถูกต้อง",
96
+ "uuidv8": "ต้องเป็น UUID v8 ที่ถูกต้อง",
78
97
  "objectId": "ต้องเป็น MongoDB ObjectId ที่ถูกต้อง",
79
98
  "nanoid": "ต้องเป็น Nano ID ที่ถูกต้อง",
80
99
  "snowflake": "ต้องเป็น Snowflake ID ที่ถูกต้อง",
@@ -17,6 +17,13 @@
17
17
  "businessOnly": "Chỉ cho phép địa chỉ email doanh nghiệp",
18
18
  "noDisposable": "Không cho phép địa chỉ email dùng một lần"
19
19
  },
20
+ "manyOf": {
21
+ "required": "Bắt buộc",
22
+ "invalid": "Phải thuộc: ${values}",
23
+ "minSelect": "Phải chọn ít nhất ${min} mục",
24
+ "maxSelect": "Chỉ được chọn tối đa ${max} mục",
25
+ "duplicate": "Không được phép có giá trị trùng lặp"
26
+ },
20
27
  "url": {
21
28
  "required": "Bắt buộc",
22
29
  "invalid": "Định dạng URL không hợp lệ",
@@ -68,6 +75,10 @@
68
75
  "finite": "Phải là số hữu hạn",
69
76
  "precision": "Phải có tối đa ${precision} chữ số thập phân"
70
77
  },
78
+ "oneOf": {
79
+ "required": "Bắt buộc",
80
+ "invalid": "Phải là một trong: ${values}"
81
+ },
71
82
  "id": {
72
83
  "required": "Bắt buộc",
73
84
  "invalid": "Định dạng ID không hợp lệ",
@@ -75,6 +86,14 @@
75
86
  "maxLength": "Phải có tối đa ${maxLength} ký tự",
76
87
  "numeric": "Phải là ID dạng số",
77
88
  "uuid": "Phải là UUID hợp lệ",
89
+ "uuidv1": "Phải là UUID v1 hợp lệ",
90
+ "uuidv2": "Phải là UUID v2 hợp lệ",
91
+ "uuidv3": "Phải là UUID v3 hợp lệ",
92
+ "uuidv4": "Phải là UUID v4 hợp lệ",
93
+ "uuidv5": "Phải là UUID v5 hợp lệ",
94
+ "uuidv6": "Phải là UUID v6 hợp lệ",
95
+ "uuidv7": "Phải là UUID v7 hợp lệ",
96
+ "uuidv8": "Phải là UUID v8 hợp lệ",
78
97
  "objectId": "Phải là MongoDB ObjectId hợp lệ",
79
98
  "nanoid": "Phải là Nano ID hợp lệ",
80
99
  "snowflake": "Phải là Snowflake ID hợp lệ",
@@ -17,6 +17,13 @@
17
17
  "businessOnly": "仅允许企业邮箱地址",
18
18
  "noDisposable": "不允许使用临时邮箱地址"
19
19
  },
20
+ "manyOf": {
21
+ "required": "必填",
22
+ "invalid": "必须为以下值之一:${values}",
23
+ "minSelect": "至少需选择 ${min} 项",
24
+ "maxSelect": "最多只能选择 ${max} 项",
25
+ "duplicate": "不允许重复的值"
26
+ },
20
27
  "url": {
21
28
  "required": "必填",
22
29
  "invalid": "无效的 URL 格式",
@@ -68,6 +75,10 @@
68
75
  "finite": "必须为有限数字",
69
76
  "precision": "小数位数不可超过 ${precision} 位"
70
77
  },
78
+ "oneOf": {
79
+ "required": "必填",
80
+ "invalid": "必须为以下其中一个值:${values}"
81
+ },
71
82
  "id": {
72
83
  "required": "必填",
73
84
  "invalid": "无效的 ID 格式",
@@ -75,6 +86,14 @@
75
86
  "maxLength": "长度最多 ${maxLength} 个字符",
76
87
  "numeric": "必须为数字 ID",
77
88
  "uuid": "必须为有效的 UUID",
89
+ "uuidv1": "必须为有效的 UUID v1",
90
+ "uuidv2": "必须为有效的 UUID v2",
91
+ "uuidv3": "必须为有效的 UUID v3",
92
+ "uuidv4": "必须为有效的 UUID v4",
93
+ "uuidv5": "必须为有效的 UUID v5",
94
+ "uuidv6": "必须为有效的 UUID v6",
95
+ "uuidv7": "必须为有效的 UUID v7",
96
+ "uuidv8": "必须为有效的 UUID v8",
78
97
  "objectId": "必须为有效的 MongoDB ObjectId",
79
98
  "nanoid": "必须为有效的 Nano ID",
80
99
  "snowflake": "必须为有效的 Snowflake ID",
@@ -17,6 +17,13 @@
17
17
  "businessOnly": "僅允許企業郵箱地址",
18
18
  "noDisposable": "不允許使用臨時郵箱地址"
19
19
  },
20
+ "manyOf": {
21
+ "required": "必填",
22
+ "invalid": "必須為以下值之一:${values}",
23
+ "minSelect": "至少需選擇 ${min} 項",
24
+ "maxSelect": "最多只能選擇 ${max} 項",
25
+ "duplicate": "不允許重複的值"
26
+ },
20
27
  "url": {
21
28
  "required": "必填",
22
29
  "invalid": "無效的 URL 格式",
@@ -68,6 +75,10 @@
68
75
  "finite": "必須為有限數字",
69
76
  "precision": "小數位數不可超過 ${precision} 位"
70
77
  },
78
+ "oneOf": {
79
+ "required": "必填",
80
+ "invalid": "必須為以下其中一個值:${values}"
81
+ },
71
82
  "id": {
72
83
  "required": "必填",
73
84
  "invalid": "無效的 ID 格式",
@@ -75,6 +86,14 @@
75
86
  "maxLength": "長度最多 ${maxLength} 字元",
76
87
  "numeric": "必須為數字 ID",
77
88
  "uuid": "必須為有效的 UUID",
89
+ "uuidv1": "必須為有效的 UUID v1",
90
+ "uuidv2": "必須為有效的 UUID v2",
91
+ "uuidv3": "必須為有效的 UUID v3",
92
+ "uuidv4": "必須為有效的 UUID v4",
93
+ "uuidv5": "必須為有效的 UUID v5",
94
+ "uuidv6": "必須為有效的 UUID v6",
95
+ "uuidv7": "必須為有效的 UUID v7",
96
+ "uuidv8": "必須為有效的 UUID v8",
78
97
  "objectId": "必須為有效的 MongoDB ObjectId",
79
98
  "nanoid": "必須為有效的 Nano ID",
80
99
  "snowflake": "必須為有效的 Snowflake ID",
package/src/index.ts CHANGED
@@ -7,8 +7,10 @@ export * from "./validators/common/datetime"
7
7
  export * from "./validators/common/email"
8
8
  export * from "./validators/common/file"
9
9
  export * from "./validators/common/id"
10
+ export * from "./validators/common/many-of"
10
11
  export * from "./validators/common/ip"
11
12
  export * from "./validators/common/number"
13
+ export * from "./validators/common/one-of"
12
14
  export * from "./validators/common/password"
13
15
  export * from "./validators/common/text"
14
16
  export * from "./validators/common/time"
@@ -22,6 +22,14 @@ import { getLocale, type Locale } from "../../config"
22
22
  * @property {string} [maxLength] - Message when ID is too long
23
23
  * @property {string} [numeric] - Message when numeric ID format is invalid
24
24
  * @property {string} [uuid] - Message when UUID format is invalid
25
+ * @property {string} [uuidv1] - Message when UUID v1 format is invalid
26
+ * @property {string} [uuidv2] - Message when UUID v2 format is invalid
27
+ * @property {string} [uuidv3] - Message when UUID v3 format is invalid
28
+ * @property {string} [uuidv4] - Message when UUID v4 format is invalid
29
+ * @property {string} [uuidv5] - Message when UUID v5 format is invalid
30
+ * @property {string} [uuidv6] - Message when UUID v6 format is invalid
31
+ * @property {string} [uuidv7] - Message when UUID v7 format is invalid
32
+ * @property {string} [uuidv8] - Message when UUID v8 format is invalid
25
33
  * @property {string} [objectId] - Message when MongoDB ObjectId format is invalid
26
34
  * @property {string} [nanoid] - Message when Nano ID format is invalid
27
35
  * @property {string} [snowflake] - Message when Snowflake ID format is invalid
@@ -41,6 +49,14 @@ export type IdMessages = {
41
49
  maxLength?: string
42
50
  numeric?: string
43
51
  uuid?: string
52
+ uuidv1?: string
53
+ uuidv2?: string
54
+ uuidv3?: string
55
+ uuidv4?: string
56
+ uuidv5?: string
57
+ uuidv6?: string
58
+ uuidv7?: string
59
+ uuidv8?: string
44
60
  objectId?: string
45
61
  nanoid?: string
46
62
  snowflake?: string
@@ -61,7 +77,15 @@ export type IdMessages = {
61
77
  *
62
78
  * Available types:
63
79
  * - numeric: Pure numeric IDs (1, 123, 999999)
64
- * - uuid: UUID v4 format (xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx)
80
+ * - uuid: UUID any version (v1–v8)
81
+ * - uuidv1: UUID v1 (timestamp-based)
82
+ * - uuidv2: UUID v2 (DCE security)
83
+ * - uuidv3: UUID v3 (MD5 name-based)
84
+ * - uuidv4: UUID v4 (random)
85
+ * - uuidv5: UUID v5 (SHA-1 name-based)
86
+ * - uuidv6: UUID v6 (reordered timestamp, RFC 9562)
87
+ * - uuidv7: UUID v7 (Unix timestamp, sortable, RFC 9562)
88
+ * - uuidv8: UUID v8 (custom, RFC 9562)
65
89
  * - objectId: MongoDB ObjectId (24-character hexadecimal)
66
90
  * - nanoid: Nano ID format (21-character URL-safe)
67
91
  * - snowflake: Twitter Snowflake (19-digit number)
@@ -72,7 +96,15 @@ export type IdMessages = {
72
96
  */
73
97
  export type IdType =
74
98
  | "numeric" // Pure numeric IDs (1, 123, 999999)
75
- | "uuid" // UUID v4 format
99
+ | "uuid" // UUID any version (v1–v8)
100
+ | "uuidv1" // UUID v1 (timestamp-based)
101
+ | "uuidv2" // UUID v2 (DCE security)
102
+ | "uuidv3" // UUID v3 (MD5 name-based)
103
+ | "uuidv4" // UUID v4 (random)
104
+ | "uuidv5" // UUID v5 (SHA-1 name-based)
105
+ | "uuidv6" // UUID v6 (reordered timestamp, RFC 9562)
106
+ | "uuidv7" // UUID v7 (Unix timestamp, sortable, RFC 9562)
107
+ | "uuidv8" // UUID v8 (custom, RFC 9562)
76
108
  | "objectId" // MongoDB ObjectId (24-character hexadecimal)
77
109
  | "nanoid" // Nano ID
78
110
  | "snowflake" // Twitter Snowflake (19-digit number)
@@ -143,9 +175,19 @@ export type IdSchema<IsRequired extends boolean, Type extends IdType | undefined
143
175
  * @constant {Record<string, RegExp>} ID_PATTERNS
144
176
  * @description Maps each ID type to its corresponding regex pattern
145
177
  */
178
+ const UUID_BASE = (version: string) => new RegExp(`^[0-9a-f]{8}-[0-9a-f]{4}-${version}[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$`, "i")
179
+
146
180
  const ID_PATTERNS = {
147
181
  numeric: /^\d+$/,
148
- uuid: /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i,
182
+ uuid: /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i,
183
+ uuidv1: UUID_BASE("1"),
184
+ uuidv2: UUID_BASE("2"),
185
+ uuidv3: UUID_BASE("3"),
186
+ uuidv4: UUID_BASE("4"),
187
+ uuidv5: UUID_BASE("5"),
188
+ uuidv6: UUID_BASE("6"),
189
+ uuidv7: UUID_BASE("7"),
190
+ uuidv8: UUID_BASE("8"),
149
191
  objectId: /^[0-9a-f]{24}$/i,
150
192
  nanoid: /^[A-Za-z0-9_-]{21}$/,
151
193
  snowflake: /^\d{19}$/,
@@ -0,0 +1,219 @@
1
+ /**
2
+ * @fileoverview ManyOf validator for Zod Kit
3
+ *
4
+ * Provides multi-select validation that restricts input to an array of values
5
+ * from a predefined set, with min/max selection, duplicate control, and transformation.
6
+ *
7
+ * @author Ong Hoe Yuan
8
+ * @version 0.2.2
9
+ */
10
+
11
+ import { z, ZodType } from "zod"
12
+ import { t } from "../../i18n"
13
+ import { getLocale, type Locale } from "../../config"
14
+
15
+ /**
16
+ * Type definition for manyOf validation error messages
17
+ *
18
+ * @interface ManyOfMessages
19
+ * @property {string} [required] - Message when field is required but empty
20
+ * @property {string} [invalid] - Message when a value is not in the allowed list
21
+ * @property {string} [minSelect] - Message when fewer than minimum items are selected
22
+ * @property {string} [maxSelect] - Message when more than maximum items are selected
23
+ * @property {string} [duplicate] - Message when duplicate values are found
24
+ */
25
+ export type ManyOfMessages = {
26
+ required?: string
27
+ invalid?: string
28
+ minSelect?: string
29
+ maxSelect?: string
30
+ duplicate?: string
31
+ }
32
+
33
+ /**
34
+ * Configuration options for manyOf validation
35
+ *
36
+ * @template IsRequired - Whether the field is required (affects return type)
37
+ * @template T - The type of allowed values
38
+ *
39
+ * @interface ManyOfOptions
40
+ * @property {T[]} values - Array of allowed values
41
+ * @property {T[] | null} [defaultValue] - Default value when input is empty
42
+ * @property {number} [min] - Minimum number of selections
43
+ * @property {number} [max] - Maximum number of selections
44
+ * @property {boolean} [allowDuplicates=false] - Whether to allow duplicate selections
45
+ * @property {boolean} [caseSensitive=true] - Whether string matching is case-sensitive
46
+ * @property {Function} [transform] - Custom transformation function applied to each value
47
+ * @property {Record<Locale, ManyOfMessages>} [i18n] - Custom error messages for different locales
48
+ */
49
+ export type ManyOfOptions<IsRequired extends boolean = true, T extends string | number = string | number> = {
50
+ values: T[]
51
+ defaultValue?: IsRequired extends true ? T[] : T[] | null
52
+ min?: number
53
+ max?: number
54
+ allowDuplicates?: boolean
55
+ caseSensitive?: boolean
56
+ transform?: (value: T[]) => T[]
57
+ i18n?: Partial<Record<Locale, Partial<ManyOfMessages>>>
58
+ }
59
+
60
+ /**
61
+ * Type alias for manyOf validation schema based on required flag
62
+ *
63
+ * @template IsRequired - Whether the field is required
64
+ * @template T - The type of allowed values
65
+ */
66
+ export type ManyOfSchema<IsRequired extends boolean, T> = IsRequired extends true ? ZodType<T[]> : ZodType<T[] | null>
67
+
68
+ /**
69
+ * Creates a Zod schema for multi-select validation that restricts values to a predefined set
70
+ *
71
+ * @template IsRequired - Whether the field is required (affects return type)
72
+ * @template T - The type of allowed values (string | number)
73
+ * @param {IsRequired} [required=false] - Whether the field is required
74
+ * @param {ManyOfOptions<IsRequired, T>} options - Configuration options (values is required)
75
+ * @returns {ManyOfSchema<IsRequired, T>} Zod schema for manyOf validation
76
+ *
77
+ * @example
78
+ * ```typescript
79
+ * // Basic multi-select (optional by default)
80
+ * const tagsSchema = manyOf(false, { values: ["js", "ts", "rust", "go"] })
81
+ * tagsSchema.parse(["js", "ts"]) // ✓ ["js", "ts"]
82
+ * tagsSchema.parse(null) // ✓ null
83
+ *
84
+ * // Required with min/max
85
+ * const skillsSchema = manyOf(true, {
86
+ * values: ["react", "vue", "angular", "svelte"],
87
+ * min: 1,
88
+ * max: 3,
89
+ * })
90
+ * skillsSchema.parse(["react"]) // ✓
91
+ * skillsSchema.parse([]) // ✗ minSelect
92
+ * skillsSchema.parse(["react", "vue", "angular", "svelte"]) // ✗ maxSelect
93
+ *
94
+ * // Case-insensitive matching
95
+ * const colorsSchema = manyOf(true, {
96
+ * values: ["red", "green", "blue"],
97
+ * caseSensitive: false,
98
+ * })
99
+ * colorsSchema.parse(["RED", "Green"]) // ✓ ["red", "green"]
100
+ *
101
+ * // No duplicates (default)
102
+ * const rolesSchema = manyOf(true, { values: ["admin", "editor", "viewer"] })
103
+ * rolesSchema.parse(["admin", "admin"]) // ✗ duplicate
104
+ *
105
+ * // Allow duplicates
106
+ * const itemsSchema = manyOf(true, {
107
+ * values: [1, 2, 3],
108
+ * allowDuplicates: true,
109
+ * })
110
+ * itemsSchema.parse([1, 1, 2]) // ✓ [1, 1, 2]
111
+ * ```
112
+ */
113
+ export function manyOf<IsRequired extends boolean = false, T extends string | number = string | number>(
114
+ required?: IsRequired,
115
+ options?: Omit<ManyOfOptions<IsRequired, T>, "required">,
116
+ ): ManyOfSchema<IsRequired, T> {
117
+ const { values = [] as unknown as T[], defaultValue = null, min, max, allowDuplicates = false, caseSensitive = true, transform, i18n } = options ?? {}
118
+
119
+ const isRequired = required ?? (false as IsRequired)
120
+
121
+ const getMessage = (key: keyof ManyOfMessages, params?: Record<string, any>) => {
122
+ if (i18n) {
123
+ const currentLocale = getLocale()
124
+ const customMessages = i18n[currentLocale]
125
+ if (customMessages && customMessages[key]) {
126
+ const template = customMessages[key]!
127
+ return template.replace(/\$\{(\w+)}/g, (_, k) => params?.[k] ?? "")
128
+ }
129
+ }
130
+ return t(`common.manyOf.${key}`, params)
131
+ }
132
+
133
+ const normalizeItem = (item: unknown): unknown => {
134
+ // Coerce number strings to numbers when values contains numbers
135
+ const hasNumbers = values.some((v) => typeof v === "number")
136
+ if (hasNumbers && typeof item === "string" && !isNaN(Number(item)) && item.trim() !== "") {
137
+ const numVal = Number(item)
138
+ if ((values as number[]).includes(numVal)) return numVal
139
+ }
140
+
141
+ // Case-insensitive normalization
142
+ if (!caseSensitive && typeof item === "string") {
143
+ const match = values.find((v) => typeof v === "string" && v.toLowerCase() === item.toLowerCase())
144
+ if (match !== undefined) return match
145
+ return item
146
+ }
147
+
148
+ return item
149
+ }
150
+
151
+ const preprocessFn = (val: unknown) => {
152
+ if (val === null || val === undefined || val === "") {
153
+ return defaultValue
154
+ }
155
+
156
+ if (!Array.isArray(val)) {
157
+ // Accept single value and wrap in array
158
+ return [normalizeItem(val)]
159
+ }
160
+
161
+ return val.map(normalizeItem)
162
+ }
163
+
164
+ const baseSchema = z.preprocess(preprocessFn, z.any())
165
+
166
+ const schema = baseSchema.superRefine((val, ctx) => {
167
+ if (val === null) {
168
+ if (isRequired) {
169
+ ctx.addIssue({ code: "custom", message: getMessage("required") })
170
+ }
171
+ return
172
+ }
173
+
174
+ if (!Array.isArray(val)) {
175
+ ctx.addIssue({ code: "custom", message: getMessage("invalid", { values: values.join(", ") }) })
176
+ return
177
+ }
178
+
179
+ // Check each item is in the allowed values
180
+ for (const item of val) {
181
+ if (!values.includes(item as T)) {
182
+ ctx.addIssue({
183
+ code: "custom",
184
+ message: getMessage("invalid", { values: values.join(", ") }),
185
+ })
186
+ return
187
+ }
188
+ }
189
+
190
+ // Duplicate check
191
+ if (!allowDuplicates) {
192
+ const seen = new Set()
193
+ for (const item of val) {
194
+ if (seen.has(item)) {
195
+ ctx.addIssue({ code: "custom", message: getMessage("duplicate") })
196
+ return
197
+ }
198
+ seen.add(item)
199
+ }
200
+ }
201
+
202
+ // Min/max selection count
203
+ if (min !== undefined && val.length < min) {
204
+ ctx.addIssue({ code: "custom", message: getMessage("minSelect", { min }) })
205
+ return
206
+ }
207
+
208
+ if (max !== undefined && val.length > max) {
209
+ ctx.addIssue({ code: "custom", message: getMessage("maxSelect", { max }) })
210
+ return
211
+ }
212
+ })
213
+ .transform((val) => {
214
+ if (val === null || !Array.isArray(val) || !transform) return val
215
+ return transform(val as T[])
216
+ })
217
+
218
+ return schema as unknown as ManyOfSchema<IsRequired, T>
219
+ }