@lacspace/email-validate 1.0.4 → 1.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -96,18 +96,24 @@ You can also read/extend the exported sets: `DISPOSABLE_DOMAINS`, `FREE_PROVIDER
96
96
 
97
97
  ## Licensing
98
98
 
99
- This package is **free** under the **[Lacspace Free Licence](https://lacspace.com/licenses/lacspace-free-1.0)** — MIT-equivalent freedoms. Use it in personal and commercial projects at no cost; just keep the notice.
99
+ This package is **free** under the **[Lacspace Free Licence](https://lacspace.com/licenses/lacspace-free-1.0)** — permissive freedoms. Use it in personal and commercial projects at no cost; just keep the notice.
100
100
 
101
101
  Not every Lacspace package is free. We also offer **Commercial** (paid), **Client-specific**, and **Private** (proprietary) packages under separate terms. See the full **[Lacspace Licence Centre](https://lacspace.com/licenses)**.
102
102
 
103
+ <!-- LACSPACE-DEV-PLATFORM -->
104
+
103
105
  ---
104
106
 
105
- <div align="center">
107
+ ## The Lacspace Developer Platform
106
108
 
107
- **Part of the Lacspace ecosystem — 35 zero-dependency, isomorphic TypeScript packages.**
109
+ `@lacspace/email-validate` is part of **63 zero-dependency, isomorphic TypeScript packages** — one standard library for the modern web. Explore the ecosystem:
108
110
 
109
- [All packages ↗](https://lacspace.com/packages) · [npm org ↗](https://www.npmjs.com/org/lacspace) · [Licence Centre ↗](https://lacspace.com/licenses) · [GitHub ↗](https://github.com/lacspace/npm-packages)
111
+ - 📦 **This package, documented** https://developer.lacspace.com/packages/email-validate
112
+ - 🗂️ **All 63 packages** — https://developer.lacspace.com/packages
113
+ - 🧭 **Developer handbook** — guides & runnable recipes — https://developer.lacspace.com/handbook
114
+ - 🧪 **Live playground** — run any package in your browser — https://developer.lacspace.com/playground
115
+ - 🖥️ **Finished app templates** — https://templates.lacspace.com
116
+ - 🚀 **Scaffold a full app** — `npm create lacspace-app@latest`
110
117
 
111
- </div>
118
+ Free under the **[Lacspace Free Licence](https://lacspace.com/licenses/lacspace-free-1.0)** — a permissive, free-to-use licence.
112
119
 
113
- <div align="center"><sub>Built with care by <a href="https://lacspace.com">Lacspace</a> · Lacspace Free Licence · <a href="https://github.com/lacspace/npm-packages">source</a></sub></div>
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":";AAWO,IAAM,cAAA,uBAAkC,GAAA,CAAI;AAAA,EACjD,WAAA;AAAA,EAAa,gBAAA;AAAA,EAAkB,WAAA;AAAA,EAAa,aAAA;AAAA,EAAe,aAAA;AAAA,EAC3D,aAAA;AAAA,EAAe,aAAA;AAAA,EAAe,UAAA;AAAA,EAAY,SAAA;AAAA,EAAW,YAAA;AAAA,EAAc,QAAA;AAAA,EACnE,SAAA;AAAA,EAAW,gBAAA;AAAA,EAAkB,WAAA;AAAA,EAAa,UAAA;AAAA,EAAY,SAAA;AAAA,EAAW,SAAA;AAAA,EACjE,YAAA;AAAA,EAAc,WAAA;AAAA,EAAa,UAAA;AAAA,EAAY,SAAA;AAAA,EAAW;AACpD,CAAC;AAGM,IAAM,kBAAA,uBAAsC,GAAA,CAAI;AAAA,EACrD,gBAAA;AAAA,EAAkB,mBAAA;AAAA,EAAqB,oBAAA;AAAA,EAAsB,QAAA;AAAA,EAC7D,kBAAA;AAAA,EAAoB,kBAAA;AAAA,EAAoB,cAAA;AAAA,EAAgB,eAAA;AAAA,EACxD,mBAAA;AAAA,EAAqB,aAAA;AAAA,EAAe,YAAA;AAAA,EAAc,aAAA;AAAA,EAAe,YAAA;AAAA,EACjE,eAAA;AAAA,EAAiB,cAAA;AAAA,EAAgB,iBAAA;AAAA,EAAmB,UAAA;AAAA,EAAY,iBAAA;AAAA,EAChE,aAAA;AAAA,EAAe,eAAA;AAAA,EAAiB,YAAA;AAAA,EAAc,eAAA;AAAA,EAAiB,eAAA;AAAA,EAC/D,eAAA;AAAA,EAAiB,cAAA;AAAA,EAAgB,iBAAA;AAAA,EAAmB,eAAA;AAAA,EAAiB,YAAA;AAAA,EACrE,iBAAA;AAAA,EAAmB,aAAA;AAAA,EAAe,eAAA;AAAA,EAAiB,eAAA;AAAA,EAAiB,eAAA;AAAA,EACpE,eAAA;AAAA,EAAiB,cAAA;AAAA,EAAgB,cAAA;AAAA,EAAgB,WAAA;AAAA,EAAa,aAAA;AAAA,EAC9D,wBAAA;AAAA,EAA0B,cAAA;AAAA,EAAgB,aAAA;AAAA,EAAe;AAC3D,CAAC;AAGM,IAAM,WAAA,uBAA+B,GAAA,CAAI;AAAA,EAC9C,OAAA;AAAA,EAAS,eAAA;AAAA,EAAiB,MAAA;AAAA,EAAQ,SAAA;AAAA,EAAW,OAAA;AAAA,EAAS,SAAA;AAAA,EAAW,MAAA;AAAA,EACjE,UAAA;AAAA,EAAY,SAAA;AAAA,EAAW,YAAA;AAAA,EAAc,cAAA;AAAA,EAAgB,YAAA;AAAA,EAAc,WAAA;AAAA,EACnE,OAAA;AAAA,EAAS,SAAA;AAAA,EAAW,IAAA;AAAA,EAAM,MAAA;AAAA,EAAQ,SAAA;AAAA,EAAW,WAAA;AAAA,EAAa,MAAA;AAAA,EAAQ,OAAA;AAAA,EAClE,QAAA;AAAA,EAAU,SAAA;AAAA,EAAW,WAAA;AAAA,EAAa,SAAA;AAAA,EAAW,MAAA;AAAA,EAAQ,UAAA;AAAA,EAAY;AACnE,CAAC;AAGD,IAAM,cAAA,GAAiB;AAAA,EACrB,WAAA;AAAA,EAAa,gBAAA;AAAA,EAAkB,WAAA;AAAA,EAAa,aAAA;AAAA,EAAe,aAAA;AAAA,EAC3D,UAAA;AAAA,EAAY,YAAA;AAAA,EAAc,SAAA;AAAA,EAAW,gBAAA;AAAA,EAAkB,UAAA;AAAA,EACvD,YAAA;AAAA,EAAc,UAAA;AAAA,EAAY;AAC5B,CAAA;AAGA,IAAM,QAAA,GAAW,yEAAA;AAIjB,IAAM,SAAA,GAAY,6FAAA;AA0BlB,SAAS,WAAW,KAAA,EAAyD;AAC3E,EAAA,MAAM,EAAA,GAAK,KAAA,CAAM,WAAA,CAAY,GAAG,CAAA;AAChC,EAAA,IAAI,MAAM,CAAA,IAAK,EAAA,KAAO,KAAA,CAAM,MAAA,GAAS,GAAG,OAAO,IAAA;AAC/C,EAAA,OAAO,EAAE,KAAA,EAAO,KAAA,CAAM,KAAA,CAAM,CAAA,EAAG,EAAE,CAAA,EAAG,MAAA,EAAQ,KAAA,CAAM,KAAA,CAAM,EAAA,GAAK,CAAC,CAAA,EAAE;AAClE;AAGO,SAAS,aAAa,KAAA,EAAwB;AACnD,EAAA,IAAI,OAAO,KAAA,KAAU,QAAA,EAAU,OAAO,KAAA;AACtC,EAAA,MAAM,OAAA,GAAU,MAAM,IAAA,EAAK;AAC3B,EAAA,IAAI,OAAA,CAAQ,MAAA,GAAS,GAAA,EAAK,OAAO,KAAA;AACjC,EAAA,MAAM,KAAA,GAAQ,WAAW,OAAO,CAAA;AAChC,EAAA,IAAI,CAAC,OAAO,OAAO,KAAA;AACnB,EAAA,MAAM,EAAE,KAAA,EAAO,MAAA,EAAO,GAAI,KAAA;AAC1B,EAAA,IAAI,MAAM,MAAA,GAAS,EAAA,IAAM,KAAA,CAAM,MAAA,KAAW,GAAG,OAAO,KAAA;AACpD,EAAA,IAAI,MAAA,CAAO,MAAA,GAAS,GAAA,EAAK,OAAO,KAAA;AAChC,EAAA,IAAI,CAAC,QAAA,CAAS,IAAA,CAAK,KAAK,GAAG,OAAO,KAAA;AAClC,EAAA,IAAI,CAAC,SAAA,CAAU,IAAA,CAAK,MAAM,GAAG,OAAO,KAAA;AACpC,EAAA,OAAO,IAAA;AACT;AAEO,SAAS,YAAA,CAAa,QAAgB,KAAA,EAA2B;AACtE,EAAA,MAAM,CAAA,GAAI,OAAO,WAAA,EAAY;AAC7B,EAAA,IAAI,kBAAA,CAAmB,GAAA,CAAI,CAAC,CAAA,EAAG,OAAO,IAAA;AACtC,EAAA,OAAO,KAAA,GAAQ,KAAA,CAAM,GAAA,CAAI,CAAC,CAAA,KAAM,CAAA,CAAE,WAAA,EAAa,CAAA,CAAE,QAAA,CAAS,CAAC,CAAA,GAAI,KAAA;AACjE;AAEO,SAAS,cAAc,KAAA,EAAwB;AACpD,EAAA,OAAO,WAAA,CAAY,GAAA,CAAI,KAAA,CAAM,WAAA,EAAa,CAAA;AAC5C;AAEO,SAAS,eAAe,MAAA,EAAyB;AACtD,EAAA,OAAO,cAAA,CAAe,GAAA,CAAI,MAAA,CAAO,WAAA,EAAa,CAAA;AAChD;AAGO,SAAS,eAAe,KAAA,EAAuB;AACpD,EAAA,MAAM,KAAA,GAAQ,UAAA,CAAW,KAAA,CAAM,IAAA,EAAM,CAAA;AACrC,EAAA,IAAI,CAAC,KAAA,EAAO,OAAO,KAAA,CAAM,IAAA,GAAO,WAAA,EAAY;AAC5C,EAAA,IAAI,EAAE,OAAM,GAAI,KAAA;AAChB,EAAA,MAAM,MAAA,GAAS,KAAA,CAAM,MAAA,CAAO,WAAA,EAAY;AACxC,EAAA,KAAA,GAAQ,MAAM,WAAA,EAAY;AAC1B,EAAA,IAAI,MAAA,KAAW,WAAA,IAAe,MAAA,KAAW,gBAAA,EAAkB;AACzD,IAAA,KAAA,GAAQ,KAAA,CAAM,MAAM,GAAG,CAAA,CAAE,CAAC,CAAA,CAAG,OAAA,CAAQ,OAAO,EAAE,CAAA;AAC9C,IAAA,OAAO,GAAG,KAAK,CAAA,UAAA,CAAA;AAAA,EACjB;AACA,EAAA,MAAM,IAAA,GAAO,KAAA,CAAM,OAAA,CAAQ,GAAG,CAAA;AAC9B,EAAA,IAAI,OAAO,CAAA,EAAG,KAAA,GAAQ,KAAA,CAAM,KAAA,CAAM,GAAG,IAAI,CAAA;AACzC,EAAA,OAAO,CAAA,EAAG,KAAK,CAAA,CAAA,EAAI,MAAM,CAAA,CAAA;AAC3B;AAEA,SAAS,WAAA,CAAY,GAAW,CAAA,EAAmB;AACjD,EAAA,MAAM,IAAI,CAAA,CAAE,MAAA;AACZ,EAAA,MAAM,IAAI,CAAA,CAAE,MAAA;AACZ,EAAA,MAAM,KAAK,KAAA,CAAM,IAAA,CAAK,EAAE,MAAA,EAAQ,IAAI,CAAA,EAAE,EAAG,MAAM,IAAI,MAAc,CAAA,GAAI,CAAC,CAAA,CAAE,IAAA,CAAK,CAAC,CAAC,CAAA;AAC/E,EAAA,KAAA,IAAS,CAAA,GAAI,GAAG,CAAA,IAAK,CAAA,EAAG,KAAK,EAAA,CAAG,CAAC,CAAA,CAAG,CAAC,CAAA,GAAI,CAAA;AACzC,EAAA,KAAA,IAAS,CAAA,GAAI,GAAG,CAAA,IAAK,CAAA,EAAG,KAAK,EAAA,CAAG,CAAC,CAAA,CAAG,CAAC,CAAA,GAAI,CAAA;AACzC,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,IAAK,CAAA,EAAG,CAAA,EAAA,EAAK;AAC3B,IAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,IAAK,CAAA,EAAG,CAAA,EAAA,EAAK;AAC3B,MAAA,MAAM,IAAA,GAAO,EAAE,CAAA,GAAI,CAAC,MAAM,CAAA,CAAE,CAAA,GAAI,CAAC,CAAA,GAAI,CAAA,GAAI,CAAA;AACzC,MAAA,EAAA,CAAG,CAAC,CAAA,CAAG,CAAC,CAAA,GAAI,IAAA,CAAK,GAAA,CAAI,EAAA,CAAG,CAAA,GAAI,CAAC,CAAA,CAAG,CAAC,CAAA,GAAK,CAAA,EAAG,EAAA,CAAG,CAAC,CAAA,CAAG,CAAA,GAAI,CAAC,CAAA,GAAK,CAAA,EAAG,EAAA,CAAG,CAAA,GAAI,CAAC,CAAA,CAAG,CAAA,GAAI,CAAC,CAAA,GAAK,IAAI,CAAA;AAAA,IACxF;AAAA,EACF;AACA,EAAA,OAAO,EAAA,CAAG,CAAC,CAAA,CAAG,CAAC,CAAA;AACjB;AAGO,SAAS,aAAa,KAAA,EAA8B;AACzD,EAAA,MAAM,QAAQ,UAAA,CAAW,KAAA,CAAM,IAAA,EAAK,CAAE,aAAa,CAAA;AACnD,EAAA,IAAI,CAAC,OAAO,OAAO,IAAA;AACnB,EAAA,MAAM,EAAE,KAAA,EAAO,MAAA,EAAO,GAAI,KAAA;AAC1B,EAAA,IAAI,cAAA,CAAe,QAAA,CAAS,MAAM,CAAA,EAAG,OAAO,IAAA;AAC5C,EAAA,IAAI,IAAA,GAAsB,IAAA;AAC1B,EAAA,IAAI,QAAA,GAAW,QAAA;AACf,EAAA,KAAA,MAAW,aAAa,cAAA,EAAgB;AACtC,IAAA,MAAM,IAAA,GAAO,WAAA,CAAY,MAAA,EAAQ,SAAS,CAAA;AAC1C,IAAA,IAAI,OAAO,QAAA,EAAU;AACnB,MAAA,QAAA,GAAW,IAAA;AACX,MAAA,IAAA,GAAO,SAAA;AAAA,IACT;AAAA,EACF;AAEA,EAAA,IAAI,IAAA,IAAQ,QAAA,GAAW,CAAA,IAAK,QAAA,IAAY,CAAA,IAAK,IAAA,CAAK,GAAA,CAAI,MAAA,CAAO,MAAA,GAAS,IAAA,CAAK,MAAM,CAAA,IAAK,CAAA,EAAG;AACvF,IAAA,OAAO,CAAA,EAAG,KAAK,CAAA,CAAA,EAAI,IAAI,CAAA,CAAA;AAAA,EACzB;AACA,EAAA,OAAO,IAAA;AACT;AAGO,SAAS,aAAA,CAAc,KAAA,EAAe,IAAA,GAA0B,EAAC,EAAqB;AAC3F,EAAA,MAAM,MAAM,OAAO,KAAA,KAAU,QAAA,GAAW,KAAA,CAAM,MAAK,GAAI,EAAA;AACvD,EAAA,MAAM,KAAA,GAAQ,WAAW,GAAG,CAAA;AAE5B,EAAA,IAAI,CAAC,YAAA,CAAa,GAAG,CAAA,IAAK,CAAC,KAAA,EAAO;AAChC,IAAA,OAAO;AAAA,MACL,KAAA,EAAO,KAAA;AAAA,MACP,UAAA,EAAY,IAAA;AAAA,MACZ,KAAA,EAAO,IAAA;AAAA,MACP,MAAA,EAAQ,IAAA;AAAA,MACR,UAAA,EAAY,KAAA;AAAA,MACZ,IAAA,EAAM,KAAA;AAAA,MACN,IAAA,EAAM,KAAA;AAAA,MACN,YAAY,IAAA,CAAK,WAAA,KAAgB,KAAA,GAAQ,IAAA,GAAO,aAAa,GAAG,CAAA;AAAA,MAChE,MAAA,EAAQ;AAAA,KACV;AAAA,EACF;AAEA,EAAA,MAAM,QAAQ,KAAA,CAAM,KAAA;AACpB,EAAA,MAAM,MAAA,GAAS,KAAA,CAAM,MAAA,CAAO,WAAA,EAAY;AACxC,EAAA,OAAO;AAAA,IACL,KAAA,EAAO,IAAA;AAAA,IACP,UAAA,EAAY,eAAe,GAAG,CAAA;AAAA,IAC9B,KAAA;AAAA,IACA,MAAA;AAAA,IACA,UAAA,EAAY,YAAA,CAAa,MAAA,EAAQ,IAAA,CAAK,eAAe,CAAA;AAAA,IACrD,IAAA,EAAM,cAAc,KAAK,CAAA;AAAA,IACzB,IAAA,EAAM,eAAe,MAAM,CAAA;AAAA,IAC3B,YAAY,IAAA,CAAK,WAAA,KAAgB,KAAA,GAAQ,IAAA,GAAO,aAAa,GAAG;AAAA,GAClE;AACF","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.6",
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",
@@ -44,7 +44,7 @@
44
44
  ],
45
45
  "author": "Lacspace <contact@lacspace.com>",
46
46
  "license": "SEE LICENSE IN LICENSE",
47
- "homepage": "https://lacspace.com/packages",
47
+ "homepage": "https://developer.lacspace.com/packages/email-validate",
48
48
  "repository": {
49
49
  "type": "git",
50
50
  "url": "git+https://github.com/lacspace/npm-packages.git",