zod-ir 1.0.0 → 1.1.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/README.md CHANGED
@@ -1,18 +1,31 @@
1
1
  # Zod Iranian Utils 🇮🇷
2
2
 
3
- A lightweight, TypeScript-first collection of Zod validators for Iranian specific data structures.
3
+ <div align="center">
4
4
 
5
- ## Features
5
+ ![npm version](https://img.shields.io/npm/v/zod-ir?style=flat-square)
6
+ ![npm bundle size](https://img.shields.io/bundlephobia/minzip/zod-ir?style=flat-square)
7
+ ![license](https://img.shields.io/npm/l/zod-ir?style=flat-square)
6
8
 
7
- - **Melli Code** (National ID) validation with official algorithm.
8
- - 💳 **Bank Card** validation using Luhn algorithm.
9
- - 📱 **Mobile Number** validation (supports +98, 09xx, 9xx).
10
- - 🌍 **Bilingual Error Messages** (Persian & English).
11
- - 🌲 **Tree-shakable** & Type-safe.
9
+ **A lightweight, type-safe Zod extension for validating Iranian specific data.**
10
+ Compatible with **React Hook Form**, **Next.js**, and Node.js backends.
12
11
 
13
- ## Installation
12
+ </div>
13
+
14
+ ## Features ✨
15
+
16
+ - ✅ **National Code:** Official checksum validation.
17
+ - 💳 **Bank Card:** 16-digit Luhn algorithm check.
18
+ - 📱 **Mobile Number:** Supports `09xx`, `+989xx`, `9xx`.
19
+ - 🏦 **Sheba (IBAN):** ISO 7064 structure validation.
20
+ - 📮 **Postal Code:** Iranian 10-digit postal code format.
21
+ - ☎️ **Landline:** Fixed line validation with area code.
22
+ - 🌍 **Bilingual:** Built-in Persian & English error messages.
23
+ - 🌲 **Tree-shakable:** Minimal bundle size.
24
+
25
+ ---
26
+
27
+ ## Installation 📦
14
28
 
15
29
  ```bash
16
- npm install zod zod-iranian-utils
17
- # or
18
- yarn add zod zod-iranian-utils
30
+ npm install zod zod-ir
31
+ ```
package/dist/index.d.mts CHANGED
@@ -1,68 +1,47 @@
1
1
  import { z } from 'zod';
2
2
 
3
- /**
4
- * Validates Iranian National Code (Melli Code) based on the official algorithm.
5
- * @param code The 10-digit national code string.
6
- */
7
3
  declare function isMelliCode(code: string): boolean;
8
- /**
9
- * Validates Bank Card Number using the Luhn algorithm.
10
- * @param code The 16-digit card number.
11
- */
12
4
  declare function isCardNumber(code: string): boolean;
13
5
  interface MobileValidationOptions {
14
- /**
15
- * strictZero:
16
- * - true: Must start with 0 (e.g., 0912...)
17
- * - false: Must NOT start with 0 (e.g., 912...)
18
- * - "optional": Both are accepted (default)
19
- */
20
6
  strictZero?: boolean | "optional";
21
7
  }
22
- /**
23
- * Validates Iranian Mobile Number.
24
- * Supports 09xx, 9xx, and +989xx formats.
25
- */
26
8
  declare function isIranianMobile(mobile: string, { strictZero }?: MobileValidationOptions): boolean;
9
+ declare function isSheba(code: string): boolean;
10
+ declare function isPostalCode(code: string): boolean;
11
+ declare function isLandline(code: string): boolean;
27
12
 
28
- /**
29
- * Default error messages for validation failures.
30
- * Supports Persian (fa) and English (en).
31
- */
32
13
  declare const ERROR_MESSAGES: {
33
14
  readonly fa: {
34
15
  readonly melliCode: "کد ملی نامعتبر است";
35
16
  readonly cardNumber: "شماره کارت نامعتبر است";
36
17
  readonly mobile: "شماره موبایل نامعتبر است";
18
+ readonly sheba: "شماره شبا نامعتبر است";
19
+ readonly postalCode: "کد پستی نامعتبر است";
20
+ readonly landline: "شماره تلفن ثابت نامعتبر است";
37
21
  };
38
22
  readonly en: {
39
23
  readonly melliCode: "Invalid national code";
40
24
  readonly cardNumber: "Invalid card number";
41
25
  readonly mobile: "Invalid mobile number";
26
+ readonly sheba: "Invalid Sheba (IBAN) number";
27
+ readonly postalCode: "Invalid postal code";
28
+ readonly landline: "Invalid landline number";
42
29
  };
43
30
  };
44
31
  type Language = keyof typeof ERROR_MESSAGES;
45
32
  interface BaseOptions {
46
- /** Custom error message to override the default one */
47
33
  message?: string;
48
- /** Language for the default error message (default: 'fa') */
49
34
  locale?: Language;
50
35
  }
51
36
 
52
- /**
53
- * Zod schema for validating Iranian National Code (Melli Code).
54
- */
55
37
  declare const zMelliCode: (options?: BaseOptions) => z.ZodString;
56
- /**
57
- * Zod schema for validating Iranian Bank Card Numbers (Luhn Algorithm).
58
- */
59
38
  declare const zCardNumber: (options?: BaseOptions) => z.ZodString;
60
39
  interface MobileOptions extends BaseOptions {
61
40
  strictZero?: boolean | "optional";
62
41
  }
63
- /**
64
- * Zod schema for validating Iranian Mobile Numbers.
65
- */
66
42
  declare const zIranianMobile: (options?: MobileOptions) => z.ZodString;
43
+ declare const zSheba: (options?: BaseOptions) => z.ZodString;
44
+ declare const zPostalCode: (options?: BaseOptions) => z.ZodString;
45
+ declare const zLandline: (options?: BaseOptions) => z.ZodString;
67
46
 
68
- export { isCardNumber, isIranianMobile, isMelliCode, zCardNumber, zIranianMobile, zMelliCode };
47
+ export { isCardNumber, isIranianMobile, isLandline, isMelliCode, isPostalCode, isSheba, zCardNumber, zIranianMobile, zLandline, zMelliCode, zPostalCode, zSheba };
package/dist/index.d.ts CHANGED
@@ -1,68 +1,47 @@
1
1
  import { z } from 'zod';
2
2
 
3
- /**
4
- * Validates Iranian National Code (Melli Code) based on the official algorithm.
5
- * @param code The 10-digit national code string.
6
- */
7
3
  declare function isMelliCode(code: string): boolean;
8
- /**
9
- * Validates Bank Card Number using the Luhn algorithm.
10
- * @param code The 16-digit card number.
11
- */
12
4
  declare function isCardNumber(code: string): boolean;
13
5
  interface MobileValidationOptions {
14
- /**
15
- * strictZero:
16
- * - true: Must start with 0 (e.g., 0912...)
17
- * - false: Must NOT start with 0 (e.g., 912...)
18
- * - "optional": Both are accepted (default)
19
- */
20
6
  strictZero?: boolean | "optional";
21
7
  }
22
- /**
23
- * Validates Iranian Mobile Number.
24
- * Supports 09xx, 9xx, and +989xx formats.
25
- */
26
8
  declare function isIranianMobile(mobile: string, { strictZero }?: MobileValidationOptions): boolean;
9
+ declare function isSheba(code: string): boolean;
10
+ declare function isPostalCode(code: string): boolean;
11
+ declare function isLandline(code: string): boolean;
27
12
 
28
- /**
29
- * Default error messages for validation failures.
30
- * Supports Persian (fa) and English (en).
31
- */
32
13
  declare const ERROR_MESSAGES: {
33
14
  readonly fa: {
34
15
  readonly melliCode: "کد ملی نامعتبر است";
35
16
  readonly cardNumber: "شماره کارت نامعتبر است";
36
17
  readonly mobile: "شماره موبایل نامعتبر است";
18
+ readonly sheba: "شماره شبا نامعتبر است";
19
+ readonly postalCode: "کد پستی نامعتبر است";
20
+ readonly landline: "شماره تلفن ثابت نامعتبر است";
37
21
  };
38
22
  readonly en: {
39
23
  readonly melliCode: "Invalid national code";
40
24
  readonly cardNumber: "Invalid card number";
41
25
  readonly mobile: "Invalid mobile number";
26
+ readonly sheba: "Invalid Sheba (IBAN) number";
27
+ readonly postalCode: "Invalid postal code";
28
+ readonly landline: "Invalid landline number";
42
29
  };
43
30
  };
44
31
  type Language = keyof typeof ERROR_MESSAGES;
45
32
  interface BaseOptions {
46
- /** Custom error message to override the default one */
47
33
  message?: string;
48
- /** Language for the default error message (default: 'fa') */
49
34
  locale?: Language;
50
35
  }
51
36
 
52
- /**
53
- * Zod schema for validating Iranian National Code (Melli Code).
54
- */
55
37
  declare const zMelliCode: (options?: BaseOptions) => z.ZodString;
56
- /**
57
- * Zod schema for validating Iranian Bank Card Numbers (Luhn Algorithm).
58
- */
59
38
  declare const zCardNumber: (options?: BaseOptions) => z.ZodString;
60
39
  interface MobileOptions extends BaseOptions {
61
40
  strictZero?: boolean | "optional";
62
41
  }
63
- /**
64
- * Zod schema for validating Iranian Mobile Numbers.
65
- */
66
42
  declare const zIranianMobile: (options?: MobileOptions) => z.ZodString;
43
+ declare const zSheba: (options?: BaseOptions) => z.ZodString;
44
+ declare const zPostalCode: (options?: BaseOptions) => z.ZodString;
45
+ declare const zLandline: (options?: BaseOptions) => z.ZodString;
67
46
 
68
- export { isCardNumber, isIranianMobile, isMelliCode, zCardNumber, zIranianMobile, zMelliCode };
47
+ export { isCardNumber, isIranianMobile, isLandline, isMelliCode, isPostalCode, isSheba, zCardNumber, zIranianMobile, zLandline, zMelliCode, zPostalCode, zSheba };
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";var l=Object.defineProperty;var m=Object.getOwnPropertyDescriptor;var g=Object.getOwnPropertyNames;var u=Object.prototype.hasOwnProperty;var b=(t,e)=>{for(var n in e)l(t,n,{get:e[n],enumerable:!0})},d=(t,e,n,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let o of g(e))!u.call(t,o)&&o!==n&&l(t,o,{get:()=>e[o],enumerable:!(i=m(e,o))||i.enumerable});return t};var M=t=>d(l({},"__esModule",{value:!0}),t);var C={};b(C,{isCardNumber:()=>f,isIranianMobile:()=>c,isMelliCode:()=>p,zCardNumber:()=>E,zIranianMobile:()=>R,zMelliCode:()=>O});module.exports=M(C);var a=require("zod");function p(t){if(!/^\d{10}$/.test(t))return!1;let e=parseInt(t[9]);if(/^(\d)\1+$/.test(t))return!1;let n=t.substring(0,9).split("").reduce((i,o,r)=>i+parseInt(o)*(10-r),0)%11;return n<2?e===n:e===11-n}function f(t){let e=t.replace(/[\-\s]/g,"");if(!/^\d{16}$/.test(e))return!1;let n=0,i=!1;for(let o=e.length-1;o>=0;o--){let r=parseInt(e[o]);i&&(r*=2,r>9&&(r-=9)),n+=r,i=!i}return n%10===0}function c(t,{strictZero:e="optional"}={}){let n="9\\d{9}",i="";return e===!0?i=`^0${n}$`:e===!1?i=`^${n}$`:i=`^(?:0|\\+98)?${n}$`,new RegExp(i).test(t)}var x={fa:{melliCode:"\u06A9\u062F \u0645\u0644\u06CC \u0646\u0627\u0645\u0639\u062A\u0628\u0631 \u0627\u0633\u062A",cardNumber:"\u0634\u0645\u0627\u0631\u0647 \u06A9\u0627\u0631\u062A \u0646\u0627\u0645\u0639\u062A\u0628\u0631 \u0627\u0633\u062A",mobile:"\u0634\u0645\u0627\u0631\u0647 \u0645\u0648\u0628\u0627\u06CC\u0644 \u0646\u0627\u0645\u0639\u062A\u0628\u0631 \u0627\u0633\u062A"},en:{melliCode:"Invalid national code",cardNumber:"Invalid card number",mobile:"Invalid mobile number"}},s=(t,e)=>{if(e?.message)return e.message;let n=e?.locale||"fa";return x[n][t]};var O=t=>a.z.string().refine(e=>p(e),{message:s("melliCode",t)}),E=t=>a.z.string().refine(e=>f(e),{message:s("cardNumber",t)}),R=t=>a.z.string().refine(e=>c(e,{strictZero:t?.strictZero}),{message:s("mobile",t)});0&&(module.exports={isCardNumber,isIranianMobile,isMelliCode,zCardNumber,zIranianMobile,zMelliCode});
1
+ "use strict";var l=Object.defineProperty;var m=Object.getOwnPropertyDescriptor;var x=Object.getOwnPropertyNames;var C=Object.prototype.hasOwnProperty;var I=(t,e)=>{for(var n in e)l(t,n,{get:e[n],enumerable:!0})},O=(t,e,n,s)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of x(e))!C.call(t,r)&&r!==n&&l(t,r,{get:()=>e[r],enumerable:!(s=m(e,r))||s.enumerable});return t};var M=t=>O(l({},"__esModule",{value:!0}),t);var N={};I(N,{isCardNumber:()=>f,isIranianMobile:()=>d,isLandline:()=>g,isMelliCode:()=>p,isPostalCode:()=>b,isSheba:()=>u,zCardNumber:()=>R,zIranianMobile:()=>$,zLandline:()=>z,zMelliCode:()=>h,zPostalCode:()=>E,zSheba:()=>B});module.exports=M(N);var a=require("zod");function p(t){if(!/^\d{10}$/.test(t))return!1;let e=parseInt(t[9]);if(/^(\d)\1+$/.test(t))return!1;let n=t.substring(0,9).split("").reduce((s,r,i)=>s+parseInt(r)*(10-i),0)%11;return n<2?e===n:e===11-n}function f(t){let e=t.replace(/[\-\s]/g,"");if(!/^\d{16}$/.test(e))return!1;let n=0,s=!1;for(let r=e.length-1;r>=0;r--){let i=parseInt(e[r]);s&&(i*=2,i>9&&(i-=9)),n+=i,s=!s}return n%10===0}function d(t,{strictZero:e="optional"}={}){let n="9\\d{9}",s="";return e===!0?s=`^0${n}$`:e===!1?s=`^${n}$`:s=`^(?:0|\\+98)?${n}$`,new RegExp(s).test(t)}function u(t){let e=t.toUpperCase().replace(/[\-\s]/g,"");if(e.length!==26||!e.startsWith("IR"))return!1;let n=e.substring(4)+"1827"+e.substring(2,4),s=Array.from(n).map(r=>parseInt(r,36)).reduce((r,i)=>{let c=(i<10,i);return(+(r+""+c)%97).toString()},"");return parseInt(s)===1}function b(t){return!(!/^\d{10}$/.test(t)||t.startsWith("0"))}function g(t){return/^0\d{2}\d{8}$/.test(t)}var S={fa:{melliCode:"\u06A9\u062F \u0645\u0644\u06CC \u0646\u0627\u0645\u0639\u062A\u0628\u0631 \u0627\u0633\u062A",cardNumber:"\u0634\u0645\u0627\u0631\u0647 \u06A9\u0627\u0631\u062A \u0646\u0627\u0645\u0639\u062A\u0628\u0631 \u0627\u0633\u062A",mobile:"\u0634\u0645\u0627\u0631\u0647 \u0645\u0648\u0628\u0627\u06CC\u0644 \u0646\u0627\u0645\u0639\u062A\u0628\u0631 \u0627\u0633\u062A",sheba:"\u0634\u0645\u0627\u0631\u0647 \u0634\u0628\u0627 \u0646\u0627\u0645\u0639\u062A\u0628\u0631 \u0627\u0633\u062A",postalCode:"\u06A9\u062F \u067E\u0633\u062A\u06CC \u0646\u0627\u0645\u0639\u062A\u0628\u0631 \u0627\u0633\u062A",landline:"\u0634\u0645\u0627\u0631\u0647 \u062A\u0644\u0641\u0646 \u062B\u0627\u0628\u062A \u0646\u0627\u0645\u0639\u062A\u0628\u0631 \u0627\u0633\u062A"},en:{melliCode:"Invalid national code",cardNumber:"Invalid card number",mobile:"Invalid mobile number",sheba:"Invalid Sheba (IBAN) number",postalCode:"Invalid postal code",landline:"Invalid landline number"}},o=(t,e)=>{if(e?.message)return e.message;let n=e?.locale||"fa";return S[n][t]};var h=t=>a.z.string().refine(e=>p(e),{message:o("melliCode",t)}),R=t=>a.z.string().refine(e=>f(e),{message:o("cardNumber",t)}),$=t=>a.z.string().refine(e=>d(e,{strictZero:t?.strictZero}),{message:o("mobile",t)}),B=t=>a.z.string().refine(e=>u(e),{message:o("sheba",t)}),E=t=>a.z.string().refine(e=>b(e),{message:o("postalCode",t)}),z=t=>a.z.string().refine(e=>g(e),{message:o("landline",t)});0&&(module.exports={isCardNumber,isIranianMobile,isLandline,isMelliCode,isPostalCode,isSheba,zCardNumber,zIranianMobile,zLandline,zMelliCode,zPostalCode,zSheba});
2
2
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/utils.ts","../src/constants.ts"],"sourcesContent":["import { z } from \"zod\";\nimport { isMelliCode, isCardNumber, isIranianMobile } from \"./utils\";\nimport { getMessage, BaseOptions } from \"./constants\";\n\n/**\n * Zod schema for validating Iranian National Code (Melli Code).\n */\nexport const zMelliCode = (options?: BaseOptions) =>\n z.string().refine((val) => isMelliCode(val), {\n message: getMessage(\"melliCode\", options),\n });\n\n/**\n * Zod schema for validating Iranian Bank Card Numbers (Luhn Algorithm).\n */\nexport const zCardNumber = (options?: BaseOptions) =>\n z.string().refine((val) => isCardNumber(val), {\n message: getMessage(\"cardNumber\", options),\n });\n\ninterface MobileOptions extends BaseOptions {\n strictZero?: boolean | \"optional\";\n}\n\n/**\n * Zod schema for validating Iranian Mobile Numbers.\n */\nexport const zIranianMobile = (options?: MobileOptions) =>\n z\n .string()\n .refine(\n (val) => isIranianMobile(val, { strictZero: options?.strictZero }),\n {\n message: getMessage(\"mobile\", options),\n }\n );\n\n// Export raw utility functions for non-Zod usage\nexport { isMelliCode, isCardNumber, isIranianMobile };\n","/**\n * Validates Iranian National Code (Melli Code) based on the official algorithm.\n * @param code The 10-digit national code string.\n */\nexport function isMelliCode(code: string): boolean {\n // Basic format check\n if (!/^\\d{10}$/.test(code)) return false;\n\n // Check for repeated digits (e.g., 1111111111) which are invalid\n const check = parseInt(code[9]);\n if (/^(\\d)\\1+$/.test(code)) return false;\n\n // Calculate control digit\n const sum =\n code\n .substring(0, 9)\n .split(\"\")\n .reduce((acc, x, i) => acc + parseInt(x) * (10 - i), 0) % 11;\n\n return sum < 2 ? check === sum : check === 11 - sum;\n}\n\n/**\n * Validates Bank Card Number using the Luhn algorithm.\n * @param code The 16-digit card number.\n */\nexport function isCardNumber(code: string): boolean {\n // Remove hyphens or spaces if present\n const sanitized = code.replace(/[\\-\\s]/g, \"\");\n if (!/^\\d{16}$/.test(sanitized)) return false;\n\n let sum = 0;\n let shouldDouble = false;\n\n // Traverse from right to left\n for (let i = sanitized.length - 1; i >= 0; i--) {\n let digit = parseInt(sanitized[i]);\n\n if (shouldDouble) {\n digit *= 2;\n if (digit > 9) digit -= 9;\n }\n\n sum += digit;\n shouldDouble = !shouldDouble;\n }\n\n return sum % 10 === 0;\n}\n\ninterface MobileValidationOptions {\n /**\n * strictZero:\n * - true: Must start with 0 (e.g., 0912...)\n * - false: Must NOT start with 0 (e.g., 912...)\n * - \"optional\": Both are accepted (default)\n */\n strictZero?: boolean | \"optional\";\n}\n\n/**\n * Validates Iranian Mobile Number.\n * Supports 09xx, 9xx, and +989xx formats.\n */\nexport function isIranianMobile(\n mobile: string,\n { strictZero = \"optional\" }: MobileValidationOptions = {}\n): boolean {\n const corePattern = \"9\\\\d{9}\";\n let pattern = \"\";\n\n if (strictZero === true) {\n pattern = `^0${corePattern}$`;\n } else if (strictZero === false) {\n pattern = `^${corePattern}$`;\n } else {\n pattern = `^(?:0|\\\\+98)?${corePattern}$`;\n }\n\n return new RegExp(pattern).test(mobile);\n}\n","/**\n * Default error messages for validation failures.\n * Supports Persian (fa) and English (en).\n */\nexport const ERROR_MESSAGES = {\n fa: {\n melliCode: \"کد ملی نامعتبر است\",\n cardNumber: \"شماره کارت نامعتبر است\",\n mobile: \"شماره موبایل نامعتبر است\",\n },\n en: {\n melliCode: \"Invalid national code\",\n cardNumber: \"Invalid card number\",\n mobile: \"Invalid mobile number\",\n },\n} as const;\n\nexport type Language = keyof typeof ERROR_MESSAGES;\n\nexport interface BaseOptions {\n /** Custom error message to override the default one */\n message?: string;\n /** Language for the default error message (default: 'fa') */\n locale?: Language;\n}\n\n/**\n * Helper to resolve the error message based on options.\n */\nexport const getMessage = (\n key: keyof (typeof ERROR_MESSAGES)[\"fa\"],\n options?: BaseOptions\n): string => {\n if (options?.message) return options.message;\n const lang = options?.locale || \"fa\";\n return ERROR_MESSAGES[lang][key];\n};\n"],"mappings":"yaAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,kBAAAE,EAAA,oBAAAC,EAAA,gBAAAC,EAAA,gBAAAC,EAAA,mBAAAC,EAAA,eAAAC,IAAA,eAAAC,EAAAR,GAAA,IAAAS,EAAkB,eCIX,SAASC,EAAYC,EAAuB,CAEjD,GAAI,CAAC,WAAW,KAAKA,CAAI,EAAG,MAAO,GAGnC,IAAMC,EAAQ,SAASD,EAAK,CAAC,CAAC,EAC9B,GAAI,YAAY,KAAKA,CAAI,EAAG,MAAO,GAGnC,IAAME,EACJF,EACG,UAAU,EAAG,CAAC,EACd,MAAM,EAAE,EACR,OAAO,CAACG,EAAKC,EAAGC,IAAMF,EAAM,SAASC,CAAC,GAAK,GAAKC,GAAI,CAAC,EAAI,GAE9D,OAAOH,EAAM,EAAID,IAAUC,EAAMD,IAAU,GAAKC,CAClD,CAMO,SAASI,EAAaN,EAAuB,CAElD,IAAMO,EAAYP,EAAK,QAAQ,UAAW,EAAE,EAC5C,GAAI,CAAC,WAAW,KAAKO,CAAS,EAAG,MAAO,GAExC,IAAIL,EAAM,EACNM,EAAe,GAGnB,QAASH,EAAIE,EAAU,OAAS,EAAGF,GAAK,EAAGA,IAAK,CAC9C,IAAII,EAAQ,SAASF,EAAUF,CAAC,CAAC,EAE7BG,IACFC,GAAS,EACLA,EAAQ,IAAGA,GAAS,IAG1BP,GAAOO,EACPD,EAAe,CAACA,CAClB,CAEA,OAAON,EAAM,KAAO,CACtB,CAgBO,SAASQ,EACdC,EACA,CAAE,WAAAC,EAAa,UAAW,EAA6B,CAAC,EAC/C,CACT,IAAMC,EAAc,UAChBC,EAAU,GAEd,OAAIF,IAAe,GACjBE,EAAU,KAAKD,CAAW,IACjBD,IAAe,GACxBE,EAAU,IAAID,CAAW,IAEzBC,EAAU,gBAAgBD,CAAW,IAGhC,IAAI,OAAOC,CAAO,EAAE,KAAKH,CAAM,CACxC,CC5EO,IAAMI,EAAiB,CAC5B,GAAI,CACF,UAAW,gGACX,WAAY,wHACZ,OAAQ,mIACV,EACA,GAAI,CACF,UAAW,wBACX,WAAY,sBACZ,OAAQ,uBACV,CACF,EAcaC,EAAa,CACxBC,EACAC,IACW,CACX,GAAIA,GAAS,QAAS,OAAOA,EAAQ,QACrC,IAAMC,EAAOD,GAAS,QAAU,KAChC,OAAOH,EAAeI,CAAI,EAAEF,CAAG,CACjC,EF7BO,IAAMG,EAAcC,GACzB,IAAE,OAAO,EAAE,OAAQC,GAAQC,EAAYD,CAAG,EAAG,CAC3C,QAASE,EAAW,YAAaH,CAAO,CAC1C,CAAC,EAKUI,EAAeJ,GAC1B,IAAE,OAAO,EAAE,OAAQC,GAAQI,EAAaJ,CAAG,EAAG,CAC5C,QAASE,EAAW,aAAcH,CAAO,CAC3C,CAAC,EASUM,EAAkBN,GAC7B,IACG,OAAO,EACP,OACEC,GAAQM,EAAgBN,EAAK,CAAE,WAAYD,GAAS,UAAW,CAAC,EACjE,CACE,QAASG,EAAW,SAAUH,CAAO,CACvC,CACF","names":["index_exports","__export","isCardNumber","isIranianMobile","isMelliCode","zCardNumber","zIranianMobile","zMelliCode","__toCommonJS","import_zod","isMelliCode","code","check","sum","acc","x","i","isCardNumber","sanitized","shouldDouble","digit","isIranianMobile","mobile","strictZero","corePattern","pattern","ERROR_MESSAGES","getMessage","key","options","lang","zMelliCode","options","val","isMelliCode","getMessage","zCardNumber","isCardNumber","zIranianMobile","isIranianMobile"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/utils.ts","../src/constants.ts"],"sourcesContent":["import { z } from \"zod\";\nimport {\n isMelliCode,\n isCardNumber,\n isIranianMobile,\n isSheba,\n isPostalCode,\n isLandline,\n} from \"./utils\";\nimport { getMessage, BaseOptions } from \"./constants\";\n\nexport const zMelliCode = (options?: BaseOptions) =>\n z.string().refine((val) => isMelliCode(val), {\n message: getMessage(\"melliCode\", options),\n });\n\nexport const zCardNumber = (options?: BaseOptions) =>\n z.string().refine((val) => isCardNumber(val), {\n message: getMessage(\"cardNumber\", options),\n });\n\ninterface MobileOptions extends BaseOptions {\n strictZero?: boolean | \"optional\";\n}\n\nexport const zIranianMobile = (options?: MobileOptions) =>\n z\n .string()\n .refine(\n (val) => isIranianMobile(val, { strictZero: options?.strictZero }),\n {\n message: getMessage(\"mobile\", options),\n }\n );\n\nexport const zSheba = (options?: BaseOptions) =>\n z.string().refine((val) => isSheba(val), {\n message: getMessage(\"sheba\", options),\n });\n\nexport const zPostalCode = (options?: BaseOptions) =>\n z.string().refine((val) => isPostalCode(val), {\n message: getMessage(\"postalCode\", options),\n });\n\nexport const zLandline = (options?: BaseOptions) =>\n z.string().refine((val) => isLandline(val), {\n message: getMessage(\"landline\", options),\n });\n\nexport {\n isMelliCode,\n isCardNumber,\n isIranianMobile,\n isSheba,\n isPostalCode,\n isLandline,\n};\n","export function isMelliCode(code: string): boolean {\n if (!/^\\d{10}$/.test(code)) return false;\n\n const check = parseInt(code[9]);\n if (/^(\\d)\\1+$/.test(code)) return false;\n\n const sum =\n code\n .substring(0, 9)\n .split(\"\")\n .reduce((acc, x, i) => acc + parseInt(x) * (10 - i), 0) % 11;\n\n return sum < 2 ? check === sum : check === 11 - sum;\n}\n\nexport function isCardNumber(code: string): boolean {\n const sanitized = code.replace(/[\\-\\s]/g, \"\");\n if (!/^\\d{16}$/.test(sanitized)) return false;\n\n let sum = 0;\n let shouldDouble = false;\n\n for (let i = sanitized.length - 1; i >= 0; i--) {\n let digit = parseInt(sanitized[i]);\n\n if (shouldDouble) {\n digit *= 2;\n if (digit > 9) digit -= 9;\n }\n\n sum += digit;\n shouldDouble = !shouldDouble;\n }\n\n return sum % 10 === 0;\n}\n\ninterface MobileValidationOptions {\n strictZero?: boolean | \"optional\";\n}\n\nexport function isIranianMobile(\n mobile: string,\n { strictZero = \"optional\" }: MobileValidationOptions = {}\n): boolean {\n const corePattern = \"9\\\\d{9}\";\n let pattern = \"\";\n\n if (strictZero === true) {\n pattern = `^0${corePattern}$`;\n } else if (strictZero === false) {\n pattern = `^${corePattern}$`;\n } else {\n pattern = `^(?:0|\\\\+98)?${corePattern}$`;\n }\n\n return new RegExp(pattern).test(mobile);\n}\n\nexport function isSheba(code: string): boolean {\n const iban = code.toUpperCase().replace(/[\\-\\s]/g, \"\");\n\n if (iban.length !== 26) return false;\n if (!iban.startsWith(\"IR\")) return false;\n\n const newStr = iban.substring(4) + \"1827\" + iban.substring(2, 4);\n\n const remainder = Array.from(newStr)\n .map((c) => parseInt(c, 36))\n .reduce((remainder, value) => {\n const v = value < 10 ? value : value;\n return (Number(remainder + \"\" + v) % 97).toString();\n }, \"\");\n\n return parseInt(remainder) === 1;\n}\n\nexport function isPostalCode(code: string): boolean {\n if (!/^\\d{10}$/.test(code)) return false;\n if (code.startsWith(\"0\")) return false;\n return true;\n}\n\nexport function isLandline(code: string): boolean {\n return /^0\\d{2}\\d{8}$/.test(code);\n}\n","export const ERROR_MESSAGES = {\n fa: {\n melliCode: \"کد ملی نامعتبر است\",\n cardNumber: \"شماره کارت نامعتبر است\",\n mobile: \"شماره موبایل نامعتبر است\",\n sheba: \"شماره شبا نامعتبر است\",\n postalCode: \"کد پستی نامعتبر است\",\n landline: \"شماره تلفن ثابت نامعتبر است\",\n },\n en: {\n melliCode: \"Invalid national code\",\n cardNumber: \"Invalid card number\",\n mobile: \"Invalid mobile number\",\n sheba: \"Invalid Sheba (IBAN) number\",\n postalCode: \"Invalid postal code\",\n landline: \"Invalid landline number\",\n },\n} as const;\n\nexport type Language = keyof typeof ERROR_MESSAGES;\n\nexport interface BaseOptions {\n message?: string;\n locale?: Language;\n}\n\nexport const getMessage = (\n key: keyof (typeof ERROR_MESSAGES)[\"fa\"],\n options?: BaseOptions\n): string => {\n if (options?.message) return options.message;\n const lang = options?.locale || \"fa\";\n return ERROR_MESSAGES[lang][key];\n};\n"],"mappings":"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","names":["index_exports","__export","isCardNumber","isIranianMobile","isLandline","isMelliCode","isPostalCode","isSheba","zCardNumber","zIranianMobile","zLandline","zMelliCode","zPostalCode","zSheba","__toCommonJS","import_zod","isMelliCode","code","check","sum","acc","x","isCardNumber","sanitized","shouldDouble","i","digit","isIranianMobile","mobile","strictZero","corePattern","pattern","isSheba","iban","newStr","remainder","c","value","v","isPostalCode","isLandline","ERROR_MESSAGES","getMessage","key","options","lang","zMelliCode","options","val","isMelliCode","getMessage","zCardNumber","isCardNumber","zIranianMobile","isIranianMobile","zSheba","isSheba","zPostalCode","isPostalCode","zLandline","isLandline"]}
package/dist/index.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import{z as a}from"zod";function l(t){if(!/^\d{10}$/.test(t))return!1;let e=parseInt(t[9]);if(/^(\d)\1+$/.test(t))return!1;let n=t.substring(0,9).split("").reduce((i,r,o)=>i+parseInt(r)*(10-o),0)%11;return n<2?e===n:e===11-n}function p(t){let e=t.replace(/[\-\s]/g,"");if(!/^\d{16}$/.test(e))return!1;let n=0,i=!1;for(let r=e.length-1;r>=0;r--){let o=parseInt(e[r]);i&&(o*=2,o>9&&(o-=9)),n+=o,i=!i}return n%10===0}function f(t,{strictZero:e="optional"}={}){let n="9\\d{9}",i="";return e===!0?i=`^0${n}$`:e===!1?i=`^${n}$`:i=`^(?:0|\\+98)?${n}$`,new RegExp(i).test(t)}var c={fa:{melliCode:"\u06A9\u062F \u0645\u0644\u06CC \u0646\u0627\u0645\u0639\u062A\u0628\u0631 \u0627\u0633\u062A",cardNumber:"\u0634\u0645\u0627\u0631\u0647 \u06A9\u0627\u0631\u062A \u0646\u0627\u0645\u0639\u062A\u0628\u0631 \u0627\u0633\u062A",mobile:"\u0634\u0645\u0627\u0631\u0647 \u0645\u0648\u0628\u0627\u06CC\u0644 \u0646\u0627\u0645\u0639\u062A\u0628\u0631 \u0627\u0633\u062A"},en:{melliCode:"Invalid national code",cardNumber:"Invalid card number",mobile:"Invalid mobile number"}},s=(t,e)=>{if(e?.message)return e.message;let n=e?.locale||"fa";return c[n][t]};var x=t=>a.string().refine(e=>l(e),{message:s("melliCode",t)}),O=t=>a.string().refine(e=>p(e),{message:s("cardNumber",t)}),E=t=>a.string().refine(e=>f(e,{strictZero:t?.strictZero}),{message:s("mobile",t)});export{p as isCardNumber,f as isIranianMobile,l as isMelliCode,O as zCardNumber,E as zIranianMobile,x as zMelliCode};
1
+ import{z as a}from"zod";function l(e){if(!/^\d{10}$/.test(e))return!1;let t=parseInt(e[9]);if(/^(\d)\1+$/.test(e))return!1;let n=e.substring(0,9).split("").reduce((s,i,r)=>s+parseInt(i)*(10-r),0)%11;return n<2?t===n:t===11-n}function p(e){let t=e.replace(/[\-\s]/g,"");if(!/^\d{16}$/.test(t))return!1;let n=0,s=!1;for(let i=t.length-1;i>=0;i--){let r=parseInt(t[i]);s&&(r*=2,r>9&&(r-=9)),n+=r,s=!s}return n%10===0}function f(e,{strictZero:t="optional"}={}){let n="9\\d{9}",s="";return t===!0?s=`^0${n}$`:t===!1?s=`^${n}$`:s=`^(?:0|\\+98)?${n}$`,new RegExp(s).test(e)}function d(e){let t=e.toUpperCase().replace(/[\-\s]/g,"");if(t.length!==26||!t.startsWith("IR"))return!1;let n=t.substring(4)+"1827"+t.substring(2,4),s=Array.from(n).map(i=>parseInt(i,36)).reduce((i,r)=>{let g=(r<10,r);return(+(i+""+g)%97).toString()},"");return parseInt(s)===1}function u(e){return!(!/^\d{10}$/.test(e)||e.startsWith("0"))}function b(e){return/^0\d{2}\d{8}$/.test(e)}var c={fa:{melliCode:"\u06A9\u062F \u0645\u0644\u06CC \u0646\u0627\u0645\u0639\u062A\u0628\u0631 \u0627\u0633\u062A",cardNumber:"\u0634\u0645\u0627\u0631\u0647 \u06A9\u0627\u0631\u062A \u0646\u0627\u0645\u0639\u062A\u0628\u0631 \u0627\u0633\u062A",mobile:"\u0634\u0645\u0627\u0631\u0647 \u0645\u0648\u0628\u0627\u06CC\u0644 \u0646\u0627\u0645\u0639\u062A\u0628\u0631 \u0627\u0633\u062A",sheba:"\u0634\u0645\u0627\u0631\u0647 \u0634\u0628\u0627 \u0646\u0627\u0645\u0639\u062A\u0628\u0631 \u0627\u0633\u062A",postalCode:"\u06A9\u062F \u067E\u0633\u062A\u06CC \u0646\u0627\u0645\u0639\u062A\u0628\u0631 \u0627\u0633\u062A",landline:"\u0634\u0645\u0627\u0631\u0647 \u062A\u0644\u0641\u0646 \u062B\u0627\u0628\u062A \u0646\u0627\u0645\u0639\u062A\u0628\u0631 \u0627\u0633\u062A"},en:{melliCode:"Invalid national code",cardNumber:"Invalid card number",mobile:"Invalid mobile number",sheba:"Invalid Sheba (IBAN) number",postalCode:"Invalid postal code",landline:"Invalid landline number"}},o=(e,t)=>{if(t?.message)return t.message;let n=t?.locale||"fa";return c[n][e]};var S=e=>a.string().refine(t=>l(t),{message:o("melliCode",e)}),h=e=>a.string().refine(t=>p(t),{message:o("cardNumber",e)}),R=e=>a.string().refine(t=>f(t,{strictZero:e?.strictZero}),{message:o("mobile",e)}),$=e=>a.string().refine(t=>d(t),{message:o("sheba",e)}),B=e=>a.string().refine(t=>u(t),{message:o("postalCode",e)}),E=e=>a.string().refine(t=>b(t),{message:o("landline",e)});export{p as isCardNumber,f as isIranianMobile,b as isLandline,l as isMelliCode,u as isPostalCode,d as isSheba,h as zCardNumber,R as zIranianMobile,E as zLandline,S as zMelliCode,B as zPostalCode,$ as zSheba};
2
2
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/utils.ts","../src/constants.ts"],"sourcesContent":["import { z } from \"zod\";\nimport { isMelliCode, isCardNumber, isIranianMobile } from \"./utils\";\nimport { getMessage, BaseOptions } from \"./constants\";\n\n/**\n * Zod schema for validating Iranian National Code (Melli Code).\n */\nexport const zMelliCode = (options?: BaseOptions) =>\n z.string().refine((val) => isMelliCode(val), {\n message: getMessage(\"melliCode\", options),\n });\n\n/**\n * Zod schema for validating Iranian Bank Card Numbers (Luhn Algorithm).\n */\nexport const zCardNumber = (options?: BaseOptions) =>\n z.string().refine((val) => isCardNumber(val), {\n message: getMessage(\"cardNumber\", options),\n });\n\ninterface MobileOptions extends BaseOptions {\n strictZero?: boolean | \"optional\";\n}\n\n/**\n * Zod schema for validating Iranian Mobile Numbers.\n */\nexport const zIranianMobile = (options?: MobileOptions) =>\n z\n .string()\n .refine(\n (val) => isIranianMobile(val, { strictZero: options?.strictZero }),\n {\n message: getMessage(\"mobile\", options),\n }\n );\n\n// Export raw utility functions for non-Zod usage\nexport { isMelliCode, isCardNumber, isIranianMobile };\n","/**\n * Validates Iranian National Code (Melli Code) based on the official algorithm.\n * @param code The 10-digit national code string.\n */\nexport function isMelliCode(code: string): boolean {\n // Basic format check\n if (!/^\\d{10}$/.test(code)) return false;\n\n // Check for repeated digits (e.g., 1111111111) which are invalid\n const check = parseInt(code[9]);\n if (/^(\\d)\\1+$/.test(code)) return false;\n\n // Calculate control digit\n const sum =\n code\n .substring(0, 9)\n .split(\"\")\n .reduce((acc, x, i) => acc + parseInt(x) * (10 - i), 0) % 11;\n\n return sum < 2 ? check === sum : check === 11 - sum;\n}\n\n/**\n * Validates Bank Card Number using the Luhn algorithm.\n * @param code The 16-digit card number.\n */\nexport function isCardNumber(code: string): boolean {\n // Remove hyphens or spaces if present\n const sanitized = code.replace(/[\\-\\s]/g, \"\");\n if (!/^\\d{16}$/.test(sanitized)) return false;\n\n let sum = 0;\n let shouldDouble = false;\n\n // Traverse from right to left\n for (let i = sanitized.length - 1; i >= 0; i--) {\n let digit = parseInt(sanitized[i]);\n\n if (shouldDouble) {\n digit *= 2;\n if (digit > 9) digit -= 9;\n }\n\n sum += digit;\n shouldDouble = !shouldDouble;\n }\n\n return sum % 10 === 0;\n}\n\ninterface MobileValidationOptions {\n /**\n * strictZero:\n * - true: Must start with 0 (e.g., 0912...)\n * - false: Must NOT start with 0 (e.g., 912...)\n * - \"optional\": Both are accepted (default)\n */\n strictZero?: boolean | \"optional\";\n}\n\n/**\n * Validates Iranian Mobile Number.\n * Supports 09xx, 9xx, and +989xx formats.\n */\nexport function isIranianMobile(\n mobile: string,\n { strictZero = \"optional\" }: MobileValidationOptions = {}\n): boolean {\n const corePattern = \"9\\\\d{9}\";\n let pattern = \"\";\n\n if (strictZero === true) {\n pattern = `^0${corePattern}$`;\n } else if (strictZero === false) {\n pattern = `^${corePattern}$`;\n } else {\n pattern = `^(?:0|\\\\+98)?${corePattern}$`;\n }\n\n return new RegExp(pattern).test(mobile);\n}\n","/**\n * Default error messages for validation failures.\n * Supports Persian (fa) and English (en).\n */\nexport const ERROR_MESSAGES = {\n fa: {\n melliCode: \"کد ملی نامعتبر است\",\n cardNumber: \"شماره کارت نامعتبر است\",\n mobile: \"شماره موبایل نامعتبر است\",\n },\n en: {\n melliCode: \"Invalid national code\",\n cardNumber: \"Invalid card number\",\n mobile: \"Invalid mobile number\",\n },\n} as const;\n\nexport type Language = keyof typeof ERROR_MESSAGES;\n\nexport interface BaseOptions {\n /** Custom error message to override the default one */\n message?: string;\n /** Language for the default error message (default: 'fa') */\n locale?: Language;\n}\n\n/**\n * Helper to resolve the error message based on options.\n */\nexport const getMessage = (\n key: keyof (typeof ERROR_MESSAGES)[\"fa\"],\n options?: BaseOptions\n): string => {\n if (options?.message) return options.message;\n const lang = options?.locale || \"fa\";\n return ERROR_MESSAGES[lang][key];\n};\n"],"mappings":"AAAA,OAAS,KAAAA,MAAS,MCIX,SAASC,EAAYC,EAAuB,CAEjD,GAAI,CAAC,WAAW,KAAKA,CAAI,EAAG,MAAO,GAGnC,IAAMC,EAAQ,SAASD,EAAK,CAAC,CAAC,EAC9B,GAAI,YAAY,KAAKA,CAAI,EAAG,MAAO,GAGnC,IAAME,EACJF,EACG,UAAU,EAAG,CAAC,EACd,MAAM,EAAE,EACR,OAAO,CAACG,EAAKC,EAAGC,IAAMF,EAAM,SAASC,CAAC,GAAK,GAAKC,GAAI,CAAC,EAAI,GAE9D,OAAOH,EAAM,EAAID,IAAUC,EAAMD,IAAU,GAAKC,CAClD,CAMO,SAASI,EAAaN,EAAuB,CAElD,IAAMO,EAAYP,EAAK,QAAQ,UAAW,EAAE,EAC5C,GAAI,CAAC,WAAW,KAAKO,CAAS,EAAG,MAAO,GAExC,IAAIL,EAAM,EACNM,EAAe,GAGnB,QAASH,EAAIE,EAAU,OAAS,EAAGF,GAAK,EAAGA,IAAK,CAC9C,IAAII,EAAQ,SAASF,EAAUF,CAAC,CAAC,EAE7BG,IACFC,GAAS,EACLA,EAAQ,IAAGA,GAAS,IAG1BP,GAAOO,EACPD,EAAe,CAACA,CAClB,CAEA,OAAON,EAAM,KAAO,CACtB,CAgBO,SAASQ,EACdC,EACA,CAAE,WAAAC,EAAa,UAAW,EAA6B,CAAC,EAC/C,CACT,IAAMC,EAAc,UAChBC,EAAU,GAEd,OAAIF,IAAe,GACjBE,EAAU,KAAKD,CAAW,IACjBD,IAAe,GACxBE,EAAU,IAAID,CAAW,IAEzBC,EAAU,gBAAgBD,CAAW,IAGhC,IAAI,OAAOC,CAAO,EAAE,KAAKH,CAAM,CACxC,CC5EO,IAAMI,EAAiB,CAC5B,GAAI,CACF,UAAW,gGACX,WAAY,wHACZ,OAAQ,mIACV,EACA,GAAI,CACF,UAAW,wBACX,WAAY,sBACZ,OAAQ,uBACV,CACF,EAcaC,EAAa,CACxBC,EACAC,IACW,CACX,GAAIA,GAAS,QAAS,OAAOA,EAAQ,QACrC,IAAMC,EAAOD,GAAS,QAAU,KAChC,OAAOH,EAAeI,CAAI,EAAEF,CAAG,CACjC,EF7BO,IAAMG,EAAcC,GACzBC,EAAE,OAAO,EAAE,OAAQC,GAAQC,EAAYD,CAAG,EAAG,CAC3C,QAASE,EAAW,YAAaJ,CAAO,CAC1C,CAAC,EAKUK,EAAeL,GAC1BC,EAAE,OAAO,EAAE,OAAQC,GAAQI,EAAaJ,CAAG,EAAG,CAC5C,QAASE,EAAW,aAAcJ,CAAO,CAC3C,CAAC,EASUO,EAAkBP,GAC7BC,EACG,OAAO,EACP,OACEC,GAAQM,EAAgBN,EAAK,CAAE,WAAYF,GAAS,UAAW,CAAC,EACjE,CACE,QAASI,EAAW,SAAUJ,CAAO,CACvC,CACF","names":["z","isMelliCode","code","check","sum","acc","x","i","isCardNumber","sanitized","shouldDouble","digit","isIranianMobile","mobile","strictZero","corePattern","pattern","ERROR_MESSAGES","getMessage","key","options","lang","zMelliCode","options","z","val","isMelliCode","getMessage","zCardNumber","isCardNumber","zIranianMobile","isIranianMobile"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/utils.ts","../src/constants.ts"],"sourcesContent":["import { z } from \"zod\";\nimport {\n isMelliCode,\n isCardNumber,\n isIranianMobile,\n isSheba,\n isPostalCode,\n isLandline,\n} from \"./utils\";\nimport { getMessage, BaseOptions } from \"./constants\";\n\nexport const zMelliCode = (options?: BaseOptions) =>\n z.string().refine((val) => isMelliCode(val), {\n message: getMessage(\"melliCode\", options),\n });\n\nexport const zCardNumber = (options?: BaseOptions) =>\n z.string().refine((val) => isCardNumber(val), {\n message: getMessage(\"cardNumber\", options),\n });\n\ninterface MobileOptions extends BaseOptions {\n strictZero?: boolean | \"optional\";\n}\n\nexport const zIranianMobile = (options?: MobileOptions) =>\n z\n .string()\n .refine(\n (val) => isIranianMobile(val, { strictZero: options?.strictZero }),\n {\n message: getMessage(\"mobile\", options),\n }\n );\n\nexport const zSheba = (options?: BaseOptions) =>\n z.string().refine((val) => isSheba(val), {\n message: getMessage(\"sheba\", options),\n });\n\nexport const zPostalCode = (options?: BaseOptions) =>\n z.string().refine((val) => isPostalCode(val), {\n message: getMessage(\"postalCode\", options),\n });\n\nexport const zLandline = (options?: BaseOptions) =>\n z.string().refine((val) => isLandline(val), {\n message: getMessage(\"landline\", options),\n });\n\nexport {\n isMelliCode,\n isCardNumber,\n isIranianMobile,\n isSheba,\n isPostalCode,\n isLandline,\n};\n","export function isMelliCode(code: string): boolean {\n if (!/^\\d{10}$/.test(code)) return false;\n\n const check = parseInt(code[9]);\n if (/^(\\d)\\1+$/.test(code)) return false;\n\n const sum =\n code\n .substring(0, 9)\n .split(\"\")\n .reduce((acc, x, i) => acc + parseInt(x) * (10 - i), 0) % 11;\n\n return sum < 2 ? check === sum : check === 11 - sum;\n}\n\nexport function isCardNumber(code: string): boolean {\n const sanitized = code.replace(/[\\-\\s]/g, \"\");\n if (!/^\\d{16}$/.test(sanitized)) return false;\n\n let sum = 0;\n let shouldDouble = false;\n\n for (let i = sanitized.length - 1; i >= 0; i--) {\n let digit = parseInt(sanitized[i]);\n\n if (shouldDouble) {\n digit *= 2;\n if (digit > 9) digit -= 9;\n }\n\n sum += digit;\n shouldDouble = !shouldDouble;\n }\n\n return sum % 10 === 0;\n}\n\ninterface MobileValidationOptions {\n strictZero?: boolean | \"optional\";\n}\n\nexport function isIranianMobile(\n mobile: string,\n { strictZero = \"optional\" }: MobileValidationOptions = {}\n): boolean {\n const corePattern = \"9\\\\d{9}\";\n let pattern = \"\";\n\n if (strictZero === true) {\n pattern = `^0${corePattern}$`;\n } else if (strictZero === false) {\n pattern = `^${corePattern}$`;\n } else {\n pattern = `^(?:0|\\\\+98)?${corePattern}$`;\n }\n\n return new RegExp(pattern).test(mobile);\n}\n\nexport function isSheba(code: string): boolean {\n const iban = code.toUpperCase().replace(/[\\-\\s]/g, \"\");\n\n if (iban.length !== 26) return false;\n if (!iban.startsWith(\"IR\")) return false;\n\n const newStr = iban.substring(4) + \"1827\" + iban.substring(2, 4);\n\n const remainder = Array.from(newStr)\n .map((c) => parseInt(c, 36))\n .reduce((remainder, value) => {\n const v = value < 10 ? value : value;\n return (Number(remainder + \"\" + v) % 97).toString();\n }, \"\");\n\n return parseInt(remainder) === 1;\n}\n\nexport function isPostalCode(code: string): boolean {\n if (!/^\\d{10}$/.test(code)) return false;\n if (code.startsWith(\"0\")) return false;\n return true;\n}\n\nexport function isLandline(code: string): boolean {\n return /^0\\d{2}\\d{8}$/.test(code);\n}\n","export const ERROR_MESSAGES = {\n fa: {\n melliCode: \"کد ملی نامعتبر است\",\n cardNumber: \"شماره کارت نامعتبر است\",\n mobile: \"شماره موبایل نامعتبر است\",\n sheba: \"شماره شبا نامعتبر است\",\n postalCode: \"کد پستی نامعتبر است\",\n landline: \"شماره تلفن ثابت نامعتبر است\",\n },\n en: {\n melliCode: \"Invalid national code\",\n cardNumber: \"Invalid card number\",\n mobile: \"Invalid mobile number\",\n sheba: \"Invalid Sheba (IBAN) number\",\n postalCode: \"Invalid postal code\",\n landline: \"Invalid landline number\",\n },\n} as const;\n\nexport type Language = keyof typeof ERROR_MESSAGES;\n\nexport interface BaseOptions {\n message?: string;\n locale?: Language;\n}\n\nexport const getMessage = (\n key: keyof (typeof ERROR_MESSAGES)[\"fa\"],\n options?: BaseOptions\n): string => {\n if (options?.message) return options.message;\n const lang = options?.locale || \"fa\";\n return ERROR_MESSAGES[lang][key];\n};\n"],"mappings":"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","names":["z","isMelliCode","code","check","sum","acc","x","i","isCardNumber","sanitized","shouldDouble","digit","isIranianMobile","mobile","strictZero","corePattern","pattern","isSheba","iban","newStr","remainder","c","value","v","isPostalCode","isLandline","ERROR_MESSAGES","getMessage","key","options","lang","zMelliCode","options","z","val","isMelliCode","getMessage","zCardNumber","isCardNumber","zIranianMobile","isIranianMobile","zSheba","isSheba","zPostalCode","isPostalCode","zLandline","isLandline"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zod-ir",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Essential Zod validators for Iranian specific data (National Code, Card Number, Mobile)",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",