@hy_ong/zod-kit 0.0.4 → 0.0.6

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 (59) hide show
  1. package/.claude/settings.local.json +28 -0
  2. package/LICENSE +21 -0
  3. package/README.md +465 -97
  4. package/debug.js +21 -0
  5. package/debug.ts +16 -0
  6. package/dist/index.cjs +3127 -146
  7. package/dist/index.d.cts +3021 -25
  8. package/dist/index.d.ts +3021 -25
  9. package/dist/index.js +3081 -144
  10. package/eslint.config.mts +8 -0
  11. package/package.json +10 -9
  12. package/src/config.ts +1 -1
  13. package/src/i18n/locales/en.json +161 -25
  14. package/src/i18n/locales/zh-TW.json +165 -26
  15. package/src/index.ts +17 -7
  16. package/src/validators/common/boolean.ts +191 -0
  17. package/src/validators/common/date.ts +299 -0
  18. package/src/validators/common/datetime.ts +673 -0
  19. package/src/validators/common/email.ts +313 -0
  20. package/src/validators/common/file.ts +384 -0
  21. package/src/validators/common/id.ts +471 -0
  22. package/src/validators/common/number.ts +319 -0
  23. package/src/validators/common/password.ts +386 -0
  24. package/src/validators/common/text.ts +271 -0
  25. package/src/validators/common/time.ts +600 -0
  26. package/src/validators/common/url.ts +347 -0
  27. package/src/validators/taiwan/business-id.ts +262 -0
  28. package/src/validators/taiwan/fax.ts +327 -0
  29. package/src/validators/taiwan/mobile.ts +242 -0
  30. package/src/validators/taiwan/national-id.ts +425 -0
  31. package/src/validators/taiwan/postal-code.ts +1049 -0
  32. package/src/validators/taiwan/tel.ts +330 -0
  33. package/tests/common/boolean.test.ts +340 -92
  34. package/tests/common/date.test.ts +458 -0
  35. package/tests/common/datetime.test.ts +693 -0
  36. package/tests/common/email.test.ts +232 -60
  37. package/tests/common/file.test.ts +479 -0
  38. package/tests/common/id.test.ts +535 -0
  39. package/tests/common/number.test.ts +230 -60
  40. package/tests/common/password.test.ts +271 -44
  41. package/tests/common/text.test.ts +210 -13
  42. package/tests/common/time.test.ts +528 -0
  43. package/tests/common/url.test.ts +492 -67
  44. package/tests/taiwan/business-id.test.ts +240 -0
  45. package/tests/taiwan/fax.test.ts +463 -0
  46. package/tests/taiwan/mobile.test.ts +373 -0
  47. package/tests/taiwan/national-id.test.ts +435 -0
  48. package/tests/taiwan/postal-code.test.ts +705 -0
  49. package/tests/taiwan/tel.test.ts +467 -0
  50. package/eslint.config.mjs +0 -10
  51. package/src/common/boolean.ts +0 -36
  52. package/src/common/date.ts +0 -43
  53. package/src/common/email.ts +0 -44
  54. package/src/common/integer.ts +0 -46
  55. package/src/common/number.ts +0 -37
  56. package/src/common/password.ts +0 -33
  57. package/src/common/text.ts +0 -34
  58. package/src/common/url.ts +0 -37
  59. package/tests/common/integer.test.ts +0 -90
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- // src/common/boolean.ts
1
+ // src/validators/common/boolean.ts
2
2
  import { z } from "zod";
3
3
 
4
4
  // src/i18n/locales/zh-TW.json
@@ -6,25 +6,40 @@ var zh_TW_default = {
6
6
  common: {
7
7
  boolean: {
8
8
  required: "\u5FC5\u586B",
9
- shouldBe: {
10
- true: "\u5FC5\u9808\u70BA\u662F",
11
- false: "\u5FC5\u9808\u70BA\u5426"
12
- }
9
+ shouldBeTrue: "\u5FC5\u9808\u70BA\u662F",
10
+ shouldBeFalse: "\u5FC5\u9808\u70BA\u5426",
11
+ invalid: "\u5FC5\u9808\u70BA\u5E03\u6797\u503C"
13
12
  },
14
13
  email: {
15
14
  required: "\u5FC5\u586B",
16
- min: "\u9577\u5EA6\u81F3\u5C11 ${min} \u5B57\u5143",
17
- max: "\u9577\u5EA6\u6700\u591A ${max} \u5B57\u5143",
15
+ invalid: "\u96FB\u5B50\u90F5\u4EF6\u683C\u5F0F\u932F\u8AA4",
16
+ minLength: "\u9577\u5EA6\u81F3\u5C11 ${minLength} \u5B57\u5143",
17
+ maxLength: "\u9577\u5EA6\u6700\u591A ${maxLength} \u5B57\u5143",
18
18
  includes: "\u5FC5\u9808\u5305\u542B\u300C${includes}\u300D",
19
- invalid: "\u683C\u5F0F\u932F\u8AA4",
20
- domain: "\u5FC5\u9808\u70BA @${domain} \u7DB2\u57DF"
19
+ domain: "\u5FC5\u9808\u70BA ${domain} \u7DB2\u57DF",
20
+ domainBlacklist: "\u4E0D\u5141\u8A31\u4F7F\u7528 ${domain} \u7DB2\u57DF",
21
+ businessOnly: "\u50C5\u5141\u8A31\u4F01\u696D\u90F5\u7BB1\u5730\u5740",
22
+ noDisposable: "\u4E0D\u5141\u8A31\u4F7F\u7528\u81E8\u6642\u90F5\u7BB1\u5730\u5740"
21
23
  },
22
24
  url: {
23
25
  required: "\u5FC5\u586B",
26
+ invalid: "\u7121\u6548\u7684 URL \u683C\u5F0F",
24
27
  min: "\u9577\u5EA6\u81F3\u5C11 ${min} \u5B57\u5143",
25
28
  max: "\u9577\u5EA6\u6700\u591A ${max} \u5B57\u5143",
26
29
  includes: "\u5FC5\u9808\u5305\u542B\u300C${includes}\u300D",
27
- invalid: "\u683C\u5F0F\u932F\u8AA4"
30
+ excludes: "\u4E0D\u5F97\u5305\u542B\u300C${excludes}\u300D",
31
+ protocol: "\u5354\u8B70\u5FC5\u9808\u70BA\uFF1A${protocols}",
32
+ domain: "\u7DB2\u57DF\u5FC5\u9808\u70BA\uFF1A${domains}",
33
+ domainBlacklist: "\u4E0D\u5141\u8A31\u4F7F\u7528\u7DB2\u57DF ${domain}",
34
+ port: "\u9023\u63A5\u57E0\u5FC5\u9808\u70BA\uFF1A${ports}",
35
+ pathStartsWith: "\u8DEF\u5F91\u5FC5\u9808\u4EE5\u300C${path}\u300D\u958B\u982D",
36
+ pathEndsWith: "\u8DEF\u5F91\u5FC5\u9808\u4EE5\u300C${path}\u300D\u7D50\u5C3E",
37
+ hasQuery: "URL \u5FC5\u9808\u5305\u542B\u67E5\u8A62\u53C3\u6578",
38
+ noQuery: "URL \u4E0D\u5F97\u5305\u542B\u67E5\u8A62\u53C3\u6578",
39
+ hasFragment: "URL \u5FC5\u9808\u5305\u542B\u9328\u9EDE",
40
+ noFragment: "URL \u4E0D\u5F97\u5305\u542B\u9328\u9EDE",
41
+ localhost: "\u4E0D\u5141\u8A31\u4F7F\u7528\u672C\u6A5F URL",
42
+ noLocalhost: "\u4E0D\u5141\u8A31\u4F7F\u7528\u672C\u6A5F URL"
28
43
  },
29
44
  password: {
30
45
  required: "\u5FC5\u586B",
@@ -33,37 +48,161 @@ var zh_TW_default = {
33
48
  uppercase: "\u5FC5\u9808\u5305\u542B\u81F3\u5C11\u4E00\u500B\u5927\u5BEB\u5B57\u6BCD",
34
49
  lowercase: "\u5FC5\u9808\u5305\u542B\u81F3\u5C11\u4E00\u500B\u5C0F\u5BEB\u5B57\u6BCD",
35
50
  digits: "\u5FC5\u9808\u5305\u542B\u81F3\u5C11\u4E00\u500B\u6578\u5B57",
36
- special: "\u5FC5\u9808\u5305\u542B\u81F3\u5C11\u4E00\u500B\u7279\u6B8A\u7B26\u865F"
51
+ special: "\u5FC5\u9808\u5305\u542B\u81F3\u5C11\u4E00\u500B\u7279\u6B8A\u7B26\u865F",
52
+ noRepeating: "\u4E0D\u53EF\u5305\u542B\u91CD\u8907\u5B57\u5143",
53
+ noSequential: "\u4E0D\u53EF\u5305\u542B\u9023\u7E8C\u5B57\u5143",
54
+ noCommonWords: "\u4E0D\u53EF\u5305\u542B\u5E38\u898B\u5BC6\u78BC\u6216\u6A21\u5F0F",
55
+ minStrength: "\u5BC6\u78BC\u5F37\u5EA6\u5FC5\u9808\u81F3\u5C11\u70BA ${minStrength}",
56
+ excludes: "\u4E0D\u5F97\u5305\u542B\u300C${excludes}\u300D",
57
+ includes: "\u5FC5\u9808\u5305\u542B\u300C${includes}\u300D",
58
+ invalid: "\u5BC6\u78BC\u683C\u5F0F\u932F\u8AA4"
37
59
  },
38
60
  number: {
39
61
  required: "\u5FC5\u586B",
40
- min: "\u6700\u5C0F\u503C ${min}",
41
- max: "\u6700\u5927\u503C ${max}"
42
- },
43
- id: {
44
- required: "\u8ACB\u9078\u64C7",
45
- invalid: "\u7121\u6548"
46
- },
47
- integer: {
48
- required: "\u5FC5\u586B",
62
+ invalid: "\u5FC5\u9808\u70BA\u6709\u6548\u6578\u5B57",
63
+ integer: "\u5FC5\u9808\u70BA\u6574\u6578",
64
+ float: "\u5FC5\u9808\u70BA\u5C0F\u6578",
49
65
  min: "\u6700\u5C0F\u503C ${min}",
50
66
  max: "\u6700\u5927\u503C ${max}",
51
- integer: "\u5FC5\u9808\u70BA\u6574\u6578"
67
+ positive: "\u5FC5\u9808\u70BA\u6B63\u6578",
68
+ negative: "\u5FC5\u9808\u70BA\u8CA0\u6578",
69
+ nonNegative: "\u4E0D\u53EF\u70BA\u8CA0\u6578",
70
+ nonPositive: "\u4E0D\u53EF\u70BA\u6B63\u6578",
71
+ multipleOf: "\u5FC5\u9808\u70BA ${multipleOf} \u7684\u500D\u6578",
72
+ finite: "\u5FC5\u9808\u70BA\u6709\u9650\u6578\u5B57",
73
+ precision: "\u5C0F\u6578\u4F4D\u6578\u4E0D\u53EF\u8D85\u904E ${precision} \u4F4D"
52
74
  },
53
- float: {
75
+ id: {
54
76
  required: "\u5FC5\u586B",
55
- float: "\u5FC5\u9808\u70BA\u6D6E\u9EDE\u6578",
56
- min: "\u6700\u5C0F\u503C ${min}",
57
- max: "\u6700\u5927\u503C ${max}"
77
+ invalid: "\u7121\u6548\u7684 ID \u683C\u5F0F",
78
+ minLength: "\u9577\u5EA6\u81F3\u5C11 ${minLength} \u5B57\u5143",
79
+ maxLength: "\u9577\u5EA6\u6700\u591A ${maxLength} \u5B57\u5143",
80
+ numeric: "\u5FC5\u9808\u70BA\u6578\u5B57 ID",
81
+ uuid: "\u5FC5\u9808\u70BA\u6709\u6548\u7684 UUID",
82
+ objectId: "\u5FC5\u9808\u70BA\u6709\u6548\u7684 MongoDB ObjectId",
83
+ nanoid: "\u5FC5\u9808\u70BA\u6709\u6548\u7684 Nano ID",
84
+ snowflake: "\u5FC5\u9808\u70BA\u6709\u6548\u7684 Snowflake ID",
85
+ cuid: "\u5FC5\u9808\u70BA\u6709\u6548\u7684 CUID",
86
+ ulid: "\u5FC5\u9808\u70BA\u6709\u6548\u7684 ULID",
87
+ shortid: "\u5FC5\u9808\u70BA\u6709\u6548\u7684 Short ID",
88
+ customFormat: "\u7121\u6548\u7684 ID \u683C\u5F0F",
89
+ includes: "\u5FC5\u9808\u5305\u542B\u300C${includes}\u300D",
90
+ excludes: "\u4E0D\u5F97\u5305\u542B\u300C${excludes}\u300D",
91
+ startsWith: "\u5FC5\u9808\u4EE5\u300C${startsWith}\u300D\u958B\u982D",
92
+ endsWith: "\u5FC5\u9808\u4EE5\u300C${endsWith}\u300D\u7D50\u5C3E"
58
93
  },
59
94
  text: {
60
95
  required: "\u5FC5\u586B",
61
- min: "\u9577\u5EA6\u81F3\u5C11 ${min} \u5B57\u5143",
62
- max: "\u9577\u5EA6\u6700\u591A ${max} \u5B57\u5143",
96
+ notEmpty: "\u4E0D\u53EF\u70BA\u7A7A\u767D\u6216\u50C5\u542B\u7A7A\u683C",
97
+ minLength: "\u9577\u5EA6\u81F3\u5C11 ${minLength} \u5B57\u5143",
98
+ maxLength: "\u9577\u5EA6\u6700\u591A ${maxLength} \u5B57\u5143",
63
99
  startsWith: "\u5FC5\u9808\u4EE5\u300C${startsWith}\u300D\u958B\u982D",
64
100
  endsWith: "\u5FC5\u9808\u4EE5\u300C${endsWith}\u300D\u7D50\u5C3E",
65
101
  includes: "\u5FC5\u9808\u5305\u542B\u300C${includes}\u300D",
102
+ excludes: "\u4E0D\u5F97\u5305\u542B\u300C${excludes}\u300D",
66
103
  invalid: "\u683C\u5F0F\u932F\u8AA4"
104
+ },
105
+ date: {
106
+ required: "\u5FC5\u586B",
107
+ invalid: "\u7121\u6548\u65E5\u671F",
108
+ format: "\u5FC5\u9808\u70BA ${format} \u683C\u5F0F",
109
+ min: "\u65E5\u671F\u4E0D\u5F97\u65E9\u65BC ${min}",
110
+ max: "\u65E5\u671F\u4E0D\u5F97\u665A\u65BC ${max}",
111
+ includes: "\u5FC5\u9808\u5305\u542B\u300C${includes}\u300D",
112
+ excludes: "\u4E0D\u5F97\u5305\u542B\u300C${excludes}\u300D",
113
+ past: "\u5FC5\u9808\u70BA\u904E\u53BB\u7684\u65E5\u671F",
114
+ future: "\u5FC5\u9808\u70BA\u672A\u4F86\u7684\u65E5\u671F",
115
+ today: "\u5FC5\u9808\u70BA\u4ECA\u5929",
116
+ notToday: "\u4E0D\u5F97\u70BA\u4ECA\u5929",
117
+ weekday: "\u5FC5\u9808\u70BA\u5DE5\u4F5C\u65E5\uFF08\u9031\u4E00\u81F3\u9031\u4E94\uFF09",
118
+ weekend: "\u5FC5\u9808\u70BA\u9031\u672B\uFF08\u9031\u516D\u81F3\u9031\u65E5\uFF09"
119
+ },
120
+ time: {
121
+ required: "\u5FC5\u586B",
122
+ invalid: "\u7121\u6548\u7684\u6642\u9593\u683C\u5F0F",
123
+ format: "\u5FC5\u9808\u70BA ${format} \u683C\u5F0F",
124
+ min: "\u6642\u9593\u4E0D\u5F97\u65E9\u65BC ${min}",
125
+ max: "\u6642\u9593\u4E0D\u5F97\u665A\u65BC ${max}",
126
+ hour: "\u5C0F\u6642\u5FC5\u9808\u4ECB\u65BC ${minHour} \u8207 ${maxHour} \u4E4B\u9593",
127
+ minute: "\u5206\u9418\u5FC5\u9808\u70BA ${minuteStep} \u5206\u9418\u9593\u9694",
128
+ second: "\u79D2\u6578\u5FC5\u9808\u70BA ${secondStep} \u79D2\u9593\u9694",
129
+ includes: "\u5FC5\u9808\u5305\u542B\u300C${includes}\u300D",
130
+ excludes: "\u4E0D\u5F97\u5305\u542B\u300C${excludes}\u300D",
131
+ customRegex: "\u7121\u6548\u7684\u6642\u9593\u683C\u5F0F",
132
+ notInWhitelist: "\u6642\u9593\u4E0D\u5728\u5141\u8A31\u6E05\u55AE\u4E2D"
133
+ },
134
+ datetime: {
135
+ required: "\u5FC5\u586B",
136
+ invalid: "\u7121\u6548\u7684\u65E5\u671F\u6642\u9593\u683C\u5F0F",
137
+ format: "\u5FC5\u9808\u70BA ${format} \u683C\u5F0F",
138
+ min: "\u65E5\u671F\u6642\u9593\u4E0D\u5F97\u65E9\u65BC ${min}",
139
+ max: "\u65E5\u671F\u6642\u9593\u4E0D\u5F97\u665A\u65BC ${max}",
140
+ hour: "\u5C0F\u6642\u5FC5\u9808\u4ECB\u65BC ${minHour} \u8207 ${maxHour} \u4E4B\u9593",
141
+ minute: "\u5206\u9418\u5FC5\u9808\u70BA ${minuteStep} \u5206\u9418\u9593\u9694",
142
+ includes: "\u5FC5\u9808\u5305\u542B\u300C${includes}\u300D",
143
+ excludes: "\u4E0D\u5F97\u5305\u542B\u300C${excludes}\u300D",
144
+ past: "\u5FC5\u9808\u70BA\u904E\u53BB\u7684\u65E5\u671F\u6642\u9593",
145
+ future: "\u5FC5\u9808\u70BA\u672A\u4F86\u7684\u65E5\u671F\u6642\u9593",
146
+ today: "\u5FC5\u9808\u70BA\u4ECA\u5929",
147
+ notToday: "\u4E0D\u5F97\u70BA\u4ECA\u5929",
148
+ weekday: "\u5FC5\u9808\u70BA\u5DE5\u4F5C\u65E5\uFF08\u9031\u4E00\u81F3\u9031\u4E94\uFF09",
149
+ weekend: "\u5FC5\u9808\u70BA\u9031\u672B\uFF08\u9031\u516D\u81F3\u9031\u65E5\uFF09",
150
+ customRegex: "\u7121\u6548\u7684\u65E5\u671F\u6642\u9593\u683C\u5F0F",
151
+ notInWhitelist: "\u65E5\u671F\u6642\u9593\u4E0D\u5728\u5141\u8A31\u6E05\u55AE\u4E2D"
152
+ },
153
+ file: {
154
+ required: "\u5FC5\u586B",
155
+ invalid: "\u7121\u6548\u7684\u6A94\u6848\u683C\u5F0F",
156
+ size: "\u6A94\u6848\u5927\u5C0F\u4E0D\u5F97\u8D85\u904E ${size}",
157
+ minSize: "\u6A94\u6848\u5927\u5C0F\u81F3\u5C11 ${minSize}",
158
+ maxSize: "\u6A94\u6848\u5927\u5C0F\u4E0D\u5F97\u8D85\u904E ${maxSize}",
159
+ type: "\u6A94\u6848\u985E\u578B\u5FC5\u9808\u70BA\uFF1A${type}",
160
+ extension: "\u526F\u6A94\u540D\u5FC5\u9808\u70BA\uFF1A${extension}",
161
+ extensionBlacklist: "\u4E0D\u5141\u8A31\u4F7F\u7528\u526F\u6A94\u540D ${extension}",
162
+ name: "\u6A94\u6848\u540D\u7A31\u5FC5\u9808\u7B26\u5408\u683C\u5F0F ${pattern}",
163
+ nameBlacklist: "\u6A94\u6848\u540D\u7A31\u4E0D\u5F97\u7B26\u5408\u683C\u5F0F ${pattern}",
164
+ imageOnly: "\u50C5\u5141\u8A31\u5716\u7247\u6A94\u6848",
165
+ documentOnly: "\u50C5\u5141\u8A31\u6587\u4EF6\u6A94\u6848",
166
+ videoOnly: "\u50C5\u5141\u8A31\u5F71\u7247\u6A94\u6848",
167
+ audioOnly: "\u50C5\u5141\u8A31\u97F3\u8A0A\u6A94\u6848",
168
+ archiveOnly: "\u50C5\u5141\u8A31\u58D3\u7E2E\u6A94\u6848"
169
+ }
170
+ },
171
+ taiwan: {
172
+ businessId: {
173
+ required: "\u5FC5\u586B",
174
+ invalid: "\u7121\u6548\u7684\u7D71\u4E00\u7DE8\u865F"
175
+ },
176
+ nationalId: {
177
+ required: "\u5FC5\u586B",
178
+ invalid: "\u7121\u6548\u7684\u8EAB\u5206\u8B49\u5B57\u865F"
179
+ },
180
+ mobile: {
181
+ required: "\u5FC5\u586B",
182
+ invalid: "\u7121\u6548\u7684\u624B\u6A5F\u865F\u78BC\u683C\u5F0F",
183
+ notInWhitelist: "\u4E0D\u5728\u5141\u8A31\u7684\u624B\u6A5F\u865F\u78BC\u6E05\u55AE\u4E2D"
184
+ },
185
+ tel: {
186
+ required: "\u5FC5\u586B",
187
+ invalid: "\u7121\u6548\u7684\u5E02\u8A71\u865F\u78BC\u683C\u5F0F",
188
+ notInWhitelist: "\u4E0D\u5728\u5141\u8A31\u7684\u5E02\u8A71\u865F\u78BC\u6E05\u55AE\u4E2D"
189
+ },
190
+ fax: {
191
+ required: "\u5FC5\u586B",
192
+ invalid: "\u7121\u6548\u7684\u50B3\u771F\u865F\u78BC\u683C\u5F0F",
193
+ notInWhitelist: "\u4E0D\u5728\u5141\u8A31\u7684\u50B3\u771F\u865F\u78BC\u6E05\u55AE\u4E2D"
194
+ },
195
+ postalCode: {
196
+ required: "\u5FC5\u586B",
197
+ invalid: "\u7121\u6548\u7684\u90F5\u905E\u5340\u865F",
198
+ invalidFormat: "\u90F5\u905E\u5340\u865F\u683C\u5F0F\u932F\u8AA4",
199
+ invalidRange: "\u90F5\u905E\u5340\u865F\u8D85\u51FA\u6709\u6548\u7BC4\u570D",
200
+ legacy5DigitWarning: "5 \u78BC\u90F5\u905E\u5340\u865F\u70BA\u820A\u5F0F\u683C\u5F0F\uFF0C\u5EFA\u8B70\u4F7F\u7528 6 \u78BC\u683C\u5F0F",
201
+ format3Only: "\u50C5\u5141\u8A31 3 \u78BC\u90F5\u905E\u5340\u865F",
202
+ format5Only: "\u50C5\u5141\u8A31 5 \u78BC\u90F5\u905E\u5340\u865F",
203
+ format6Only: "\u50C5\u5141\u8A31 6 \u78BC\u90F5\u905E\u5340\u865F",
204
+ invalidSuffix: "\u7121\u6548\u7684\u90F5\u905E\u5340\u865F\u5F8C\u78BC - 5 \u78BC\u683C\u5F0F\u9808\u70BA 01-99\uFF0C6 \u78BC\u683C\u5F0F\u9808\u70BA 001-999",
205
+ deprecated5Digit: "5 \u78BC\u90F5\u905E\u5340\u865F\u5DF2\u68C4\u7528\u4E14\u4E0D\u518D\u652F\u63F4"
67
206
  }
68
207
  }
69
208
  };
@@ -73,25 +212,40 @@ var en_default = {
73
212
  common: {
74
213
  boolean: {
75
214
  required: "Required",
76
- shouldBe: {
77
- true: "Must be True",
78
- false: "Must be False"
79
- }
215
+ shouldBeTrue: "Must be True",
216
+ shouldBeFalse: "Must be False",
217
+ invalid: "Must be a boolean value"
80
218
  },
81
219
  email: {
82
220
  required: "Required",
83
- min: "Must be at least ${min} characters",
84
- max: "Must be at most ${max} characters",
221
+ invalid: "Invalid email format",
222
+ minLength: "Must be at least ${minLength} characters",
223
+ maxLength: "Must be at most ${maxLength} characters",
85
224
  includes: "Must include ${includes}",
86
- invalid: "Invalid format",
87
- domain: "Must be under the domain @${domain}"
225
+ domain: "Must be from domain: ${domain}",
226
+ domainBlacklist: "Domain ${domain} is not allowed",
227
+ businessOnly: "Only business email addresses are allowed",
228
+ noDisposable: "Disposable email addresses are not allowed"
88
229
  },
89
230
  url: {
90
231
  required: "Required",
232
+ invalid: "Invalid URL format",
91
233
  min: "Must be at least ${min} characters",
92
234
  max: "Must be at most ${max} characters",
93
235
  includes: "Must include ${includes}",
94
- invalid: "Invalid format"
236
+ excludes: "Must not contain ${excludes}",
237
+ protocol: "Protocol must be one of: ${protocols}",
238
+ domain: "Domain must be one of: ${domains}",
239
+ domainBlacklist: "Domain ${domain} is not allowed",
240
+ port: "Port must be one of: ${ports}",
241
+ pathStartsWith: "Path must start with ${path}",
242
+ pathEndsWith: "Path must end with ${path}",
243
+ hasQuery: "URL must have query parameters",
244
+ noQuery: "URL must not have query parameters",
245
+ hasFragment: "URL must have a fragment",
246
+ noFragment: "URL must not have a fragment",
247
+ localhost: "Localhost URLs are not allowed",
248
+ noLocalhost: "Localhost URLs are not allowed"
95
249
  },
96
250
  password: {
97
251
  required: "Required",
@@ -100,46 +254,167 @@ var en_default = {
100
254
  uppercase: "Must include at least one uppercase letter",
101
255
  lowercase: "Must include at least one lowercase letter",
102
256
  digits: "Must include at least one digit",
103
- special: "Must include at least one special character"
257
+ special: "Must include at least one special character",
258
+ noRepeating: "Must not contain repeating characters",
259
+ noSequential: "Must not contain sequential characters",
260
+ noCommonWords: "Must not contain common words or patterns",
261
+ minStrength: "Password strength must be at least ${minStrength}",
262
+ excludes: "Must not contain ${excludes}",
263
+ includes: "Must include ${includes}",
264
+ invalid: "Invalid password format"
104
265
  },
105
266
  number: {
106
267
  required: "Required",
107
- min: "Must be at least ${min}",
108
- max: "Must be at most ${max}"
109
- },
110
- integer: {
111
- required: "Required",
268
+ invalid: "Must be a valid number",
269
+ integer: "Must be an integer",
270
+ float: "Must be a decimal number",
112
271
  min: "Must be at least ${min}",
113
272
  max: "Must be at most ${max}",
114
- integer: "Must be an integer"
273
+ positive: "Must be positive",
274
+ negative: "Must be negative",
275
+ nonNegative: "Must be non-negative",
276
+ nonPositive: "Must be non-positive",
277
+ multipleOf: "Must be a multiple of ${multipleOf}",
278
+ finite: "Must be a finite number",
279
+ precision: "Must have at most ${precision} decimal places"
115
280
  },
116
- float: {
281
+ id: {
117
282
  required: "Required",
118
- min: "Must be at least ${min}",
119
- max: "Must be at most ${max}",
120
- float: "Must be an float"
283
+ invalid: "Invalid ID format",
284
+ minLength: "Must be at least ${minLength} characters",
285
+ maxLength: "Must be at most ${maxLength} characters",
286
+ numeric: "Must be a numeric ID",
287
+ uuid: "Must be a valid UUID",
288
+ objectId: "Must be a valid MongoDB ObjectId",
289
+ nanoid: "Must be a valid Nano ID",
290
+ snowflake: "Must be a valid Snowflake ID",
291
+ cuid: "Must be a valid CUID",
292
+ ulid: "Must be a valid ULID",
293
+ shortid: "Must be a valid Short ID",
294
+ customFormat: "Invalid ID format",
295
+ includes: "Must include ${includes}",
296
+ excludes: "Must not contain ${excludes}",
297
+ startsWith: "Must start with ${startsWith}",
298
+ endsWith: "Must end with ${endsWith}"
121
299
  },
122
300
  text: {
123
301
  required: "Required",
124
- min: "Must be at least ${min} characters",
125
- max: "Must be at most ${max} characters",
302
+ notEmpty: "Cannot be empty or whitespace only",
303
+ minLength: "Must be at least ${minLength} characters",
304
+ maxLength: "Must be at most ${maxLength} characters",
126
305
  startsWith: "Must start with ${startsWith}",
127
306
  endsWith: "Must end with ${endsWith}",
128
307
  includes: "Must include ${includes}",
308
+ excludes: "Must not contain ${excludes}",
129
309
  invalid: "Invalid format"
130
310
  },
131
311
  date: {
132
312
  required: "Required",
133
- min: "Must be at least ${min} characters",
134
- max: "Must be at most ${max} characters",
313
+ invalid: "Invalid date",
314
+ format: "Must be in ${format} format",
315
+ min: "Date must be on or after ${min}",
316
+ max: "Date must be on or before ${max}",
317
+ includes: "Must include ${includes}",
318
+ excludes: "Must not contain ${excludes}",
319
+ past: "Date must be in the past",
320
+ future: "Date must be in the future",
321
+ today: "Date must be today",
322
+ notToday: "Date must not be today",
323
+ weekday: "Date must be a weekday (Monday-Friday)",
324
+ weekend: "Date must be a weekend (Saturday-Sunday)"
325
+ },
326
+ time: {
327
+ required: "Required",
328
+ invalid: "Invalid time format",
329
+ format: "Must be in ${format} format",
330
+ min: "Time must be after ${min}",
331
+ max: "Time must be before ${max}",
332
+ hour: "Hour must be between ${minHour} and ${maxHour}",
333
+ minute: "Minutes must be in ${minuteStep}-minute intervals",
334
+ second: "Seconds must be in ${secondStep}-second intervals",
135
335
  includes: "Must include ${includes}",
136
- format: "Must be in ${format} format"
336
+ excludes: "Must not contain ${excludes}",
337
+ customRegex: "Invalid time format",
338
+ notInWhitelist: "Time is not in the allowed list"
339
+ },
340
+ datetime: {
341
+ required: "Required",
342
+ invalid: "Invalid datetime format",
343
+ format: "Must be in ${format} format",
344
+ min: "DateTime must be after ${min}",
345
+ max: "DateTime must be before ${max}",
346
+ hour: "Hour must be between ${minHour} and ${maxHour}",
347
+ minute: "Minutes must be in ${minuteStep}-minute intervals",
348
+ includes: "Must include ${includes}",
349
+ excludes: "Must not contain ${excludes}",
350
+ past: "DateTime must be in the past",
351
+ future: "DateTime must be in the future",
352
+ today: "DateTime must be today",
353
+ notToday: "DateTime must not be today",
354
+ weekday: "DateTime must be a weekday (Monday-Friday)",
355
+ weekend: "DateTime must be a weekend (Saturday-Sunday)",
356
+ customRegex: "Invalid datetime format",
357
+ notInWhitelist: "DateTime is not in the allowed list"
358
+ },
359
+ file: {
360
+ required: "Required",
361
+ invalid: "Invalid file format",
362
+ size: "File size must not exceed ${size}",
363
+ minSize: "File size must be at least ${minSize}",
364
+ maxSize: "File size must not exceed ${maxSize}",
365
+ type: "File type must be one of: ${type}",
366
+ extension: "File extension must be one of: ${extension}",
367
+ extensionBlacklist: "File extension ${extension} is not allowed",
368
+ name: "File name must match pattern ${pattern}",
369
+ nameBlacklist: "File name must not match pattern ${pattern}",
370
+ imageOnly: "Only image files are allowed",
371
+ documentOnly: "Only document files are allowed",
372
+ videoOnly: "Only video files are allowed",
373
+ audioOnly: "Only audio files are allowed",
374
+ archiveOnly: "Only archive files are allowed"
375
+ }
376
+ },
377
+ taiwan: {
378
+ businessId: {
379
+ required: "Required",
380
+ invalid: "Invalid Taiwan Business ID"
381
+ },
382
+ nationalId: {
383
+ required: "Required",
384
+ invalid: "Invalid Taiwan National ID"
385
+ },
386
+ mobile: {
387
+ required: "Required",
388
+ invalid: "Invalid Taiwan mobile phone format",
389
+ notInWhitelist: "Not in allowed mobile phone list"
390
+ },
391
+ tel: {
392
+ required: "Required",
393
+ invalid: "Invalid Taiwan telephone format",
394
+ notInWhitelist: "Not in allowed telephone list"
395
+ },
396
+ fax: {
397
+ required: "Required",
398
+ invalid: "Invalid Taiwan fax format",
399
+ notInWhitelist: "Not in allowed fax list"
400
+ },
401
+ postalCode: {
402
+ required: "Required",
403
+ invalid: "Invalid Taiwan postal code",
404
+ invalidFormat: "Invalid postal code format",
405
+ invalidRange: "Postal code is outside valid range",
406
+ legacy5DigitWarning: "5-digit postal codes are legacy format, consider using 6-digit format",
407
+ format3Only: "Only 3-digit postal codes are allowed",
408
+ format5Only: "Only 5-digit postal codes are allowed",
409
+ format6Only: "Only 6-digit postal codes are allowed",
410
+ invalidSuffix: "Invalid postal code suffix - must be 01-99 for 5-digit or 001-999 for 6-digit codes",
411
+ deprecated5Digit: "5-digit postal codes are deprecated and no longer supported"
137
412
  }
138
413
  }
139
414
  };
140
415
 
141
416
  // src/config.ts
142
- var currentLocale = "zh-TW";
417
+ var currentLocale = "en";
143
418
  var setLocale = (locale) => {
144
419
  currentLocale = locale;
145
420
  };
@@ -161,148 +436,2810 @@ function getNestedValue(obj, path) {
161
436
  return typeof result === "string" ? result : void 0;
162
437
  }
163
438
 
164
- // src/common/boolean.ts
439
+ // src/validators/common/boolean.ts
165
440
  function boolean(options) {
166
- const { required = true, defaultValue = null, shouldBe } = options ?? {};
441
+ const {
442
+ required = true,
443
+ defaultValue = null,
444
+ shouldBe,
445
+ truthyValues = [true, "true", 1, "1", "yes", "on"],
446
+ falsyValues = [false, "false", 0, "0", "no", "off"],
447
+ strict = false,
448
+ transform,
449
+ i18n
450
+ } = options ?? {};
451
+ const getMessage = (key, params) => {
452
+ if (i18n) {
453
+ const currentLocale2 = getLocale();
454
+ const customMessages = i18n[currentLocale2];
455
+ if (customMessages && customMessages[key]) {
456
+ const template = customMessages[key];
457
+ return template.replace(/\$\{(\w+)}/g, (_, k) => params?.[k] ?? "");
458
+ }
459
+ }
460
+ return t(`common.boolean.${key}`, params);
461
+ };
167
462
  let result = z.preprocess(
168
463
  (val) => {
169
464
  if (val === "" || val === void 0 || val === null) return defaultValue;
170
- if (val === "true" || val === 1 || val === "1") return true;
171
- if (val === "false" || val === 0 || val === "0") return false;
465
+ if (strict && typeof val !== "boolean" && val !== null) {
466
+ return val;
467
+ }
468
+ if (truthyValues.includes(val)) {
469
+ let processed = true;
470
+ if (transform) processed = transform(processed);
471
+ return processed;
472
+ }
473
+ if (falsyValues.includes(val)) {
474
+ let processed = false;
475
+ if (transform) processed = transform(processed);
476
+ return processed;
477
+ }
172
478
  return val;
173
479
  },
174
480
  z.union([z.literal(true), z.literal(false), z.literal(null)])
175
481
  );
176
482
  if (required && defaultValue === null) {
177
- result = result.refine((val) => val !== null, { message: t("common.boolean.required") });
483
+ result = result.refine((val) => val !== null, { message: getMessage("required") });
178
484
  }
179
485
  if (shouldBe === true) {
180
- result = result.refine((val) => val === true, { message: t("common.boolean.shouldBe.true") });
486
+ result = result.refine((val) => val === true, { message: getMessage("shouldBeTrue") });
181
487
  } else if (shouldBe === false) {
182
- result = result.refine((val) => val === false, { message: t("common.boolean.shouldBe.false") });
488
+ result = result.refine((val) => val === false, { message: getMessage("shouldBeFalse") });
489
+ }
490
+ if (strict) {
491
+ result = result.refine(
492
+ (val) => {
493
+ return val === null || typeof val === "boolean";
494
+ },
495
+ { message: getMessage("invalid") }
496
+ );
183
497
  }
184
498
  return result;
185
499
  }
186
500
 
187
- // src/common/email.ts
501
+ // src/validators/common/date.ts
188
502
  import { z as z2 } from "zod";
189
- function email(options) {
190
- const { required = true, domain, min, max, includes } = options ?? {};
191
- const baseSchema = required ? z2.preprocess(
192
- (val) => val === "" || val === null || val === void 0 ? null : val,
193
- z2.email({
194
- error: (issue) => {
195
- if (issue.code === "invalid_type") return t("common.email.required");
196
- else if (issue.code === "invalid_format") return t("common.email.invalid");
197
- return t("common.email.invalid");
198
- }
199
- })
200
- ) : z2.preprocess((val) => val === "" || val === null || val === void 0 ? null : val, z2.email({ message: t("common.email.invalid") }).nullable());
201
- const schema = baseSchema.refine((val) => required ? val !== "" && val !== "null" && val !== "undefined" : true, { message: t("common.text.required") }).refine((val) => val === null || min === void 0 || val.length >= min, { message: t("common.text.min", { min }) }).refine((val) => val === null || max === void 0 || val.length <= max, { message: t("common.text.max", { max }) }).refine((val) => val === null || includes === void 0 || val.includes(includes), { message: t("common.text.includes", { includes }) }).refine(
202
- (val) => {
203
- if (val === null || domain === void 0) return true;
204
- return val.split("@")[1]?.toLowerCase() === domain.toLowerCase();
205
- },
206
- { message: t("common.email.domain", { domain }) }
207
- );
503
+ import dayjs from "dayjs";
504
+ import customParseFormat from "dayjs/plugin/customParseFormat";
505
+ import isSameOrAfter from "dayjs/plugin/isSameOrAfter";
506
+ import isSameOrBefore from "dayjs/plugin/isSameOrBefore";
507
+ import isToday from "dayjs/plugin/isToday";
508
+ import weekday from "dayjs/plugin/weekday";
509
+ dayjs.extend(isSameOrAfter);
510
+ dayjs.extend(isSameOrBefore);
511
+ dayjs.extend(customParseFormat);
512
+ dayjs.extend(isToday);
513
+ dayjs.extend(weekday);
514
+ function date(options) {
515
+ const {
516
+ required = true,
517
+ min,
518
+ max,
519
+ format = "YYYY-MM-DD",
520
+ includes,
521
+ excludes,
522
+ mustBePast,
523
+ mustBeFuture,
524
+ mustBeToday,
525
+ mustNotBeToday,
526
+ weekdaysOnly,
527
+ weekendsOnly,
528
+ transform,
529
+ defaultValue = null,
530
+ i18n
531
+ } = options ?? {};
532
+ const actualDefaultValue = defaultValue ?? (required ? "" : null);
533
+ const getMessage = (key, params) => {
534
+ if (i18n) {
535
+ const currentLocale2 = getLocale();
536
+ const customMessages = i18n[currentLocale2];
537
+ if (customMessages && customMessages[key]) {
538
+ const template = customMessages[key];
539
+ return template.replace(/\$\{(\w+)}/g, (_, k) => params?.[k] ?? "");
540
+ }
541
+ }
542
+ return t(`common.date.${key}`, params);
543
+ };
544
+ const preprocessFn = (val) => {
545
+ if (val === "" || val === null || val === void 0) {
546
+ return actualDefaultValue;
547
+ }
548
+ let processed = String(val).trim();
549
+ if (transform) {
550
+ processed = transform(processed);
551
+ }
552
+ return processed;
553
+ };
554
+ const baseSchema = required ? z2.preprocess(preprocessFn, z2.string()) : z2.preprocess(preprocessFn, z2.string().nullable());
555
+ const schema = baseSchema.refine((val) => {
556
+ if (val === null) return true;
557
+ if (required && (val === "" || val === "null" || val === "undefined")) {
558
+ throw new z2.ZodError([{ code: "custom", message: getMessage("required"), path: [] }]);
559
+ }
560
+ if (val !== null && !dayjs(val, format, true).isValid()) {
561
+ throw new z2.ZodError([{ code: "custom", message: getMessage("format", { format }), path: [] }]);
562
+ }
563
+ const dateObj = dayjs(val, format);
564
+ if (val !== null && min !== void 0 && !dateObj.isSameOrAfter(dayjs(min, format))) {
565
+ throw new z2.ZodError([{ code: "custom", message: getMessage("min", { min }), path: [] }]);
566
+ }
567
+ if (val !== null && max !== void 0 && !dateObj.isSameOrBefore(dayjs(max, format))) {
568
+ throw new z2.ZodError([{ code: "custom", message: getMessage("max", { max }), path: [] }]);
569
+ }
570
+ if (val !== null && includes !== void 0 && !val.includes(includes)) {
571
+ throw new z2.ZodError([{ code: "custom", message: getMessage("includes", { includes }), path: [] }]);
572
+ }
573
+ if (val !== null && excludes !== void 0) {
574
+ const excludeList = Array.isArray(excludes) ? excludes : [excludes];
575
+ for (const exclude of excludeList) {
576
+ if (val.includes(exclude)) {
577
+ throw new z2.ZodError([{ code: "custom", message: getMessage("excludes", { excludes: exclude }), path: [] }]);
578
+ }
579
+ }
580
+ }
581
+ const today = dayjs().startOf("day");
582
+ const targetDate = dateObj.startOf("day");
583
+ if (val !== null && mustBePast && !targetDate.isBefore(today)) {
584
+ throw new z2.ZodError([{ code: "custom", message: getMessage("past"), path: [] }]);
585
+ }
586
+ if (val !== null && mustBeFuture && !targetDate.isAfter(today)) {
587
+ throw new z2.ZodError([{ code: "custom", message: getMessage("future"), path: [] }]);
588
+ }
589
+ if (val !== null && mustBeToday && !targetDate.isSame(today)) {
590
+ throw new z2.ZodError([{ code: "custom", message: getMessage("today"), path: [] }]);
591
+ }
592
+ if (val !== null && mustNotBeToday && targetDate.isSame(today)) {
593
+ throw new z2.ZodError([{ code: "custom", message: getMessage("notToday"), path: [] }]);
594
+ }
595
+ if (val !== null && weekdaysOnly && (dateObj.day() === 0 || dateObj.day() === 6)) {
596
+ throw new z2.ZodError([{ code: "custom", message: getMessage("weekday"), path: [] }]);
597
+ }
598
+ if (val !== null && weekendsOnly && dateObj.day() !== 0 && dateObj.day() !== 6) {
599
+ throw new z2.ZodError([{ code: "custom", message: getMessage("weekend"), path: [] }]);
600
+ }
601
+ return true;
602
+ });
208
603
  return schema;
209
604
  }
210
605
 
211
- // src/common/url.ts
606
+ // src/validators/common/datetime.ts
212
607
  import { z as z3 } from "zod";
213
- function url(options) {
214
- const { required = true, min, max, includes } = options ?? {};
215
- const baseSchema = required ? z3.preprocess(
216
- (val) => val === "" || val === null || val === void 0 ? null : val,
217
- z3.url({
218
- error: (issue) => {
219
- if (issue.code === "invalid_type") return t("common.url.required");
220
- else if (issue.code === "invalid_format") return t("common.url.invalid");
221
- return t("common.url.invalid");
222
- }
223
- })
224
- ) : z3.preprocess((val) => val === "" || val === null || val === void 0 ? null : val, z3.url({ message: t("common.url.invalid") }).nullable());
225
- const schema = baseSchema.refine((val) => required ? val !== "" && val !== "null" && val !== "undefined" : true, { message: t("common.text.required") }).refine((val) => val === null || min === void 0 || val.length >= min, { message: t("common.text.min", { min }) }).refine((val) => val === null || max === void 0 || val.length <= max, { message: t("common.text.max", { max }) }).refine((val) => val === null || includes === void 0 || val.includes(includes), { message: t("common.text.includes", { includes }) });
608
+ import dayjs2 from "dayjs";
609
+ import customParseFormat2 from "dayjs/plugin/customParseFormat";
610
+ import isSameOrAfter2 from "dayjs/plugin/isSameOrAfter";
611
+ import isSameOrBefore2 from "dayjs/plugin/isSameOrBefore";
612
+ import isToday2 from "dayjs/plugin/isToday";
613
+ import weekday2 from "dayjs/plugin/weekday";
614
+ import timezone from "dayjs/plugin/timezone";
615
+ import utc from "dayjs/plugin/utc";
616
+ dayjs2.extend(isSameOrAfter2);
617
+ dayjs2.extend(isSameOrBefore2);
618
+ dayjs2.extend(customParseFormat2);
619
+ dayjs2.extend(isToday2);
620
+ dayjs2.extend(weekday2);
621
+ dayjs2.extend(timezone);
622
+ dayjs2.extend(utc);
623
+ var DATETIME_PATTERNS = {
624
+ "YYYY-MM-DD HH:mm": /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}$/,
625
+ "YYYY-MM-DD HH:mm:ss": /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/,
626
+ "YYYY-MM-DD hh:mm A": /^\d{4}-\d{2}-\d{2} \d{1,2}:\d{2} (AM|PM)$/i,
627
+ "YYYY-MM-DD hh:mm:ss A": /^\d{4}-\d{2}-\d{2} \d{1,2}:\d{2}:\d{2} (AM|PM)$/i,
628
+ "DD/MM/YYYY HH:mm": /^\d{1,2}\/\d{1,2}\/\d{4} \d{2}:\d{2}$/,
629
+ "DD/MM/YYYY HH:mm:ss": /^\d{1,2}\/\d{1,2}\/\d{4} \d{2}:\d{2}:\d{2}$/,
630
+ "DD/MM/YYYY hh:mm A": /^\d{1,2}\/\d{1,2}\/\d{4} \d{1,2}:\d{2} (AM|PM)$/i,
631
+ "MM/DD/YYYY HH:mm": /^\d{1,2}\/\d{1,2}\/\d{4} \d{2}:\d{2}$/,
632
+ "MM/DD/YYYY hh:mm A": /^\d{1,2}\/\d{1,2}\/\d{4} \d{1,2}:\d{2} (AM|PM)$/i,
633
+ "YYYY/MM/DD HH:mm": /^\d{4}\/\d{2}\/\d{2} \d{2}:\d{2}$/,
634
+ "DD-MM-YYYY HH:mm": /^\d{1,2}-\d{1,2}-\d{4} \d{2}:\d{2}$/,
635
+ "MM-DD-YYYY HH:mm": /^\d{1,2}-\d{1,2}-\d{4} \d{2}:\d{2}$/,
636
+ ISO: /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{3})?Z?$/,
637
+ RFC: /^[A-Za-z]{3}, \d{1,2} [A-Za-z]{3} \d{4} \d{2}:\d{2}:\d{2} [A-Z]{3}$/,
638
+ UNIX: /^\d{10}$/
639
+ };
640
+ var validateDateTimeFormat = (value, format) => {
641
+ const pattern = DATETIME_PATTERNS[format];
642
+ if (!pattern.test(value.trim())) {
643
+ return false;
644
+ }
645
+ const parsed = parseDateTimeValue(value, format);
646
+ return parsed !== null;
647
+ };
648
+ var parseDateTimeValue = (value, format, timezone2) => {
649
+ try {
650
+ const cleanValue = value.trim();
651
+ let parsed;
652
+ switch (format) {
653
+ case "ISO":
654
+ parsed = dayjs2(cleanValue);
655
+ break;
656
+ case "RFC":
657
+ parsed = dayjs2(cleanValue);
658
+ break;
659
+ case "UNIX":
660
+ parsed = dayjs2.unix(parseInt(cleanValue, 10));
661
+ break;
662
+ default:
663
+ parsed = dayjs2(cleanValue, format, true);
664
+ break;
665
+ }
666
+ if (!parsed.isValid()) {
667
+ return null;
668
+ }
669
+ if (timezone2) {
670
+ parsed = parsed.tz(timezone2);
671
+ }
672
+ return parsed;
673
+ } catch {
674
+ return null;
675
+ }
676
+ };
677
+ var normalizeDateTimeValue = (value, format, timezone2) => {
678
+ const parsed = parseDateTimeValue(value, format, timezone2);
679
+ if (!parsed) return null;
680
+ switch (format) {
681
+ case "ISO":
682
+ return parsed.toISOString();
683
+ case "RFC":
684
+ return parsed.format("ddd, DD MMM YYYY HH:mm:ss [GMT]");
685
+ case "UNIX":
686
+ return parsed.unix().toString();
687
+ default:
688
+ return parsed.format(format);
689
+ }
690
+ };
691
+ function datetime(options) {
692
+ const {
693
+ required = true,
694
+ format = "YYYY-MM-DD HH:mm",
695
+ min,
696
+ max,
697
+ minHour,
698
+ maxHour,
699
+ allowedHours,
700
+ minuteStep,
701
+ timezone: timezone2,
702
+ includes,
703
+ excludes,
704
+ regex,
705
+ trimMode = "trim",
706
+ casing = "none",
707
+ mustBePast,
708
+ mustBeFuture,
709
+ mustBeToday,
710
+ mustNotBeToday,
711
+ weekdaysOnly,
712
+ weekendsOnly,
713
+ whitelist,
714
+ whitelistOnly = false,
715
+ transform,
716
+ defaultValue,
717
+ i18n
718
+ } = options ?? {};
719
+ const actualDefaultValue = defaultValue ?? (required ? "" : null);
720
+ const getMessage = (key, params) => {
721
+ if (i18n) {
722
+ const currentLocale2 = getLocale();
723
+ const customMessages = i18n[currentLocale2];
724
+ if (customMessages && customMessages[key]) {
725
+ const template = customMessages[key];
726
+ return template.replace(/\$\{(\w+)}/g, (_, k) => params?.[k] ?? "");
727
+ }
728
+ }
729
+ return t(`common.datetime.${key}`, params);
730
+ };
731
+ const preprocessFn = (val) => {
732
+ if (val === null || val === void 0) {
733
+ return actualDefaultValue;
734
+ }
735
+ let processed = String(val);
736
+ switch (trimMode) {
737
+ case "trim":
738
+ processed = processed.trim();
739
+ break;
740
+ case "trimStart":
741
+ processed = processed.trimStart();
742
+ break;
743
+ case "trimEnd":
744
+ processed = processed.trimEnd();
745
+ break;
746
+ case "none":
747
+ break;
748
+ }
749
+ if (processed === "") {
750
+ if (whitelist && whitelist.includes("")) {
751
+ return "";
752
+ }
753
+ if (!required) {
754
+ return actualDefaultValue;
755
+ }
756
+ return actualDefaultValue;
757
+ }
758
+ switch (casing) {
759
+ case "upper":
760
+ processed = processed.toUpperCase();
761
+ break;
762
+ case "lower":
763
+ processed = processed.toLowerCase();
764
+ break;
765
+ case "none":
766
+ break;
767
+ }
768
+ if (transform) {
769
+ processed = transform(processed);
770
+ }
771
+ return processed;
772
+ };
773
+ const baseSchema = required ? z3.preprocess(preprocessFn, z3.string()) : z3.preprocess(preprocessFn, z3.string().nullable());
774
+ const schema = baseSchema.refine((val) => {
775
+ if (val === null) return true;
776
+ if (required && (val === "" || val === "null" || val === "undefined")) {
777
+ throw new z3.ZodError([{ code: "custom", message: getMessage("required"), path: [] }]);
778
+ }
779
+ if (val === null) return true;
780
+ if (!required && val === "") return true;
781
+ if (whitelist && whitelist.length > 0) {
782
+ if (whitelist.includes(val)) {
783
+ return true;
784
+ }
785
+ if (whitelistOnly) {
786
+ throw new z3.ZodError([{ code: "custom", message: getMessage("notInWhitelist"), path: [] }]);
787
+ }
788
+ }
789
+ if (regex) {
790
+ if (!regex.test(val)) {
791
+ throw new z3.ZodError([{ code: "custom", message: getMessage("customRegex"), path: [] }]);
792
+ }
793
+ } else {
794
+ if (!validateDateTimeFormat(val, format)) {
795
+ throw new z3.ZodError([{ code: "custom", message: getMessage("format", { format }), path: [] }]);
796
+ }
797
+ }
798
+ if (includes && !val.includes(includes)) {
799
+ throw new z3.ZodError([{ code: "custom", message: getMessage("includes", { includes }), path: [] }]);
800
+ }
801
+ if (excludes) {
802
+ const excludeList = Array.isArray(excludes) ? excludes : [excludes];
803
+ for (const exclude of excludeList) {
804
+ if (val.includes(exclude)) {
805
+ throw new z3.ZodError([{ code: "custom", message: getMessage("excludes", { excludes: exclude }), path: [] }]);
806
+ }
807
+ }
808
+ }
809
+ if (regex) {
810
+ return true;
811
+ }
812
+ const parsed = parseDateTimeValue(val, format, timezone2);
813
+ if (!parsed) {
814
+ const pattern = DATETIME_PATTERNS[format];
815
+ if (!pattern.test(val.trim())) {
816
+ throw new z3.ZodError([{ code: "custom", message: getMessage("format", { format }), path: [] }]);
817
+ } else {
818
+ throw new z3.ZodError([{ code: "custom", message: getMessage("invalid"), path: [] }]);
819
+ }
820
+ }
821
+ const hour = parsed.hour();
822
+ if (minHour !== void 0 && hour < minHour) {
823
+ throw new z3.ZodError([{ code: "custom", message: getMessage("hour", { minHour, maxHour: maxHour ?? 23 }), path: [] }]);
824
+ }
825
+ if (maxHour !== void 0 && hour > maxHour) {
826
+ throw new z3.ZodError([{ code: "custom", message: getMessage("hour", { minHour: minHour ?? 0, maxHour }), path: [] }]);
827
+ }
828
+ if (allowedHours && allowedHours.length > 0) {
829
+ if (!allowedHours.includes(hour)) {
830
+ throw new z3.ZodError([{ code: "custom", message: getMessage("hour", { allowedHours: allowedHours.join(", ") }), path: [] }]);
831
+ }
832
+ }
833
+ const minute = parsed.minute();
834
+ if (minuteStep !== void 0 && minute % minuteStep !== 0) {
835
+ throw new z3.ZodError([{ code: "custom", message: getMessage("minute", { minuteStep }), path: [] }]);
836
+ }
837
+ if (min) {
838
+ const minParsed = typeof min === "string" ? parseDateTimeValue(min, format, timezone2) : dayjs2(min);
839
+ if (minParsed && parsed.isBefore(minParsed)) {
840
+ const minFormatted = typeof min === "string" ? min : minParsed.format(format);
841
+ throw new z3.ZodError([{ code: "custom", message: getMessage("min", { min: minFormatted }), path: [] }]);
842
+ }
843
+ }
844
+ if (max) {
845
+ const maxParsed = typeof max === "string" ? parseDateTimeValue(max, format, timezone2) : dayjs2(max);
846
+ if (maxParsed && parsed.isAfter(maxParsed)) {
847
+ const maxFormatted = typeof max === "string" ? max : maxParsed.format(format);
848
+ throw new z3.ZodError([{ code: "custom", message: getMessage("max", { max: maxFormatted }), path: [] }]);
849
+ }
850
+ }
851
+ const now = timezone2 ? dayjs2().tz(timezone2) : dayjs2();
852
+ if (mustBePast && !parsed.isBefore(now)) {
853
+ throw new z3.ZodError([{ code: "custom", message: getMessage("past"), path: [] }]);
854
+ }
855
+ if (mustBeFuture && !parsed.isAfter(now)) {
856
+ throw new z3.ZodError([{ code: "custom", message: getMessage("future"), path: [] }]);
857
+ }
858
+ if (mustBeToday && !parsed.isSame(now, "day")) {
859
+ throw new z3.ZodError([{ code: "custom", message: getMessage("today"), path: [] }]);
860
+ }
861
+ if (mustNotBeToday && parsed.isSame(now, "day")) {
862
+ throw new z3.ZodError([{ code: "custom", message: getMessage("notToday"), path: [] }]);
863
+ }
864
+ const dayOfWeek = parsed.day();
865
+ if (weekdaysOnly && (dayOfWeek === 0 || dayOfWeek === 6)) {
866
+ throw new z3.ZodError([{ code: "custom", message: getMessage("weekday"), path: [] }]);
867
+ }
868
+ if (weekendsOnly && dayOfWeek !== 0 && dayOfWeek !== 6) {
869
+ throw new z3.ZodError([{ code: "custom", message: getMessage("weekend"), path: [] }]);
870
+ }
871
+ return true;
872
+ });
226
873
  return schema;
227
874
  }
228
875
 
229
- // src/common/text.ts
876
+ // src/validators/common/email.ts
230
877
  import { z as z4 } from "zod";
231
- function text(options) {
232
- const { required = true, min, max, startsWith, endsWith, includes, regex, defaultValue = null } = options ?? {};
233
- const baseSchema = required ? z4.preprocess((val) => val === "" || val === null || val === void 0 ? defaultValue : val, z4.coerce.string().trim()) : z4.preprocess((val) => val === "" || val === null || val === void 0 ? defaultValue : val, z4.coerce.string().trim().nullable());
234
- const schema = baseSchema.refine((val) => required ? val !== "" && val !== "null" && val !== "undefined" : true, { message: t("common.text.required") }).refine((val) => val === null || min === void 0 || val.length >= min, { message: t("common.text.min", { min }) }).refine((val) => val === null || max === void 0 || val.length <= max, { message: t("common.text.max", { max }) }).refine((val) => val === null || startsWith === void 0 || val.startsWith(startsWith), { message: t("common.text.startsWith", { startsWith }) }).refine((val) => val === null || endsWith === void 0 || val.endsWith(endsWith), { message: t("common.text.endsWith", { endsWith }) }).refine((val) => val === null || includes === void 0 || val.includes(includes), { message: t("common.text.includes", { includes }) }).refine((val) => val === null || regex === void 0 || regex.test(val), { message: t("common.text.invalid", { regex }) });
878
+ function email(options) {
879
+ const {
880
+ required = true,
881
+ domain,
882
+ domainBlacklist,
883
+ minLength,
884
+ maxLength,
885
+ includes,
886
+ excludes,
887
+ allowSubdomains = true,
888
+ businessOnly = false,
889
+ noDisposable = false,
890
+ lowercase = true,
891
+ transform,
892
+ defaultValue,
893
+ i18n
894
+ } = options ?? {};
895
+ const getMessage = (key, params) => {
896
+ if (i18n) {
897
+ const currentLocale2 = getLocale();
898
+ const customMessages = i18n[currentLocale2];
899
+ if (customMessages && customMessages[key]) {
900
+ const template = customMessages[key];
901
+ return template.replace(/\$\{(\w+)}/g, (_, k) => params?.[k] ?? "");
902
+ }
903
+ }
904
+ return t(`common.email.${key}`, params);
905
+ };
906
+ const disposableDomains = ["10minutemail.com", "tempmail.org", "guerrillamail.com", "mailinator.com", "yopmail.com", "temp-mail.org", "throwaway.email", "getnada.com", "maildrop.cc"];
907
+ const freeEmailDomains = ["gmail.com", "yahoo.com", "hotmail.com", "outlook.com", "icloud.com", "aol.com", "protonmail.com", "zoho.com"];
908
+ const actualDefaultValue = defaultValue ?? null;
909
+ const baseSchema = z4.preprocess(
910
+ (val) => {
911
+ if (val === "" || val === null || val === void 0) {
912
+ return actualDefaultValue;
913
+ }
914
+ let processed = String(val).trim();
915
+ if (lowercase) {
916
+ processed = processed.toLowerCase();
917
+ }
918
+ if (transform) {
919
+ processed = transform(processed);
920
+ }
921
+ return processed;
922
+ },
923
+ z4.union([z4.string().email(), z4.null()])
924
+ );
925
+ const schema = baseSchema.refine((val) => {
926
+ if (required && val === null) {
927
+ throw new z4.ZodError([{ code: "custom", message: getMessage("required"), path: [] }]);
928
+ }
929
+ if (val === null) return true;
930
+ if (typeof val !== "string") {
931
+ throw new z4.ZodError([{ code: "custom", message: getMessage("invalid"), path: [] }]);
932
+ }
933
+ if (minLength !== void 0 && val.length < minLength) {
934
+ throw new z4.ZodError([{ code: "custom", message: getMessage("minLength", { minLength }), path: [] }]);
935
+ }
936
+ if (maxLength !== void 0 && val.length > maxLength) {
937
+ throw new z4.ZodError([{ code: "custom", message: getMessage("maxLength", { maxLength }), path: [] }]);
938
+ }
939
+ if (includes !== void 0 && !val.includes(includes)) {
940
+ throw new z4.ZodError([{ code: "custom", message: getMessage("includes", { includes }), path: [] }]);
941
+ }
942
+ if (excludes !== void 0) {
943
+ const excludeList = Array.isArray(excludes) ? excludes : [excludes];
944
+ for (const exclude of excludeList) {
945
+ if (val.includes(exclude)) {
946
+ throw new z4.ZodError([{ code: "custom", message: getMessage("includes", { includes: exclude }), path: [] }]);
947
+ }
948
+ }
949
+ }
950
+ const emailDomain = val.split("@")[1]?.toLowerCase();
951
+ if (!emailDomain) {
952
+ throw new z4.ZodError([{ code: "custom", message: getMessage("invalid"), path: [] }]);
953
+ }
954
+ if (businessOnly) {
955
+ const isFreeProvider = freeEmailDomains.some((freeDomain) => {
956
+ if (allowSubdomains) {
957
+ return emailDomain === freeDomain || emailDomain.endsWith("." + freeDomain);
958
+ }
959
+ return emailDomain === freeDomain;
960
+ });
961
+ if (isFreeProvider) {
962
+ throw new z4.ZodError([{ code: "custom", message: getMessage("businessOnly"), path: [] }]);
963
+ }
964
+ }
965
+ if (domainBlacklist && domainBlacklist.length > 0) {
966
+ const isBlacklisted = domainBlacklist.some((blacklistedDomain) => {
967
+ const lowerDomain = blacklistedDomain.toLowerCase();
968
+ if (allowSubdomains) {
969
+ return emailDomain === lowerDomain || emailDomain.endsWith("." + lowerDomain);
970
+ }
971
+ return emailDomain === lowerDomain;
972
+ });
973
+ if (isBlacklisted) {
974
+ throw new z4.ZodError([{ code: "custom", message: getMessage("domainBlacklist", { domain: emailDomain }), path: [] }]);
975
+ }
976
+ }
977
+ if (domain !== void 0) {
978
+ const allowedDomains = Array.isArray(domain) ? domain : [domain];
979
+ const isAllowed = allowedDomains.some((allowedDomain) => {
980
+ const lowerDomain = allowedDomain.toLowerCase();
981
+ if (allowSubdomains) {
982
+ return emailDomain === lowerDomain || emailDomain.endsWith("." + lowerDomain);
983
+ }
984
+ return emailDomain === lowerDomain;
985
+ });
986
+ if (!isAllowed) {
987
+ throw new z4.ZodError([{ code: "custom", message: getMessage("domain", { domain: Array.isArray(domain) ? domain.join(", ") : domain }), path: [] }]);
988
+ }
989
+ }
990
+ if (noDisposable) {
991
+ const isDisposable = disposableDomains.some((disposableDomain) => {
992
+ if (allowSubdomains) {
993
+ return emailDomain === disposableDomain || emailDomain.endsWith("." + disposableDomain);
994
+ }
995
+ return emailDomain === disposableDomain;
996
+ });
997
+ if (isDisposable) {
998
+ throw new z4.ZodError([{ code: "custom", message: getMessage("noDisposable"), path: [] }]);
999
+ }
1000
+ }
1001
+ return true;
1002
+ });
235
1003
  return schema;
236
1004
  }
237
1005
 
238
- // src/common/number.ts
1006
+ // src/validators/common/file.ts
239
1007
  import { z as z5 } from "zod";
240
- function number(options) {
241
- const { required = true, min, max, defaultValue } = options ?? {};
242
- const schema = z5.preprocess(
1008
+ function file(options) {
1009
+ const {
1010
+ required = true,
1011
+ maxSize,
1012
+ minSize,
1013
+ type,
1014
+ typeBlacklist,
1015
+ extension,
1016
+ extensionBlacklist,
1017
+ namePattern,
1018
+ nameBlacklist,
1019
+ imageOnly = false,
1020
+ documentOnly = false,
1021
+ videoOnly = false,
1022
+ audioOnly = false,
1023
+ archiveOnly = false,
1024
+ caseSensitive = false,
1025
+ transform,
1026
+ defaultValue,
1027
+ i18n
1028
+ } = options ?? {};
1029
+ const getMessage = (key, params) => {
1030
+ if (i18n) {
1031
+ const currentLocale2 = getLocale();
1032
+ const customMessages = i18n[currentLocale2];
1033
+ if (customMessages && customMessages[key]) {
1034
+ const template = customMessages[key];
1035
+ return template.replace(/\$\{(\w+)}/g, (_, k) => params?.[k] ?? "");
1036
+ }
1037
+ }
1038
+ return t(`common.file.${key}`, params);
1039
+ };
1040
+ const imageTypes = ["image/jpeg", "image/jpg", "image/png", "image/gif", "image/webp", "image/svg+xml", "image/bmp", "image/tiff"];
1041
+ const documentTypes = [
1042
+ "application/pdf",
1043
+ "application/msword",
1044
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
1045
+ "application/vnd.ms-excel",
1046
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
1047
+ "application/vnd.ms-powerpoint",
1048
+ "application/vnd.openxmlformats-officedocument.presentationml.presentation",
1049
+ "text/plain",
1050
+ "text/csv"
1051
+ ];
1052
+ const videoTypes = ["video/mp4", "video/mpeg", "video/quicktime", "video/x-msvideo", "video/x-ms-wmv", "video/webm", "video/ogg"];
1053
+ const audioTypes = ["audio/mpeg", "audio/wav", "audio/ogg", "audio/aac", "audio/webm", "audio/mp3", "audio/x-wav"];
1054
+ const archiveTypes = ["application/zip", "application/x-rar-compressed", "application/x-7z-compressed", "application/x-tar", "application/gzip"];
1055
+ const actualDefaultValue = defaultValue ?? null;
1056
+ const baseSchema = z5.preprocess(
1057
+ (val) => {
1058
+ if (val === "" || val === null || val === void 0) {
1059
+ return actualDefaultValue;
1060
+ }
1061
+ if (!(val instanceof File)) {
1062
+ return val;
1063
+ }
1064
+ let processed = val;
1065
+ if (transform) {
1066
+ processed = transform(processed);
1067
+ }
1068
+ return processed;
1069
+ },
1070
+ z5.union([z5.instanceof(File).refine(() => true, { message: getMessage("invalid") }), z5.null()])
1071
+ );
1072
+ const schema = baseSchema.refine((val) => required === false || val !== null, {
1073
+ message: getMessage("required")
1074
+ }).refine((val) => val === null || val instanceof File, {
1075
+ message: getMessage("invalid")
1076
+ }).refine((val) => val === null || minSize === void 0 || val.size >= minSize, {
1077
+ message: getMessage("minSize", { minSize: formatFileSize(minSize || 0) })
1078
+ }).refine((val) => val === null || maxSize === void 0 || val.size <= maxSize, {
1079
+ message: getMessage("maxSize", { maxSize: formatFileSize(maxSize || 0) })
1080
+ }).refine((val) => val === null || !imageOnly || imageTypes.includes(val.type), {
1081
+ message: getMessage("imageOnly")
1082
+ }).refine((val) => val === null || !documentOnly || documentTypes.includes(val.type), {
1083
+ message: getMessage("documentOnly")
1084
+ }).refine((val) => val === null || !videoOnly || videoTypes.includes(val.type), {
1085
+ message: getMessage("videoOnly")
1086
+ }).refine((val) => val === null || !audioOnly || audioTypes.includes(val.type), {
1087
+ message: getMessage("audioOnly")
1088
+ }).refine((val) => val === null || !archiveOnly || archiveTypes.includes(val.type), {
1089
+ message: getMessage("archiveOnly")
1090
+ }).refine(
1091
+ (val) => {
1092
+ if (val === null || !typeBlacklist || typeBlacklist.length === 0) return true;
1093
+ return !typeBlacklist.includes(val.type);
1094
+ },
1095
+ {
1096
+ message: getMessage("type", { type: typeBlacklist?.join(", ") || "" })
1097
+ }
1098
+ ).refine(
1099
+ (val) => {
1100
+ if (val === null || type === void 0) return true;
1101
+ const allowedTypes = Array.isArray(type) ? type : [type];
1102
+ return allowedTypes.includes(val.type);
1103
+ },
1104
+ {
1105
+ message: getMessage("type", { type: Array.isArray(type) ? type.join(", ") : type || "" })
1106
+ }
1107
+ ).refine(
1108
+ (val) => {
1109
+ if (val === null || extensionBlacklist === void 0 || extensionBlacklist.length === 0) return true;
1110
+ const fileExtension = getFileExtension(val.name, caseSensitive);
1111
+ const normalizedBlacklist = extensionBlacklist.map((ext) => normalizeExtension(ext, caseSensitive));
1112
+ return !normalizedBlacklist.includes(fileExtension);
1113
+ },
1114
+ {
1115
+ message: getMessage("extensionBlacklist", { extension: extensionBlacklist?.join(", ") || "" })
1116
+ }
1117
+ ).refine(
1118
+ (val) => {
1119
+ if (val === null || extension === void 0) return true;
1120
+ const fileName = val.name;
1121
+ const fileExtension = getFileExtension(fileName, caseSensitive);
1122
+ const allowedExtensions = Array.isArray(extension) ? extension : [extension];
1123
+ const normalizedExtensions = allowedExtensions.map((ext) => normalizeExtension(ext, caseSensitive));
1124
+ return normalizedExtensions.includes(fileExtension);
1125
+ },
1126
+ {
1127
+ message: getMessage("extension", { extension: Array.isArray(extension) ? extension.join(", ") : extension || "" })
1128
+ }
1129
+ ).refine(
1130
+ (val) => {
1131
+ if (val === null || namePattern === void 0) return true;
1132
+ const pattern = typeof namePattern === "string" ? new RegExp(namePattern) : namePattern;
1133
+ return pattern.test(val.name);
1134
+ },
1135
+ {
1136
+ message: getMessage("name", { pattern: namePattern?.toString() || "" })
1137
+ }
1138
+ ).refine(
243
1139
  (val) => {
244
- if (val === "" || val === void 0 || val === null) return defaultValue ?? null;
245
- return typeof val === "string" ? Number(val) : val;
246
- },
247
- z5.union([
248
- z5.number({
249
- error: (issue) => {
250
- if (issue.code === "invalid_type") return t("common.number.integer");
251
- return t("common.number.required");
1140
+ if (val === null || nameBlacklist === void 0) return true;
1141
+ const blacklistPatterns = Array.isArray(nameBlacklist) ? nameBlacklist : [nameBlacklist];
1142
+ for (const blacklistPattern of blacklistPatterns) {
1143
+ const pattern = typeof blacklistPattern === "string" ? new RegExp(blacklistPattern) : blacklistPattern;
1144
+ if (pattern.test(val.name)) {
1145
+ return false;
252
1146
  }
253
- }),
254
- z5.null()
255
- ])
256
- ).refine((val) => !required || val !== null, { message: t("common.number.required") }).refine((val) => val === null || min === void 0 || val >= min, { message: t("common.number.min", { min }) }).refine((val) => val === null || max === void 0 || val <= max, { message: t("common.number.max", { max }) });
1147
+ }
1148
+ return true;
1149
+ },
1150
+ {
1151
+ message: getMessage("nameBlacklist", { pattern: "" })
1152
+ }
1153
+ );
257
1154
  return schema;
258
1155
  }
1156
+ function getFileExtension(fileName, caseSensitive) {
1157
+ const lastDotIndex = fileName.lastIndexOf(".");
1158
+ if (lastDotIndex === -1) return "";
1159
+ const extension = fileName.substring(lastDotIndex);
1160
+ return caseSensitive ? extension : extension.toLowerCase();
1161
+ }
1162
+ function normalizeExtension(extension, caseSensitive) {
1163
+ const normalized = extension.startsWith(".") ? extension : `.${extension}`;
1164
+ return caseSensitive ? normalized : normalized.toLowerCase();
1165
+ }
1166
+ function formatFileSize(bytes) {
1167
+ const units = ["B", "KB", "MB", "GB", "TB"];
1168
+ let size = bytes;
1169
+ let unitIndex = 0;
1170
+ while (size >= 1024 && unitIndex < units.length - 1) {
1171
+ size /= 1024;
1172
+ unitIndex++;
1173
+ }
1174
+ return `${Math.round(size * 100) / 100} ${units[unitIndex]}`;
1175
+ }
259
1176
 
260
- // src/common/password.ts
1177
+ // src/validators/common/id.ts
261
1178
  import { z as z6 } from "zod";
262
- function password(options) {
263
- const { required = true, min, max, uppercase, lowercase, digits, special } = options ?? {};
264
- const baseSchema = required ? z6.preprocess((val) => val === "" || val === null || val === void 0 ? null : val, z6.coerce.string().trim()) : z6.preprocess((val) => val === "" || val === null || val === void 0 ? null : val, z6.coerce.string().trim().nullable());
265
- const schema = baseSchema.refine((val) => required ? val !== "" && val !== "null" && val !== "undefined" : true, { message: t("common.password.required") }).refine((val) => val === null || min === void 0 || val.length >= min, { message: t("common.password.min", { min }) }).refine((val) => val === null || max === void 0 || val.length <= max, { message: t("common.password.max", { max }) }).refine((val) => val === null || !uppercase || /[A-Z]/.test(val), { message: t("common.password.uppercase") }).refine((val) => val === null || !lowercase || /[a-z]/.test(val), { message: t("common.password.lowercase") }).refine((val) => val === null || !special || /[!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?]+/.test(val), { message: t("common.password.special") }).refine((val) => val === null || !digits || /[0-9]/.test(val), { message: t("common.password.digits") });
1179
+ var ID_PATTERNS = {
1180
+ numeric: /^\d+$/,
1181
+ 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,
1182
+ objectId: /^[0-9a-f]{24}$/i,
1183
+ nanoid: /^[A-Za-z0-9_-]{21}$/,
1184
+ snowflake: /^\d{19}$/,
1185
+ cuid: /^c[a-z0-9]{24}$/,
1186
+ ulid: /^[0-9A-HJKMNP-TV-Z]{26}$/,
1187
+ shortid: /^[A-Za-z0-9_-]{7,14}$/
1188
+ };
1189
+ var detectIdType = (value) => {
1190
+ const orderedTypes = [
1191
+ ["uuid", ID_PATTERNS.uuid],
1192
+ ["objectId", ID_PATTERNS.objectId],
1193
+ ["snowflake", ID_PATTERNS.snowflake],
1194
+ ["cuid", ID_PATTERNS.cuid],
1195
+ ["ulid", ID_PATTERNS.ulid],
1196
+ ["nanoid", ID_PATTERNS.nanoid],
1197
+ ["numeric", ID_PATTERNS.numeric],
1198
+ ["shortid", ID_PATTERNS.shortid]
1199
+ // 放最後,因為最通用
1200
+ ];
1201
+ for (const [type, pattern] of orderedTypes) {
1202
+ if (pattern.test(value)) {
1203
+ return type;
1204
+ }
1205
+ }
1206
+ return null;
1207
+ };
1208
+ var validateIdType = (value, type) => {
1209
+ if (type === "auto") {
1210
+ return detectIdType(value) !== null;
1211
+ }
1212
+ const pattern = ID_PATTERNS[type];
1213
+ return pattern ? pattern.test(value) : false;
1214
+ };
1215
+ function id(options) {
1216
+ const {
1217
+ required = true,
1218
+ type = "auto",
1219
+ minLength,
1220
+ maxLength,
1221
+ allowedTypes,
1222
+ customRegex,
1223
+ includes,
1224
+ excludes,
1225
+ startsWith,
1226
+ endsWith,
1227
+ caseSensitive = true,
1228
+ transform,
1229
+ defaultValue,
1230
+ i18n
1231
+ } = options ?? {};
1232
+ const actualDefaultValue = defaultValue ?? (required ? "" : null);
1233
+ const getMessage = (key, params) => {
1234
+ if (i18n) {
1235
+ const currentLocale2 = getLocale();
1236
+ const customMessages = i18n[currentLocale2];
1237
+ if (customMessages && customMessages[key]) {
1238
+ const template = customMessages[key];
1239
+ return template.replace(/\$\{(\w+)}/g, (_, k) => params?.[k] ?? "");
1240
+ }
1241
+ }
1242
+ return t(`common.id.${key}`, params);
1243
+ };
1244
+ const preprocessFn = (val) => {
1245
+ if (val === "" || val === null || val === void 0) {
1246
+ return actualDefaultValue;
1247
+ }
1248
+ let processed = String(val);
1249
+ if (transform) {
1250
+ processed = transform(processed);
1251
+ }
1252
+ return processed;
1253
+ };
1254
+ const baseSchema = required ? z6.preprocess(preprocessFn, z6.string()) : z6.preprocess(preprocessFn, z6.string().nullable());
1255
+ const schema = baseSchema.refine((val) => {
1256
+ if (val === null) return true;
1257
+ if (required && (val === "" || val === "null" || val === "undefined")) {
1258
+ throw new z6.ZodError([{ code: "custom", message: getMessage("required"), path: [] }]);
1259
+ }
1260
+ const comparisonVal = !caseSensitive ? val.toLowerCase() : val;
1261
+ if (val !== null && minLength !== void 0 && val.length < minLength) {
1262
+ throw new z6.ZodError([{ code: "custom", message: getMessage("minLength", { minLength }), path: [] }]);
1263
+ }
1264
+ if (val !== null && maxLength !== void 0 && val.length > maxLength) {
1265
+ throw new z6.ZodError([{ code: "custom", message: getMessage("maxLength", { maxLength }), path: [] }]);
1266
+ }
1267
+ const hasContentValidations = customRegex !== void 0 || startsWith !== void 0 || endsWith !== void 0 || includes !== void 0 || excludes !== void 0;
1268
+ if (val !== null && customRegex !== void 0) {
1269
+ if (!customRegex.test(val)) {
1270
+ throw new z6.ZodError([{ code: "custom", message: getMessage("customFormat"), path: [] }]);
1271
+ }
1272
+ } else if (val !== null && !hasContentValidations) {
1273
+ let isValidId;
1274
+ if (allowedTypes && allowedTypes.length > 0) {
1275
+ isValidId = allowedTypes.some((allowedType) => validateIdType(val, allowedType));
1276
+ if (!isValidId) {
1277
+ const typeNames = allowedTypes.join(", ");
1278
+ throw new z6.ZodError([{ code: "custom", message: getMessage("invalid") + ` (allowed types: ${typeNames})`, path: [] }]);
1279
+ }
1280
+ } else if (type !== "auto") {
1281
+ isValidId = validateIdType(val, type);
1282
+ if (!isValidId) {
1283
+ throw new z6.ZodError([{ code: "custom", message: getMessage(type) || getMessage("invalid"), path: [] }]);
1284
+ }
1285
+ } else {
1286
+ isValidId = detectIdType(val) !== null;
1287
+ if (!isValidId) {
1288
+ throw new z6.ZodError([{ code: "custom", message: getMessage("invalid"), path: [] }]);
1289
+ }
1290
+ }
1291
+ } else if (val !== null && hasContentValidations && type !== "auto" && !customRegex) {
1292
+ if (allowedTypes && allowedTypes.length > 0) {
1293
+ const isValidType = allowedTypes.some((allowedType) => validateIdType(val, allowedType));
1294
+ if (!isValidType) {
1295
+ const typeNames = allowedTypes.join(", ");
1296
+ throw new z6.ZodError([{ code: "custom", message: getMessage("invalid") + ` (allowed types: ${typeNames})`, path: [] }]);
1297
+ }
1298
+ } else {
1299
+ if (!validateIdType(val, type)) {
1300
+ throw new z6.ZodError([{ code: "custom", message: getMessage(type) || getMessage("invalid"), path: [] }]);
1301
+ }
1302
+ }
1303
+ }
1304
+ const searchStartsWith = !caseSensitive && startsWith ? startsWith.toLowerCase() : startsWith;
1305
+ const searchEndsWith = !caseSensitive && endsWith ? endsWith.toLowerCase() : endsWith;
1306
+ const searchIncludes = !caseSensitive && includes ? includes.toLowerCase() : includes;
1307
+ if (val !== null && startsWith !== void 0 && !comparisonVal.startsWith(searchStartsWith)) {
1308
+ throw new z6.ZodError([{ code: "custom", message: getMessage("startsWith", { startsWith }), path: [] }]);
1309
+ }
1310
+ if (val !== null && endsWith !== void 0 && !comparisonVal.endsWith(searchEndsWith)) {
1311
+ throw new z6.ZodError([{ code: "custom", message: getMessage("endsWith", { endsWith }), path: [] }]);
1312
+ }
1313
+ if (val !== null && includes !== void 0 && !comparisonVal.includes(searchIncludes)) {
1314
+ throw new z6.ZodError([{ code: "custom", message: getMessage("includes", { includes }), path: [] }]);
1315
+ }
1316
+ if (val !== null && excludes !== void 0) {
1317
+ const excludeList = Array.isArray(excludes) ? excludes : [excludes];
1318
+ for (const exclude of excludeList) {
1319
+ const searchExclude = !caseSensitive ? exclude.toLowerCase() : exclude;
1320
+ if (comparisonVal.includes(searchExclude)) {
1321
+ throw new z6.ZodError([{ code: "custom", message: getMessage("excludes", { excludes: exclude }), path: [] }]);
1322
+ }
1323
+ }
1324
+ }
1325
+ return true;
1326
+ }).transform((val) => {
1327
+ if (val === null) return val;
1328
+ const shouldPreserveCase = type === "uuid" || type === "objectId";
1329
+ if (!caseSensitive && !shouldPreserveCase) {
1330
+ return val.toLowerCase();
1331
+ }
1332
+ return val;
1333
+ });
266
1334
  return schema;
267
1335
  }
268
1336
 
269
- // src/common/integer.ts
1337
+ // src/validators/common/number.ts
270
1338
  import { z as z7 } from "zod";
271
- function integer(options) {
272
- const { required = true, min, max, defaultValue } = options ?? {};
1339
+ function number(options) {
1340
+ const {
1341
+ required = true,
1342
+ min,
1343
+ max,
1344
+ defaultValue,
1345
+ type = "both",
1346
+ positive,
1347
+ negative,
1348
+ nonNegative,
1349
+ nonPositive,
1350
+ multipleOf,
1351
+ precision,
1352
+ finite = true,
1353
+ transform,
1354
+ parseCommas = false,
1355
+ i18n
1356
+ } = options ?? {};
1357
+ const getMessage = (key, params) => {
1358
+ if (i18n) {
1359
+ const currentLocale2 = getLocale();
1360
+ const customMessages = i18n[currentLocale2];
1361
+ if (customMessages && customMessages[key]) {
1362
+ const template = customMessages[key];
1363
+ return template.replace(/\$\{(\w+)}/g, (_, k) => params?.[k] ?? "");
1364
+ }
1365
+ }
1366
+ return t(`common.number.${key}`, params);
1367
+ };
1368
+ const actualDefaultValue = defaultValue ?? null;
273
1369
  const schema = z7.preprocess(
274
1370
  (val) => {
275
- if (val === "" || val === void 0 || val === null) return defaultValue ?? null;
276
- return typeof val === "string" ? Number(val) : val;
277
- },
278
- z7.union([
279
- z7.number({
280
- error: (issue) => {
281
- if (issue.code === "invalid_type") return t("common.integer.integer");
282
- return t("common.integer.required");
1371
+ if (val === "" || val === void 0 || val === null) {
1372
+ return actualDefaultValue;
1373
+ }
1374
+ if (typeof val === "string") {
1375
+ let processedVal = val.trim();
1376
+ if (parseCommas) {
1377
+ processedVal = processedVal.replace(/,/g, "");
1378
+ }
1379
+ const parsed = Number(processedVal);
1380
+ if (isNaN(parsed)) {
1381
+ return parsed;
283
1382
  }
284
- }),
285
- z7.null()
286
- ])
287
- ).refine((val) => !required || val !== null, {
288
- message: t("common.integer.required")
289
- }).refine((val) => val === null || Number.isInteger(val), {
290
- message: t("common.integer.integer")
291
- }).refine((val) => val === null || min === void 0 || val >= min, {
292
- message: t("common.integer.min", { min })
293
- }).refine((val) => val === null || max === void 0 || val <= max, {
294
- message: t("common.integer.max", { max })
1383
+ if (transform) {
1384
+ return transform(parsed);
1385
+ }
1386
+ return parsed;
1387
+ }
1388
+ if (typeof val === "number") {
1389
+ if (transform && Number.isFinite(val)) {
1390
+ return transform(val);
1391
+ }
1392
+ return val;
1393
+ }
1394
+ return val;
1395
+ },
1396
+ z7.union([z7.number(), z7.null(), z7.nan(), z7.custom((val) => val === Infinity || val === -Infinity)])
1397
+ ).refine((val) => {
1398
+ if (required && val === null) {
1399
+ throw new z7.ZodError([{ code: "custom", message: getMessage("required"), path: [] }]);
1400
+ }
1401
+ if (val === null) return true;
1402
+ if (typeof val === "number" && isNaN(val)) {
1403
+ if (type === "integer") {
1404
+ throw new z7.ZodError([{ code: "custom", message: getMessage("integer"), path: [] }]);
1405
+ } else if (type === "float") {
1406
+ throw new z7.ZodError([{ code: "custom", message: getMessage("float"), path: [] }]);
1407
+ } else {
1408
+ throw new z7.ZodError([{ code: "custom", message: getMessage("invalid"), path: [] }]);
1409
+ }
1410
+ }
1411
+ if (typeof val !== "number") {
1412
+ throw new z7.ZodError([{ code: "custom", message: getMessage("invalid"), path: [] }]);
1413
+ }
1414
+ if (finite && !Number.isFinite(val)) {
1415
+ throw new z7.ZodError([{ code: "custom", message: getMessage("finite"), path: [] }]);
1416
+ }
1417
+ if (type === "integer" && !Number.isInteger(val)) {
1418
+ throw new z7.ZodError([{ code: "custom", message: getMessage("integer"), path: [] }]);
1419
+ }
1420
+ if (type === "float" && Number.isInteger(val)) {
1421
+ throw new z7.ZodError([{ code: "custom", message: getMessage("float"), path: [] }]);
1422
+ }
1423
+ if (positive && val <= 0) {
1424
+ throw new z7.ZodError([{ code: "custom", message: getMessage("positive"), path: [] }]);
1425
+ }
1426
+ if (negative && val >= 0) {
1427
+ throw new z7.ZodError([{ code: "custom", message: getMessage("negative"), path: [] }]);
1428
+ }
1429
+ if (nonNegative && val < 0) {
1430
+ throw new z7.ZodError([{ code: "custom", message: getMessage("nonNegative"), path: [] }]);
1431
+ }
1432
+ if (nonPositive && val > 0) {
1433
+ throw new z7.ZodError([{ code: "custom", message: getMessage("nonPositive"), path: [] }]);
1434
+ }
1435
+ if (min !== void 0 && val < min) {
1436
+ throw new z7.ZodError([{ code: "custom", message: getMessage("min", { min }), path: [] }]);
1437
+ }
1438
+ if (max !== void 0 && val > max) {
1439
+ throw new z7.ZodError([{ code: "custom", message: getMessage("max", { max }), path: [] }]);
1440
+ }
1441
+ if (multipleOf !== void 0 && val % multipleOf !== 0) {
1442
+ throw new z7.ZodError([{ code: "custom", message: getMessage("multipleOf", { multipleOf }), path: [] }]);
1443
+ }
1444
+ if (precision !== void 0) {
1445
+ const decimalPlaces = (val.toString().split(".")[1] || "").length;
1446
+ if (decimalPlaces > precision) {
1447
+ throw new z7.ZodError([{ code: "custom", message: getMessage("precision", { precision }), path: [] }]);
1448
+ }
1449
+ }
1450
+ return true;
1451
+ });
1452
+ return schema;
1453
+ }
1454
+
1455
+ // src/validators/common/password.ts
1456
+ import { z as z8 } from "zod";
1457
+ var COMMON_PASSWORDS = [
1458
+ "password",
1459
+ "123456",
1460
+ "123456789",
1461
+ "12345678",
1462
+ "12345",
1463
+ "1234567",
1464
+ "admin",
1465
+ "qwerty",
1466
+ "abc123",
1467
+ "password123",
1468
+ "letmein",
1469
+ "welcome",
1470
+ "monkey",
1471
+ "dragon",
1472
+ "sunshine",
1473
+ "princess"
1474
+ ];
1475
+ var calculatePasswordStrength = (password2) => {
1476
+ let score = 0;
1477
+ if (password2.length >= 8) score += 1;
1478
+ if (password2.length >= 12) score += 1;
1479
+ if (password2.length >= 16) score += 1;
1480
+ if (/[a-z]/.test(password2)) score += 1;
1481
+ if (/[A-Z]/.test(password2)) score += 1;
1482
+ if (/[0-9]/.test(password2)) score += 1;
1483
+ if (/[!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?]/.test(password2)) score += 1;
1484
+ if (/(.)\1{2,}/.test(password2)) score -= 1;
1485
+ if (/(?:abc|bcd|cde|def|efg|fgh|ghi|hij|ijk|jkl|klm|lmn|mno|nop|opq|pqr|qrs|rst|stu|tuv|uvw|vwx|wxy|xyz|012|123|234|345|456|567|678|789)/i.test(password2)) score -= 1;
1486
+ if (score <= 2) return "weak";
1487
+ if (score <= 4) return "medium";
1488
+ if (score <= 6) return "strong";
1489
+ return "very-strong";
1490
+ };
1491
+ function password(options) {
1492
+ const {
1493
+ required = true,
1494
+ min,
1495
+ max,
1496
+ uppercase,
1497
+ lowercase,
1498
+ digits,
1499
+ special,
1500
+ noRepeating,
1501
+ noSequential,
1502
+ noCommonWords,
1503
+ minStrength,
1504
+ excludes,
1505
+ includes,
1506
+ regex,
1507
+ transform,
1508
+ defaultValue,
1509
+ i18n
1510
+ } = options ?? {};
1511
+ const actualDefaultValue = defaultValue ?? (required ? "" : null);
1512
+ const getMessage = (key, params) => {
1513
+ if (i18n) {
1514
+ const currentLocale2 = getLocale();
1515
+ const customMessages = i18n[currentLocale2];
1516
+ if (customMessages && customMessages[key]) {
1517
+ const template = customMessages[key];
1518
+ return template.replace(/\$\{(\w+)}/g, (_, k) => params?.[k] ?? "");
1519
+ }
1520
+ }
1521
+ return t(`common.password.${key}`, params);
1522
+ };
1523
+ const preprocessFn = (val) => {
1524
+ if (val === "" || val === null || val === void 0) {
1525
+ return actualDefaultValue;
1526
+ }
1527
+ let processed = String(val);
1528
+ if (transform) {
1529
+ processed = transform(processed);
1530
+ }
1531
+ return processed;
1532
+ };
1533
+ const baseSchema = required ? z8.preprocess(preprocessFn, z8.string()) : z8.preprocess(preprocessFn, z8.string().nullable());
1534
+ const schema = baseSchema.refine((val) => {
1535
+ if (val === null) return true;
1536
+ if (required && (val === "" || val === "null" || val === "undefined")) {
1537
+ throw new z8.ZodError([{ code: "custom", message: getMessage("required"), path: [] }]);
1538
+ }
1539
+ if (val !== null && min !== void 0 && val.length < min) {
1540
+ throw new z8.ZodError([{ code: "custom", message: getMessage("min", { min }), path: [] }]);
1541
+ }
1542
+ if (val !== null && max !== void 0 && val.length > max) {
1543
+ throw new z8.ZodError([{ code: "custom", message: getMessage("max", { max }), path: [] }]);
1544
+ }
1545
+ if (val !== null && uppercase && !/[A-Z]/.test(val)) {
1546
+ throw new z8.ZodError([{ code: "custom", message: getMessage("uppercase"), path: [] }]);
1547
+ }
1548
+ if (val !== null && lowercase && !/[a-z]/.test(val)) {
1549
+ throw new z8.ZodError([{ code: "custom", message: getMessage("lowercase"), path: [] }]);
1550
+ }
1551
+ if (val !== null && digits && !/[0-9]/.test(val)) {
1552
+ throw new z8.ZodError([{ code: "custom", message: getMessage("digits"), path: [] }]);
1553
+ }
1554
+ if (val !== null && special && !/[!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?]/.test(val)) {
1555
+ throw new z8.ZodError([{ code: "custom", message: getMessage("special"), path: [] }]);
1556
+ }
1557
+ if (val !== null && noRepeating && /(.)\1{2,}/.test(val)) {
1558
+ throw new z8.ZodError([{ code: "custom", message: getMessage("noRepeating"), path: [] }]);
1559
+ }
1560
+ if (val !== null && noSequential && /(?:abc|bcd|cde|def|efg|fgh|ghi|hij|ijk|jkl|klm|lmn|mno|nop|opq|pqr|qrs|rst|stu|tuv|uvw|vwx|wxy|xyz|012|123|234|345|456|567|678|789)/i.test(val)) {
1561
+ throw new z8.ZodError([{ code: "custom", message: getMessage("noSequential"), path: [] }]);
1562
+ }
1563
+ if (val !== null && noCommonWords && COMMON_PASSWORDS.some((common) => val.toLowerCase().includes(common.toLowerCase()))) {
1564
+ throw new z8.ZodError([{ code: "custom", message: getMessage("noCommonWords"), path: [] }]);
1565
+ }
1566
+ if (val !== null && minStrength) {
1567
+ const strength = calculatePasswordStrength(val);
1568
+ const strengthLevels = ["weak", "medium", "strong", "very-strong"];
1569
+ const currentLevel = strengthLevels.indexOf(strength);
1570
+ const requiredLevel = strengthLevels.indexOf(minStrength);
1571
+ if (currentLevel < requiredLevel) {
1572
+ throw new z8.ZodError([{ code: "custom", message: getMessage("minStrength", { minStrength }), path: [] }]);
1573
+ }
1574
+ }
1575
+ if (val !== null && includes !== void 0 && !val.includes(includes)) {
1576
+ throw new z8.ZodError([{ code: "custom", message: getMessage("includes", { includes }), path: [] }]);
1577
+ }
1578
+ if (val !== null && excludes !== void 0) {
1579
+ const excludeList = Array.isArray(excludes) ? excludes : [excludes];
1580
+ for (const exclude of excludeList) {
1581
+ if (val.includes(exclude)) {
1582
+ throw new z8.ZodError([{ code: "custom", message: getMessage("excludes", { excludes: exclude }), path: [] }]);
1583
+ }
1584
+ }
1585
+ }
1586
+ if (val !== null && regex !== void 0 && !regex.test(val)) {
1587
+ throw new z8.ZodError([{ code: "custom", message: getMessage("invalid", { regex }), path: [] }]);
1588
+ }
1589
+ return true;
1590
+ });
1591
+ return schema;
1592
+ }
1593
+
1594
+ // src/validators/common/text.ts
1595
+ import { z as z9 } from "zod";
1596
+ function text(options) {
1597
+ const { required = true, minLength, maxLength, startsWith, endsWith, includes, excludes, regex, trimMode = "trim", casing = "none", transform, notEmpty, defaultValue, i18n } = options ?? {};
1598
+ const actualDefaultValue = defaultValue ?? (required ? "" : null);
1599
+ const getMessage = (key, params) => {
1600
+ if (i18n) {
1601
+ const currentLocale2 = getLocale();
1602
+ const customMessages = i18n[currentLocale2];
1603
+ if (customMessages && customMessages[key]) {
1604
+ const template = customMessages[key];
1605
+ return template.replace(/\$\{(\w+)}/g, (_, k) => params?.[k] ?? "");
1606
+ }
1607
+ }
1608
+ return t(`common.text.${key}`, params);
1609
+ };
1610
+ const applyTrim = (str) => {
1611
+ switch (trimMode) {
1612
+ case "trimStart":
1613
+ return str.trimStart();
1614
+ case "trimEnd":
1615
+ return str.trimEnd();
1616
+ case "none":
1617
+ return str;
1618
+ default:
1619
+ return str.trim();
1620
+ }
1621
+ };
1622
+ const applyCasing = (str) => {
1623
+ switch (casing) {
1624
+ case "upper":
1625
+ return str.toUpperCase();
1626
+ case "lower":
1627
+ return str.toLowerCase();
1628
+ case "title":
1629
+ return str.replace(/\w\S*/g, (txt) => txt.charAt(0).toUpperCase() + txt.substring(1).toLowerCase());
1630
+ default:
1631
+ return str;
1632
+ }
1633
+ };
1634
+ const preprocessFn = (val) => {
1635
+ if (val === "" || val === null || val === void 0) {
1636
+ return actualDefaultValue;
1637
+ }
1638
+ let processed = String(val);
1639
+ processed = applyTrim(processed);
1640
+ processed = applyCasing(processed);
1641
+ if (transform) {
1642
+ processed = transform(processed);
1643
+ }
1644
+ return processed;
1645
+ };
1646
+ const baseSchema = required ? z9.preprocess(preprocessFn, z9.string()) : z9.preprocess(preprocessFn, z9.string().nullable());
1647
+ const schema = baseSchema.refine((val) => {
1648
+ if (val === null) return true;
1649
+ if (required && (val === "" || val === "null" || val === "undefined")) {
1650
+ throw new z9.ZodError([{ code: "custom", message: getMessage("required"), path: [] }]);
1651
+ }
1652
+ if (notEmpty && val !== null && val.trim() === "") {
1653
+ throw new z9.ZodError([{ code: "custom", message: getMessage("notEmpty"), path: [] }]);
1654
+ }
1655
+ if (val !== null && minLength !== void 0 && val.length < minLength) {
1656
+ throw new z9.ZodError([{ code: "custom", message: getMessage("minLength", { minLength }), path: [] }]);
1657
+ }
1658
+ if (val !== null && maxLength !== void 0 && val.length > maxLength) {
1659
+ throw new z9.ZodError([{ code: "custom", message: getMessage("maxLength", { maxLength }), path: [] }]);
1660
+ }
1661
+ if (val !== null && startsWith !== void 0 && !val.startsWith(startsWith)) {
1662
+ throw new z9.ZodError([{ code: "custom", message: getMessage("startsWith", { startsWith }), path: [] }]);
1663
+ }
1664
+ if (val !== null && endsWith !== void 0 && !val.endsWith(endsWith)) {
1665
+ throw new z9.ZodError([{ code: "custom", message: getMessage("endsWith", { endsWith }), path: [] }]);
1666
+ }
1667
+ if (val !== null && includes !== void 0 && !val.includes(includes)) {
1668
+ throw new z9.ZodError([{ code: "custom", message: getMessage("includes", { includes }), path: [] }]);
1669
+ }
1670
+ if (val !== null && excludes !== void 0) {
1671
+ const excludeList = Array.isArray(excludes) ? excludes : [excludes];
1672
+ for (const exclude of excludeList) {
1673
+ if (val.includes(exclude)) {
1674
+ throw new z9.ZodError([{ code: "custom", message: getMessage("excludes", { excludes: exclude }), path: [] }]);
1675
+ }
1676
+ }
1677
+ }
1678
+ if (val !== null && regex !== void 0 && !regex.test(val)) {
1679
+ throw new z9.ZodError([{ code: "custom", message: getMessage("invalid", { regex }), path: [] }]);
1680
+ }
1681
+ return true;
1682
+ });
1683
+ return schema;
1684
+ }
1685
+
1686
+ // src/validators/common/time.ts
1687
+ import { z as z10 } from "zod";
1688
+ var TIME_PATTERNS = {
1689
+ "HH:mm": /^([01]?[0-9]|2[0-3]):[0-5][0-9]$/,
1690
+ "HH:mm:ss": /^([01]?[0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]$/,
1691
+ "hh:mm A": /^(0?[1-9]|1[0-2]):[0-5][0-9]\s?(AM|PM)$/i,
1692
+ "hh:mm:ss A": /^(0?[1-9]|1[0-2]):[0-5][0-9]:[0-5][0-9]\s?(AM|PM)$/i,
1693
+ "H:mm": /^([0-9]|1[0-9]|2[0-3]):[0-5][0-9]$/,
1694
+ "h:mm A": /^([1-9]|1[0-2]):[0-5][0-9]\s?(AM|PM)$/i
1695
+ };
1696
+ var parseTimeToMinutes = (timeStr, format) => {
1697
+ const cleanTime = timeStr.trim();
1698
+ try {
1699
+ if (format.includes("A")) {
1700
+ const match = cleanTime.match(/^(\d{1,2}):(\d{2})(?::(\d{2}))?\s?(AM|PM)$/i);
1701
+ if (!match) return null;
1702
+ const [, hourStr, minuteStr, , period] = match;
1703
+ let hour = parseInt(hourStr, 10);
1704
+ const minute = parseInt(minuteStr, 10);
1705
+ if (hour < 1 || hour > 12 || minute < 0 || minute > 59) {
1706
+ return null;
1707
+ }
1708
+ if (period.toUpperCase() === "PM" && hour !== 12) {
1709
+ hour += 12;
1710
+ } else if (period.toUpperCase() === "AM" && hour === 12) {
1711
+ hour = 0;
1712
+ }
1713
+ return hour * 60 + minute;
1714
+ } else {
1715
+ const match = cleanTime.match(/^(\d{1,2}):(\d{2})(?::(\d{2}))?$/);
1716
+ if (!match) return null;
1717
+ const [, hourStr, minuteStr] = match;
1718
+ const hour = parseInt(hourStr, 10);
1719
+ const minute = parseInt(minuteStr, 10);
1720
+ if (hour < 0 || hour > 23 || minute < 0 || minute > 59) {
1721
+ return null;
1722
+ }
1723
+ return hour * 60 + minute;
1724
+ }
1725
+ } catch {
1726
+ return null;
1727
+ }
1728
+ };
1729
+ var validateTimeFormat = (value, format) => {
1730
+ const pattern = TIME_PATTERNS[format];
1731
+ return pattern.test(value.trim());
1732
+ };
1733
+ var normalizeTime = (timeStr, format) => {
1734
+ const cleanTime = timeStr.trim();
1735
+ if (format.includes("A")) {
1736
+ const match = cleanTime.match(/^(\d{1,2}):(\d{2})(?::(\d{2}))?\s?(AM|PM)$/i);
1737
+ if (!match) return null;
1738
+ const [, hourStr, minuteStr, secondStr = "00", period] = match;
1739
+ let hour = parseInt(hourStr, 10);
1740
+ const minute = parseInt(minuteStr, 10);
1741
+ const second = parseInt(secondStr, 10);
1742
+ if (period.toUpperCase() === "PM" && hour !== 12) {
1743
+ hour += 12;
1744
+ } else if (period.toUpperCase() === "AM" && hour === 12) {
1745
+ hour = 0;
1746
+ }
1747
+ return format.includes("ss") ? `${hour.toString().padStart(2, "0")}:${minute.toString().padStart(2, "0")}:${second.toString().padStart(2, "0")}` : `${hour.toString().padStart(2, "0")}:${minute.toString().padStart(2, "0")}`;
1748
+ }
1749
+ if (format === "H:mm") {
1750
+ const match = cleanTime.match(/^(\d{1,2}):(\d{2})$/);
1751
+ if (!match) return null;
1752
+ const [, hourStr, minuteStr] = match;
1753
+ return `${hourStr.padStart(2, "0")}:${minuteStr}`;
1754
+ }
1755
+ return cleanTime;
1756
+ };
1757
+ function time(options) {
1758
+ const {
1759
+ required = true,
1760
+ format = "HH:mm",
1761
+ min,
1762
+ max,
1763
+ minHour,
1764
+ maxHour,
1765
+ allowedHours,
1766
+ minuteStep,
1767
+ secondStep,
1768
+ includes,
1769
+ excludes,
1770
+ regex,
1771
+ trimMode = "trim",
1772
+ casing = "none",
1773
+ whitelist,
1774
+ whitelistOnly = false,
1775
+ transform,
1776
+ defaultValue,
1777
+ i18n
1778
+ } = options ?? {};
1779
+ const actualDefaultValue = defaultValue ?? (required ? "" : null);
1780
+ const getMessage = (key, params) => {
1781
+ if (i18n) {
1782
+ const currentLocale2 = getLocale();
1783
+ const customMessages = i18n[currentLocale2];
1784
+ if (customMessages && customMessages[key]) {
1785
+ const template = customMessages[key];
1786
+ return template.replace(/\$\{(\w+)}/g, (_, k) => params?.[k] ?? "");
1787
+ }
1788
+ }
1789
+ return t(`common.time.${key}`, params);
1790
+ };
1791
+ const preprocessFn = (val) => {
1792
+ if (val === null || val === void 0) {
1793
+ return actualDefaultValue;
1794
+ }
1795
+ let processed = String(val);
1796
+ switch (trimMode) {
1797
+ case "trim":
1798
+ processed = processed.trim();
1799
+ break;
1800
+ case "trimStart":
1801
+ processed = processed.trimStart();
1802
+ break;
1803
+ case "trimEnd":
1804
+ processed = processed.trimEnd();
1805
+ break;
1806
+ case "none":
1807
+ break;
1808
+ }
1809
+ if (processed === "") {
1810
+ if (whitelist && whitelist.includes("")) {
1811
+ return "";
1812
+ }
1813
+ if (!required) {
1814
+ return actualDefaultValue;
1815
+ }
1816
+ return actualDefaultValue;
1817
+ }
1818
+ switch (casing) {
1819
+ case "upper":
1820
+ processed = processed.toUpperCase();
1821
+ break;
1822
+ case "lower":
1823
+ processed = processed.toLowerCase();
1824
+ break;
1825
+ case "none":
1826
+ break;
1827
+ }
1828
+ if (transform) {
1829
+ processed = transform(processed);
1830
+ }
1831
+ return processed;
1832
+ };
1833
+ const baseSchema = required ? z10.preprocess(preprocessFn, z10.string()) : z10.preprocess(preprocessFn, z10.string().nullable());
1834
+ const schema = baseSchema.refine((val) => {
1835
+ if (val === null) return true;
1836
+ if (required && (val === "" || val === "null" || val === "undefined")) {
1837
+ throw new z10.ZodError([{ code: "custom", message: getMessage("required"), path: [] }]);
1838
+ }
1839
+ if (val === null) return true;
1840
+ if (!required && val === "") return true;
1841
+ if (whitelist && whitelist.length > 0) {
1842
+ if (whitelist.includes(val)) {
1843
+ return true;
1844
+ }
1845
+ if (whitelistOnly) {
1846
+ throw new z10.ZodError([{ code: "custom", message: getMessage("notInWhitelist"), path: [] }]);
1847
+ }
1848
+ }
1849
+ if (regex) {
1850
+ if (!regex.test(val)) {
1851
+ throw new z10.ZodError([{ code: "custom", message: getMessage("customRegex"), path: [] }]);
1852
+ }
1853
+ } else {
1854
+ if (!validateTimeFormat(val, format)) {
1855
+ throw new z10.ZodError([{ code: "custom", message: getMessage("format", { format }), path: [] }]);
1856
+ }
1857
+ }
1858
+ if (includes && !val.includes(includes)) {
1859
+ throw new z10.ZodError([{ code: "custom", message: getMessage("includes", { includes }), path: [] }]);
1860
+ }
1861
+ if (excludes) {
1862
+ const excludeList = Array.isArray(excludes) ? excludes : [excludes];
1863
+ for (const exclude of excludeList) {
1864
+ if (val.includes(exclude)) {
1865
+ throw new z10.ZodError([{ code: "custom", message: getMessage("excludes", { excludes: exclude }), path: [] }]);
1866
+ }
1867
+ }
1868
+ }
1869
+ if (regex) {
1870
+ return true;
1871
+ }
1872
+ const timeMinutes = parseTimeToMinutes(val, format);
1873
+ if (timeMinutes === null) {
1874
+ throw new z10.ZodError([{ code: "custom", message: getMessage("invalid"), path: [] }]);
1875
+ }
1876
+ const hour = Math.floor(timeMinutes / 60);
1877
+ if (minHour !== void 0 && hour < minHour) {
1878
+ throw new z10.ZodError([{ code: "custom", message: getMessage("hour", { minHour, maxHour: maxHour ?? 23 }), path: [] }]);
1879
+ }
1880
+ if (maxHour !== void 0 && hour > maxHour) {
1881
+ throw new z10.ZodError([{ code: "custom", message: getMessage("hour", { minHour: minHour ?? 0, maxHour }), path: [] }]);
1882
+ }
1883
+ if (allowedHours && allowedHours.length > 0) {
1884
+ if (!allowedHours.includes(hour)) {
1885
+ throw new z10.ZodError([{ code: "custom", message: getMessage("hour", { allowedHours: allowedHours.join(", ") }), path: [] }]);
1886
+ }
1887
+ }
1888
+ const minute = timeMinutes % 60;
1889
+ if (minuteStep !== void 0 && minute % minuteStep !== 0) {
1890
+ throw new z10.ZodError([{ code: "custom", message: getMessage("minute", { minuteStep }), path: [] }]);
1891
+ }
1892
+ if (secondStep !== void 0 && format.includes("ss")) {
1893
+ const secondMatch = val.match(/:(\d{2})$/);
1894
+ if (secondMatch) {
1895
+ const seconds = parseInt(secondMatch[1], 10);
1896
+ if (seconds % secondStep !== 0) {
1897
+ throw new z10.ZodError([{ code: "custom", message: getMessage("second", { secondStep }), path: [] }]);
1898
+ }
1899
+ }
1900
+ }
1901
+ if (min) {
1902
+ const minMinutes = parseTimeToMinutes(min, format);
1903
+ if (minMinutes !== null && timeMinutes < minMinutes) {
1904
+ throw new z10.ZodError([{ code: "custom", message: getMessage("min", { min }), path: [] }]);
1905
+ }
1906
+ }
1907
+ if (max) {
1908
+ const maxMinutes = parseTimeToMinutes(max, format);
1909
+ if (maxMinutes !== null && timeMinutes > maxMinutes) {
1910
+ throw new z10.ZodError([{ code: "custom", message: getMessage("max", { max }), path: [] }]);
1911
+ }
1912
+ }
1913
+ return true;
1914
+ });
1915
+ return schema;
1916
+ }
1917
+
1918
+ // src/validators/common/url.ts
1919
+ import { z as z11 } from "zod";
1920
+ function url(options) {
1921
+ const {
1922
+ required = true,
1923
+ min,
1924
+ max,
1925
+ includes,
1926
+ excludes,
1927
+ protocols,
1928
+ allowedDomains,
1929
+ blockedDomains,
1930
+ allowedPorts,
1931
+ blockedPorts,
1932
+ pathStartsWith,
1933
+ pathEndsWith,
1934
+ mustHaveQuery,
1935
+ mustNotHaveQuery,
1936
+ mustHaveFragment,
1937
+ mustNotHaveFragment,
1938
+ allowLocalhost = true,
1939
+ blockLocalhost,
1940
+ transform,
1941
+ defaultValue = null,
1942
+ i18n
1943
+ } = options ?? {};
1944
+ const actualDefaultValue = defaultValue ?? (required ? "" : null);
1945
+ const getMessage = (key, params) => {
1946
+ if (i18n) {
1947
+ const currentLocale2 = getLocale();
1948
+ const customMessages = i18n[currentLocale2];
1949
+ if (customMessages && customMessages[key]) {
1950
+ const template = customMessages[key];
1951
+ return template.replace(/\$\{(\w+)}/g, (_, k) => params?.[k] ?? "");
1952
+ }
1953
+ }
1954
+ return t(`common.url.${key}`, params);
1955
+ };
1956
+ const preprocessFn = (val) => {
1957
+ if (val === "" || val === null || val === void 0) {
1958
+ return actualDefaultValue;
1959
+ }
1960
+ let processed = String(val).trim();
1961
+ if (transform) {
1962
+ processed = transform(processed);
1963
+ }
1964
+ return processed;
1965
+ };
1966
+ const baseSchema = required ? z11.preprocess(preprocessFn, z11.string()) : z11.preprocess(preprocessFn, z11.string().nullable());
1967
+ const schema = baseSchema.refine((val) => {
1968
+ if (val === null) return true;
1969
+ if (required && (val === "" || val === "null" || val === "undefined")) {
1970
+ throw new z11.ZodError([{ code: "custom", message: getMessage("required"), path: [] }]);
1971
+ }
1972
+ let urlObj;
1973
+ try {
1974
+ urlObj = new URL(val);
1975
+ } catch {
1976
+ throw new z11.ZodError([{ code: "custom", message: getMessage("invalid"), path: [] }]);
1977
+ }
1978
+ if (val !== null && min !== void 0 && val.length < min) {
1979
+ throw new z11.ZodError([{ code: "custom", message: getMessage("min", { min }), path: [] }]);
1980
+ }
1981
+ if (val !== null && max !== void 0 && val.length > max) {
1982
+ throw new z11.ZodError([{ code: "custom", message: getMessage("max", { max }), path: [] }]);
1983
+ }
1984
+ if (val !== null && includes !== void 0 && !val.includes(includes)) {
1985
+ throw new z11.ZodError([{ code: "custom", message: getMessage("includes", { includes }), path: [] }]);
1986
+ }
1987
+ if (val !== null && excludes !== void 0) {
1988
+ const excludeList = Array.isArray(excludes) ? excludes : [excludes];
1989
+ for (const exclude of excludeList) {
1990
+ if (val.includes(exclude)) {
1991
+ throw new z11.ZodError([{ code: "custom", message: getMessage("excludes", { excludes: exclude }), path: [] }]);
1992
+ }
1993
+ }
1994
+ }
1995
+ if (protocols && !protocols.includes(urlObj.protocol.slice(0, -1))) {
1996
+ throw new z11.ZodError([{ code: "custom", message: getMessage("protocol", { protocols: protocols.join(", ") }), path: [] }]);
1997
+ }
1998
+ const hostname = urlObj.hostname.toLowerCase();
1999
+ if (allowedDomains && !allowedDomains.some((domain) => hostname === domain || hostname.endsWith(`.${domain}`))) {
2000
+ throw new z11.ZodError([{ code: "custom", message: getMessage("domain", { domains: allowedDomains.join(", ") }), path: [] }]);
2001
+ }
2002
+ if (blockedDomains && blockedDomains.some((domain) => hostname === domain || hostname.endsWith(`.${domain}`))) {
2003
+ const blockedDomain = blockedDomains.find((domain) => hostname === domain || hostname.endsWith(`.${domain}`));
2004
+ throw new z11.ZodError([{ code: "custom", message: getMessage("domainBlacklist", { domain: blockedDomain }), path: [] }]);
2005
+ }
2006
+ const port = urlObj.port ? parseInt(urlObj.port) : urlObj.protocol === "https:" ? 443 : 80;
2007
+ if (allowedPorts && !allowedPorts.includes(port)) {
2008
+ throw new z11.ZodError([{ code: "custom", message: getMessage("port", { ports: allowedPorts.join(", ") }), path: [] }]);
2009
+ }
2010
+ if (blockedPorts && blockedPorts.includes(port)) {
2011
+ throw new z11.ZodError([{ code: "custom", message: getMessage("port", { port }), path: [] }]);
2012
+ }
2013
+ if (pathStartsWith && !urlObj.pathname.startsWith(pathStartsWith)) {
2014
+ throw new z11.ZodError([{ code: "custom", message: getMessage("pathStartsWith", { path: pathStartsWith }), path: [] }]);
2015
+ }
2016
+ if (pathEndsWith && !urlObj.pathname.endsWith(pathEndsWith)) {
2017
+ throw new z11.ZodError([{ code: "custom", message: getMessage("pathEndsWith", { path: pathEndsWith }), path: [] }]);
2018
+ }
2019
+ if (mustHaveQuery && !urlObj.search) {
2020
+ throw new z11.ZodError([{ code: "custom", message: getMessage("hasQuery"), path: [] }]);
2021
+ }
2022
+ if (mustNotHaveQuery && urlObj.search) {
2023
+ throw new z11.ZodError([{ code: "custom", message: getMessage("noQuery"), path: [] }]);
2024
+ }
2025
+ if (mustHaveFragment && !urlObj.hash) {
2026
+ throw new z11.ZodError([{ code: "custom", message: getMessage("hasFragment"), path: [] }]);
2027
+ }
2028
+ if (mustNotHaveFragment && urlObj.hash) {
2029
+ throw new z11.ZodError([{ code: "custom", message: getMessage("noFragment"), path: [] }]);
2030
+ }
2031
+ const isLocalhost = hostname === "localhost" || hostname === "127.0.0.1" || hostname.startsWith("192.168.") || hostname.startsWith("10.") || hostname.match(/^172\.(1[6-9]|2[0-9]|3[0-1])\./);
2032
+ if (blockLocalhost && isLocalhost) {
2033
+ throw new z11.ZodError([{ code: "custom", message: getMessage("noLocalhost"), path: [] }]);
2034
+ }
2035
+ if (!allowLocalhost && isLocalhost) {
2036
+ throw new z11.ZodError([{ code: "custom", message: getMessage("localhost"), path: [] }]);
2037
+ }
2038
+ return true;
2039
+ });
2040
+ return schema;
2041
+ }
2042
+
2043
+ // src/validators/taiwan/business-id.ts
2044
+ import { z as z12 } from "zod";
2045
+ var validateTaiwanBusinessId = (value) => {
2046
+ if (!/^\d{8}$/.test(value)) {
2047
+ return false;
2048
+ }
2049
+ const digits = value.split("").map(Number);
2050
+ const coefficients = [1, 2, 1, 2, 1, 2, 4];
2051
+ let sum = 0;
2052
+ for (let i = 0; i < 7; i++) {
2053
+ const product = digits[i] * coefficients[i];
2054
+ sum += Math.floor(product / 10) + product % 10;
2055
+ }
2056
+ sum += digits[7];
2057
+ if (sum % 5 === 0) {
2058
+ return true;
2059
+ }
2060
+ if (sum % 10 === 0) {
2061
+ return true;
2062
+ }
2063
+ if (digits[6] === 7) {
2064
+ let altSum = 0;
2065
+ for (let i = 0; i < 7; i++) {
2066
+ const product = digits[i] * coefficients[i];
2067
+ altSum += Math.floor(product / 10) + product % 10;
2068
+ }
2069
+ altSum += 1 + digits[7];
2070
+ if (altSum % 5 === 0 || altSum % 10 === 0) {
2071
+ return true;
2072
+ }
2073
+ }
2074
+ return false;
2075
+ };
2076
+ function businessId(options) {
2077
+ const {
2078
+ required = true,
2079
+ transform,
2080
+ defaultValue,
2081
+ i18n
2082
+ } = options ?? {};
2083
+ const actualDefaultValue = defaultValue ?? (required ? "" : null);
2084
+ const getMessage = (key, params) => {
2085
+ if (i18n) {
2086
+ const currentLocale2 = getLocale();
2087
+ const customMessages = i18n[currentLocale2];
2088
+ if (customMessages && customMessages[key]) {
2089
+ const template = customMessages[key];
2090
+ return template.replace(/\$\{(\w+)}/g, (_, k) => params?.[k] ?? "");
2091
+ }
2092
+ }
2093
+ return t(`taiwan.businessId.${key}`, params);
2094
+ };
2095
+ const preprocessFn = (val) => {
2096
+ if (val === "" || val === null || val === void 0) {
2097
+ return actualDefaultValue;
2098
+ }
2099
+ let processed = String(val).trim();
2100
+ if (processed === "" && !required) {
2101
+ return null;
2102
+ }
2103
+ if (transform) {
2104
+ processed = transform(processed);
2105
+ }
2106
+ return processed;
2107
+ };
2108
+ const baseSchema = required ? z12.preprocess(preprocessFn, z12.string()) : z12.preprocess(preprocessFn, z12.string().nullable());
2109
+ const schema = baseSchema.refine((val) => {
2110
+ if (val === null) return true;
2111
+ if (required && (val === "" || val === "null" || val === "undefined")) {
2112
+ throw new z12.ZodError([{ code: "custom", message: getMessage("required"), path: [] }]);
2113
+ }
2114
+ if (val === null) return true;
2115
+ if (!required && val === "") return true;
2116
+ if (!validateTaiwanBusinessId(val)) {
2117
+ throw new z12.ZodError([{ code: "custom", message: getMessage("invalid"), path: [] }]);
2118
+ }
2119
+ return true;
2120
+ });
2121
+ return schema;
2122
+ }
2123
+
2124
+ // src/validators/taiwan/national-id.ts
2125
+ import { z as z13 } from "zod";
2126
+ var CITY_CODES = {
2127
+ "A": 10,
2128
+ "B": 11,
2129
+ "C": 12,
2130
+ "D": 13,
2131
+ "E": 14,
2132
+ "F": 15,
2133
+ "G": 16,
2134
+ "H": 17,
2135
+ "I": 34,
2136
+ "J": 18,
2137
+ "K": 19,
2138
+ "L": 20,
2139
+ "M": 21,
2140
+ "N": 22,
2141
+ "O": 35,
2142
+ "P": 23,
2143
+ "Q": 24,
2144
+ "R": 25,
2145
+ "S": 26,
2146
+ "T": 27,
2147
+ "U": 28,
2148
+ "V": 29,
2149
+ "W": 32,
2150
+ "X": 30,
2151
+ "Y": 31,
2152
+ "Z": 33
2153
+ };
2154
+ var validateCitizenId = (value) => {
2155
+ if (!/^[A-Z][1-2]\d{8}$/.test(value)) {
2156
+ return false;
2157
+ }
2158
+ const letter = value[0];
2159
+ const digits = value.slice(1).split("").map(Number);
2160
+ const cityCode = CITY_CODES[letter];
2161
+ if (!cityCode) return false;
2162
+ const cityDigits = [Math.floor(cityCode / 10), cityCode % 10];
2163
+ const coefficients = [1, 9, 8, 7, 6, 5, 4, 3, 2, 1];
2164
+ let sum = cityDigits[0] * coefficients[0] + cityDigits[1] * coefficients[1];
2165
+ for (let i = 0; i < 8; i++) {
2166
+ sum += digits[i] * coefficients[i + 2];
2167
+ }
2168
+ const checksum = (10 - sum % 10) % 10;
2169
+ return checksum === digits[8];
2170
+ };
2171
+ var validateOldResidentId = (value) => {
2172
+ if (!/^[A-Z][ABCD]\d{8}$/.test(value)) {
2173
+ return false;
2174
+ }
2175
+ const letter = value[0];
2176
+ const genderCode = value[1];
2177
+ const digits = value.slice(2).split("").map(Number);
2178
+ const cityCode = CITY_CODES[letter];
2179
+ if (!cityCode) return false;
2180
+ const genderValue = genderCode === "A" || genderCode === "C" ? 1 : 0;
2181
+ const cityDigits = [Math.floor(cityCode / 10), cityCode % 10];
2182
+ const coefficients = [1, 9, 8, 7, 6, 5, 4, 3, 2, 1];
2183
+ let sum = cityDigits[0] * coefficients[0] + cityDigits[1] * coefficients[1] + genderValue * coefficients[2];
2184
+ for (let i = 0; i < 7; i++) {
2185
+ sum += digits[i] * coefficients[i + 3];
2186
+ }
2187
+ const checksum = (10 - sum % 10) % 10;
2188
+ return checksum === digits[7];
2189
+ };
2190
+ var validateNewResidentId = (value) => {
2191
+ if (!/^[A-Z][89]\d{8}$/.test(value)) {
2192
+ return false;
2193
+ }
2194
+ const letter = value[0];
2195
+ const digits = value.slice(1).split("").map(Number);
2196
+ const cityCode = CITY_CODES[letter];
2197
+ if (!cityCode) return false;
2198
+ const cityDigits = [Math.floor(cityCode / 10), cityCode % 10];
2199
+ const coefficients = [1, 9, 8, 7, 6, 5, 4, 3, 2, 1];
2200
+ let sum = cityDigits[0] * coefficients[0] + cityDigits[1] * coefficients[1];
2201
+ for (let i = 0; i < 8; i++) {
2202
+ sum += digits[i] * coefficients[i + 2];
2203
+ }
2204
+ const checksum = (10 - sum % 10) % 10;
2205
+ return checksum === digits[8];
2206
+ };
2207
+ var validateTaiwanNationalId = (value, type = "both", allowOldResident = true) => {
2208
+ if (!/^[A-Z].{9}$/.test(value)) {
2209
+ return false;
2210
+ }
2211
+ switch (type) {
2212
+ case "citizen":
2213
+ return validateCitizenId(value);
2214
+ case "resident":
2215
+ return (allowOldResident ? validateOldResidentId(value) : false) || validateNewResidentId(value);
2216
+ case "both":
2217
+ return validateCitizenId(value) || (allowOldResident ? validateOldResidentId(value) : false) || validateNewResidentId(value);
2218
+ default:
2219
+ return false;
2220
+ }
2221
+ };
2222
+ function nationalId(options) {
2223
+ const {
2224
+ required = true,
2225
+ type = "both",
2226
+ allowOldResident = true,
2227
+ transform,
2228
+ defaultValue,
2229
+ i18n
2230
+ } = options ?? {};
2231
+ const actualDefaultValue = defaultValue ?? (required ? "" : null);
2232
+ const getMessage = (key, params) => {
2233
+ if (i18n) {
2234
+ const currentLocale2 = getLocale();
2235
+ const customMessages = i18n[currentLocale2];
2236
+ if (customMessages && customMessages[key]) {
2237
+ const template = customMessages[key];
2238
+ return template.replace(/\$\{(\w+)}/g, (_, k) => params?.[k] ?? "");
2239
+ }
2240
+ }
2241
+ return t(`taiwan.nationalId.${key}`, params);
2242
+ };
2243
+ const preprocessFn = (val) => {
2244
+ if (val === "" || val === null || val === void 0) {
2245
+ return actualDefaultValue;
2246
+ }
2247
+ let processed = String(val).trim().toUpperCase();
2248
+ if (processed === "" && !required) {
2249
+ return null;
2250
+ }
2251
+ if (transform) {
2252
+ processed = transform(processed);
2253
+ }
2254
+ return processed;
2255
+ };
2256
+ const baseSchema = required ? z13.preprocess(preprocessFn, z13.string()) : z13.preprocess(preprocessFn, z13.string().nullable());
2257
+ const schema = baseSchema.refine((val) => {
2258
+ if (val === null) return true;
2259
+ if (required && (val === "" || val === "null" || val === "undefined")) {
2260
+ throw new z13.ZodError([{ code: "custom", message: getMessage("required"), path: [] }]);
2261
+ }
2262
+ if (val === null) return true;
2263
+ if (!required && val === "") return true;
2264
+ if (!validateTaiwanNationalId(val, type, allowOldResident)) {
2265
+ throw new z13.ZodError([{ code: "custom", message: getMessage("invalid"), path: [] }]);
2266
+ }
2267
+ return true;
2268
+ });
2269
+ return schema;
2270
+ }
2271
+
2272
+ // src/validators/taiwan/mobile.ts
2273
+ import { z as z14 } from "zod";
2274
+ var validateTaiwanMobile = (value) => {
2275
+ return /^09[0-9]\d{7}$/.test(value);
2276
+ };
2277
+ function mobile(options) {
2278
+ const { required = true, whitelist, transform, defaultValue, i18n } = options ?? {};
2279
+ const actualDefaultValue = defaultValue ?? (required ? "" : null);
2280
+ const getMessage = (key, params) => {
2281
+ if (i18n) {
2282
+ const currentLocale2 = getLocale();
2283
+ const customMessages = i18n[currentLocale2];
2284
+ if (customMessages && customMessages[key]) {
2285
+ const template = customMessages[key];
2286
+ return template.replace(/\$\{(\w+)}/g, (_, k) => params?.[k] ?? "");
2287
+ }
2288
+ }
2289
+ return t(`taiwan.mobile.${key}`, params);
2290
+ };
2291
+ const preprocessFn = (val) => {
2292
+ if (val === null || val === void 0) {
2293
+ return actualDefaultValue;
2294
+ }
2295
+ let processed = String(val).trim();
2296
+ if (processed === "") {
2297
+ if (whitelist && whitelist.includes("")) {
2298
+ return "";
2299
+ }
2300
+ if (!required) {
2301
+ return actualDefaultValue;
2302
+ }
2303
+ return actualDefaultValue;
2304
+ }
2305
+ if (transform) {
2306
+ processed = transform(processed);
2307
+ }
2308
+ return processed;
2309
+ };
2310
+ const baseSchema = required ? z14.preprocess(preprocessFn, z14.string()) : z14.preprocess(preprocessFn, z14.string().nullable());
2311
+ const schema = baseSchema.refine((val) => {
2312
+ if (val === null) return true;
2313
+ if (required && (val === "" || val === "null" || val === "undefined")) {
2314
+ throw new z14.ZodError([{ code: "custom", message: getMessage("required"), path: [] }]);
2315
+ }
2316
+ if (val === null) return true;
2317
+ if (!required && val === "") return true;
2318
+ if (whitelist && whitelist.length > 0) {
2319
+ if (whitelist.includes(val)) {
2320
+ return true;
2321
+ }
2322
+ throw new z14.ZodError([{ code: "custom", message: getMessage("notInWhitelist"), path: [] }]);
2323
+ }
2324
+ if (!validateTaiwanMobile(val)) {
2325
+ throw new z14.ZodError([{ code: "custom", message: getMessage("invalid"), path: [] }]);
2326
+ }
2327
+ return true;
2328
+ });
2329
+ return schema;
2330
+ }
2331
+
2332
+ // src/validators/taiwan/postal-code.ts
2333
+ import { z as z15 } from "zod";
2334
+ var VALID_3_DIGIT_PREFIXES = [
2335
+ // Taipei City (台北市) - 100-116
2336
+ "100",
2337
+ "103",
2338
+ "104",
2339
+ "105",
2340
+ "106",
2341
+ "108",
2342
+ "110",
2343
+ "111",
2344
+ "112",
2345
+ "114",
2346
+ "115",
2347
+ "116",
2348
+ // New Taipei City (新北市) - 200-253
2349
+ "200",
2350
+ "201",
2351
+ "202",
2352
+ "203",
2353
+ "204",
2354
+ "205",
2355
+ "206",
2356
+ "207",
2357
+ "208",
2358
+ "220",
2359
+ "221",
2360
+ "222",
2361
+ "223",
2362
+ "224",
2363
+ "226",
2364
+ "227",
2365
+ "228",
2366
+ "231",
2367
+ "232",
2368
+ "233",
2369
+ "234",
2370
+ "235",
2371
+ "236",
2372
+ "237",
2373
+ "238",
2374
+ "239",
2375
+ "241",
2376
+ "242",
2377
+ "243",
2378
+ "244",
2379
+ "247",
2380
+ "248",
2381
+ "249",
2382
+ "251",
2383
+ "252",
2384
+ "253",
2385
+ // Keelung City (基隆市) - 200-206
2386
+ "200",
2387
+ "201",
2388
+ "202",
2389
+ "203",
2390
+ "204",
2391
+ "205",
2392
+ "206",
2393
+ // Taoyuan City (桃園市) - 300-338
2394
+ "300",
2395
+ "302",
2396
+ "303",
2397
+ "304",
2398
+ "305",
2399
+ "306",
2400
+ "307",
2401
+ "308",
2402
+ "310",
2403
+ "311",
2404
+ "312",
2405
+ "313",
2406
+ "314",
2407
+ "315",
2408
+ "316",
2409
+ "317",
2410
+ "318",
2411
+ "320",
2412
+ "324",
2413
+ "325",
2414
+ "326",
2415
+ "327",
2416
+ "328",
2417
+ "330",
2418
+ "333",
2419
+ "334",
2420
+ "335",
2421
+ "336",
2422
+ "337",
2423
+ "338",
2424
+ // Hsinchu County (新竹縣) - 300-315
2425
+ "300",
2426
+ "302",
2427
+ "303",
2428
+ "304",
2429
+ "305",
2430
+ "306",
2431
+ "307",
2432
+ "308",
2433
+ "310",
2434
+ "311",
2435
+ "312",
2436
+ "313",
2437
+ "314",
2438
+ "315",
2439
+ // Hsinchu City (新竹市) - 300
2440
+ "300",
2441
+ // Miaoli County (苗栗縣) - 350-369
2442
+ "350",
2443
+ "351",
2444
+ "352",
2445
+ "353",
2446
+ "354",
2447
+ "356",
2448
+ "357",
2449
+ "358",
2450
+ "360",
2451
+ "361",
2452
+ "362",
2453
+ "363",
2454
+ "364",
2455
+ "365",
2456
+ "366",
2457
+ "367",
2458
+ "368",
2459
+ "369",
2460
+ // Taichung City (台中市) - 400-439
2461
+ "400",
2462
+ "401",
2463
+ "402",
2464
+ "403",
2465
+ "404",
2466
+ "406",
2467
+ "407",
2468
+ "408",
2469
+ "411",
2470
+ "412",
2471
+ "413",
2472
+ "414",
2473
+ "420",
2474
+ "421",
2475
+ "422",
2476
+ "423",
2477
+ "424",
2478
+ "426",
2479
+ "427",
2480
+ "428",
2481
+ "429",
2482
+ "432",
2483
+ "433",
2484
+ "434",
2485
+ "435",
2486
+ "436",
2487
+ "437",
2488
+ "438",
2489
+ "439",
2490
+ // Changhua County (彰化縣) - 500-530
2491
+ "500",
2492
+ "502",
2493
+ "503",
2494
+ "504",
2495
+ "505",
2496
+ "506",
2497
+ "507",
2498
+ "508",
2499
+ "509",
2500
+ "510",
2501
+ "511",
2502
+ "512",
2503
+ "513",
2504
+ "514",
2505
+ "515",
2506
+ "516",
2507
+ "520",
2508
+ "521",
2509
+ "522",
2510
+ "523",
2511
+ "524",
2512
+ "525",
2513
+ "526",
2514
+ "527",
2515
+ "528",
2516
+ "530",
2517
+ // Nantou County (南投縣) - 540-558
2518
+ "540",
2519
+ "541",
2520
+ "542",
2521
+ "544",
2522
+ "545",
2523
+ "546",
2524
+ "551",
2525
+ "552",
2526
+ "553",
2527
+ "555",
2528
+ "556",
2529
+ "557",
2530
+ "558",
2531
+ // Yunlin County (雲林縣) - 630-655
2532
+ "630",
2533
+ "631",
2534
+ "632",
2535
+ "633",
2536
+ "634",
2537
+ "635",
2538
+ "636",
2539
+ "637",
2540
+ "638",
2541
+ "640",
2542
+ "643",
2543
+ "646",
2544
+ "647",
2545
+ "648",
2546
+ "649",
2547
+ "651",
2548
+ "652",
2549
+ "653",
2550
+ "654",
2551
+ "655",
2552
+ // Chiayi County (嘉義縣) - 600-625
2553
+ "600",
2554
+ "602",
2555
+ "603",
2556
+ "604",
2557
+ "605",
2558
+ "606",
2559
+ "607",
2560
+ "608",
2561
+ "611",
2562
+ "612",
2563
+ "613",
2564
+ "614",
2565
+ "615",
2566
+ "616",
2567
+ "621",
2568
+ "622",
2569
+ "623",
2570
+ "624",
2571
+ "625",
2572
+ // Chiayi City (嘉義市) - 600
2573
+ "600",
2574
+ // Tainan City (台南市) - 700-745
2575
+ "700",
2576
+ "701",
2577
+ "702",
2578
+ "704",
2579
+ "708",
2580
+ "709",
2581
+ "710",
2582
+ "711",
2583
+ "712",
2584
+ "713",
2585
+ "714",
2586
+ "715",
2587
+ "716",
2588
+ "717",
2589
+ "718",
2590
+ "719",
2591
+ "720",
2592
+ "721",
2593
+ "722",
2594
+ "723",
2595
+ "724",
2596
+ "725",
2597
+ "726",
2598
+ "727",
2599
+ "730",
2600
+ "731",
2601
+ "732",
2602
+ "733",
2603
+ "734",
2604
+ "735",
2605
+ "736",
2606
+ "737",
2607
+ "741",
2608
+ "742",
2609
+ "743",
2610
+ "744",
2611
+ "745",
2612
+ // Kaohsiung City (高雄市) - 800-852
2613
+ "800",
2614
+ "801",
2615
+ "802",
2616
+ "803",
2617
+ "804",
2618
+ "805",
2619
+ "806",
2620
+ "807",
2621
+ "811",
2622
+ "812",
2623
+ "813",
2624
+ "814",
2625
+ "815",
2626
+ "820",
2627
+ "821",
2628
+ "822",
2629
+ "823",
2630
+ "824",
2631
+ "825",
2632
+ "826",
2633
+ "827",
2634
+ "828",
2635
+ "829",
2636
+ "830",
2637
+ "831",
2638
+ "832",
2639
+ "833",
2640
+ "840",
2641
+ "842",
2642
+ "843",
2643
+ "844",
2644
+ "845",
2645
+ "846",
2646
+ "847",
2647
+ "848",
2648
+ "849",
2649
+ "851",
2650
+ "852",
2651
+ // Pingtung County (屏東縣) - 900-947
2652
+ "900",
2653
+ "901",
2654
+ "902",
2655
+ "903",
2656
+ "904",
2657
+ "905",
2658
+ "906",
2659
+ "907",
2660
+ "908",
2661
+ "909",
2662
+ "911",
2663
+ "912",
2664
+ "913",
2665
+ "920",
2666
+ "921",
2667
+ "922",
2668
+ "923",
2669
+ "924",
2670
+ "925",
2671
+ "926",
2672
+ "927",
2673
+ "928",
2674
+ "929",
2675
+ "931",
2676
+ "932",
2677
+ "940",
2678
+ "941",
2679
+ "942",
2680
+ "943",
2681
+ "944",
2682
+ "945",
2683
+ "946",
2684
+ "947",
2685
+ // Yilan County (宜蘭縣) - 260-269
2686
+ "260",
2687
+ "261",
2688
+ "262",
2689
+ "263",
2690
+ "264",
2691
+ "265",
2692
+ "266",
2693
+ "267",
2694
+ "268",
2695
+ "269",
2696
+ // Hualien County (花蓮縣) - 970-983
2697
+ "970",
2698
+ "971",
2699
+ "972",
2700
+ "973",
2701
+ "974",
2702
+ "975",
2703
+ "976",
2704
+ "977",
2705
+ "978",
2706
+ "979",
2707
+ "981",
2708
+ "982",
2709
+ "983",
2710
+ // Taitung County (台東縣) - 950-966
2711
+ "950",
2712
+ "951",
2713
+ "952",
2714
+ "953",
2715
+ "954",
2716
+ "955",
2717
+ "956",
2718
+ "957",
2719
+ "958",
2720
+ "959",
2721
+ "961",
2722
+ "962",
2723
+ "963",
2724
+ "964",
2725
+ "965",
2726
+ "966",
2727
+ // Penghu County (澎湖縣) - 880-885
2728
+ "880",
2729
+ "881",
2730
+ "882",
2731
+ "883",
2732
+ "884",
2733
+ "885",
2734
+ // Kinmen County (金門縣) - 890-896
2735
+ "890",
2736
+ "891",
2737
+ "892",
2738
+ "893",
2739
+ "894",
2740
+ "895",
2741
+ "896",
2742
+ // Lienchiang County (連江縣/馬祖) - 209-212
2743
+ "209",
2744
+ "210",
2745
+ "211",
2746
+ "212"
2747
+ ];
2748
+ var POSTAL_CODE_RANGES = {
2749
+ // Major cities with extensive postal networks
2750
+ "100": { range5: [1, 99], range6: [1, 999] },
2751
+ // Taipei City - Zhongzheng
2752
+ "103": { range5: [1, 99], range6: [1, 999] },
2753
+ // Taipei City - Datong
2754
+ "104": { range5: [1, 99], range6: [1, 999] },
2755
+ // Taipei City - Zhongshan
2756
+ "105": { range5: [1, 99], range6: [1, 999] },
2757
+ // Taipei City - Songshan
2758
+ "106": { range5: [1, 99], range6: [1, 999] },
2759
+ // Taipei City - Da'an
2760
+ "108": { range5: [1, 99], range6: [1, 999] },
2761
+ // Taipei City - Wanhua
2762
+ "110": { range5: [1, 99], range6: [1, 999] },
2763
+ // Taipei City - Xinyi
2764
+ "111": { range5: [1, 99], range6: [1, 999] },
2765
+ // Taipei City - Shilin
2766
+ "112": { range5: [1, 99], range6: [1, 999] },
2767
+ // Taipei City - Beitou
2768
+ "114": { range5: [1, 99], range6: [1, 999] },
2769
+ // Taipei City - Neihu
2770
+ "115": { range5: [1, 99], range6: [1, 999] },
2771
+ // Taipei City - Nangang
2772
+ "116": { range5: [1, 99], range6: [1, 999] },
2773
+ // Taipei City - Wenshan
2774
+ // New Taipei City major areas
2775
+ "220": { range5: [1, 99], range6: [1, 999] },
2776
+ // Banqiao
2777
+ "221": { range5: [1, 99], range6: [1, 999] },
2778
+ // Xizhi
2779
+ "222": { range5: [1, 99], range6: [1, 999] },
2780
+ // Shenkeng
2781
+ "223": { range5: [1, 99], range6: [1, 999] },
2782
+ // Shiding
2783
+ "224": { range5: [1, 99], range6: [1, 999] },
2784
+ // Ruifang
2785
+ // Taoyuan City
2786
+ "320": { range5: [1, 99], range6: [1, 999] },
2787
+ // Zhongli
2788
+ "324": { range5: [1, 99], range6: [1, 999] },
2789
+ // Pingzhen
2790
+ "330": { range5: [1, 99], range6: [1, 999] },
2791
+ // Taoyuan
2792
+ // Taichung City
2793
+ "400": { range5: [1, 99], range6: [1, 999] },
2794
+ // Central District
2795
+ "401": { range5: [1, 99], range6: [1, 999] },
2796
+ // East District
2797
+ "402": { range5: [1, 99], range6: [1, 999] },
2798
+ // South District
2799
+ "403": { range5: [1, 99], range6: [1, 999] },
2800
+ // West District
2801
+ "404": { range5: [1, 99], range6: [1, 999] },
2802
+ // North District
2803
+ // Tainan City
2804
+ "700": { range5: [1, 99], range6: [1, 999] },
2805
+ // Central District
2806
+ "701": { range5: [1, 99], range6: [1, 999] },
2807
+ // East District
2808
+ "702": { range5: [1, 99], range6: [1, 999] },
2809
+ // South District
2810
+ // Kaohsiung City
2811
+ "800": { range5: [1, 99], range6: [1, 999] },
2812
+ // Xinxing
2813
+ "801": { range5: [1, 99], range6: [1, 999] },
2814
+ // Qianjin
2815
+ "802": { range5: [1, 99], range6: [1, 999] },
2816
+ // Lingya
2817
+ "803": { range5: [1, 99], range6: [1, 999] },
2818
+ // Yancheng
2819
+ // Smaller areas with more limited ranges
2820
+ "880": { range5: [1, 50], range6: [1, 500] },
2821
+ // Penghu (smaller population)
2822
+ "890": { range5: [1, 30], range6: [1, 300] },
2823
+ // Kinmen (smaller population)
2824
+ "209": { range5: [1, 20], range6: [1, 200] }
2825
+ // Lienchiang/Matsu (smallest population)
2826
+ };
2827
+ var getPostalCodeRanges = (prefix) => {
2828
+ return POSTAL_CODE_RANGES[prefix] || {
2829
+ range5: [1, 99],
2830
+ // Default range for 5-digit
2831
+ range6: [1, 999]
2832
+ // Default range for 6-digit
2833
+ };
2834
+ };
2835
+ var validate3DigitPostalCode = (value, strictValidation = true, allowedPrefixes, blockedPrefixes) => {
2836
+ if (!/^\d{3}$/.test(value)) {
2837
+ return false;
2838
+ }
2839
+ if (blockedPrefixes && blockedPrefixes.includes(value)) {
2840
+ return false;
2841
+ }
2842
+ if (allowedPrefixes) {
2843
+ return allowedPrefixes.includes(value);
2844
+ }
2845
+ if (strictValidation) {
2846
+ return VALID_3_DIGIT_PREFIXES.includes(value);
2847
+ }
2848
+ const num = parseInt(value, 10);
2849
+ return num >= 100 && num <= 999;
2850
+ };
2851
+ var validate5DigitPostalCode = (value, strictValidation = true, strictSuffixValidation = false, allowedPrefixes, blockedPrefixes) => {
2852
+ if (!/^\d{5}$/.test(value)) {
2853
+ return false;
2854
+ }
2855
+ const prefix = value.substring(0, 3);
2856
+ const suffix = value.substring(3, 5);
2857
+ if (!validate3DigitPostalCode(prefix, strictValidation, allowedPrefixes, blockedPrefixes)) {
2858
+ return false;
2859
+ }
2860
+ if (strictSuffixValidation) {
2861
+ const suffixNum = parseInt(suffix, 10);
2862
+ const ranges = getPostalCodeRanges(prefix);
2863
+ if (suffixNum < ranges.range5[0] || suffixNum > ranges.range5[1]) {
2864
+ return false;
2865
+ }
2866
+ }
2867
+ return true;
2868
+ };
2869
+ var validate6DigitPostalCode = (value, strictValidation = true, strictSuffixValidation = false, allowedPrefixes, blockedPrefixes) => {
2870
+ if (!/^\d{6}$/.test(value)) {
2871
+ return false;
2872
+ }
2873
+ const prefix = value.substring(0, 3);
2874
+ const suffix = value.substring(3, 6);
2875
+ if (!validate3DigitPostalCode(prefix, strictValidation, allowedPrefixes, blockedPrefixes)) {
2876
+ return false;
2877
+ }
2878
+ if (strictSuffixValidation) {
2879
+ const suffixNum = parseInt(suffix, 10);
2880
+ const ranges = getPostalCodeRanges(prefix);
2881
+ if (suffixNum < ranges.range6[0] || suffixNum > ranges.range6[1]) {
2882
+ return false;
2883
+ }
2884
+ }
2885
+ return true;
2886
+ };
2887
+ var validateTaiwanPostalCode = (value, format = "3+6", strictValidation = true, strictSuffixValidation = false, allowDashes = true, allowedPrefixes, blockedPrefixes) => {
2888
+ if (!value || typeof value !== "string") {
2889
+ return false;
2890
+ }
2891
+ const cleanValue = allowDashes ? value.replace(/[-\s]/g, "") : value;
2892
+ if (!allowDashes && /[-\s]/.test(value)) {
2893
+ return false;
2894
+ }
2895
+ switch (format) {
2896
+ case "3":
2897
+ return cleanValue.length === 3 && validate3DigitPostalCode(cleanValue, strictValidation, allowedPrefixes, blockedPrefixes);
2898
+ case "5":
2899
+ return cleanValue.length === 5 && validate5DigitPostalCode(cleanValue, strictValidation, strictSuffixValidation, allowedPrefixes, blockedPrefixes);
2900
+ case "6":
2901
+ return cleanValue.length === 6 && validate6DigitPostalCode(cleanValue, strictValidation, strictSuffixValidation, allowedPrefixes, blockedPrefixes);
2902
+ case "3+5":
2903
+ return cleanValue.length === 3 && validate3DigitPostalCode(cleanValue, strictValidation, allowedPrefixes, blockedPrefixes) || cleanValue.length === 5 && validate5DigitPostalCode(cleanValue, strictValidation, strictSuffixValidation, allowedPrefixes, blockedPrefixes);
2904
+ case "3+6":
2905
+ return cleanValue.length === 3 && validate3DigitPostalCode(cleanValue, strictValidation, allowedPrefixes, blockedPrefixes) || cleanValue.length === 6 && validate6DigitPostalCode(cleanValue, strictValidation, strictSuffixValidation, allowedPrefixes, blockedPrefixes);
2906
+ case "5+6":
2907
+ return cleanValue.length === 5 && validate5DigitPostalCode(cleanValue, strictValidation, strictSuffixValidation, allowedPrefixes, blockedPrefixes) || cleanValue.length === 6 && validate6DigitPostalCode(cleanValue, strictValidation, strictSuffixValidation, allowedPrefixes, blockedPrefixes);
2908
+ case "all":
2909
+ return cleanValue.length === 3 && validate3DigitPostalCode(cleanValue, strictValidation, allowedPrefixes, blockedPrefixes) || cleanValue.length === 5 && validate5DigitPostalCode(cleanValue, strictValidation, strictSuffixValidation, allowedPrefixes, blockedPrefixes) || cleanValue.length === 6 && validate6DigitPostalCode(cleanValue, strictValidation, strictSuffixValidation, allowedPrefixes, blockedPrefixes);
2910
+ default:
2911
+ return false;
2912
+ }
2913
+ };
2914
+ function postalCode(options) {
2915
+ const {
2916
+ required = true,
2917
+ format = "3+6",
2918
+ strictValidation = true,
2919
+ allowDashes = true,
2920
+ warn5Digit = true,
2921
+ allowedPrefixes,
2922
+ blockedPrefixes,
2923
+ transform,
2924
+ defaultValue,
2925
+ i18n,
2926
+ strictSuffixValidation = false,
2927
+ deprecate5Digit = false
2928
+ } = options ?? {};
2929
+ const actualDefaultValue = defaultValue ?? (required ? "" : null);
2930
+ const getMessage = (key, params) => {
2931
+ if (i18n) {
2932
+ const currentLocale2 = getLocale();
2933
+ const customMessages = i18n[currentLocale2];
2934
+ if (customMessages && customMessages[key]) {
2935
+ const template = customMessages[key];
2936
+ return template.replace(/\$\{(\w+)}/g, (_, k) => params?.[k] ?? "");
2937
+ }
2938
+ }
2939
+ return t(`taiwan.postalCode.${key}`, params);
2940
+ };
2941
+ const preprocessFn = (val) => {
2942
+ if (val === "" || val === null || val === void 0) {
2943
+ return actualDefaultValue;
2944
+ }
2945
+ let processed = String(val).trim();
2946
+ if (allowDashes) {
2947
+ processed = processed.replace(/[-\s]/g, "");
2948
+ }
2949
+ if (processed === "" && !required) {
2950
+ return null;
2951
+ }
2952
+ if (transform) {
2953
+ processed = transform(processed);
2954
+ }
2955
+ return processed;
2956
+ };
2957
+ const baseSchema = required ? z15.preprocess(preprocessFn, z15.string()) : z15.preprocess(preprocessFn, z15.string().nullable());
2958
+ const schema = baseSchema.refine((val) => {
2959
+ if (val === null) return true;
2960
+ if (required && (val === "" || val === "null" || val === "undefined")) {
2961
+ throw new z15.ZodError([{ code: "custom", message: getMessage("required"), path: [] }]);
2962
+ }
2963
+ if (val === null) return true;
2964
+ if (!required && val === "") return true;
2965
+ const cleanValue = val.replace(/[-\s]/g, "");
2966
+ if (format === "3" && cleanValue.length !== 3) {
2967
+ throw new z15.ZodError([{ code: "custom", message: getMessage("format3Only"), path: [] }]);
2968
+ }
2969
+ if (format === "5" && cleanValue.length !== 5) {
2970
+ throw new z15.ZodError([{ code: "custom", message: getMessage("format5Only"), path: [] }]);
2971
+ }
2972
+ if (format === "6" && cleanValue.length !== 6) {
2973
+ throw new z15.ZodError([{ code: "custom", message: getMessage("format6Only"), path: [] }]);
2974
+ }
2975
+ if (deprecate5Digit && cleanValue.length === 5) {
2976
+ throw new z15.ZodError([{ code: "custom", message: getMessage("deprecated5Digit"), path: [] }]);
2977
+ }
2978
+ if (strictSuffixValidation) {
2979
+ if (cleanValue.length === 5) {
2980
+ const prefix = cleanValue.substring(0, 3);
2981
+ const suffix = cleanValue.substring(3, 5);
2982
+ const suffixNum = parseInt(suffix, 10);
2983
+ const ranges = getPostalCodeRanges(prefix);
2984
+ if (suffixNum < ranges.range5[0] || suffixNum > ranges.range5[1]) {
2985
+ throw new z15.ZodError([{ code: "custom", message: getMessage("invalidSuffix"), path: [] }]);
2986
+ }
2987
+ } else if (cleanValue.length === 6) {
2988
+ const prefix = cleanValue.substring(0, 3);
2989
+ const suffix = cleanValue.substring(3, 6);
2990
+ const suffixNum = parseInt(suffix, 10);
2991
+ const ranges = getPostalCodeRanges(prefix);
2992
+ if (suffixNum < ranges.range6[0] || suffixNum > ranges.range6[1]) {
2993
+ throw new z15.ZodError([{ code: "custom", message: getMessage("invalidSuffix"), path: [] }]);
2994
+ }
2995
+ }
2996
+ }
2997
+ if (!validateTaiwanPostalCode(val, format, strictValidation, strictSuffixValidation, allowDashes, allowedPrefixes, blockedPrefixes)) {
2998
+ throw new z15.ZodError([{ code: "custom", message: getMessage("invalid"), path: [] }]);
2999
+ }
3000
+ if (warn5Digit && cleanValue.length === 5 && format !== "5" && !deprecate5Digit) {
3001
+ console.warn(getMessage("legacy5DigitWarning"));
3002
+ }
3003
+ return true;
3004
+ });
3005
+ return schema;
3006
+ }
3007
+
3008
+ // src/validators/taiwan/tel.ts
3009
+ import { z as z16 } from "zod";
3010
+ var validateTaiwanTel = (value) => {
3011
+ const cleanValue = value.replace(/[-\s]/g, "");
3012
+ if (!/^0\d{7,10}$/.test(cleanValue)) {
3013
+ return false;
3014
+ }
3015
+ const areaCode4 = cleanValue.substring(0, 4);
3016
+ if (areaCode4 === "0826") {
3017
+ return cleanValue.length === 9 && /^0826[6]\d{4}$/.test(cleanValue);
3018
+ }
3019
+ if (areaCode4 === "0836") {
3020
+ return cleanValue.length === 9 && /^0836[2-9]\d{4}$/.test(cleanValue);
3021
+ }
3022
+ const areaCode3 = cleanValue.substring(0, 3);
3023
+ if (areaCode3 === "037") {
3024
+ return cleanValue.length === 9 && /^037[2-9]\d{5}$/.test(cleanValue);
3025
+ }
3026
+ if (areaCode3 === "049") {
3027
+ return cleanValue.length === 10 && /^049[2-9]\d{6}$/.test(cleanValue);
3028
+ }
3029
+ if (areaCode3 === "082") {
3030
+ return cleanValue.length === 9 && /^082[2-57-9]\d{5}$/.test(cleanValue);
3031
+ }
3032
+ if (areaCode3 === "089") {
3033
+ return cleanValue.length === 9 && /^089[2-9]\d{5}$/.test(cleanValue);
3034
+ }
3035
+ const areaCode2 = cleanValue.substring(0, 2);
3036
+ if (areaCode2 === "02") {
3037
+ return cleanValue.length === 10 && /^02[235-8]\d{7}$/.test(cleanValue);
3038
+ }
3039
+ if (["03", "04", "05", "06"].includes(areaCode2)) {
3040
+ return cleanValue.length === 9;
3041
+ }
3042
+ if (areaCode2 === "07") {
3043
+ return cleanValue.length === 9 && /^07[2-9]\d{6}$/.test(cleanValue);
3044
+ }
3045
+ if (areaCode2 === "08") {
3046
+ return cleanValue.length === 9 && /^08[478]\d{6}$/.test(cleanValue);
3047
+ }
3048
+ return false;
3049
+ };
3050
+ function tel(options) {
3051
+ const { required = true, whitelist, transform, defaultValue, i18n } = options ?? {};
3052
+ const actualDefaultValue = defaultValue ?? (required ? "" : null);
3053
+ const getMessage = (key, params) => {
3054
+ if (i18n) {
3055
+ const currentLocale2 = getLocale();
3056
+ const customMessages = i18n[currentLocale2];
3057
+ if (customMessages && customMessages[key]) {
3058
+ const template = customMessages[key];
3059
+ return template.replace(/\$\{(\w+)}/g, (_, k) => params?.[k] ?? "");
3060
+ }
3061
+ }
3062
+ return t(`taiwan.tel.${key}`, params);
3063
+ };
3064
+ const preprocessFn = (val) => {
3065
+ if (val === null || val === void 0) {
3066
+ return actualDefaultValue;
3067
+ }
3068
+ let processed = String(val).trim();
3069
+ if (processed === "") {
3070
+ if (whitelist && whitelist.includes("")) {
3071
+ return "";
3072
+ }
3073
+ if (!required) {
3074
+ return actualDefaultValue;
3075
+ }
3076
+ return actualDefaultValue;
3077
+ }
3078
+ if (transform) {
3079
+ processed = transform(processed);
3080
+ }
3081
+ return processed;
3082
+ };
3083
+ const baseSchema = required ? z16.preprocess(preprocessFn, z16.string()) : z16.preprocess(preprocessFn, z16.string().nullable());
3084
+ const schema = baseSchema.refine((val) => {
3085
+ if (val === null) return true;
3086
+ if (required && (val === "" || val === "null" || val === "undefined")) {
3087
+ throw new z16.ZodError([{ code: "custom", message: getMessage("required"), path: [] }]);
3088
+ }
3089
+ if (val === null) return true;
3090
+ if (!required && val === "") return true;
3091
+ if (whitelist && whitelist.length > 0) {
3092
+ if (whitelist.includes(val)) {
3093
+ return true;
3094
+ }
3095
+ throw new z16.ZodError([{ code: "custom", message: getMessage("notInWhitelist"), path: [] }]);
3096
+ }
3097
+ if (!validateTaiwanTel(val)) {
3098
+ throw new z16.ZodError([{ code: "custom", message: getMessage("invalid"), path: [] }]);
3099
+ }
3100
+ return true;
3101
+ });
3102
+ return schema;
3103
+ }
3104
+
3105
+ // src/validators/taiwan/fax.ts
3106
+ import { z as z17 } from "zod";
3107
+ var validateTaiwanFax = (value) => {
3108
+ const cleanValue = value.replace(/[-\s]/g, "");
3109
+ if (!/^0\d{7,10}$/.test(cleanValue)) {
3110
+ return false;
3111
+ }
3112
+ const areaCode4 = cleanValue.substring(0, 4);
3113
+ if (areaCode4 === "0826") {
3114
+ return cleanValue.length === 9 && /^0826[6]\d{4}$/.test(cleanValue);
3115
+ }
3116
+ if (areaCode4 === "0836") {
3117
+ return cleanValue.length === 9 && /^0836[2-9]\d{4}$/.test(cleanValue);
3118
+ }
3119
+ const areaCode3 = cleanValue.substring(0, 3);
3120
+ if (areaCode3 === "037") {
3121
+ return cleanValue.length === 9 && /^037[2-9]\d{5}$/.test(cleanValue);
3122
+ }
3123
+ if (areaCode3 === "049") {
3124
+ return cleanValue.length === 10 && /^049[2-9]\d{6}$/.test(cleanValue);
3125
+ }
3126
+ if (areaCode3 === "082") {
3127
+ return cleanValue.length === 9 && /^082[2-57-9]\d{5}$/.test(cleanValue);
3128
+ }
3129
+ if (areaCode3 === "089") {
3130
+ return cleanValue.length === 9 && /^089[2-9]\d{5}$/.test(cleanValue);
3131
+ }
3132
+ const areaCode2 = cleanValue.substring(0, 2);
3133
+ if (areaCode2 === "02") {
3134
+ return cleanValue.length === 10 && /^02[235-8]\d{7}$/.test(cleanValue);
3135
+ }
3136
+ if (["03", "04", "05", "06"].includes(areaCode2)) {
3137
+ return cleanValue.length === 9;
3138
+ }
3139
+ if (areaCode2 === "07") {
3140
+ return cleanValue.length === 9 && /^07[2-9]\d{6}$/.test(cleanValue);
3141
+ }
3142
+ if (areaCode2 === "08") {
3143
+ return cleanValue.length === 9 && /^08[478]\d{6}$/.test(cleanValue);
3144
+ }
3145
+ return false;
3146
+ };
3147
+ function fax(options) {
3148
+ const { required = true, whitelist, transform, defaultValue, i18n } = options ?? {};
3149
+ const actualDefaultValue = defaultValue ?? (required ? "" : null);
3150
+ const getMessage = (key, params) => {
3151
+ if (i18n) {
3152
+ const currentLocale2 = getLocale();
3153
+ const customMessages = i18n[currentLocale2];
3154
+ if (customMessages && customMessages[key]) {
3155
+ const template = customMessages[key];
3156
+ return template.replace(/\$\{(\w+)}/g, (_, k) => params?.[k] ?? "");
3157
+ }
3158
+ }
3159
+ return t(`taiwan.fax.${key}`, params);
3160
+ };
3161
+ const preprocessFn = (val) => {
3162
+ if (val === null || val === void 0) {
3163
+ return actualDefaultValue;
3164
+ }
3165
+ let processed = String(val).trim();
3166
+ if (processed === "") {
3167
+ if (whitelist && whitelist.includes("")) {
3168
+ return "";
3169
+ }
3170
+ if (!required) {
3171
+ return actualDefaultValue;
3172
+ }
3173
+ return actualDefaultValue;
3174
+ }
3175
+ if (transform) {
3176
+ processed = transform(processed);
3177
+ }
3178
+ return processed;
3179
+ };
3180
+ const baseSchema = required ? z17.preprocess(preprocessFn, z17.string()) : z17.preprocess(preprocessFn, z17.string().nullable());
3181
+ const schema = baseSchema.refine((val) => {
3182
+ if (val === null) return true;
3183
+ if (required && (val === "" || val === "null" || val === "undefined")) {
3184
+ throw new z17.ZodError([{ code: "custom", message: getMessage("required"), path: [] }]);
3185
+ }
3186
+ if (val === null) return true;
3187
+ if (!required && val === "") return true;
3188
+ if (whitelist && whitelist.length > 0) {
3189
+ if (whitelist.includes(val)) {
3190
+ return true;
3191
+ }
3192
+ throw new z17.ZodError([{ code: "custom", message: getMessage("notInWhitelist"), path: [] }]);
3193
+ }
3194
+ if (!validateTaiwanFax(val)) {
3195
+ throw new z17.ZodError([{ code: "custom", message: getMessage("invalid"), path: [] }]);
3196
+ }
3197
+ return true;
295
3198
  });
296
3199
  return schema;
297
3200
  }
298
3201
  export {
3202
+ DATETIME_PATTERNS,
3203
+ ID_PATTERNS,
3204
+ TIME_PATTERNS,
3205
+ VALID_3_DIGIT_PREFIXES,
299
3206
  boolean,
3207
+ businessId,
3208
+ date,
3209
+ datetime,
3210
+ detectIdType,
300
3211
  email,
3212
+ fax,
3213
+ file,
301
3214
  getLocale,
302
- integer,
3215
+ id,
3216
+ mobile,
3217
+ nationalId,
3218
+ normalizeDateTimeValue,
3219
+ normalizeTime,
303
3220
  number,
3221
+ parseDateTimeValue,
3222
+ parseTimeToMinutes,
304
3223
  password,
3224
+ postalCode,
305
3225
  setLocale,
3226
+ tel,
306
3227
  text,
307
- url
3228
+ time,
3229
+ url,
3230
+ validate3DigitPostalCode,
3231
+ validate5DigitPostalCode,
3232
+ validate6DigitPostalCode,
3233
+ validateCitizenId,
3234
+ validateDateTimeFormat,
3235
+ validateIdType,
3236
+ validateNewResidentId,
3237
+ validateOldResidentId,
3238
+ validateTaiwanBusinessId,
3239
+ validateTaiwanFax,
3240
+ validateTaiwanMobile,
3241
+ validateTaiwanNationalId,
3242
+ validateTaiwanPostalCode,
3243
+ validateTaiwanTel,
3244
+ validateTimeFormat
308
3245
  };