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