@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,378 @@
1
+ import { describe, it, expect, beforeEach } from "vitest"
2
+ import { creditCard, setLocale, validateCreditCard, detectCardType } from "../../src"
3
+
4
+ describe("creditCard(true) validator", () => {
5
+ beforeEach(() => setLocale("en-US"))
6
+
7
+ describe("basic functionality", () => {
8
+ it("should validate correct Luhn numbers", () => {
9
+ const schema = creditCard(true)
10
+
11
+ expect(schema.parse("4111111111111111")).toBe("4111111111111111")
12
+ expect(schema.parse("4012888888881881")).toBe("4012888888881881")
13
+ expect(schema.parse("5500000000000004")).toBe("5500000000000004")
14
+ expect(schema.parse("5105105105105100")).toBe("5105105105105100")
15
+ expect(schema.parse("378282246310005")).toBe("378282246310005")
16
+ expect(schema.parse("371449635398431")).toBe("371449635398431")
17
+ expect(schema.parse("3530111333300000")).toBe("3530111333300000")
18
+ expect(schema.parse("6011111111111117")).toBe("6011111111111117")
19
+ expect(schema.parse("6200000000000005")).toBe("6200000000000005")
20
+ })
21
+
22
+ it("should reject invalid Luhn numbers", () => {
23
+ const schema = creditCard(true)
24
+
25
+ expect(() => schema.parse("4111111111111112")).toThrow("Invalid credit card number")
26
+ expect(() => schema.parse("1234567890123456")).toThrow("Invalid credit card number")
27
+ })
28
+ })
29
+
30
+ describe("card type detection", () => {
31
+ it("should detect Visa cards", () => {
32
+ const schema = creditCard(true, { cardType: "visa" })
33
+
34
+ expect(schema.parse("4111111111111111")).toBe("4111111111111111")
35
+ expect(schema.parse("4012888888881881")).toBe("4012888888881881")
36
+ })
37
+
38
+ it("should detect Mastercard cards", () => {
39
+ const schema = creditCard(true, { cardType: "mastercard" })
40
+
41
+ expect(schema.parse("5500000000000004")).toBe("5500000000000004")
42
+ expect(schema.parse("5105105105105100")).toBe("5105105105105100")
43
+ })
44
+
45
+ it("should detect AMEX cards", () => {
46
+ const schema = creditCard(true, { cardType: "amex" })
47
+
48
+ expect(schema.parse("378282246310005")).toBe("378282246310005")
49
+ expect(schema.parse("371449635398431")).toBe("371449635398431")
50
+ })
51
+
52
+ it("should detect JCB cards", () => {
53
+ const schema = creditCard(true, { cardType: "jcb" })
54
+
55
+ expect(schema.parse("3530111333300000")).toBe("3530111333300000")
56
+ })
57
+
58
+ it("should detect Discover cards", () => {
59
+ const schema = creditCard(true, { cardType: "discover" })
60
+
61
+ expect(schema.parse("6011111111111117")).toBe("6011111111111117")
62
+ })
63
+
64
+ it("should detect UnionPay cards", () => {
65
+ const schema = creditCard(true, { cardType: "unionpay" })
66
+
67
+ expect(schema.parse("6200000000000005")).toBe("6200000000000005")
68
+ })
69
+ })
70
+
71
+ describe("card type filtering", () => {
72
+ it("should reject cards not matching the required type", () => {
73
+ const visaOnly = creditCard(true, { cardType: "visa" })
74
+
75
+ expect(() => visaOnly.parse("5500000000000004")).toThrow("Invalid credit card number")
76
+ expect(() => visaOnly.parse("378282246310005")).toThrow("Invalid credit card number")
77
+ expect(() => visaOnly.parse("3530111333300000")).toThrow("Invalid credit card number")
78
+ })
79
+
80
+ it("should accept multiple card types", () => {
81
+ const visaOrMc = creditCard(true, { cardType: ["visa", "mastercard"] })
82
+
83
+ expect(visaOrMc.parse("4111111111111111")).toBe("4111111111111111")
84
+ expect(visaOrMc.parse("5500000000000004")).toBe("5500000000000004")
85
+ expect(() => visaOrMc.parse("378282246310005")).toThrow("Invalid credit card number")
86
+ })
87
+
88
+ it("should accept any card type when 'any' is specified", () => {
89
+ const anyType = creditCard(true, { cardType: "any" })
90
+
91
+ expect(anyType.parse("4111111111111111")).toBe("4111111111111111")
92
+ expect(anyType.parse("5500000000000004")).toBe("5500000000000004")
93
+ expect(anyType.parse("378282246310005")).toBe("378282246310005")
94
+ expect(anyType.parse("3530111333300000")).toBe("3530111333300000")
95
+ expect(anyType.parse("6011111111111117")).toBe("6011111111111117")
96
+ expect(anyType.parse("6200000000000005")).toBe("6200000000000005")
97
+ })
98
+
99
+ it("should accept any card type when 'any' is included in array", () => {
100
+ const withAny = creditCard(true, { cardType: ["any", "visa"] })
101
+
102
+ expect(withAny.parse("5500000000000004")).toBe("5500000000000004")
103
+ expect(withAny.parse("378282246310005")).toBe("378282246310005")
104
+ })
105
+ })
106
+
107
+ describe("whitelist", () => {
108
+ it("should accept whitelisted card numbers", () => {
109
+ const schema = creditCard(true, { whitelist: ["4111111111111111", "5500000000000004"] })
110
+
111
+ expect(schema.parse("4111111111111111")).toBe("4111111111111111")
112
+ expect(schema.parse("5500000000000004")).toBe("5500000000000004")
113
+ })
114
+
115
+ it("should reject non-whitelisted card numbers", () => {
116
+ const schema = creditCard(true, { whitelist: ["4111111111111111"] })
117
+
118
+ expect(() => schema.parse("5500000000000004")).toThrow("Credit card number is not in the allowed list")
119
+ })
120
+
121
+ it("should normalize whitelist entries by stripping spaces and hyphens", () => {
122
+ const schema = creditCard(true, { whitelist: ["4111-1111-1111-1111"] })
123
+
124
+ expect(schema.parse("4111111111111111")).toBe("4111111111111111")
125
+ })
126
+ })
127
+
128
+ describe("required/optional behavior", () => {
129
+ it("should handle required=true", () => {
130
+ const schema = creditCard(true)
131
+
132
+ expect(() => schema.parse("")).toThrow("Required")
133
+ expect(() => schema.parse(null)).toThrow()
134
+ expect(() => schema.parse(undefined)).toThrow()
135
+ })
136
+
137
+ it("should handle required=false", () => {
138
+ const schema = creditCard(false)
139
+
140
+ expect(schema.parse("")).toBe(null)
141
+ expect(schema.parse(null)).toBe(null)
142
+ expect(schema.parse(undefined)).toBe(null)
143
+ expect(schema.parse("4111111111111111")).toBe("4111111111111111")
144
+ })
145
+
146
+ it("should use default values", () => {
147
+ const requiredSchema = creditCard(true, { defaultValue: "4111111111111111" })
148
+ const optionalSchema = creditCard(false, { defaultValue: "4111111111111111" })
149
+
150
+ expect(requiredSchema.parse("")).toBe("4111111111111111")
151
+ expect(optionalSchema.parse("")).toBe("4111111111111111")
152
+ })
153
+ })
154
+
155
+ describe("transform function", () => {
156
+ it("should apply custom transform before validation", () => {
157
+ const schema = creditCard(true, {
158
+ transform: (val) => val.toUpperCase(),
159
+ })
160
+
161
+ expect(schema.parse("4111111111111111")).toBe("4111111111111111")
162
+ })
163
+
164
+ it("should apply transform on the stripped value", () => {
165
+ const transformed: string[] = []
166
+ const schema = creditCard(true, {
167
+ transform: (val) => {
168
+ transformed.push(val)
169
+ return val
170
+ },
171
+ })
172
+
173
+ schema.parse("4111 1111 1111 1111")
174
+ expect(transformed[0]).toBe("4111111111111111")
175
+ })
176
+ })
177
+
178
+ describe("input preprocessing", () => {
179
+ it("should strip spaces from input", () => {
180
+ const schema = creditCard(true)
181
+
182
+ expect(schema.parse("4111 1111 1111 1111")).toBe("4111111111111111")
183
+ expect(schema.parse(" 4111 1111 1111 1111 ")).toBe("4111111111111111")
184
+ })
185
+
186
+ it("should strip hyphens from input", () => {
187
+ const schema = creditCard(true)
188
+
189
+ expect(schema.parse("4111-1111-1111-1111")).toBe("4111111111111111")
190
+ expect(schema.parse("5500-0000-0000-0004")).toBe("5500000000000004")
191
+ })
192
+
193
+ it("should handle mixed spaces and hyphens", () => {
194
+ const schema = creditCard(true)
195
+
196
+ expect(schema.parse("4111 - 1111 - 1111 - 1111")).toBe("4111111111111111")
197
+ })
198
+
199
+ it("should handle string conversion from numbers", () => {
200
+ const schema = creditCard(true)
201
+
202
+ expect(schema.parse(4111111111111111)).toBe("4111111111111111")
203
+ })
204
+ })
205
+
206
+ describe("i18n support", () => {
207
+ it("should use English messages by default", () => {
208
+ setLocale("en-US")
209
+ const schema = creditCard(true)
210
+
211
+ expect(() => schema.parse("")).toThrow("Required")
212
+ expect(() => schema.parse("4111111111111112")).toThrow("Invalid credit card number")
213
+ })
214
+
215
+ it("should use Chinese messages when locale is zh-TW", () => {
216
+ setLocale("zh-TW")
217
+ const schema = creditCard(true)
218
+
219
+ expect(() => schema.parse("")).toThrow("必填")
220
+ expect(() => schema.parse("4111111111111112")).toThrow("無效的信用卡號碼")
221
+ })
222
+
223
+ it("should support custom i18n messages", () => {
224
+ const schema = creditCard(true, {
225
+ i18n: {
226
+ "en-US": {
227
+ required: "Card number is required",
228
+ invalid: "Card number is invalid",
229
+ notInWhitelist: "Card not allowed",
230
+ },
231
+ "zh-TW": {
232
+ required: "請輸入信用卡號",
233
+ invalid: "信用卡號格式錯誤",
234
+ notInWhitelist: "此卡號不允許使用",
235
+ },
236
+ },
237
+ })
238
+
239
+ setLocale("en-US")
240
+ expect(() => schema.parse("")).toThrow("Card number is required")
241
+ expect(() => schema.parse("4111111111111112")).toThrow("Card number is invalid")
242
+
243
+ setLocale("zh-TW")
244
+ expect(() => schema.parse("")).toThrow("請輸入信用卡號")
245
+ expect(() => schema.parse("4111111111111112")).toThrow("信用卡號格式錯誤")
246
+ })
247
+
248
+ it("should use custom notInWhitelist message", () => {
249
+ const schema = creditCard(true, {
250
+ whitelist: ["4111111111111111"],
251
+ i18n: {
252
+ "en-US": {
253
+ notInWhitelist: "This card is not accepted",
254
+ },
255
+ },
256
+ })
257
+
258
+ expect(() => schema.parse("5500000000000004")).toThrow("This card is not accepted")
259
+ })
260
+ })
261
+
262
+ describe("edge cases", () => {
263
+ it("should reject numbers that are too short", () => {
264
+ const schema = creditCard(true)
265
+
266
+ expect(() => schema.parse("411111111111")).toThrow("Invalid credit card number")
267
+ expect(() => schema.parse("41111")).toThrow("Invalid credit card number")
268
+ expect(() => schema.parse("1")).toThrow("Invalid credit card number")
269
+ })
270
+
271
+ it("should reject numbers that are too long", () => {
272
+ const schema = creditCard(true)
273
+
274
+ expect(() => schema.parse("41111111111111111111")).toThrow("Invalid credit card number")
275
+ })
276
+
277
+ it("should reject non-numeric input", () => {
278
+ const schema = creditCard(true)
279
+
280
+ expect(() => schema.parse("abcdefghijklmnop")).toThrow("Invalid credit card number")
281
+ expect(() => schema.parse("4111-ABCD-1111-1111")).toThrow("Invalid credit card number")
282
+ expect(() => schema.parse("41111111111111xx")).toThrow("Invalid credit card number")
283
+ })
284
+
285
+ it("should handle 'null' and 'undefined' string values as required", () => {
286
+ const schema = creditCard(true)
287
+
288
+ expect(() => schema.parse("null")).toThrow("Required")
289
+ expect(() => schema.parse("undefined")).toThrow("Required")
290
+ })
291
+
292
+ it("should handle empty and whitespace inputs", () => {
293
+ const schema = creditCard(true)
294
+ const optionalSchema = creditCard(false)
295
+
296
+ expect(() => schema.parse("")).toThrow("Required")
297
+ expect(() => schema.parse(" ")).toThrow("Required")
298
+
299
+ expect(optionalSchema.parse("")).toBe(null)
300
+ expect(optionalSchema.parse(" ")).toBe(null)
301
+ })
302
+ })
303
+
304
+ describe("utility functions", () => {
305
+ describe("validateCreditCard", () => {
306
+ it("should return true for valid Luhn numbers", () => {
307
+ expect(validateCreditCard("4111111111111111")).toBe(true)
308
+ expect(validateCreditCard("4012888888881881")).toBe(true)
309
+ expect(validateCreditCard("5500000000000004")).toBe(true)
310
+ expect(validateCreditCard("5105105105105100")).toBe(true)
311
+ expect(validateCreditCard("378282246310005")).toBe(true)
312
+ expect(validateCreditCard("371449635398431")).toBe(true)
313
+ expect(validateCreditCard("3530111333300000")).toBe(true)
314
+ expect(validateCreditCard("6011111111111117")).toBe(true)
315
+ expect(validateCreditCard("6200000000000005")).toBe(true)
316
+ })
317
+
318
+ it("should return false for invalid Luhn numbers", () => {
319
+ expect(validateCreditCard("4111111111111112")).toBe(false)
320
+ expect(validateCreditCard("1234567890123456")).toBe(false)
321
+ })
322
+
323
+ it("should return false for non-numeric strings", () => {
324
+ expect(validateCreditCard("abcdefghijklmnop")).toBe(false)
325
+ expect(validateCreditCard("")).toBe(false)
326
+ })
327
+
328
+ it("should return false for wrong-length strings", () => {
329
+ expect(validateCreditCard("41111")).toBe(false)
330
+ expect(validateCreditCard("41111111111111111111")).toBe(false)
331
+ })
332
+
333
+ it("should handle strings with spaces and hyphens", () => {
334
+ expect(validateCreditCard("4111 1111 1111 1111")).toBe(true)
335
+ expect(validateCreditCard("4111-1111-1111-1111")).toBe(true)
336
+ })
337
+ })
338
+
339
+ describe("detectCardType", () => {
340
+ it("should detect Visa", () => {
341
+ expect(detectCardType("4111111111111111")).toBe("visa")
342
+ expect(detectCardType("4012888888881881")).toBe("visa")
343
+ })
344
+
345
+ it("should detect Mastercard", () => {
346
+ expect(detectCardType("5500000000000004")).toBe("mastercard")
347
+ expect(detectCardType("5105105105105100")).toBe("mastercard")
348
+ })
349
+
350
+ it("should detect AMEX", () => {
351
+ expect(detectCardType("378282246310005")).toBe("amex")
352
+ expect(detectCardType("371449635398431")).toBe("amex")
353
+ })
354
+
355
+ it("should detect JCB", () => {
356
+ expect(detectCardType("3530111333300000")).toBe("jcb")
357
+ })
358
+
359
+ it("should detect Discover", () => {
360
+ expect(detectCardType("6011111111111117")).toBe("discover")
361
+ })
362
+
363
+ it("should detect UnionPay", () => {
364
+ expect(detectCardType("6200000000000005")).toBe("unionpay")
365
+ })
366
+
367
+ it("should return 'any' for unrecognized prefixes", () => {
368
+ expect(detectCardType("9999999999999999")).toBe("any")
369
+ expect(detectCardType("1111111111111111")).toBe("any")
370
+ })
371
+
372
+ it("should handle strings with spaces and hyphens", () => {
373
+ expect(detectCardType("4111 1111 1111 1111")).toBe("visa")
374
+ expect(detectCardType("5500-0000-0000-0004")).toBe("mastercard")
375
+ })
376
+ })
377
+ })
378
+ })
@@ -11,6 +11,9 @@ const locales = [
11
11
  maxLength: "Must be at most 10 characters",
12
12
  numeric: "Must be a numeric ID",
13
13
  uuid: "Must be a valid UUID",
14
+ uuidv1: "Must be a valid UUID v1",
15
+ uuidv4: "Must be a valid UUID v4",
16
+ uuidv7: "Must be a valid UUID v7",
14
17
  objectId: "Must be a valid MongoDB ObjectId",
15
18
  nanoid: "Must be a valid Nano ID",
16
19
  snowflake: "Must be a valid Snowflake ID",
@@ -33,6 +36,9 @@ const locales = [
33
36
  maxLength: "長度最多 10 字元",
34
37
  numeric: "必須為數字 ID",
35
38
  uuid: "必須為有效的 UUID",
39
+ uuidv1: "必須為有效的 UUID v1",
40
+ uuidv4: "必須為有效的 UUID v4",
41
+ uuidv7: "必須為有效的 UUID v7",
36
42
  objectId: "必須為有效的 MongoDB ObjectId",
37
43
  nanoid: "必須為有效的 Nano ID",
38
44
  snowflake: "必須為有效的 Snowflake ID",
@@ -51,7 +57,20 @@ const locales = [
51
57
  // Valid test IDs for different formats
52
58
  const validIds = {
53
59
  numeric: ["1", "123", "999999", "0"],
54
- uuid: ["550e8400-e29b-41d4-a716-446655440000", "6ba7b810-9dad-11d1-80b4-00c04fd430c8", "f47ac10b-58cc-4372-a567-0e02b2c3d479"],
60
+ uuid: [
61
+ "550e8400-e29b-41d4-a716-446655440000", // v4
62
+ "6ba7b810-9dad-11d1-80b4-00c04fd430c8", // v1
63
+ "f47ac10b-58cc-4372-a567-0e02b2c3d479", // v4
64
+ "01939fee-4b0b-7da3-a9e4-79fb4c267eb8", // v7
65
+ "320c3d4d-cc00-875b-8ec9-32d5f69181c0", // v8
66
+ ],
67
+ uuidv1: ["6ba7b810-9dad-11d1-80b4-00c04fd430c8", "f47ac10b-58cc-1372-a567-0e02b2c3d479"],
68
+ uuidv3: ["a3bb189e-8bf9-3888-9912-ace4e6543002"],
69
+ uuidv4: ["550e8400-e29b-41d4-a716-446655440000", "f47ac10b-58cc-4372-a567-0e02b2c3d479"],
70
+ uuidv5: ["886313e1-3b8a-5372-9b90-0c9aee199e5d"],
71
+ uuidv6: ["1ef21d2f-1207-6b90-8b50-a2e4100c5480"],
72
+ uuidv7: ["01939fee-4b0b-7da3-a9e4-79fb4c267eb8", "018fce07-42dc-7dc0-ba2c-9f7e777caee3"],
73
+ uuidv8: ["320c3d4d-cc00-875b-8ec9-32d5f69181c0"],
55
74
  objectId: ["507f1f77bcf86cd799439011", "507f191e810c19729de860ea", "5a9427648b0beebeb69579cc"],
56
75
  nanoid: ["V1StGXR8_Z5jdHi6B-myT", "3IBBoOd_b1YSlnKdvQ8fK", "9_xnJ2QZt8vKl3_Kj5f7N"],
57
76
  snowflake: ["1234567890123456789", "9876543210987654321", "5555555555555555555"],
@@ -65,11 +84,19 @@ const invalidIds = {
65
84
  numeric: ["abc", "12a", ""],
66
85
  uuid: [
67
86
  "550e8400-e29b-41d4-a716-44665544000", // too short
68
- "550e8400-e29b-41d4-a716-44665544000g", // too long
69
- "550e8400-e29b-61d4-a716-446655440000", // wrong version (6 instead of 1-5)
87
+ "550e8400-e29b-41d4-a716-44665544000g", // invalid character
88
+ "550e8400-e29b-91d4-a716-446655440000", // wrong version (9)
70
89
  "550e8400-e29b-41d4-c716-446655440000", // wrong variant (c instead of 8,9,a,b)
71
90
  "not-a-uuid-at-all",
72
91
  ],
92
+ uuidv4: [
93
+ "6ba7b810-9dad-11d1-80b4-00c04fd430c8", // v1, not v4
94
+ "01939fee-4b0b-7da3-a9e4-79fb4c267eb8", // v7, not v4
95
+ ],
96
+ uuidv7: [
97
+ "550e8400-e29b-41d4-a716-446655440000", // v4, not v7
98
+ "6ba7b810-9dad-11d1-80b4-00c04fd430c8", // v1, not v7
99
+ ],
73
100
  objectId: [
74
101
  "507f1f77bcf86cd79943901", // too short
75
102
  "507f1f77bcf86cd799439011g", // invalid character
@@ -192,6 +219,44 @@ describe.each(locales)("id(true) locale: $locale", ({ locale, messages }) => {
192
219
  })
193
220
  })
194
221
 
222
+ describe("UUID version-specific validation", () => {
223
+ it("should accept valid UUIDv4 and reject other versions", () => {
224
+ const schema = id(true, { type: "uuidv4" })
225
+ validIds.uuidv4.forEach((v) => expect(schema.parse(v)).toBe(v))
226
+ invalidIds.uuidv4.forEach((v) => expect(() => schema.parse(v)).toThrow(messages.uuidv4))
227
+ })
228
+
229
+ it("should accept valid UUIDv7 and reject other versions", () => {
230
+ const schema = id(true, { type: "uuidv7" })
231
+ validIds.uuidv7.forEach((v) => expect(schema.parse(v)).toBe(v))
232
+ invalidIds.uuidv7.forEach((v) => expect(() => schema.parse(v)).toThrow(messages.uuidv7))
233
+ })
234
+
235
+ it("should accept valid UUIDv1", () => {
236
+ const schema = id(true, { type: "uuidv1" })
237
+ validIds.uuidv1.forEach((v) => expect(schema.parse(v)).toBe(v))
238
+ })
239
+
240
+ it("should accept valid UUIDv6", () => {
241
+ const schema = id(true, { type: "uuidv6" })
242
+ validIds.uuidv6.forEach((v) => expect(schema.parse(v)).toBe(v))
243
+ })
244
+
245
+ it("should accept any version with generic uuid type", () => {
246
+ const schema = id(true, { type: "uuid" })
247
+ // All version-specific IDs should pass generic uuid validation
248
+ ;[...validIds.uuidv1, ...validIds.uuidv4, ...validIds.uuidv7, ...validIds.uuidv8].forEach((v) => expect(schema.parse(v)).toBe(v))
249
+ })
250
+
251
+ it("should work with allowedTypes for specific versions", () => {
252
+ const schema = id(true, { allowedTypes: ["uuidv4", "uuidv7"] })
253
+ validIds.uuidv4.forEach((v) => expect(schema.parse(v)).toBe(v))
254
+ validIds.uuidv7.forEach((v) => expect(schema.parse(v)).toBe(v))
255
+ // v1 should be rejected
256
+ expect(() => schema.parse("6ba7b810-9dad-11d1-80b4-00c04fd430c8")).toThrow()
257
+ })
258
+ })
259
+
195
260
  describe("ObjectId validation", () => {
196
261
  it("should accept valid ObjectIds", () => {
197
262
  const schema = id(true, { type: "objectId" })