@lacspace/email-validate 1.0.4 → 1.0.5

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/dist/index.cjs CHANGED
@@ -115,7 +115,7 @@ var COMMON_DOMAINS = [
115
115
  "msn.com"
116
116
  ];
117
117
  var LOCAL_RE = /^[A-Za-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[A-Za-z0-9!#$%&'*+/=?^_`{|}~-]+)*$/;
118
- var DOMAIN_RE = /^(?:[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?\.)+[A-Za-z]{2,}$/;
118
+ var DOMAIN_RE = /^(?:[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?\.)+(?:xn--[A-Za-z0-9-]{2,}|[A-Za-z]{2,})$/;
119
119
  function splitEmail(email) {
120
120
  const at = email.lastIndexOf("@");
121
121
  if (at <= 0 || at === email.length - 1) return null;
@@ -129,7 +129,7 @@ function isValidEmail(email) {
129
129
  if (!parts) return false;
130
130
  const { local, domain } = parts;
131
131
  if (local.length > 64 || local.length === 0) return false;
132
- if (domain.length > 255) return false;
132
+ if (domain.length > 253) return false;
133
133
  if (!LOCAL_RE.test(local)) return false;
134
134
  if (!DOMAIN_RE.test(domain)) return false;
135
135
  return true;
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * @lacspace/email-validate\n * Smart email validation — beyond a regex.\n *\n * Syntax + disposable/temp-mail detection, role-address detection, free-provider\n * flags, Gmail normalization and \"did you mean?\" typo suggestions.\n *\n * Zero dependencies · isomorphic · fully typed.\n */\n\n/** Common free/consumer email providers. */\nexport const FREE_PROVIDERS: Set<string> = new Set([\n \"gmail.com\", \"googlemail.com\", \"yahoo.com\", \"yahoo.co.in\", \"yahoo.co.uk\",\n \"hotmail.com\", \"outlook.com\", \"live.com\", \"msn.com\", \"icloud.com\", \"me.com\",\n \"aol.com\", \"protonmail.com\", \"proton.me\", \"zoho.com\", \"gmx.com\", \"gmx.net\",\n \"yandex.com\", \"yandex.ru\", \"mail.com\", \"mail.ru\", \"rediffmail.com\",\n]);\n\n/** Well-known disposable / temporary-mail domains. Extend via {@link validateEmail} options. */\nexport const DISPOSABLE_DOMAINS: Set<string> = new Set([\n \"mailinator.com\", \"guerrillamail.com\", \"guerrillamail.info\", \"grr.la\",\n \"10minutemail.com\", \"10minutemail.net\", \"tempmail.com\", \"temp-mail.org\",\n \"throwawaymail.com\", \"yopmail.com\", \"yopmail.fr\", \"getnada.com\", \"nada.email\",\n \"trashmail.com\", \"trashmail.de\", \"sharklasers.com\", \"spam4.me\", \"dispostable.com\",\n \"maildrop.cc\", \"mailnesia.com\", \"mohmal.com\", \"fakeinbox.com\", \"tempinbox.com\",\n \"mintemail.com\", \"mytemp.email\", \"emailondeck.com\", \"burnermail.io\", \"33mail.com\",\n \"spamgourmet.com\", \"tempr.email\", \"discard.email\", \"mailcatch.com\", \"inboxbear.com\",\n \"tempmailo.com\", \"temp-mail.io\", \"1secmail.com\", \"moakt.com\", \"tmpmail.org\",\n \"guerrillamailblock.com\", \"pokemail.net\", \"spambog.com\", \"einrot.com\",\n]);\n\n/** Local-parts that indicate a shared/role mailbox rather than a person. */\nexport const ROLE_LOCALS: Set<string> = new Set([\n \"admin\", \"administrator\", \"info\", \"support\", \"sales\", \"contact\", \"help\",\n \"no-reply\", \"noreply\", \"donotreply\", \"do-not-reply\", \"postmaster\", \"webmaster\",\n \"abuse\", \"billing\", \"hr\", \"jobs\", \"careers\", \"marketing\", \"team\", \"hello\",\n \"office\", \"enquiry\", \"enquiries\", \"service\", \"root\", \"security\", \"privacy\",\n]);\n\n/** Popular domains used for typo suggestions. */\nconst COMMON_DOMAINS = [\n \"gmail.com\", \"googlemail.com\", \"yahoo.com\", \"hotmail.com\", \"outlook.com\",\n \"live.com\", \"icloud.com\", \"aol.com\", \"protonmail.com\", \"zoho.com\",\n \"yandex.com\", \"mail.com\", \"msn.com\",\n];\n\n// Pragmatic RFC-5321-ish address grammar (no comments/quoted-strings; good for real-world use).\nconst LOCAL_RE = /^[A-Za-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[A-Za-z0-9!#$%&'*+/=?^_`{|}~-]+)*$/;\nconst DOMAIN_RE = /^(?:[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?\\.)+[A-Za-z]{2,}$/;\n\nexport interface ValidationOptions {\n /** Extra disposable domains to treat as disposable. */\n extraDisposable?: string[];\n /** Turn off \"did you mean?\" suggestions. */\n suggestions?: boolean;\n}\n\nexport interface ValidationResult {\n valid: boolean;\n /** Lowercased address; for Gmail, dots and +tags stripped. `null` if invalid. */\n normalized: string | null;\n local: string | null;\n domain: string | null;\n disposable: boolean;\n /** A shared/role mailbox (info@, admin@, …). */\n role: boolean;\n /** A free consumer provider (gmail, yahoo, …). */\n free: boolean;\n /** A likely-correct address if a typo was detected, else `null`. */\n suggestion: string | null;\n /** Why it failed, when `valid` is false. */\n reason?: string;\n}\n\nfunction splitEmail(email: string): { local: string; domain: string } | null {\n const at = email.lastIndexOf(\"@\");\n if (at <= 0 || at === email.length - 1) return null;\n return { local: email.slice(0, at), domain: email.slice(at + 1) };\n}\n\n/** Fast boolean syntax + structure check. */\nexport function isValidEmail(email: string): boolean {\n if (typeof email !== \"string\") return false;\n const trimmed = email.trim();\n if (trimmed.length > 254) return false;\n const parts = splitEmail(trimmed);\n if (!parts) return false;\n const { local, domain } = parts;\n if (local.length > 64 || local.length === 0) return false;\n if (domain.length > 255) return false;\n if (!LOCAL_RE.test(local)) return false;\n if (!DOMAIN_RE.test(domain)) return false;\n return true;\n}\n\nexport function isDisposable(domain: string, extra?: string[]): boolean {\n const d = domain.toLowerCase();\n if (DISPOSABLE_DOMAINS.has(d)) return true;\n return extra ? extra.map((x) => x.toLowerCase()).includes(d) : false;\n}\n\nexport function isRoleAddress(local: string): boolean {\n return ROLE_LOCALS.has(local.toLowerCase());\n}\n\nexport function isFreeProvider(domain: string): boolean {\n return FREE_PROVIDERS.has(domain.toLowerCase());\n}\n\n/** Canonical form: lowercased; Gmail addresses get dots + `+tags` removed. */\nexport function normalizeEmail(email: string): string {\n const parts = splitEmail(email.trim());\n if (!parts) return email.trim().toLowerCase();\n let { local } = parts;\n const domain = parts.domain.toLowerCase();\n local = local.toLowerCase();\n if (domain === \"gmail.com\" || domain === \"googlemail.com\") {\n local = local.split(\"+\")[0]!.replace(/\\./g, \"\");\n return `${local}@gmail.com`;\n }\n const plus = local.indexOf(\"+\");\n if (plus > 0) local = local.slice(0, plus);\n return `${local}@${domain}`;\n}\n\nfunction levenshtein(a: string, b: string): number {\n const m = a.length;\n const n = b.length;\n const dp = Array.from({ length: m + 1 }, () => new Array<number>(n + 1).fill(0));\n for (let i = 0; i <= m; i++) dp[i]![0] = i;\n for (let j = 0; j <= n; j++) dp[0]![j] = j;\n for (let i = 1; i <= m; i++) {\n for (let j = 1; j <= n; j++) {\n const cost = a[i - 1] === b[j - 1] ? 0 : 1;\n dp[i]![j] = Math.min(dp[i - 1]![j]! + 1, dp[i]![j - 1]! + 1, dp[i - 1]![j - 1]! + cost);\n }\n }\n return dp[m]![n]!;\n}\n\n/** Suggest a corrected address when the domain looks like a typo of a common one. */\nexport function suggestEmail(email: string): string | null {\n const parts = splitEmail(email.trim().toLowerCase());\n if (!parts) return null;\n const { local, domain } = parts;\n if (COMMON_DOMAINS.includes(domain)) return null;\n let best: string | null = null;\n let bestDist = Infinity;\n for (const candidate of COMMON_DOMAINS) {\n const dist = levenshtein(domain, candidate);\n if (dist < bestDist) {\n bestDist = dist;\n best = candidate;\n }\n }\n // Only suggest for close typos, and never \"correct\" a longer legit domain.\n if (best && bestDist > 0 && bestDist <= 2 && Math.abs(domain.length - best.length) <= 2) {\n return `${local}@${best}`;\n }\n return null;\n}\n\n/** Full analysis of an email address. */\nexport function validateEmail(email: string, opts: ValidationOptions = {}): ValidationResult {\n const raw = typeof email === \"string\" ? email.trim() : \"\";\n const parts = splitEmail(raw);\n\n if (!isValidEmail(raw) || !parts) {\n return {\n valid: false,\n normalized: null,\n local: null,\n domain: null,\n disposable: false,\n role: false,\n free: false,\n suggestion: opts.suggestions === false ? null : suggestEmail(raw),\n reason: \"invalid syntax\",\n };\n }\n\n const local = parts.local;\n const domain = parts.domain.toLowerCase();\n return {\n valid: true,\n normalized: normalizeEmail(raw),\n local,\n domain,\n disposable: isDisposable(domain, opts.extraDisposable),\n role: isRoleAddress(local),\n free: isFreeProvider(domain),\n suggestion: opts.suggestions === false ? null : suggestEmail(raw),\n };\n}\n"]}
1
+ {"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * @lacspace/email-validate\n * Smart email validation — beyond a regex.\n *\n * Syntax + disposable/temp-mail detection, role-address detection, free-provider\n * flags, Gmail normalization and \"did you mean?\" typo suggestions.\n *\n * Zero dependencies · isomorphic · fully typed.\n */\n\n/** Common free/consumer email providers. */\nexport const FREE_PROVIDERS: Set<string> = new Set([\n \"gmail.com\", \"googlemail.com\", \"yahoo.com\", \"yahoo.co.in\", \"yahoo.co.uk\",\n \"hotmail.com\", \"outlook.com\", \"live.com\", \"msn.com\", \"icloud.com\", \"me.com\",\n \"aol.com\", \"protonmail.com\", \"proton.me\", \"zoho.com\", \"gmx.com\", \"gmx.net\",\n \"yandex.com\", \"yandex.ru\", \"mail.com\", \"mail.ru\", \"rediffmail.com\",\n]);\n\n/** Well-known disposable / temporary-mail domains. Extend via {@link validateEmail} options. */\nexport const DISPOSABLE_DOMAINS: Set<string> = new Set([\n \"mailinator.com\", \"guerrillamail.com\", \"guerrillamail.info\", \"grr.la\",\n \"10minutemail.com\", \"10minutemail.net\", \"tempmail.com\", \"temp-mail.org\",\n \"throwawaymail.com\", \"yopmail.com\", \"yopmail.fr\", \"getnada.com\", \"nada.email\",\n \"trashmail.com\", \"trashmail.de\", \"sharklasers.com\", \"spam4.me\", \"dispostable.com\",\n \"maildrop.cc\", \"mailnesia.com\", \"mohmal.com\", \"fakeinbox.com\", \"tempinbox.com\",\n \"mintemail.com\", \"mytemp.email\", \"emailondeck.com\", \"burnermail.io\", \"33mail.com\",\n \"spamgourmet.com\", \"tempr.email\", \"discard.email\", \"mailcatch.com\", \"inboxbear.com\",\n \"tempmailo.com\", \"temp-mail.io\", \"1secmail.com\", \"moakt.com\", \"tmpmail.org\",\n \"guerrillamailblock.com\", \"pokemail.net\", \"spambog.com\", \"einrot.com\",\n]);\n\n/** Local-parts that indicate a shared/role mailbox rather than a person. */\nexport const ROLE_LOCALS: Set<string> = new Set([\n \"admin\", \"administrator\", \"info\", \"support\", \"sales\", \"contact\", \"help\",\n \"no-reply\", \"noreply\", \"donotreply\", \"do-not-reply\", \"postmaster\", \"webmaster\",\n \"abuse\", \"billing\", \"hr\", \"jobs\", \"careers\", \"marketing\", \"team\", \"hello\",\n \"office\", \"enquiry\", \"enquiries\", \"service\", \"root\", \"security\", \"privacy\",\n]);\n\n/** Popular domains used for typo suggestions. */\nconst COMMON_DOMAINS = [\n \"gmail.com\", \"googlemail.com\", \"yahoo.com\", \"hotmail.com\", \"outlook.com\",\n \"live.com\", \"icloud.com\", \"aol.com\", \"protonmail.com\", \"zoho.com\",\n \"yandex.com\", \"mail.com\", \"msn.com\",\n];\n\n// Pragmatic RFC-5321-ish address grammar (no comments/quoted-strings; good for real-world use).\nconst LOCAL_RE = /^[A-Za-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[A-Za-z0-9!#$%&'*+/=?^_`{|}~-]+)*$/;\n// TLD is either an alphabetic label (com, org, …) or an IDN/punycode A-label\n// (e.g. `xn--p1ai`). The `.` separator stays outside every character class so\n// there is no ambiguous overlap that could cause catastrophic backtracking.\nconst DOMAIN_RE = /^(?:[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?\\.)+(?:xn--[A-Za-z0-9-]{2,}|[A-Za-z]{2,})$/;\n\nexport interface ValidationOptions {\n /** Extra disposable domains to treat as disposable. */\n extraDisposable?: string[];\n /** Turn off \"did you mean?\" suggestions. */\n suggestions?: boolean;\n}\n\nexport interface ValidationResult {\n valid: boolean;\n /** Lowercased address; for Gmail, dots and +tags stripped. `null` if invalid. */\n normalized: string | null;\n local: string | null;\n domain: string | null;\n disposable: boolean;\n /** A shared/role mailbox (info@, admin@, …). */\n role: boolean;\n /** A free consumer provider (gmail, yahoo, …). */\n free: boolean;\n /** A likely-correct address if a typo was detected, else `null`. */\n suggestion: string | null;\n /** Why it failed, when `valid` is false. */\n reason?: string;\n}\n\nfunction splitEmail(email: string): { local: string; domain: string } | null {\n const at = email.lastIndexOf(\"@\");\n if (at <= 0 || at === email.length - 1) return null;\n return { local: email.slice(0, at), domain: email.slice(at + 1) };\n}\n\n/** Fast boolean syntax + structure check. */\nexport function isValidEmail(email: string): boolean {\n if (typeof email !== \"string\") return false;\n const trimmed = email.trim();\n if (trimmed.length > 254) return false;\n const parts = splitEmail(trimmed);\n if (!parts) return false;\n const { local, domain } = parts;\n if (local.length > 64 || local.length === 0) return false;\n if (domain.length > 253) return false;\n if (!LOCAL_RE.test(local)) return false;\n if (!DOMAIN_RE.test(domain)) return false;\n return true;\n}\n\nexport function isDisposable(domain: string, extra?: string[]): boolean {\n const d = domain.toLowerCase();\n if (DISPOSABLE_DOMAINS.has(d)) return true;\n return extra ? extra.map((x) => x.toLowerCase()).includes(d) : false;\n}\n\nexport function isRoleAddress(local: string): boolean {\n return ROLE_LOCALS.has(local.toLowerCase());\n}\n\nexport function isFreeProvider(domain: string): boolean {\n return FREE_PROVIDERS.has(domain.toLowerCase());\n}\n\n/** Canonical form: lowercased; Gmail addresses get dots + `+tags` removed. */\nexport function normalizeEmail(email: string): string {\n const parts = splitEmail(email.trim());\n if (!parts) return email.trim().toLowerCase();\n let { local } = parts;\n const domain = parts.domain.toLowerCase();\n local = local.toLowerCase();\n if (domain === \"gmail.com\" || domain === \"googlemail.com\") {\n local = local.split(\"+\")[0]!.replace(/\\./g, \"\");\n return `${local}@gmail.com`;\n }\n const plus = local.indexOf(\"+\");\n if (plus > 0) local = local.slice(0, plus);\n return `${local}@${domain}`;\n}\n\nfunction levenshtein(a: string, b: string): number {\n const m = a.length;\n const n = b.length;\n const dp = Array.from({ length: m + 1 }, () => new Array<number>(n + 1).fill(0));\n for (let i = 0; i <= m; i++) dp[i]![0] = i;\n for (let j = 0; j <= n; j++) dp[0]![j] = j;\n for (let i = 1; i <= m; i++) {\n for (let j = 1; j <= n; j++) {\n const cost = a[i - 1] === b[j - 1] ? 0 : 1;\n dp[i]![j] = Math.min(dp[i - 1]![j]! + 1, dp[i]![j - 1]! + 1, dp[i - 1]![j - 1]! + cost);\n }\n }\n return dp[m]![n]!;\n}\n\n/** Suggest a corrected address when the domain looks like a typo of a common one. */\nexport function suggestEmail(email: string): string | null {\n const parts = splitEmail(email.trim().toLowerCase());\n if (!parts) return null;\n const { local, domain } = parts;\n if (COMMON_DOMAINS.includes(domain)) return null;\n let best: string | null = null;\n let bestDist = Infinity;\n for (const candidate of COMMON_DOMAINS) {\n const dist = levenshtein(domain, candidate);\n if (dist < bestDist) {\n bestDist = dist;\n best = candidate;\n }\n }\n // Only suggest for close typos, and never \"correct\" a longer legit domain.\n if (best && bestDist > 0 && bestDist <= 2 && Math.abs(domain.length - best.length) <= 2) {\n return `${local}@${best}`;\n }\n return null;\n}\n\n/** Full analysis of an email address. */\nexport function validateEmail(email: string, opts: ValidationOptions = {}): ValidationResult {\n const raw = typeof email === \"string\" ? email.trim() : \"\";\n const parts = splitEmail(raw);\n\n if (!isValidEmail(raw) || !parts) {\n return {\n valid: false,\n normalized: null,\n local: null,\n domain: null,\n disposable: false,\n role: false,\n free: false,\n suggestion: opts.suggestions === false ? null : suggestEmail(raw),\n reason: \"invalid syntax\",\n };\n }\n\n const local = parts.local;\n const domain = parts.domain.toLowerCase();\n return {\n valid: true,\n normalized: normalizeEmail(raw),\n local,\n domain,\n disposable: isDisposable(domain, opts.extraDisposable),\n role: isRoleAddress(local),\n free: isFreeProvider(domain),\n suggestion: opts.suggestions === false ? null : suggestEmail(raw),\n };\n}\n"]}
package/dist/index.js CHANGED
@@ -113,7 +113,7 @@ var COMMON_DOMAINS = [
113
113
  "msn.com"
114
114
  ];
115
115
  var LOCAL_RE = /^[A-Za-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[A-Za-z0-9!#$%&'*+/=?^_`{|}~-]+)*$/;
116
- var DOMAIN_RE = /^(?:[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?\.)+[A-Za-z]{2,}$/;
116
+ var DOMAIN_RE = /^(?:[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?\.)+(?:xn--[A-Za-z0-9-]{2,}|[A-Za-z]{2,})$/;
117
117
  function splitEmail(email) {
118
118
  const at = email.lastIndexOf("@");
119
119
  if (at <= 0 || at === email.length - 1) return null;
@@ -127,7 +127,7 @@ function isValidEmail(email) {
127
127
  if (!parts) return false;
128
128
  const { local, domain } = parts;
129
129
  if (local.length > 64 || local.length === 0) return false;
130
- if (domain.length > 255) return false;
130
+ if (domain.length > 253) return false;
131
131
  if (!LOCAL_RE.test(local)) return false;
132
132
  if (!DOMAIN_RE.test(domain)) return false;
133
133
  return true;
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * @lacspace/email-validate\n * Smart email validation — beyond a regex.\n *\n * Syntax + disposable/temp-mail detection, role-address detection, free-provider\n * flags, Gmail normalization and \"did you mean?\" typo suggestions.\n *\n * Zero dependencies · isomorphic · fully typed.\n */\n\n/** Common free/consumer email providers. */\nexport const FREE_PROVIDERS: Set<string> = new Set([\n \"gmail.com\", \"googlemail.com\", \"yahoo.com\", \"yahoo.co.in\", \"yahoo.co.uk\",\n \"hotmail.com\", \"outlook.com\", \"live.com\", \"msn.com\", \"icloud.com\", \"me.com\",\n \"aol.com\", \"protonmail.com\", \"proton.me\", \"zoho.com\", \"gmx.com\", \"gmx.net\",\n \"yandex.com\", \"yandex.ru\", \"mail.com\", \"mail.ru\", \"rediffmail.com\",\n]);\n\n/** Well-known disposable / temporary-mail domains. Extend via {@link validateEmail} options. */\nexport const DISPOSABLE_DOMAINS: Set<string> = new Set([\n \"mailinator.com\", \"guerrillamail.com\", \"guerrillamail.info\", \"grr.la\",\n \"10minutemail.com\", \"10minutemail.net\", \"tempmail.com\", \"temp-mail.org\",\n \"throwawaymail.com\", \"yopmail.com\", \"yopmail.fr\", \"getnada.com\", \"nada.email\",\n \"trashmail.com\", \"trashmail.de\", \"sharklasers.com\", \"spam4.me\", \"dispostable.com\",\n \"maildrop.cc\", \"mailnesia.com\", \"mohmal.com\", \"fakeinbox.com\", \"tempinbox.com\",\n \"mintemail.com\", \"mytemp.email\", \"emailondeck.com\", \"burnermail.io\", \"33mail.com\",\n \"spamgourmet.com\", \"tempr.email\", \"discard.email\", \"mailcatch.com\", \"inboxbear.com\",\n \"tempmailo.com\", \"temp-mail.io\", \"1secmail.com\", \"moakt.com\", \"tmpmail.org\",\n \"guerrillamailblock.com\", \"pokemail.net\", \"spambog.com\", \"einrot.com\",\n]);\n\n/** Local-parts that indicate a shared/role mailbox rather than a person. */\nexport const ROLE_LOCALS: Set<string> = new Set([\n \"admin\", \"administrator\", \"info\", \"support\", \"sales\", \"contact\", \"help\",\n \"no-reply\", \"noreply\", \"donotreply\", \"do-not-reply\", \"postmaster\", \"webmaster\",\n \"abuse\", \"billing\", \"hr\", \"jobs\", \"careers\", \"marketing\", \"team\", \"hello\",\n \"office\", \"enquiry\", \"enquiries\", \"service\", \"root\", \"security\", \"privacy\",\n]);\n\n/** Popular domains used for typo suggestions. */\nconst COMMON_DOMAINS = [\n \"gmail.com\", \"googlemail.com\", \"yahoo.com\", \"hotmail.com\", \"outlook.com\",\n \"live.com\", \"icloud.com\", \"aol.com\", \"protonmail.com\", \"zoho.com\",\n \"yandex.com\", \"mail.com\", \"msn.com\",\n];\n\n// Pragmatic RFC-5321-ish address grammar (no comments/quoted-strings; good for real-world use).\nconst LOCAL_RE = /^[A-Za-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[A-Za-z0-9!#$%&'*+/=?^_`{|}~-]+)*$/;\nconst DOMAIN_RE = /^(?:[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?\\.)+[A-Za-z]{2,}$/;\n\nexport interface ValidationOptions {\n /** Extra disposable domains to treat as disposable. */\n extraDisposable?: string[];\n /** Turn off \"did you mean?\" suggestions. */\n suggestions?: boolean;\n}\n\nexport interface ValidationResult {\n valid: boolean;\n /** Lowercased address; for Gmail, dots and +tags stripped. `null` if invalid. */\n normalized: string | null;\n local: string | null;\n domain: string | null;\n disposable: boolean;\n /** A shared/role mailbox (info@, admin@, …). */\n role: boolean;\n /** A free consumer provider (gmail, yahoo, …). */\n free: boolean;\n /** A likely-correct address if a typo was detected, else `null`. */\n suggestion: string | null;\n /** Why it failed, when `valid` is false. */\n reason?: string;\n}\n\nfunction splitEmail(email: string): { local: string; domain: string } | null {\n const at = email.lastIndexOf(\"@\");\n if (at <= 0 || at === email.length - 1) return null;\n return { local: email.slice(0, at), domain: email.slice(at + 1) };\n}\n\n/** Fast boolean syntax + structure check. */\nexport function isValidEmail(email: string): boolean {\n if (typeof email !== \"string\") return false;\n const trimmed = email.trim();\n if (trimmed.length > 254) return false;\n const parts = splitEmail(trimmed);\n if (!parts) return false;\n const { local, domain } = parts;\n if (local.length > 64 || local.length === 0) return false;\n if (domain.length > 255) return false;\n if (!LOCAL_RE.test(local)) return false;\n if (!DOMAIN_RE.test(domain)) return false;\n return true;\n}\n\nexport function isDisposable(domain: string, extra?: string[]): boolean {\n const d = domain.toLowerCase();\n if (DISPOSABLE_DOMAINS.has(d)) return true;\n return extra ? extra.map((x) => x.toLowerCase()).includes(d) : false;\n}\n\nexport function isRoleAddress(local: string): boolean {\n return ROLE_LOCALS.has(local.toLowerCase());\n}\n\nexport function isFreeProvider(domain: string): boolean {\n return FREE_PROVIDERS.has(domain.toLowerCase());\n}\n\n/** Canonical form: lowercased; Gmail addresses get dots + `+tags` removed. */\nexport function normalizeEmail(email: string): string {\n const parts = splitEmail(email.trim());\n if (!parts) return email.trim().toLowerCase();\n let { local } = parts;\n const domain = parts.domain.toLowerCase();\n local = local.toLowerCase();\n if (domain === \"gmail.com\" || domain === \"googlemail.com\") {\n local = local.split(\"+\")[0]!.replace(/\\./g, \"\");\n return `${local}@gmail.com`;\n }\n const plus = local.indexOf(\"+\");\n if (plus > 0) local = local.slice(0, plus);\n return `${local}@${domain}`;\n}\n\nfunction levenshtein(a: string, b: string): number {\n const m = a.length;\n const n = b.length;\n const dp = Array.from({ length: m + 1 }, () => new Array<number>(n + 1).fill(0));\n for (let i = 0; i <= m; i++) dp[i]![0] = i;\n for (let j = 0; j <= n; j++) dp[0]![j] = j;\n for (let i = 1; i <= m; i++) {\n for (let j = 1; j <= n; j++) {\n const cost = a[i - 1] === b[j - 1] ? 0 : 1;\n dp[i]![j] = Math.min(dp[i - 1]![j]! + 1, dp[i]![j - 1]! + 1, dp[i - 1]![j - 1]! + cost);\n }\n }\n return dp[m]![n]!;\n}\n\n/** Suggest a corrected address when the domain looks like a typo of a common one. */\nexport function suggestEmail(email: string): string | null {\n const parts = splitEmail(email.trim().toLowerCase());\n if (!parts) return null;\n const { local, domain } = parts;\n if (COMMON_DOMAINS.includes(domain)) return null;\n let best: string | null = null;\n let bestDist = Infinity;\n for (const candidate of COMMON_DOMAINS) {\n const dist = levenshtein(domain, candidate);\n if (dist < bestDist) {\n bestDist = dist;\n best = candidate;\n }\n }\n // Only suggest for close typos, and never \"correct\" a longer legit domain.\n if (best && bestDist > 0 && bestDist <= 2 && Math.abs(domain.length - best.length) <= 2) {\n return `${local}@${best}`;\n }\n return null;\n}\n\n/** Full analysis of an email address. */\nexport function validateEmail(email: string, opts: ValidationOptions = {}): ValidationResult {\n const raw = typeof email === \"string\" ? email.trim() : \"\";\n const parts = splitEmail(raw);\n\n if (!isValidEmail(raw) || !parts) {\n return {\n valid: false,\n normalized: null,\n local: null,\n domain: null,\n disposable: false,\n role: false,\n free: false,\n suggestion: opts.suggestions === false ? null : suggestEmail(raw),\n reason: \"invalid syntax\",\n };\n }\n\n const local = parts.local;\n const domain = parts.domain.toLowerCase();\n return {\n valid: true,\n normalized: normalizeEmail(raw),\n local,\n domain,\n disposable: isDisposable(domain, opts.extraDisposable),\n role: isRoleAddress(local),\n free: isFreeProvider(domain),\n suggestion: opts.suggestions === false ? null : suggestEmail(raw),\n };\n}\n"]}
1
+ {"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * @lacspace/email-validate\n * Smart email validation — beyond a regex.\n *\n * Syntax + disposable/temp-mail detection, role-address detection, free-provider\n * flags, Gmail normalization and \"did you mean?\" typo suggestions.\n *\n * Zero dependencies · isomorphic · fully typed.\n */\n\n/** Common free/consumer email providers. */\nexport const FREE_PROVIDERS: Set<string> = new Set([\n \"gmail.com\", \"googlemail.com\", \"yahoo.com\", \"yahoo.co.in\", \"yahoo.co.uk\",\n \"hotmail.com\", \"outlook.com\", \"live.com\", \"msn.com\", \"icloud.com\", \"me.com\",\n \"aol.com\", \"protonmail.com\", \"proton.me\", \"zoho.com\", \"gmx.com\", \"gmx.net\",\n \"yandex.com\", \"yandex.ru\", \"mail.com\", \"mail.ru\", \"rediffmail.com\",\n]);\n\n/** Well-known disposable / temporary-mail domains. Extend via {@link validateEmail} options. */\nexport const DISPOSABLE_DOMAINS: Set<string> = new Set([\n \"mailinator.com\", \"guerrillamail.com\", \"guerrillamail.info\", \"grr.la\",\n \"10minutemail.com\", \"10minutemail.net\", \"tempmail.com\", \"temp-mail.org\",\n \"throwawaymail.com\", \"yopmail.com\", \"yopmail.fr\", \"getnada.com\", \"nada.email\",\n \"trashmail.com\", \"trashmail.de\", \"sharklasers.com\", \"spam4.me\", \"dispostable.com\",\n \"maildrop.cc\", \"mailnesia.com\", \"mohmal.com\", \"fakeinbox.com\", \"tempinbox.com\",\n \"mintemail.com\", \"mytemp.email\", \"emailondeck.com\", \"burnermail.io\", \"33mail.com\",\n \"spamgourmet.com\", \"tempr.email\", \"discard.email\", \"mailcatch.com\", \"inboxbear.com\",\n \"tempmailo.com\", \"temp-mail.io\", \"1secmail.com\", \"moakt.com\", \"tmpmail.org\",\n \"guerrillamailblock.com\", \"pokemail.net\", \"spambog.com\", \"einrot.com\",\n]);\n\n/** Local-parts that indicate a shared/role mailbox rather than a person. */\nexport const ROLE_LOCALS: Set<string> = new Set([\n \"admin\", \"administrator\", \"info\", \"support\", \"sales\", \"contact\", \"help\",\n \"no-reply\", \"noreply\", \"donotreply\", \"do-not-reply\", \"postmaster\", \"webmaster\",\n \"abuse\", \"billing\", \"hr\", \"jobs\", \"careers\", \"marketing\", \"team\", \"hello\",\n \"office\", \"enquiry\", \"enquiries\", \"service\", \"root\", \"security\", \"privacy\",\n]);\n\n/** Popular domains used for typo suggestions. */\nconst COMMON_DOMAINS = [\n \"gmail.com\", \"googlemail.com\", \"yahoo.com\", \"hotmail.com\", \"outlook.com\",\n \"live.com\", \"icloud.com\", \"aol.com\", \"protonmail.com\", \"zoho.com\",\n \"yandex.com\", \"mail.com\", \"msn.com\",\n];\n\n// Pragmatic RFC-5321-ish address grammar (no comments/quoted-strings; good for real-world use).\nconst LOCAL_RE = /^[A-Za-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[A-Za-z0-9!#$%&'*+/=?^_`{|}~-]+)*$/;\n// TLD is either an alphabetic label (com, org, …) or an IDN/punycode A-label\n// (e.g. `xn--p1ai`). The `.` separator stays outside every character class so\n// there is no ambiguous overlap that could cause catastrophic backtracking.\nconst DOMAIN_RE = /^(?:[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?\\.)+(?:xn--[A-Za-z0-9-]{2,}|[A-Za-z]{2,})$/;\n\nexport interface ValidationOptions {\n /** Extra disposable domains to treat as disposable. */\n extraDisposable?: string[];\n /** Turn off \"did you mean?\" suggestions. */\n suggestions?: boolean;\n}\n\nexport interface ValidationResult {\n valid: boolean;\n /** Lowercased address; for Gmail, dots and +tags stripped. `null` if invalid. */\n normalized: string | null;\n local: string | null;\n domain: string | null;\n disposable: boolean;\n /** A shared/role mailbox (info@, admin@, …). */\n role: boolean;\n /** A free consumer provider (gmail, yahoo, …). */\n free: boolean;\n /** A likely-correct address if a typo was detected, else `null`. */\n suggestion: string | null;\n /** Why it failed, when `valid` is false. */\n reason?: string;\n}\n\nfunction splitEmail(email: string): { local: string; domain: string } | null {\n const at = email.lastIndexOf(\"@\");\n if (at <= 0 || at === email.length - 1) return null;\n return { local: email.slice(0, at), domain: email.slice(at + 1) };\n}\n\n/** Fast boolean syntax + structure check. */\nexport function isValidEmail(email: string): boolean {\n if (typeof email !== \"string\") return false;\n const trimmed = email.trim();\n if (trimmed.length > 254) return false;\n const parts = splitEmail(trimmed);\n if (!parts) return false;\n const { local, domain } = parts;\n if (local.length > 64 || local.length === 0) return false;\n if (domain.length > 253) return false;\n if (!LOCAL_RE.test(local)) return false;\n if (!DOMAIN_RE.test(domain)) return false;\n return true;\n}\n\nexport function isDisposable(domain: string, extra?: string[]): boolean {\n const d = domain.toLowerCase();\n if (DISPOSABLE_DOMAINS.has(d)) return true;\n return extra ? extra.map((x) => x.toLowerCase()).includes(d) : false;\n}\n\nexport function isRoleAddress(local: string): boolean {\n return ROLE_LOCALS.has(local.toLowerCase());\n}\n\nexport function isFreeProvider(domain: string): boolean {\n return FREE_PROVIDERS.has(domain.toLowerCase());\n}\n\n/** Canonical form: lowercased; Gmail addresses get dots + `+tags` removed. */\nexport function normalizeEmail(email: string): string {\n const parts = splitEmail(email.trim());\n if (!parts) return email.trim().toLowerCase();\n let { local } = parts;\n const domain = parts.domain.toLowerCase();\n local = local.toLowerCase();\n if (domain === \"gmail.com\" || domain === \"googlemail.com\") {\n local = local.split(\"+\")[0]!.replace(/\\./g, \"\");\n return `${local}@gmail.com`;\n }\n const plus = local.indexOf(\"+\");\n if (plus > 0) local = local.slice(0, plus);\n return `${local}@${domain}`;\n}\n\nfunction levenshtein(a: string, b: string): number {\n const m = a.length;\n const n = b.length;\n const dp = Array.from({ length: m + 1 }, () => new Array<number>(n + 1).fill(0));\n for (let i = 0; i <= m; i++) dp[i]![0] = i;\n for (let j = 0; j <= n; j++) dp[0]![j] = j;\n for (let i = 1; i <= m; i++) {\n for (let j = 1; j <= n; j++) {\n const cost = a[i - 1] === b[j - 1] ? 0 : 1;\n dp[i]![j] = Math.min(dp[i - 1]![j]! + 1, dp[i]![j - 1]! + 1, dp[i - 1]![j - 1]! + cost);\n }\n }\n return dp[m]![n]!;\n}\n\n/** Suggest a corrected address when the domain looks like a typo of a common one. */\nexport function suggestEmail(email: string): string | null {\n const parts = splitEmail(email.trim().toLowerCase());\n if (!parts) return null;\n const { local, domain } = parts;\n if (COMMON_DOMAINS.includes(domain)) return null;\n let best: string | null = null;\n let bestDist = Infinity;\n for (const candidate of COMMON_DOMAINS) {\n const dist = levenshtein(domain, candidate);\n if (dist < bestDist) {\n bestDist = dist;\n best = candidate;\n }\n }\n // Only suggest for close typos, and never \"correct\" a longer legit domain.\n if (best && bestDist > 0 && bestDist <= 2 && Math.abs(domain.length - best.length) <= 2) {\n return `${local}@${best}`;\n }\n return null;\n}\n\n/** Full analysis of an email address. */\nexport function validateEmail(email: string, opts: ValidationOptions = {}): ValidationResult {\n const raw = typeof email === \"string\" ? email.trim() : \"\";\n const parts = splitEmail(raw);\n\n if (!isValidEmail(raw) || !parts) {\n return {\n valid: false,\n normalized: null,\n local: null,\n domain: null,\n disposable: false,\n role: false,\n free: false,\n suggestion: opts.suggestions === false ? null : suggestEmail(raw),\n reason: \"invalid syntax\",\n };\n }\n\n const local = parts.local;\n const domain = parts.domain.toLowerCase();\n return {\n valid: true,\n normalized: normalizeEmail(raw),\n local,\n domain,\n disposable: isDisposable(domain, opts.extraDisposable),\n role: isRoleAddress(local),\n free: isFreeProvider(domain),\n suggestion: opts.suggestions === false ? null : suggestEmail(raw),\n };\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lacspace/email-validate",
3
- "version": "1.0.4",
3
+ "version": "1.0.5",
4
4
  "description": "Smart email validation — syntax, disposable/temp-mail detection, role & free-provider flags, Gmail normalization and 'did you mean?' typo suggestions. Zero-dependency, isomorphic.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",