utilitify-core 1.0.0

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 (147) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +338 -0
  3. package/dist/async.cjs +25 -0
  4. package/dist/async.cjs.map +1 -0
  5. package/dist/async.d.cts +104 -0
  6. package/dist/async.d.ts +104 -0
  7. package/dist/async.js +4 -0
  8. package/dist/async.js.map +1 -0
  9. package/dist/chunk-2ICNRTSN.cjs +346 -0
  10. package/dist/chunk-2ICNRTSN.cjs.map +1 -0
  11. package/dist/chunk-3DPF72DY.js +170 -0
  12. package/dist/chunk-3DPF72DY.js.map +1 -0
  13. package/dist/chunk-4CV4JOE5.js +24 -0
  14. package/dist/chunk-4CV4JOE5.js.map +1 -0
  15. package/dist/chunk-4SLYNSLH.cjs +783 -0
  16. package/dist/chunk-4SLYNSLH.cjs.map +1 -0
  17. package/dist/chunk-5WP7DWCG.js +1285 -0
  18. package/dist/chunk-5WP7DWCG.js.map +1 -0
  19. package/dist/chunk-BMQ6YPKV.js +876 -0
  20. package/dist/chunk-BMQ6YPKV.js.map +1 -0
  21. package/dist/chunk-BZCMWUKS.cjs +479 -0
  22. package/dist/chunk-BZCMWUKS.cjs.map +1 -0
  23. package/dist/chunk-C5R744DY.cjs +173 -0
  24. package/dist/chunk-C5R744DY.cjs.map +1 -0
  25. package/dist/chunk-C75J62CV.cjs +913 -0
  26. package/dist/chunk-C75J62CV.cjs.map +1 -0
  27. package/dist/chunk-CZLDE2OZ.cjs +28 -0
  28. package/dist/chunk-CZLDE2OZ.cjs.map +1 -0
  29. package/dist/chunk-DSMB6AF6.cjs +193 -0
  30. package/dist/chunk-DSMB6AF6.cjs.map +1 -0
  31. package/dist/chunk-ETWGPOPY.js +426 -0
  32. package/dist/chunk-ETWGPOPY.js.map +1 -0
  33. package/dist/chunk-FQBPVN63.cjs +403 -0
  34. package/dist/chunk-FQBPVN63.cjs.map +1 -0
  35. package/dist/chunk-G4GYQGTW.cjs +178 -0
  36. package/dist/chunk-G4GYQGTW.cjs.map +1 -0
  37. package/dist/chunk-GFDMZDMI.js +486 -0
  38. package/dist/chunk-GFDMZDMI.js.map +1 -0
  39. package/dist/chunk-HOTOYIPB.js +171 -0
  40. package/dist/chunk-HOTOYIPB.js.map +1 -0
  41. package/dist/chunk-HYADH4ZX.js +176 -0
  42. package/dist/chunk-HYADH4ZX.js.map +1 -0
  43. package/dist/chunk-JBN7C5WE.js +255 -0
  44. package/dist/chunk-JBN7C5WE.js.map +1 -0
  45. package/dist/chunk-JNCTPFTD.cjs +25 -0
  46. package/dist/chunk-JNCTPFTD.cjs.map +1 -0
  47. package/dist/chunk-N3BH3BV7.js +21 -0
  48. package/dist/chunk-N3BH3BV7.js.map +1 -0
  49. package/dist/chunk-NFPGAVRQ.js +749 -0
  50. package/dist/chunk-NFPGAVRQ.js.map +1 -0
  51. package/dist/chunk-OFFRGRBN.cjs +1332 -0
  52. package/dist/chunk-OFFRGRBN.cjs.map +1 -0
  53. package/dist/chunk-OZLKYIZL.cjs +490 -0
  54. package/dist/chunk-OZLKYIZL.cjs.map +1 -0
  55. package/dist/chunk-P3NUK46X.js +145 -0
  56. package/dist/chunk-P3NUK46X.js.map +1 -0
  57. package/dist/chunk-P7P2B7ZI.cjs +429 -0
  58. package/dist/chunk-P7P2B7ZI.cjs.map +1 -0
  59. package/dist/chunk-PB6SKSJN.cjs +150 -0
  60. package/dist/chunk-PB6SKSJN.cjs.map +1 -0
  61. package/dist/chunk-R3IXCJR7.js +378 -0
  62. package/dist/chunk-R3IXCJR7.js.map +1 -0
  63. package/dist/chunk-SD6P3WEJ.js +324 -0
  64. package/dist/chunk-SD6P3WEJ.js.map +1 -0
  65. package/dist/chunk-YSCHP26P.js +451 -0
  66. package/dist/chunk-YSCHP26P.js.map +1 -0
  67. package/dist/chunk-ZLMPRPCY.cjs +274 -0
  68. package/dist/chunk-ZLMPRPCY.cjs.map +1 -0
  69. package/dist/common-CBDYNJeh.d.cts +48 -0
  70. package/dist/common-CBDYNJeh.d.ts +48 -0
  71. package/dist/constants.cjs +42 -0
  72. package/dist/constants.cjs.map +1 -0
  73. package/dist/constants.d.cts +60 -0
  74. package/dist/constants.d.ts +60 -0
  75. package/dist/constants.js +5 -0
  76. package/dist/constants.js.map +1 -0
  77. package/dist/country/index.cjs +154 -0
  78. package/dist/country/index.cjs.map +1 -0
  79. package/dist/country/index.d.cts +1 -0
  80. package/dist/country/index.d.ts +1 -0
  81. package/dist/country/index.js +5 -0
  82. package/dist/country/index.js.map +1 -0
  83. package/dist/date/index.cjs +117 -0
  84. package/dist/date/index.cjs.map +1 -0
  85. package/dist/date/index.d.cts +283 -0
  86. package/dist/date/index.d.ts +283 -0
  87. package/dist/date/index.js +4 -0
  88. package/dist/date/index.js.map +1 -0
  89. package/dist/environment/index.cjs +73 -0
  90. package/dist/environment/index.cjs.map +1 -0
  91. package/dist/environment/index.d.cts +127 -0
  92. package/dist/environment/index.d.ts +127 -0
  93. package/dist/environment/index.js +4 -0
  94. package/dist/environment/index.js.map +1 -0
  95. package/dist/form/index.cjs +81 -0
  96. package/dist/form/index.cjs.map +1 -0
  97. package/dist/form/index.d.cts +227 -0
  98. package/dist/form/index.d.ts +227 -0
  99. package/dist/form/index.js +4 -0
  100. package/dist/form/index.js.map +1 -0
  101. package/dist/i18n.cjs +37 -0
  102. package/dist/i18n.cjs.map +1 -0
  103. package/dist/i18n.d.cts +102 -0
  104. package/dist/i18n.d.ts +102 -0
  105. package/dist/i18n.js +4 -0
  106. package/dist/i18n.js.map +1 -0
  107. package/dist/index-BXBmBHyL.d.ts +718 -0
  108. package/dist/index-BYsUCP3u.d.cts +718 -0
  109. package/dist/index-Cl26FrAZ.d.cts +362 -0
  110. package/dist/index-Cl26FrAZ.d.ts +362 -0
  111. package/dist/index.cjs +1265 -0
  112. package/dist/index.cjs.map +1 -0
  113. package/dist/index.d.cts +205 -0
  114. package/dist/index.d.ts +205 -0
  115. package/dist/index.js +277 -0
  116. package/dist/index.js.map +1 -0
  117. package/dist/schema.cjs +13 -0
  118. package/dist/schema.cjs.map +1 -0
  119. package/dist/schema.d.cts +84 -0
  120. package/dist/schema.d.ts +84 -0
  121. package/dist/schema.js +4 -0
  122. package/dist/schema.js.map +1 -0
  123. package/dist/security/index.cjs +94 -0
  124. package/dist/security/index.cjs.map +1 -0
  125. package/dist/security/index.d.cts +216 -0
  126. package/dist/security/index.d.ts +216 -0
  127. package/dist/security/index.js +5 -0
  128. package/dist/security/index.js.map +1 -0
  129. package/dist/string/index.cjs +153 -0
  130. package/dist/string/index.cjs.map +1 -0
  131. package/dist/string/index.d.cts +471 -0
  132. package/dist/string/index.d.ts +471 -0
  133. package/dist/string/index.js +4 -0
  134. package/dist/string/index.js.map +1 -0
  135. package/dist/transform/index.cjs +105 -0
  136. package/dist/transform/index.cjs.map +1 -0
  137. package/dist/transform/index.d.cts +271 -0
  138. package/dist/transform/index.d.ts +271 -0
  139. package/dist/transform/index.js +4 -0
  140. package/dist/transform/index.js.map +1 -0
  141. package/dist/validators/index.cjs +195 -0
  142. package/dist/validators/index.cjs.map +1 -0
  143. package/dist/validators/index.d.cts +2 -0
  144. package/dist/validators/index.d.ts +2 -0
  145. package/dist/validators/index.js +6 -0
  146. package/dist/validators/index.js.map +1 -0
  147. package/package.json +229 -0
@@ -0,0 +1,1285 @@
1
+ import { getPhoneFormat, PHONE_FORMATS } from './chunk-GFDMZDMI.js';
2
+ import { init_constants, DISPOSABLE_EMAIL_DOMAINS, FREE_EMAIL_DOMAINS, RESERVED_USERNAMES } from './chunk-ETWGPOPY.js';
3
+
4
+ // src/validators/email.ts
5
+ init_constants();
6
+ var EMAIL_REGEX = /^(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)])$/i;
7
+ var SIMPLE_EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
8
+ var DEFAULT_OPTIONS = {
9
+ checkDisposable: true,
10
+ checkCorporate: false
11
+ };
12
+ function validateEmail(email, options = {}) {
13
+ const opts = { ...DEFAULT_OPTIONS, ...options };
14
+ const errors = [];
15
+ const trimmedEmail = email?.trim() ?? "";
16
+ if (!trimmedEmail) {
17
+ errors.push({
18
+ field: "email",
19
+ code: "REQUIRED",
20
+ message: "Email is required"
21
+ });
22
+ return createResult(false, errors, "", "", "");
23
+ }
24
+ if (email.length > 320) {
25
+ errors.push({
26
+ field: "email",
27
+ code: "INVALID_LENGTH",
28
+ message: "Email must be between 1 and 320 characters"
29
+ });
30
+ return createResult(false, errors, "", "", "");
31
+ }
32
+ const normalizedEmail = trimmedEmail.toLowerCase();
33
+ const atIndex = normalizedEmail.lastIndexOf("@");
34
+ const localPart = atIndex > 0 ? normalizedEmail.slice(0, atIndex) : "";
35
+ const domain = atIndex > 0 ? normalizedEmail.slice(atIndex + 1) : "";
36
+ if (trimmedEmail.length > 254) {
37
+ errors.push({
38
+ field: "email",
39
+ code: "TOO_LONG",
40
+ message: "Email must not exceed 254 characters"
41
+ });
42
+ }
43
+ if (!EMAIL_REGEX.test(normalizedEmail)) {
44
+ errors.push({
45
+ field: "email",
46
+ code: "INVALID_FORMAT",
47
+ message: "Email format is invalid"
48
+ });
49
+ return createResult(false, errors, normalizedEmail, localPart, domain);
50
+ }
51
+ if (localPart.length > 64) {
52
+ errors.push({
53
+ field: "email",
54
+ code: "LOCAL_PART_TOO_LONG",
55
+ message: "Local part must not exceed 64 characters"
56
+ });
57
+ }
58
+ const isDisposable = DISPOSABLE_EMAIL_DOMAINS.has(domain);
59
+ if (opts.checkDisposable && isDisposable) {
60
+ errors.push({
61
+ field: "email",
62
+ code: "DISPOSABLE_EMAIL",
63
+ message: "Disposable email addresses are not allowed"
64
+ });
65
+ }
66
+ if (opts.blockedDomains?.includes(domain)) {
67
+ errors.push({
68
+ field: "email",
69
+ code: "BLOCKED_DOMAIN",
70
+ message: "This email domain is not allowed"
71
+ });
72
+ }
73
+ if (opts.allowedDomains && opts.allowedDomains.length > 0) {
74
+ if (!opts.allowedDomains.includes(domain)) {
75
+ errors.push({
76
+ field: "email",
77
+ code: "DOMAIN_NOT_ALLOWED",
78
+ message: "This email domain is not in the allowed list"
79
+ });
80
+ }
81
+ }
82
+ const isCorporate = !FREE_EMAIL_DOMAINS.has(domain) && !isDisposable;
83
+ return {
84
+ isValid: errors.length === 0,
85
+ errors,
86
+ isDisposable,
87
+ isCorporate,
88
+ normalized: normalizeEmail(normalizedEmail),
89
+ domain,
90
+ localPart
91
+ };
92
+ }
93
+ function isValidEmail(email) {
94
+ if (!email || typeof email !== "string") return false;
95
+ const trimmed = email.trim();
96
+ return SIMPLE_EMAIL_REGEX.test(trimmed) && EMAIL_REGEX.test(trimmed.toLowerCase());
97
+ }
98
+ function isDisposableEmail(email) {
99
+ if (!email || typeof email !== "string") return false;
100
+ const domain = email.trim().toLowerCase().split("@")[1];
101
+ return domain ? DISPOSABLE_EMAIL_DOMAINS.has(domain) : false;
102
+ }
103
+ function normalizeEmail(email) {
104
+ if (!email || typeof email !== "string") return "";
105
+ const trimmed = email.trim().toLowerCase();
106
+ const [localPart, domain] = trimmed.split("@");
107
+ if (!localPart || !domain) return trimmed;
108
+ let normalizedLocal = localPart;
109
+ const plusIndex = normalizedLocal.indexOf("+");
110
+ if (plusIndex > 0) {
111
+ normalizedLocal = normalizedLocal.slice(0, plusIndex);
112
+ }
113
+ if (domain === "gmail.com" || domain === "googlemail.com") {
114
+ normalizedLocal = normalizedLocal.replace(/\./g, "");
115
+ }
116
+ return `${normalizedLocal}@${domain}`;
117
+ }
118
+ function getEmailDomain(email) {
119
+ if (!email || typeof email !== "string") return "";
120
+ const parts = email.trim().toLowerCase().split("@");
121
+ return parts[1] || "";
122
+ }
123
+ function createResult(isValid, errors, normalized, localPart, domain) {
124
+ return {
125
+ isValid,
126
+ errors,
127
+ isDisposable: domain ? DISPOSABLE_EMAIL_DOMAINS.has(domain) : false,
128
+ isCorporate: domain ? !FREE_EMAIL_DOMAINS.has(domain) : false,
129
+ normalized,
130
+ domain,
131
+ localPart
132
+ };
133
+ }
134
+
135
+ // src/validators/name.ts
136
+ var SCRIPT_PATTERNS = {
137
+ latin: /[\u0041-\u005A\u0061-\u007A\u00C0-\u00FF\u0100-\u017F\u0180-\u024F]/,
138
+ arabic: /[\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF]/,
139
+ cjk: /[\u4E00-\u9FFF\u3400-\u4DBF\u3040-\u309F\u30A0-\u30FF\uAC00-\uD7AF]/,
140
+ cyrillic: /[\u0400-\u04FF\u0500-\u052F]/,
141
+ devanagari: /[\u0900-\u097F]/
142
+ };
143
+ var ALLOWED_NAME_CHARS = /^[\p{L}\p{M}'\-\s.]+$/u;
144
+ var DEFAULT_OPTIONS2 = {
145
+ minLength: 2,
146
+ maxLength: 100,
147
+ allowSpecialChars: true,
148
+ allowNumbers: false,
149
+ allowedScripts: ["latin", "arabic", "cjk", "cyrillic", "devanagari"]
150
+ };
151
+ function validateName(name, options = {}) {
152
+ const opts = { ...DEFAULT_OPTIONS2, ...options };
153
+ const errors = [];
154
+ const sanitized = sanitizeName(name);
155
+ const detectedScript = detectScript(sanitized);
156
+ if (!name || !sanitized) {
157
+ errors.push({
158
+ field: "name",
159
+ code: "REQUIRED",
160
+ message: opts.messages?.required || "Name is required"
161
+ });
162
+ return { isValid: false, errors, sanitized: "", detectedScript: "unknown" };
163
+ }
164
+ if (sanitized.length < (opts.minLength ?? 2)) {
165
+ errors.push({
166
+ field: "name",
167
+ code: "TOO_SHORT",
168
+ message: opts.messages?.tooShort || `Name must be at least ${opts.minLength} characters`
169
+ });
170
+ }
171
+ if (sanitized.length > (opts.maxLength ?? 100)) {
172
+ errors.push({
173
+ field: "name",
174
+ code: "TOO_LONG",
175
+ message: opts.messages?.tooLong || `Name must not exceed ${opts.maxLength} characters`
176
+ });
177
+ }
178
+ if (!opts.allowNumbers && /\d/.test(sanitized)) {
179
+ errors.push({
180
+ field: "name",
181
+ code: "CONTAINS_NUMBERS",
182
+ message: opts.messages?.containsNumbers || "Name cannot contain numbers"
183
+ });
184
+ }
185
+ if (!opts.allowSpecialChars) {
186
+ if (!/^[\p{L}\p{M}\s'-]+$/u.test(sanitized)) {
187
+ errors.push({
188
+ field: "name",
189
+ code: "INVALID_CHARACTERS",
190
+ message: opts.messages?.invalidChars || "Name contains invalid characters"
191
+ });
192
+ }
193
+ } else {
194
+ if (!ALLOWED_NAME_CHARS.test(sanitized)) {
195
+ errors.push({
196
+ field: "name",
197
+ code: "INVALID_CHARACTERS",
198
+ message: opts.messages?.invalidChars || "Name contains invalid characters"
199
+ });
200
+ }
201
+ }
202
+ if (opts.allowedScripts && opts.allowedScripts.length > 0 && detectedScript !== "mixed") {
203
+ if (detectedScript !== "unknown" && !opts.allowedScripts.includes(detectedScript)) {
204
+ errors.push({
205
+ field: "name",
206
+ code: "SCRIPT_NOT_ALLOWED",
207
+ message: opts.messages?.scriptNotAllowed || `Script '${detectedScript}' is not allowed`
208
+ });
209
+ }
210
+ }
211
+ if (/^(.)\1+$/.test(sanitized.replace(/\s/g, ""))) {
212
+ errors.push({
213
+ field: "name",
214
+ code: "SUSPICIOUS_PATTERN",
215
+ message: opts.messages?.suspicious || "Name appears to be invalid"
216
+ });
217
+ }
218
+ return {
219
+ isValid: errors.length === 0,
220
+ errors,
221
+ sanitized,
222
+ detectedScript
223
+ };
224
+ }
225
+ function isValidName(name) {
226
+ if (!name || typeof name !== "string") return false;
227
+ const sanitized = sanitizeName(name);
228
+ return sanitized.length >= 2 && sanitized.length <= 100 && ALLOWED_NAME_CHARS.test(sanitized);
229
+ }
230
+ function sanitizeName(name) {
231
+ if (!name || typeof name !== "string") return "";
232
+ return name.normalize("NFC").trim().replace(/\s+/g, " ").replace(/[\x00-\x1F\x7F]/g, "");
233
+ }
234
+ function detectScript(text) {
235
+ if (!text) return "unknown";
236
+ const scripts = [];
237
+ for (const [script, pattern] of Object.entries(SCRIPT_PATTERNS)) {
238
+ if (pattern.test(text)) {
239
+ scripts.push(script);
240
+ }
241
+ }
242
+ if (scripts.length === 0) return "unknown";
243
+ if (scripts.length === 1)
244
+ return scripts[0];
245
+ return "mixed";
246
+ }
247
+ function formatName(name) {
248
+ if (!name || typeof name !== "string") return "";
249
+ return sanitizeName(name).split(" ").map((part) => {
250
+ if (part.length === 0) return "";
251
+ if (part.includes("-")) {
252
+ return part.split("-").map((p) => p.charAt(0).toUpperCase() + p.slice(1).toLowerCase()).join("-");
253
+ }
254
+ if (part.includes("'")) {
255
+ return part.split("'").map((p) => p.charAt(0).toUpperCase() + p.slice(1).toLowerCase()).join("'");
256
+ }
257
+ return part.charAt(0).toUpperCase() + part.slice(1).toLowerCase();
258
+ }).join(" ");
259
+ }
260
+ function splitName(fullName) {
261
+ const sanitized = sanitizeName(fullName);
262
+ const parts = sanitized.split(" ").filter(Boolean);
263
+ if (parts.length === 0) {
264
+ return { firstName: "", lastName: "" };
265
+ }
266
+ if (parts.length === 1) {
267
+ return { firstName: parts[0], lastName: "" };
268
+ }
269
+ if (parts.length === 2) {
270
+ return { firstName: parts[0], lastName: parts[1] };
271
+ }
272
+ return {
273
+ firstName: parts[0],
274
+ middleName: parts.slice(1, -1).join(" "),
275
+ lastName: parts[parts.length - 1]
276
+ };
277
+ }
278
+
279
+ // src/validators/phone.ts
280
+ var DEFAULT_OPTIONS3 = {
281
+ strict: false,
282
+ mobileOnly: false
283
+ };
284
+ function validatePhone(phone, options = {}) {
285
+ const opts = { ...DEFAULT_OPTIONS3, ...options };
286
+ const errors = [];
287
+ if (phone && phone.length > 50) {
288
+ errors.push({
289
+ field: "phone",
290
+ code: "INVALID_LENGTH",
291
+ message: "Phone number is too long"
292
+ });
293
+ return createEmptyResult(errors);
294
+ }
295
+ const cleaned = cleanPhoneNumber(phone);
296
+ if (!phone || !cleaned) {
297
+ errors.push({
298
+ field: "phone",
299
+ code: "REQUIRED",
300
+ message: opts.messages?.required || "Phone number is required"
301
+ });
302
+ return createEmptyResult(errors);
303
+ }
304
+ let detectedCountry = opts.countryCode?.toUpperCase() || "";
305
+ let nationalNumber = cleaned;
306
+ let dialCode = "";
307
+ if (cleaned.startsWith("+") || phone.startsWith("+")) {
308
+ const detection = detectCountryFromPhone(cleaned);
309
+ if (detection) {
310
+ detectedCountry = detection.countryCode;
311
+ nationalNumber = detection.nationalNumber;
312
+ dialCode = detection.dialCode;
313
+ }
314
+ }
315
+ const format = detectedCountry ? getPhoneFormat(detectedCountry) : null;
316
+ if (!format && opts.countryCode) {
317
+ errors.push({
318
+ field: "phone",
319
+ code: "UNSUPPORTED_COUNTRY",
320
+ message: opts.messages?.unsupportedCountry || `Country '${opts.countryCode}' is not supported`
321
+ });
322
+ return createEmptyResult(errors);
323
+ }
324
+ if (format) {
325
+ dialCode = format.dialCode;
326
+ const validLengths = format.lengths;
327
+ const dialCodeDigits = format.dialCode.replace(/\D/g, "");
328
+ if (nationalNumber.startsWith(dialCodeDigits)) {
329
+ nationalNumber = nationalNumber.slice(dialCodeDigits.length);
330
+ }
331
+ if (nationalNumber.startsWith("0")) {
332
+ nationalNumber = nationalNumber.slice(1);
333
+ }
334
+ if (!validLengths.includes(nationalNumber.length)) {
335
+ errors.push({
336
+ field: "phone",
337
+ code: "INVALID_LENGTH",
338
+ message: opts.messages?.invalidLength || `Phone number must be ${validLengths.join(" or ")} digits for ${detectedCountry}`
339
+ });
340
+ }
341
+ const phoneType = determinePhoneType(nationalNumber, format);
342
+ if (opts.mobileOnly && phoneType !== "mobile") {
343
+ errors.push({
344
+ field: "phone",
345
+ code: "MOBILE_ONLY",
346
+ message: opts.messages?.mobileOnly || "Only mobile numbers are allowed"
347
+ });
348
+ }
349
+ return {
350
+ isValid: errors.length === 0,
351
+ errors,
352
+ countryCode: detectedCountry,
353
+ nationalNumber,
354
+ formatted: {
355
+ national: formatNational(nationalNumber, format),
356
+ international: formatInternational(nationalNumber, format),
357
+ e164: formatE164(nationalNumber, format)
358
+ },
359
+ type: phoneType
360
+ };
361
+ }
362
+ if (nationalNumber.length < 7 || nationalNumber.length > 15) {
363
+ errors.push({
364
+ field: "phone",
365
+ code: "INVALID_LENGTH",
366
+ message: opts.messages?.invalidLength || "Phone number length is invalid"
367
+ });
368
+ }
369
+ return {
370
+ isValid: errors.length === 0,
371
+ errors,
372
+ countryCode: detectedCountry,
373
+ nationalNumber,
374
+ formatted: {
375
+ national: nationalNumber,
376
+ international: dialCode ? `${dialCode} ${nationalNumber}` : nationalNumber,
377
+ e164: dialCode ? `${dialCode}${nationalNumber}` : `+${nationalNumber}`
378
+ },
379
+ type: "unknown"
380
+ };
381
+ }
382
+ function isValidPhone(phone, countryCode) {
383
+ const result = validatePhone(phone, { countryCode });
384
+ return result.isValid;
385
+ }
386
+ function formatPhone(phone, format = "international", countryCode) {
387
+ const result = validatePhone(phone, { countryCode });
388
+ if (!result.isValid) return phone;
389
+ return result.formatted[format];
390
+ }
391
+ function cleanPhoneNumber(phone) {
392
+ if (!phone || typeof phone !== "string") return "";
393
+ const trimmed = phone.trim();
394
+ const hasPlus = trimmed.startsWith("+");
395
+ const digits = trimmed.replace(/\D/g, "");
396
+ return hasPlus ? `+${digits}` : digits;
397
+ }
398
+ function detectCountryFromPhone(phone) {
399
+ const digits = phone.replace(/\D/g, "");
400
+ if (digits.startsWith("1") && digits.length >= 4) {
401
+ const areaCode = digits.slice(1, 4);
402
+ const nationalNumber = digits.slice(1);
403
+ for (const format of Object.values(PHONE_FORMATS)) {
404
+ if (format.dialCode === "+1" && format.areaCodes?.includes(areaCode)) {
405
+ return {
406
+ countryCode: format.countryCode,
407
+ nationalNumber,
408
+ dialCode: "+1"
409
+ };
410
+ }
411
+ }
412
+ return {
413
+ countryCode: "US",
414
+ nationalNumber,
415
+ dialCode: "+1"
416
+ };
417
+ }
418
+ const sortedFormats = Object.values(PHONE_FORMATS).sort(
419
+ (a, b) => b.dialCode.replace(/\D/g, "").length - a.dialCode.replace(/\D/g, "").length
420
+ );
421
+ for (const format of sortedFormats) {
422
+ const dialDigits = format.dialCode.replace(/\D/g, "");
423
+ if (format.dialCode === "+1") continue;
424
+ if (digits.startsWith(dialDigits)) {
425
+ return {
426
+ countryCode: format.countryCode,
427
+ nationalNumber: digits.slice(dialDigits.length),
428
+ dialCode: format.dialCode
429
+ };
430
+ }
431
+ }
432
+ return null;
433
+ }
434
+ function determinePhoneType(nationalNumber, format) {
435
+ for (const prefix of format.mobilePrefixes) {
436
+ if (nationalNumber.startsWith(prefix)) {
437
+ return "mobile";
438
+ }
439
+ }
440
+ for (const prefix of format.landlinePrefixes) {
441
+ if (nationalNumber.startsWith(prefix)) {
442
+ return "landline";
443
+ }
444
+ }
445
+ if (format.mobilePrefixes.length === 0 && format.landlinePrefixes.length === 0) {
446
+ return "unknown";
447
+ }
448
+ return "unknown";
449
+ }
450
+ function formatNational(nationalNumber, format) {
451
+ return applyPattern(nationalNumber, format.pattern);
452
+ }
453
+ function formatInternational(nationalNumber, format) {
454
+ return `${format.dialCode} ${applyPattern(nationalNumber, format.pattern)}`;
455
+ }
456
+ function formatE164(nationalNumber, format) {
457
+ return `${format.dialCode}${nationalNumber}`;
458
+ }
459
+ function applyPattern(number, pattern) {
460
+ let result = "";
461
+ let numberIndex = 0;
462
+ for (const char of pattern) {
463
+ if (char === "X") {
464
+ if (numberIndex < number.length) {
465
+ result += number[numberIndex];
466
+ numberIndex++;
467
+ }
468
+ } else {
469
+ result += char;
470
+ }
471
+ }
472
+ if (numberIndex < number.length) {
473
+ result += number.slice(numberIndex);
474
+ }
475
+ return result.trim();
476
+ }
477
+ function createEmptyResult(errors) {
478
+ return {
479
+ isValid: false,
480
+ errors,
481
+ countryCode: "",
482
+ nationalNumber: "",
483
+ formatted: {
484
+ national: "",
485
+ international: "",
486
+ e164: ""
487
+ },
488
+ type: "unknown"
489
+ };
490
+ }
491
+ function getDialingCode(countryCode) {
492
+ const format = getPhoneFormat(countryCode);
493
+ return format?.dialCode || null;
494
+ }
495
+ function parsePhone(phone) {
496
+ const result = validatePhone(phone);
497
+ return {
498
+ countryCode: result.countryCode,
499
+ dialCode: getDialingCode(result.countryCode) || "",
500
+ nationalNumber: result.nationalNumber,
501
+ isValid: result.isValid
502
+ };
503
+ }
504
+
505
+ // src/validators/username.ts
506
+ init_constants();
507
+ var DEFAULT_USERNAME_PATTERN = /^[a-zA-Z0-9_\-.]+$/;
508
+ var DEFAULT_OPTIONS4 = {
509
+ minLength: 3,
510
+ maxLength: 30,
511
+ allowedChars: DEFAULT_USERNAME_PATTERN,
512
+ reserved: [],
513
+ allowEmail: false
514
+ };
515
+ function validateUsername(username, options = {}) {
516
+ const opts = { ...DEFAULT_OPTIONS4, ...options };
517
+ const errors = [];
518
+ const sanitized = username?.trim().toLowerCase() ?? "";
519
+ if (!username || !sanitized) {
520
+ errors.push({
521
+ field: "username",
522
+ code: "REQUIRED",
523
+ message: opts.messages?.required || "Username is required"
524
+ });
525
+ return { isValid: false, errors, sanitized: "", isReserved: false };
526
+ }
527
+ if (!opts.allowEmail && sanitized.includes("@")) {
528
+ errors.push({
529
+ field: "username",
530
+ code: "EMAIL_NOT_ALLOWED",
531
+ message: opts.messages?.emailNotAllowed || "Email addresses are not allowed as usernames"
532
+ });
533
+ }
534
+ if (sanitized.length < (opts.minLength ?? 3)) {
535
+ errors.push({
536
+ field: "username",
537
+ code: "TOO_SHORT",
538
+ message: opts.messages?.tooShort || `Username must be at least ${opts.minLength} characters`
539
+ });
540
+ }
541
+ if (sanitized.length > (opts.maxLength ?? 30)) {
542
+ errors.push({
543
+ field: "username",
544
+ code: "TOO_LONG",
545
+ message: opts.messages?.tooLong || `Username must not exceed ${opts.maxLength} characters`
546
+ });
547
+ }
548
+ const charPattern = opts.allowedChars ?? DEFAULT_USERNAME_PATTERN;
549
+ if (!charPattern.test(sanitized)) {
550
+ errors.push({
551
+ field: "username",
552
+ code: "INVALID_CHARACTERS",
553
+ message: opts.messages?.invalidChars || "Username can only contain letters, numbers, underscores, hyphens, and periods"
554
+ });
555
+ }
556
+ if (/^[_\-.]|[_\-.]$/.test(sanitized)) {
557
+ errors.push({
558
+ field: "username",
559
+ code: "INVALID_START_END",
560
+ message: opts.messages?.invalidStartEnd || "Username cannot start or end with special characters"
561
+ });
562
+ }
563
+ if (/[_\-.]{2,}/.test(sanitized)) {
564
+ errors.push({
565
+ field: "username",
566
+ code: "CONSECUTIVE_SPECIAL",
567
+ message: opts.messages?.consecutiveSpecial || "Username cannot have consecutive special characters"
568
+ });
569
+ }
570
+ const allReserved = /* @__PURE__ */ new Set([...RESERVED_USERNAMES, ...opts.reserved ?? []]);
571
+ const isReserved = allReserved.has(sanitized);
572
+ if (isReserved) {
573
+ errors.push({
574
+ field: "username",
575
+ code: "RESERVED",
576
+ message: opts.messages?.reserved || "This username is reserved"
577
+ });
578
+ }
579
+ return {
580
+ isValid: errors.length === 0,
581
+ errors,
582
+ sanitized,
583
+ isReserved
584
+ };
585
+ }
586
+ function isValidUsername(username) {
587
+ if (!username || typeof username !== "string") return false;
588
+ const trimmed = username.trim().toLowerCase();
589
+ return trimmed.length >= 3 && trimmed.length <= 30 && DEFAULT_USERNAME_PATTERN.test(trimmed) && !/^[_\-.]|[_\-.]$/.test(trimmed) && !/[_\-.]{2,}/.test(trimmed) && !RESERVED_USERNAMES.has(trimmed);
590
+ }
591
+ function isReservedUsername(username, additionalReserved = []) {
592
+ if (!username || typeof username !== "string") return false;
593
+ const normalized = username.trim().toLowerCase();
594
+ const allReserved = /* @__PURE__ */ new Set([...RESERVED_USERNAMES, ...additionalReserved]);
595
+ return allReserved.has(normalized);
596
+ }
597
+ function sanitizeUsername(username) {
598
+ if (!username || typeof username !== "string") return "";
599
+ return username.trim().toLowerCase().replace(/\s+/g, "_").replace(/[^a-z0-9_\-.]/g, "").replace(/[_\-.]+/g, (match) => match[0]).replace(/^[_\-.]|[_\-.]$/g, "");
600
+ }
601
+ function suggestUsernames(base, count = 5) {
602
+ if (!base || typeof base !== "string") return [];
603
+ let baseUsername = base.includes("@") ? base.split("@")[0] : base;
604
+ baseUsername = sanitizeUsername(baseUsername);
605
+ if (!baseUsername) return [];
606
+ const suggestions = [baseUsername];
607
+ for (let i = 1; suggestions.length < count; i++) {
608
+ suggestions.push(`${baseUsername}${i}`);
609
+ }
610
+ const randomSuffix = Math.floor(Math.random() * 1e3);
611
+ suggestions.push(`${baseUsername}_${randomSuffix}`);
612
+ return suggestions.slice(0, count);
613
+ }
614
+
615
+ // src/validators/url.ts
616
+ var DEFAULT_OPTIONS5 = {
617
+ requireHttps: false,
618
+ allowLocalhost: true,
619
+ allowIpAddress: true,
620
+ allowedProtocols: ["http:", "https:"]
621
+ };
622
+ var IPV4_REGEX = /^(\d{1,3}\.){3}\d{1,3}$/;
623
+ var IPV6_REGEX = /^\[[\da-fA-F:]+\]$/;
624
+ function validateUrl(url, options = {}) {
625
+ const opts = { ...DEFAULT_OPTIONS5, ...options };
626
+ const errors = [];
627
+ if (url && url.length > 4096) {
628
+ errors.push({
629
+ field: "url",
630
+ code: "INVALID_LENGTH",
631
+ message: "URL is too long"
632
+ });
633
+ return createEmptyResult2(errors);
634
+ }
635
+ const trimmed = url?.trim() ?? "";
636
+ if (!trimmed) {
637
+ errors.push({
638
+ field: "url",
639
+ code: "REQUIRED",
640
+ message: opts.messages?.required || "URL is required"
641
+ });
642
+ return createEmptyResult2(errors);
643
+ }
644
+ let parsed;
645
+ try {
646
+ parsed = new URL(trimmed);
647
+ } catch {
648
+ errors.push({
649
+ field: "url",
650
+ code: "INVALID_FORMAT",
651
+ message: opts.messages?.invalidFormat || "URL format is invalid"
652
+ });
653
+ return createEmptyResult2(errors);
654
+ }
655
+ const allowedProtocols = opts.allowedProtocols || ["http:", "https:"];
656
+ if (!allowedProtocols.includes(parsed.protocol)) {
657
+ errors.push({
658
+ field: "url",
659
+ code: "INVALID_PROTOCOL",
660
+ message: opts.messages?.invalidProtocol || `Protocol must be one of: ${allowedProtocols.join(", ")}`
661
+ });
662
+ }
663
+ if (opts.requireHttps && parsed.protocol !== "https:") {
664
+ errors.push({
665
+ field: "url",
666
+ code: "HTTPS_REQUIRED",
667
+ message: "HTTPS is required"
668
+ });
669
+ }
670
+ const isLocalhost = parsed.hostname === "localhost" || parsed.hostname === "127.0.0.1" || parsed.hostname === "::1" || parsed.hostname === "[::1]";
671
+ if (!opts.allowLocalhost && isLocalhost) {
672
+ errors.push({
673
+ field: "url",
674
+ code: "LOCALHOST_NOT_ALLOWED",
675
+ message: "Localhost URLs are not allowed"
676
+ });
677
+ }
678
+ const isIpAddress = IPV4_REGEX.test(parsed.hostname) || IPV6_REGEX.test(parsed.hostname);
679
+ if (!opts.allowIpAddress && isIpAddress && !isLocalhost) {
680
+ errors.push({
681
+ field: "url",
682
+ code: "IP_ADDRESS_NOT_ALLOWED",
683
+ message: "IP address URLs are not allowed"
684
+ });
685
+ }
686
+ if (opts.blockedDomains?.includes(parsed.hostname.toLowerCase())) {
687
+ errors.push({
688
+ field: "url",
689
+ code: "BLOCKED_DOMAIN",
690
+ message: opts.messages?.blockedDomain || "This domain is not allowed"
691
+ });
692
+ }
693
+ if (opts.allowedTlds && opts.allowedTlds.length > 0 && !isIpAddress && !isLocalhost) {
694
+ const parts = parsed.hostname.split(".");
695
+ const tld = parts[parts.length - 1]?.toLowerCase();
696
+ if (tld && !opts.allowedTlds.includes(tld)) {
697
+ errors.push({
698
+ field: "url",
699
+ code: "INVALID_TLD",
700
+ message: opts.messages?.invalidTld || `TLD must be one of: ${opts.allowedTlds.join(", ")}`
701
+ });
702
+ }
703
+ }
704
+ return {
705
+ isValid: errors.length === 0,
706
+ errors,
707
+ parsed: {
708
+ protocol: parsed.protocol,
709
+ hostname: parsed.hostname,
710
+ port: parsed.port,
711
+ pathname: parsed.pathname,
712
+ search: parsed.search,
713
+ hash: parsed.hash
714
+ },
715
+ normalized: parsed.href
716
+ };
717
+ }
718
+ function isValidUrl(url) {
719
+ if (!url || typeof url !== "string") return false;
720
+ try {
721
+ const parsed = new URL(url.trim());
722
+ return ["http:", "https:"].includes(parsed.protocol);
723
+ } catch {
724
+ return false;
725
+ }
726
+ }
727
+ function isHttpsUrl(url) {
728
+ if (!url || typeof url !== "string") return false;
729
+ try {
730
+ const parsed = new URL(url.trim());
731
+ return parsed.protocol === "https:";
732
+ } catch {
733
+ return false;
734
+ }
735
+ }
736
+ function getUrlDomain(url) {
737
+ if (!url || typeof url !== "string") return "";
738
+ try {
739
+ const parsed = new URL(url.trim());
740
+ return parsed.hostname;
741
+ } catch {
742
+ return "";
743
+ }
744
+ }
745
+ function normalizeUrl(url) {
746
+ if (!url || typeof url !== "string") return "";
747
+ try {
748
+ const parsed = new URL(url.trim());
749
+ if (parsed.pathname !== "/" && parsed.pathname.endsWith("/")) {
750
+ parsed.pathname = parsed.pathname.slice(0, -1);
751
+ }
752
+ return parsed.href;
753
+ } catch {
754
+ return "";
755
+ }
756
+ }
757
+ function isDataUrl(url) {
758
+ if (!url || typeof url !== "string") return false;
759
+ return url.trim().toLowerCase().startsWith("data:");
760
+ }
761
+ function isDangerousUrl(url) {
762
+ if (!url || typeof url !== "string") return false;
763
+ const dangerous = ["javascript:", "vbscript:", "data:"];
764
+ const trimmed = url.trim().toLowerCase();
765
+ return dangerous.some((scheme) => trimmed.startsWith(scheme));
766
+ }
767
+ function createEmptyResult2(errors) {
768
+ return {
769
+ isValid: false,
770
+ errors,
771
+ normalized: ""
772
+ };
773
+ }
774
+
775
+ // src/validators/creditCard.ts
776
+ var CARD_PATTERNS = {
777
+ visa: /^4[0-9]{12}(?:[0-9]{3})?$/,
778
+ mastercard: /^5[1-5][0-9]{14}$|^2(?:2(?:2[1-9]|[3-9][0-9])|[3-6][0-9][0-9]|7(?:[01][0-9]|20))[0-9]{12}$/,
779
+ amex: /^3[47][0-9]{13}$/,
780
+ discover: /^6(?:011|5[0-9]{2})[0-9]{12}$/,
781
+ diners: /^3(?:0[0-5]|[68][0-9])[0-9]{11}$/,
782
+ jcb: /^(?:2131|1800|35\d{3})\d{11}$/,
783
+ unionpay: /^62[0-9]{14,17}$/,
784
+ maestro: /^(?:5[06789]|6)[0-9]{10,17}$/,
785
+ unknown: /^[0-9]{13,19}$/
786
+ };
787
+ var CARD_PREFIXES = {
788
+ "4": "visa",
789
+ "34": "amex",
790
+ "37": "amex",
791
+ "51": "mastercard",
792
+ "52": "mastercard",
793
+ "53": "mastercard",
794
+ "54": "mastercard",
795
+ "55": "mastercard",
796
+ "22": "mastercard",
797
+ "23": "mastercard",
798
+ "24": "mastercard",
799
+ "25": "mastercard",
800
+ "26": "mastercard",
801
+ "27": "mastercard",
802
+ "6011": "discover",
803
+ "65": "discover",
804
+ "30": "diners",
805
+ "36": "diners",
806
+ "38": "diners",
807
+ "35": "jcb",
808
+ "62": "unionpay"
809
+ };
810
+ var DEFAULT_OPTIONS6 = {
811
+ checkLuhn: true
812
+ };
813
+ function validateCreditCard(cardNumber, options = {}) {
814
+ const opts = { ...DEFAULT_OPTIONS6, ...options };
815
+ const errors = [];
816
+ if (cardNumber && cardNumber.length > 50) {
817
+ errors.push({
818
+ field: "creditCard",
819
+ code: "INVALID_LENGTH",
820
+ message: "Credit card number is too long"
821
+ });
822
+ return createEmptyResult3(errors);
823
+ }
824
+ const cleaned = cleanCardNumber(cardNumber);
825
+ if (!cardNumber || !cleaned) {
826
+ errors.push({
827
+ field: "creditCard",
828
+ code: "REQUIRED",
829
+ message: opts.messages?.required || "Credit card number is required"
830
+ });
831
+ return createEmptyResult3(errors);
832
+ }
833
+ if (!/^[0-9]{13,19}$/.test(cleaned)) {
834
+ errors.push({
835
+ field: "creditCard",
836
+ code: "INVALID_FORMAT",
837
+ message: opts.messages?.invalidFormat || "Credit card number format is invalid"
838
+ });
839
+ return createEmptyResult3(errors, cleaned);
840
+ }
841
+ const cardType = detectCardType(cleaned);
842
+ if (opts.checkLuhn && !luhnCheck(cleaned)) {
843
+ errors.push({
844
+ field: "creditCard",
845
+ code: "INVALID_LUHN",
846
+ message: opts.messages?.invalidLuhn || "Credit card number is invalid"
847
+ });
848
+ }
849
+ if (opts.allowedTypes && opts.allowedTypes.length > 0) {
850
+ if (!opts.allowedTypes.includes(cardType)) {
851
+ errors.push({
852
+ field: "creditCard",
853
+ code: "TYPE_NOT_ALLOWED",
854
+ message: opts.messages?.typeNotAllowed || `Card type '${cardType}' is not allowed`
855
+ });
856
+ }
857
+ }
858
+ return {
859
+ isValid: errors.length === 0,
860
+ errors,
861
+ cardType,
862
+ masked: maskCardNumber(cleaned),
863
+ cleaned
864
+ };
865
+ }
866
+ function isValidCreditCard(cardNumber) {
867
+ if (!cardNumber || typeof cardNumber !== "string") return false;
868
+ const cleaned = cleanCardNumber(cardNumber);
869
+ return /^[0-9]{13,19}$/.test(cleaned) && luhnCheck(cleaned);
870
+ }
871
+ function detectCardType(cardNumber) {
872
+ const cleaned = cleanCardNumber(cardNumber);
873
+ for (const [type, pattern] of Object.entries(CARD_PATTERNS)) {
874
+ if (type !== "unknown" && pattern.test(cleaned)) {
875
+ return type;
876
+ }
877
+ }
878
+ return "unknown";
879
+ }
880
+ function getCardTypeFromPrefix(cardNumber) {
881
+ const cleaned = cleanCardNumber(cardNumber);
882
+ if (cleaned.length < 1) return null;
883
+ const prefixes = Object.keys(CARD_PREFIXES).sort((a, b) => b.length - a.length);
884
+ for (const prefix of prefixes) {
885
+ if (cleaned.startsWith(prefix)) {
886
+ return CARD_PREFIXES[prefix];
887
+ }
888
+ }
889
+ return null;
890
+ }
891
+ function luhnCheck(cardNumber) {
892
+ const cleaned = cleanCardNumber(cardNumber);
893
+ if (!cleaned || cleaned.length < 13) return false;
894
+ let sum = 0;
895
+ let isEven = false;
896
+ for (let i = cleaned.length - 1; i >= 0; i--) {
897
+ let digit = parseInt(cleaned[i], 10);
898
+ if (isEven) {
899
+ digit *= 2;
900
+ if (digit > 9) {
901
+ digit -= 9;
902
+ }
903
+ }
904
+ sum += digit;
905
+ isEven = !isEven;
906
+ }
907
+ return sum % 10 === 0;
908
+ }
909
+ function maskCardNumber(cardNumber, maskChar = "*") {
910
+ const cleaned = cleanCardNumber(cardNumber);
911
+ if (cleaned.length < 4) return cleaned;
912
+ const lastFour = cleaned.slice(-4);
913
+ const masked = maskChar.repeat(cleaned.length - 4);
914
+ return masked + lastFour;
915
+ }
916
+ function formatCardNumber(cardNumber) {
917
+ const cleaned = cleanCardNumber(cardNumber);
918
+ const type = detectCardType(cleaned);
919
+ if (type === "amex") {
920
+ return cleaned.replace(/(\d{4})(\d{6})(\d{5})/, "$1 $2 $3").trim();
921
+ }
922
+ return cleaned.replace(/(\d{4})(?=\d)/g, "$1 ").trim();
923
+ }
924
+ function isValidCVV(cvv, cardType) {
925
+ if (!cvv || typeof cvv !== "string") return false;
926
+ const cleaned = cvv.replace(/\D/g, "");
927
+ if (cardType === "amex") {
928
+ return /^\d{4}$/.test(cleaned);
929
+ }
930
+ return /^\d{3,4}$/.test(cleaned);
931
+ }
932
+ function isValidExpiry(month, year) {
933
+ const m = typeof month === "string" ? parseInt(month, 10) : month;
934
+ let y = typeof year === "string" ? parseInt(year, 10) : year;
935
+ if (y < 100) {
936
+ y += 2e3;
937
+ }
938
+ if (isNaN(m) || m < 1 || m > 12) return false;
939
+ if (isNaN(y) || y < 2e3) return false;
940
+ const now = /* @__PURE__ */ new Date();
941
+ const expiry = new Date(y, m, 0);
942
+ return expiry >= now;
943
+ }
944
+ function cleanCardNumber(cardNumber) {
945
+ if (!cardNumber || typeof cardNumber !== "string") return "";
946
+ return cardNumber.replace(/\D/g, "");
947
+ }
948
+ function createEmptyResult3(errors, cleaned = "") {
949
+ return {
950
+ isValid: false,
951
+ errors,
952
+ cardType: null,
953
+ masked: "",
954
+ cleaned
955
+ };
956
+ }
957
+
958
+ // src/validators/ip.ts
959
+ var IPV4_REGEX2 = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
960
+ var IPV4_CIDR_REGEX = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\/([0-9]|[12][0-9]|3[0-2])$/;
961
+ var IPV6_REGEX2 = /^(?:(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|(?:[0-9a-fA-F]{1,4}:){1,7}:|(?:[0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|(?:[0-9a-fA-F]{1,4}:){1,5}(?::[0-9a-fA-F]{1,4}){1,2}|(?:[0-9a-fA-F]{1,4}:){1,4}(?::[0-9a-fA-F]{1,4}){1,3}|(?:[0-9a-fA-F]{1,4}:){1,3}(?::[0-9a-fA-F]{1,4}){1,4}|(?:[0-9a-fA-F]{1,4}:){1,2}(?::[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:(?::[0-9a-fA-F]{1,4}){1,6}|:(?::[0-9a-fA-F]{1,4}){1,7}|::(?:[fF]{4}:)?(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)|(?:[0-9a-fA-F]{1,4}:){1,4}:(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))$/;
962
+ var IPV6_CIDR_REGEX = /^(.+)\/([0-9]|[1-9][0-9]|1[01][0-9]|12[0-8])$/;
963
+ var PRIVATE_IPV4_RANGES = [
964
+ /^10\./,
965
+ // 10.0.0.0/8
966
+ /^172\.(1[6-9]|2[0-9]|3[01])\./,
967
+ // 172.16.0.0/12
968
+ /^192\.168\./,
969
+ // 192.168.0.0/16
970
+ /^169\.254\./
971
+ // Link-local 169.254.0.0/16
972
+ ];
973
+ var DEFAULT_OPTIONS7 = {
974
+ version: "both",
975
+ allowCidr: false,
976
+ allowPrivate: true
977
+ };
978
+ function validateIp(ip, options = {}) {
979
+ const opts = { ...DEFAULT_OPTIONS7, ...options };
980
+ const errors = [];
981
+ const trimmed = ip?.trim() ?? "";
982
+ if (!ip || !trimmed) {
983
+ errors.push({
984
+ field: "ip",
985
+ code: "REQUIRED",
986
+ message: opts.messages?.required || "IP address is required"
987
+ });
988
+ return createEmptyResult4(errors);
989
+ }
990
+ let ipPart = trimmed;
991
+ let cidr = null;
992
+ if (trimmed.includes("/")) {
993
+ if (!opts.allowCidr) {
994
+ errors.push({
995
+ field: "ip",
996
+ code: "CIDR_NOT_ALLOWED",
997
+ message: "CIDR notation is not allowed"
998
+ });
999
+ }
1000
+ const parts = trimmed.split("/");
1001
+ ipPart = parts[0];
1002
+ cidr = parseInt(parts[1], 10);
1003
+ }
1004
+ let version = null;
1005
+ let isValid = false;
1006
+ if (IPV4_REGEX2.test(ipPart)) {
1007
+ version = 4;
1008
+ isValid = true;
1009
+ if (cidr !== null && (cidr < 0 || cidr > 32)) {
1010
+ errors.push({
1011
+ field: "ip",
1012
+ code: "INVALID_CIDR",
1013
+ message: "CIDR prefix must be 0-32 for IPv4"
1014
+ });
1015
+ }
1016
+ } else if (IPV6_REGEX2.test(ipPart)) {
1017
+ version = 6;
1018
+ isValid = true;
1019
+ if (cidr !== null && (cidr < 0 || cidr > 128)) {
1020
+ errors.push({
1021
+ field: "ip",
1022
+ code: "INVALID_CIDR",
1023
+ message: "CIDR prefix must be 0-128 for IPv6"
1024
+ });
1025
+ }
1026
+ }
1027
+ if (!isValid) {
1028
+ errors.push({
1029
+ field: "ip",
1030
+ code: "INVALID_FORMAT",
1031
+ message: opts.messages?.invalidFormat || "IP address format is invalid"
1032
+ });
1033
+ return createEmptyResult4(errors, trimmed);
1034
+ }
1035
+ if (opts.version !== "both" && version !== opts.version) {
1036
+ errors.push({
1037
+ field: "ip",
1038
+ code: "VERSION_NOT_ALLOWED",
1039
+ message: opts.messages?.versionNotAllowed || `IPv${version} is not allowed, expected IPv${opts.version}`
1040
+ });
1041
+ }
1042
+ const isPrivate = isPrivateIp(ipPart, version ?? void 0);
1043
+ const isLoopback = isLoopbackIp(ipPart, version ?? void 0);
1044
+ if (!opts.allowPrivate && (isPrivate || isLoopback)) {
1045
+ errors.push({
1046
+ field: "ip",
1047
+ code: "PRIVATE_NOT_ALLOWED",
1048
+ message: opts.messages?.privateNotAllowed || "Private/local IP addresses are not allowed"
1049
+ });
1050
+ }
1051
+ return {
1052
+ isValid: errors.length === 0,
1053
+ errors,
1054
+ version,
1055
+ isPrivate,
1056
+ isLoopback,
1057
+ cidr,
1058
+ normalized: trimmed.toLowerCase()
1059
+ };
1060
+ }
1061
+ function isValidIPv4(ip) {
1062
+ if (!ip || typeof ip !== "string") return false;
1063
+ return IPV4_REGEX2.test(ip.trim());
1064
+ }
1065
+ function isValidIPv6(ip) {
1066
+ if (!ip || typeof ip !== "string") return false;
1067
+ return IPV6_REGEX2.test(ip.trim());
1068
+ }
1069
+ function isValidIP(ip) {
1070
+ return isValidIPv4(ip) || isValidIPv6(ip);
1071
+ }
1072
+ function isPrivateIp(ip, version) {
1073
+ if (!ip) return false;
1074
+ const v = version ?? (isValidIPv4(ip) ? 4 : isValidIPv6(ip) ? 6 : void 0);
1075
+ if (v === 4) {
1076
+ return PRIVATE_IPV4_RANGES.some((range) => range.test(ip));
1077
+ }
1078
+ if (v === 6) {
1079
+ const lower = ip.toLowerCase();
1080
+ return lower.startsWith("fc") || lower.startsWith("fd") || // fe80::/10 (Link-local)
1081
+ lower.startsWith("fe80");
1082
+ }
1083
+ return false;
1084
+ }
1085
+ function isLoopbackIp(ip, version) {
1086
+ if (!ip) return false;
1087
+ const v = version ?? (isValidIPv4(ip) ? 4 : isValidIPv6(ip) ? 6 : void 0);
1088
+ if (v === 4) {
1089
+ return ip.startsWith("127.");
1090
+ }
1091
+ if (v === 6) {
1092
+ const lower = ip.toLowerCase();
1093
+ return lower === "::1" || lower === "0000:0000:0000:0000:0000:0000:0000:0001";
1094
+ }
1095
+ return false;
1096
+ }
1097
+ function getIpVersion(ip) {
1098
+ if (isValidIPv4(ip)) return 4;
1099
+ if (isValidIPv6(ip)) return 6;
1100
+ return null;
1101
+ }
1102
+ function isValidCidr(cidr) {
1103
+ if (!cidr || typeof cidr !== "string") return false;
1104
+ if (IPV4_CIDR_REGEX.test(cidr)) return true;
1105
+ const match = cidr.match(IPV6_CIDR_REGEX);
1106
+ if (match) {
1107
+ return IPV6_REGEX2.test(match[1]) && parseInt(match[2], 10) <= 128;
1108
+ }
1109
+ return false;
1110
+ }
1111
+ function createEmptyResult4(errors, normalized = "") {
1112
+ return {
1113
+ isValid: false,
1114
+ errors,
1115
+ version: null,
1116
+ isPrivate: false,
1117
+ isLoopback: false,
1118
+ cidr: null,
1119
+ normalized
1120
+ };
1121
+ }
1122
+
1123
+ // src/validators/financial.ts
1124
+ function validateIBAN(iban, options = {}) {
1125
+ if (!iban || typeof iban !== "string") {
1126
+ return { isValid: false, errors: [{ field: "iban", code: "REQUIRED", message: "IBAN is required" }] };
1127
+ }
1128
+ const cleanIban = iban.replace(/[\s]+/g, "").toUpperCase();
1129
+ const errors = [];
1130
+ if (iban.length > 50) {
1131
+ errors.push({
1132
+ field: "iban",
1133
+ code: "INVALID_LENGTH",
1134
+ message: "IBAN is too long"
1135
+ });
1136
+ return { isValid: false, errors };
1137
+ }
1138
+ if (!/^[A-Z]{2}[0-9]{2}[A-Z0-9]{1,30}$/.test(cleanIban)) {
1139
+ errors.push({
1140
+ field: "iban",
1141
+ code: "INVALID_FORMAT",
1142
+ message: options.messages?.invalidFormat || "Invalid IBAN format"
1143
+ });
1144
+ return { isValid: false, errors };
1145
+ }
1146
+ const lengths = {
1147
+ AL: 28,
1148
+ AD: 24,
1149
+ AT: 20,
1150
+ AZ: 28,
1151
+ BH: 22,
1152
+ BE: 16,
1153
+ BA: 20,
1154
+ BR: 29,
1155
+ BG: 22,
1156
+ CR: 21,
1157
+ HR: 21,
1158
+ CY: 28,
1159
+ CZ: 24,
1160
+ DK: 18,
1161
+ DO: 28,
1162
+ EE: 20,
1163
+ FO: 18,
1164
+ FI: 18,
1165
+ FR: 27,
1166
+ GE: 22,
1167
+ DE: 22,
1168
+ GI: 23,
1169
+ GR: 27,
1170
+ GL: 18,
1171
+ GT: 28,
1172
+ HU: 28,
1173
+ IS: 26,
1174
+ IE: 22,
1175
+ IL: 23,
1176
+ IT: 27,
1177
+ JO: 30,
1178
+ KZ: 20,
1179
+ KW: 30,
1180
+ LV: 21,
1181
+ LB: 28,
1182
+ LI: 21,
1183
+ LT: 20,
1184
+ LU: 20,
1185
+ MK: 19,
1186
+ MT: 31,
1187
+ MR: 27,
1188
+ MU: 30,
1189
+ MC: 27,
1190
+ MD: 24,
1191
+ ME: 22,
1192
+ NL: 18,
1193
+ NO: 15,
1194
+ PK: 24,
1195
+ PS: 29,
1196
+ PL: 28,
1197
+ PT: 25,
1198
+ QA: 29,
1199
+ RO: 24,
1200
+ LC: 32,
1201
+ SM: 27,
1202
+ ST: 25,
1203
+ SA: 24,
1204
+ RS: 22,
1205
+ SC: 31,
1206
+ SK: 24,
1207
+ SI: 19,
1208
+ ES: 24,
1209
+ SE: 24,
1210
+ CH: 21,
1211
+ TN: 24,
1212
+ TR: 26,
1213
+ AE: 23,
1214
+ GB: 22,
1215
+ VG: 24
1216
+ };
1217
+ const countryCode = cleanIban.substring(0, 2);
1218
+ const expectedLength = lengths[countryCode];
1219
+ if (expectedLength && cleanIban.length !== expectedLength) {
1220
+ errors.push({
1221
+ field: "iban",
1222
+ code: "INVALID_LENGTH",
1223
+ message: options.messages?.invalidLength || `IBAN for ${countryCode} must be ${expectedLength} characters`
1224
+ });
1225
+ return { isValid: false, errors };
1226
+ }
1227
+ const rearranged = cleanIban.substring(4) + cleanIban.substring(0, 4);
1228
+ const numeric = rearranged.replace(/[A-Z]/g, (char) => (char.charCodeAt(0) - 55).toString());
1229
+ if (mod97(numeric) !== 1) {
1230
+ errors.push({
1231
+ field: "iban",
1232
+ code: "INVALID_CHECKSUM",
1233
+ message: options.messages?.invalidChecksum || "Invalid IBAN checksum"
1234
+ });
1235
+ return { isValid: false, errors };
1236
+ }
1237
+ return { isValid: true, errors: [] };
1238
+ }
1239
+ function validateVAT(vat, options = {}) {
1240
+ if (!vat || typeof vat !== "string") {
1241
+ return { isValid: false, errors: [{ field: "vat", code: "REQUIRED", message: "VAT number is required" }] };
1242
+ }
1243
+ const cleanVat = vat.replace(/[\s.-]+/g, "").toUpperCase();
1244
+ const errors = [];
1245
+ if (vat.length > 50) {
1246
+ errors.push({
1247
+ field: "vat",
1248
+ code: "INVALID_LENGTH",
1249
+ message: "VAT number is too long"
1250
+ });
1251
+ return { isValid: false, errors };
1252
+ }
1253
+ if (!/^[A-Z]{2}[A-Z0-9]{2,13}$/.test(cleanVat)) {
1254
+ errors.push({
1255
+ field: "vat",
1256
+ code: "INVALID_FORMAT",
1257
+ message: "Invalid VAT number format"
1258
+ });
1259
+ return { isValid: false, errors };
1260
+ }
1261
+ if (options.countryCode) {
1262
+ const providedCode = options.countryCode.toUpperCase();
1263
+ if (!cleanVat.startsWith(providedCode)) {
1264
+ errors.push({
1265
+ field: "vat",
1266
+ code: "INVALID_COUNTRY",
1267
+ message: `VAT number must start with ${providedCode}`
1268
+ });
1269
+ return { isValid: false, errors };
1270
+ }
1271
+ }
1272
+ return { isValid: true, errors: [] };
1273
+ }
1274
+ function mod97(numericString) {
1275
+ let remainder = 0;
1276
+ for (let i = 0; i < numericString.length; i += 7) {
1277
+ const chunk = (remainder ? remainder.toString() : "") + numericString.substring(i, i + 7);
1278
+ remainder = parseInt(chunk, 10) % 97;
1279
+ }
1280
+ return remainder;
1281
+ }
1282
+
1283
+ export { detectCardType, detectScript, formatCardNumber, formatName, formatPhone, getCardTypeFromPrefix, getEmailDomain, getIpVersion, getUrlDomain, isDangerousUrl, isDataUrl, isDisposableEmail, isHttpsUrl, isLoopbackIp, isPrivateIp, isReservedUsername, isValidCVV, isValidCidr, isValidCreditCard, isValidEmail, isValidExpiry, isValidIP, isValidIPv4, isValidIPv6, isValidName, isValidPhone, isValidUrl, isValidUsername, luhnCheck, maskCardNumber, normalizeEmail, normalizeUrl, parsePhone, sanitizeName, sanitizeUsername, splitName, suggestUsernames, validateCreditCard, validateEmail, validateIBAN, validateIp, validateName, validatePhone, validateUrl, validateUsername, validateVAT };
1284
+ //# sourceMappingURL=chunk-5WP7DWCG.js.map
1285
+ //# sourceMappingURL=chunk-5WP7DWCG.js.map