@stll/stdnum 2.0.0 → 2.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 +12 -13
- package/dist/_checksums/keccak.d.ts +5 -0
- package/dist/_checksums/keccak.js +118 -0
- package/dist/_checksums/keccak.js.map +1 -0
- package/dist/_checksums/mod112.js.map +1 -1
- package/dist/_checksums/sha256.d.ts +5 -0
- package/dist/_checksums/sha256.js +164 -0
- package/dist/_checksums/sha256.js.map +1 -0
- package/dist/_checksums/verhoeff.js.map +1 -1
- package/dist/_util/arabic.js.map +1 -1
- package/dist/_util/clean.js.map +1 -1
- package/dist/_util/date.js.map +1 -1
- package/dist/ad/nrt.js.map +1 -1
- package/dist/ae/eid.js.map +1 -1
- package/dist/ai/tin.js.map +1 -1
- package/dist/al/nipt.js.map +1 -1
- package/dist/am/tin.js.map +1 -1
- package/dist/ar/cbu.js.map +1 -1
- package/dist/ar/cuit.js.map +1 -1
- package/dist/ar/dni.js.map +1 -1
- package/dist/at/businessid.js.map +1 -1
- package/dist/at/vnr.js.map +1 -1
- package/dist/au/abn.js.map +1 -1
- package/dist/au/tfn.js.map +1 -1
- package/dist/az/voen.js.map +1 -1
- package/dist/ba/jmbg.js.map +1 -1
- package/dist/be/bis.js.map +1 -1
- package/dist/be/nn.js.map +1 -1
- package/dist/be/vat.js.map +1 -1
- package/dist/bg/egn.js.map +1 -1
- package/dist/bg/vat.js.map +1 -1
- package/dist/bh/cpr.js.map +1 -1
- package/dist/bic.js.map +1 -1
- package/dist/br/cnpj.js.map +1 -1
- package/dist/br/cpf.js.map +1 -1
- package/dist/by/unp.js.map +1 -1
- package/dist/bz/tin.js.map +1 -1
- package/dist/ch/uid.js.map +1 -1
- package/dist/ch/vat.js.map +1 -1
- package/dist/cl/rut.js.map +1 -1
- package/dist/cn/ric.js.map +1 -1
- package/dist/cn/uscc.js.map +1 -1
- package/dist/cr/cpf.js.map +1 -1
- package/dist/crypto/btcbase58.d.ts +11 -0
- package/dist/crypto/btcbase58.js +76 -0
- package/dist/crypto/btcbase58.js.map +1 -0
- package/dist/crypto/btcbech32.d.ts +19 -0
- package/dist/crypto/btcbech32.js +145 -0
- package/dist/crypto/btcbech32.js.map +1 -0
- package/dist/crypto/eth.d.ts +11 -0
- package/dist/crypto/eth.js +63 -0
- package/dist/crypto/eth.js.map +1 -0
- package/dist/crypto/mod.d.ts +12 -0
- package/dist/crypto/mod.js +16 -0
- package/dist/crypto/mod.js.map +1 -0
- package/dist/crypto/wallet.d.ts +10 -0
- package/dist/crypto/wallet.js +55 -0
- package/dist/crypto/wallet.js.map +1 -0
- package/dist/cu/ni.js.map +1 -1
- package/dist/cz/rc.js.map +1 -1
- package/dist/de/handelsreg.js.map +1 -1
- package/dist/de/idnr.js.map +1 -1
- package/dist/de/stnr.js.map +1 -1
- package/dist/de/svnr.js.map +1 -1
- package/dist/dk/cpr.js.map +1 -1
- package/dist/dk/cvr.js.map +1 -1
- package/dist/dk/vat.js.map +1 -1
- package/dist/do/rnc.js.map +1 -1
- package/dist/ec/ruc.js.map +1 -1
- package/dist/ee/ik.js.map +1 -1
- package/dist/ee/vat.js.map +1 -1
- package/dist/eg/tn.js.map +1 -1
- package/dist/es/dni.js.map +1 -1
- package/dist/es/nie.js.map +1 -1
- package/dist/es/nss.js.map +1 -1
- package/dist/eu/vat.js.map +1 -1
- package/dist/fi/hetu.js.map +1 -1
- package/dist/fi/vat.js.map +1 -1
- package/dist/fi/ytunnus.js.map +1 -1
- package/dist/fr/nif.js.map +1 -1
- package/dist/fr/nir.js.map +1 -1
- package/dist/fr/tva.js.map +1 -1
- package/dist/gb/mod.d.ts +3 -2
- package/dist/gb/mod.js +3 -1
- package/dist/gb/mod.js.map +1 -1
- package/dist/gb/nhs.d.ts +11 -0
- package/dist/gb/nhs.js +51 -0
- package/dist/gb/nhs.js.map +1 -0
- package/dist/gb/nino.js.map +1 -1
- package/dist/gb/sedol.js.map +1 -1
- package/dist/gb/utr.js.map +1 -1
- package/dist/gb/vat.js.map +1 -1
- package/dist/ge/pin.js.map +1 -1
- package/dist/gh/tin.js.map +1 -1
- package/dist/gr/amka.js.map +1 -1
- package/dist/gt/nit.js.map +1 -1
- package/dist/hk/hkid.js.map +1 -1
- package/dist/hu/vat.js.map +1 -1
- package/dist/iban.js.map +1 -1
- package/dist/id/npwp.js.map +1 -1
- package/dist/ie/pps.js.map +1 -1
- package/dist/ie/vat.js.map +1 -1
- package/dist/il/idnr.js.map +1 -1
- package/dist/in/aadhaar.js.map +1 -1
- package/dist/in/gstin.js.map +1 -1
- package/dist/in/pan.js.map +1 -1
- package/dist/index.d.ts +76 -75
- package/dist/index.js +2 -1
- package/dist/ir/nid.js.map +1 -1
- package/dist/is/kennitala.js.map +1 -1
- package/dist/isin.js.map +1 -1
- package/dist/it/codicefiscale.js.map +1 -1
- package/dist/it/iva.js.map +1 -1
- package/dist/kr/rrn.js.map +1 -1
- package/dist/kw/civil.js.map +1 -1
- package/dist/kz/iin.js.map +1 -1
- package/dist/lei.js.map +1 -1
- package/dist/li/peid.js.map +1 -1
- package/dist/lk/nic.js.map +1 -1
- package/dist/lt/asmens.js.map +1 -1
- package/dist/lt/vat.js.map +1 -1
- package/dist/lu/vat.js.map +1 -1
- package/dist/lv/vat.js.map +1 -1
- package/dist/ma/ice.js.map +1 -1
- package/dist/mc/tva.js.map +1 -1
- package/dist/mt/vat.js.map +1 -1
- package/dist/mu/brn.js.map +1 -1
- package/dist/mx/clabe.js.map +1 -1
- package/dist/mx/curp.js.map +1 -1
- package/dist/mx/rfc.js.map +1 -1
- package/dist/my/nric.js.map +1 -1
- package/dist/ni/ruc.js.map +1 -1
- package/dist/nl/bsn.js.map +1 -1
- package/dist/nl/vat.js.map +1 -1
- package/dist/no/fodselsnummer.js.map +1 -1
- package/dist/no/mva.js.map +1 -1
- package/dist/no/orgnr.js.map +1 -1
- package/dist/nz/ird.js.map +1 -1
- package/dist/pa/ruc.js.map +1 -1
- package/dist/patterns.js.map +1 -1
- package/dist/pe/ruc.js.map +1 -1
- package/dist/pl/pesel.js.map +1 -1
- package/dist/pl/regon.js.map +1 -1
- package/dist/pt/cc.js.map +1 -1
- package/dist/ro/cnp.js.map +1 -1
- package/dist/ro/vat.js.map +1 -1
- package/dist/rs/pib.js.map +1 -1
- package/dist/ru/inn.js.map +1 -1
- package/dist/se/personnummer.js.map +1 -1
- package/dist/sg/uen.js.map +1 -1
- package/dist/si/emso.js.map +1 -1
- package/dist/si/vat.js.map +1 -1
- package/dist/sk/dic.js.map +1 -1
- package/dist/tw/ubn.js.map +1 -1
- package/dist/ua/edrpou.js.map +1 -1
- package/dist/us/itin.js.map +1 -1
- package/dist/us/rtn.js.map +1 -1
- package/dist/us/ssn.js.map +1 -1
- package/dist/uy/rut.js.map +1 -1
- package/dist/ve/rif.js.map +1 -1
- package/dist/vn/mst.js.map +1 -1
- package/dist/za/idnr.js.map +1 -1
- package/package.json +31 -6
- package/src/_checksums/keccak.ts +145 -0
- package/src/_checksums/sha256.ts +125 -0
- package/src/crypto/btcbase58.ts +144 -0
- package/src/crypto/btcbech32.ts +210 -0
- package/src/crypto/eth.ts +114 -0
- package/src/crypto/mod.ts +4 -0
- package/src/crypto/wallet.ts +67 -0
- package/src/gb/mod.ts +1 -0
- package/src/gb/nhs.ts +92 -0
- package/src/index.ts +1 -0
package/dist/kr/rrn.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rrn.js","names":[],"sources":["../../src/kr/rrn.ts"],"sourcesContent":["/**\n * RRN (Resident Registration Number, 주민등록번호).\n *\n * Korean Resident Registration Number. 13 digits\n * formatted as YYMMDD-GPPCCSD. Encodes date of\n * birth, gender/century, place of birth, community\n * center, serial, and a check digit.\n *\n * @see https://en.wikipedia.org/wiki/Resident_registration_number\n */\n\nimport type {\n ParsedPersonId,\n ValidateResult,\n CountryValidator,\n} from \"../types\";\n\nimport { clean } from \"#util/clean\";\nimport { isValidDate } from \"#util/date\";\nimport { randomDigits, randomInt } from \"#util/generate\";\nimport { err } from \"#util/result\";\nimport { isdigits } from \"#util/strings\";\n\nconst compact = (value: string): string =>\n clean(value, \" -/\");\n\nconst CHECK_WEIGHTS = [2, 3, 4, 5, 6, 7, 8, 9, 2, 3, 4, 5];\n\n/**\n * Compute the check digit for the first 12 digits.\n * Formula: (11 - sum(w_i * d_i) % 11) % 10.\n */\nconst checkDigit = (digits: string): number => {\n let sum = 0;\n for (let i = 0; i < 12; i++) {\n sum +=\n (CHECK_WEIGHTS[i] ?? 0) * Number(digits.charAt(i));\n }\n return (11 - (sum % 11)) % 10;\n};\n\n/** Century prefix derived from the 7th digit. */\nconst CENTURY_BY_GENDER: Record<string, number> = {\n \"0\": 1800,\n \"1\": 1900,\n \"2\": 1900,\n \"3\": 2000,\n \"4\": 2000,\n \"5\": 1900,\n \"6\": 1900,\n \"7\": 2000,\n \"8\": 2000,\n \"9\": 1800,\n};\n\n/**\n * Resolve birth date components from a compacted RRN.\n * Returns null if the date is invalid.\n */\nconst resolveBirthDate = (\n v: string,\n): { year: number; month: number; day: number } | null => {\n const century = CENTURY_BY_GENDER[v.charAt(6)];\n if (century === undefined) return null;\n\n const year = century + Number(v.slice(0, 2));\n const month = Number(v.slice(2, 4));\n const day = Number(v.slice(4, 6));\n\n if (!isValidDate(year, month, day)) return null;\n return { year, month, day };\n};\n\nconst validate = (value: string): ValidateResult => {\n const v = compact(value);\n if (v.length !== 13) {\n return err(\n \"INVALID_LENGTH\",\n \"RRN must be exactly 13 digits\",\n );\n }\n if (!isdigits(v)) {\n return err(\n \"INVALID_FORMAT\",\n \"RRN must contain only digits\",\n );\n }\n if (resolveBirthDate(v) === null) {\n return err(\n \"INVALID_COMPONENT\",\n \"RRN contains an invalid date of birth\",\n );\n }\n const place = Number(v.slice(7, 9));\n if (place > 96) {\n return err(\n \"INVALID_COMPONENT\",\n \"RRN place-of-birth code must be <= 96\",\n );\n }\n if (checkDigit(v.slice(0, 12)) !== Number(v[12])) {\n return err(\n \"INVALID_CHECKSUM\",\n \"RRN check digit does not match\",\n );\n }\n return { valid: true, compact: v };\n};\n\nconst format = (value: string): string => {\n const v = compact(value);\n if (v.length === 13) {\n return `${v.slice(0, 6)}-${v.slice(6)}`;\n }\n return v;\n};\n\n/**\n * Extract birth date and gender from an RRN.\n * Returns null if the value is not valid.\n *\n * Gender digit mapping:\n * 1, 3 = male Korean; 2, 4 = female Korean\n * 5, 7 = male foreigner; 6, 8 = female foreigner\n * 0, 9 = pre-1800 (male/female by convention)\n */\nconst parse = (value: string): ParsedPersonId | null => {\n const result = validate(value);\n if (!result.valid) return null;\n\n const v = result.compact;\n const bd = resolveBirthDate(v);\n if (bd === null) return null;\n\n const genderDigit = v.charAt(6);\n const isMale = \"13579\".includes(genderDigit);\n\n return {\n birthDate: new Date(bd.year, bd.month - 1, bd.day),\n gender: isMale ? \"male\" : \"female\",\n };\n};\n\n/** Generate a random valid Korean RRN. */\nconst generate = (): string => {\n const year = randomInt(1900, 2024);\n const month = String(randomInt(1, 12)).padStart(2, \"0\");\n const day = String(randomInt(1, 28)).padStart(2, \"0\");\n const yy = String(year % 100).padStart(2, \"0\");\n const genderDigit =\n year >= 2000\n ? String(randomInt(3, 4))\n : String(randomInt(1, 2));\n const place = String(randomInt(0, 96)).padStart(2, \"0\");\n const body = `${yy}${month}${day}${genderDigit}${place}${randomDigits(3)}`;\n return `${body}${String(checkDigit(body))}`;\n};\n\n/** Korean Resident Registration Number. */\nconst rrn: CountryValidator<\"KR\", ParsedPersonId> = {\n name: \"Korean Resident Registration Number\",\n localName: \"주민등록번호\",\n abbreviation: \"RRN\",\n aliases: [\n \"RRN\",\n \"주민등록번호\",\n \"Resident Registration Number\",\n ] as const,\n candidatePattern: \"\\\\d{6}[\\\\s-]?\\\\d{7}\",\n scope: \"country\",\n country: \"KR\",\n entityType: \"person\",\n description:\n \"13-digit personal identifier issued to all\" +\n \" residents of South Korea\",\n sourceUrl:\n \"https://en.wikipedia.org/wiki/\" +\n \"Resident_registration_number\",\n lengths: [13] as const,\n examples: [\"9710139019902\", \"9501011000109\"] as const,\n compact,\n format,\n parse,\n validate,\n generate,\n};\n\nexport default rrn;\nexport { compact, format, generate, parse, validate };\n"],"mappings":";;;;;;AAuBA,MAAM,WAAW,UACf,MAAM,OAAO,KAAK;AAEpB,MAAM,gBAAgB;CAAC;CAAG;CAAG;CAAG;CAAG;CAAG;CAAG;CAAG;CAAG;CAAG;CAAG;CAAG;AAAC;;;;;AAMzD,MAAM,cAAc,WAA2B;CAC7C,IAAI,MAAM;CACV,KAAK,IAAI,IAAI,GAAG,IAAI,IAAI,KACtB,QACG,cAAc,MAAM,KAAK,OAAO,OAAO,OAAO,CAAC,CAAC;CAErD,QAAQ,KAAM,MAAM,MAAO;AAC7B;;AAGA,MAAM,oBAA4C;CAChD,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;AACP;;;;;AAMA,MAAM,oBACJ,MACwD;CACxD,MAAM,UAAU,kBAAkB,EAAE,OAAO,CAAC;CAC5C,IAAI,YAAY,KAAA,GAAW,OAAO;CAElC,MAAM,OAAO,UAAU,OAAO,EAAE,MAAM,GAAG,CAAC,CAAC;CAC3C,MAAM,QAAQ,OAAO,EAAE,MAAM,GAAG,CAAC,CAAC;CAClC,MAAM,MAAM,OAAO,EAAE,MAAM,GAAG,CAAC,CAAC;CAEhC,IAAI,CAAC,YAAY,MAAM,OAAO,GAAG,GAAG,OAAO;CAC3C,OAAO;EAAE;EAAM;EAAO;CAAI;AAC5B;AAEA,MAAM,YAAY,UAAkC;CAClD,MAAM,IAAI,QAAQ,KAAK;CACvB,IAAI,EAAE,WAAW,IACf,OAAO,IACL,kBACA,+BACF;CAEF,IAAI,CAAC,SAAS,CAAC,GACb,OAAO,IACL,kBACA,8BACF;CAEF,IAAI,iBAAiB,CAAC,MAAM,MAC1B,OAAO,IACL,qBACA,uCACF;CAGF,IADc,OAAO,EAAE,MAAM,GAAG,CAAC,CACzB,IAAI,IACV,OAAO,IACL,qBACA,uCACF;CAEF,IAAI,WAAW,EAAE,MAAM,GAAG,EAAE,CAAC,MAAM,OAAO,EAAE,GAAG,GAC7C,OAAO,IACL,oBACA,gCACF;CAEF,OAAO;EAAE,OAAO;EAAM,SAAS;CAAE;AACnC;AAEA,MAAM,UAAU,UAA0B;CACxC,MAAM,IAAI,QAAQ,KAAK;CACvB,IAAI,EAAE,WAAW,IACf,OAAO,GAAG,EAAE,MAAM,GAAG,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC;CAEtC,OAAO;AACT;;;;;;;;;;AAWA,MAAM,SAAS,UAAyC;CACtD,MAAM,SAAS,SAAS,KAAK;CAC7B,IAAI,CAAC,OAAO,OAAO,OAAO;CAE1B,MAAM,IAAI,OAAO;CACjB,MAAM,KAAK,iBAAiB,CAAC;CAC7B,IAAI,OAAO,MAAM,OAAO;CAExB,MAAM,cAAc,EAAE,OAAO,CAAC;CAC9B,MAAM,SAAS,QAAQ,SAAS,WAAW;CAE3C,OAAO;EACL,WAAW,IAAI,KAAK,GAAG,MAAM,GAAG,QAAQ,GAAG,GAAG,GAAG;EACjD,QAAQ,SAAS,SAAS;CAC5B;AACF;;AAGA,MAAM,iBAAyB;CAC7B,MAAM,OAAO,UAAU,MAAM,IAAI;CACjC,MAAM,QAAQ,OAAO,UAAU,GAAG,EAAE,CAAC,
|
|
1
|
+
{"version":3,"file":"rrn.js","names":[],"sources":["../../src/kr/rrn.ts"],"sourcesContent":["/**\n * RRN (Resident Registration Number, 주민등록번호).\n *\n * Korean Resident Registration Number. 13 digits\n * formatted as YYMMDD-GPPCCSD. Encodes date of\n * birth, gender/century, place of birth, community\n * center, serial, and a check digit.\n *\n * @see https://en.wikipedia.org/wiki/Resident_registration_number\n */\n\nimport type {\n ParsedPersonId,\n ValidateResult,\n CountryValidator,\n} from \"../types\";\n\nimport { clean } from \"#util/clean\";\nimport { isValidDate } from \"#util/date\";\nimport { randomDigits, randomInt } from \"#util/generate\";\nimport { err } from \"#util/result\";\nimport { isdigits } from \"#util/strings\";\n\nconst compact = (value: string): string =>\n clean(value, \" -/\");\n\nconst CHECK_WEIGHTS = [2, 3, 4, 5, 6, 7, 8, 9, 2, 3, 4, 5];\n\n/**\n * Compute the check digit for the first 12 digits.\n * Formula: (11 - sum(w_i * d_i) % 11) % 10.\n */\nconst checkDigit = (digits: string): number => {\n let sum = 0;\n for (let i = 0; i < 12; i++) {\n sum +=\n (CHECK_WEIGHTS[i] ?? 0) * Number(digits.charAt(i));\n }\n return (11 - (sum % 11)) % 10;\n};\n\n/** Century prefix derived from the 7th digit. */\nconst CENTURY_BY_GENDER: Record<string, number> = {\n \"0\": 1800,\n \"1\": 1900,\n \"2\": 1900,\n \"3\": 2000,\n \"4\": 2000,\n \"5\": 1900,\n \"6\": 1900,\n \"7\": 2000,\n \"8\": 2000,\n \"9\": 1800,\n};\n\n/**\n * Resolve birth date components from a compacted RRN.\n * Returns null if the date is invalid.\n */\nconst resolveBirthDate = (\n v: string,\n): { year: number; month: number; day: number } | null => {\n const century = CENTURY_BY_GENDER[v.charAt(6)];\n if (century === undefined) return null;\n\n const year = century + Number(v.slice(0, 2));\n const month = Number(v.slice(2, 4));\n const day = Number(v.slice(4, 6));\n\n if (!isValidDate(year, month, day)) return null;\n return { year, month, day };\n};\n\nconst validate = (value: string): ValidateResult => {\n const v = compact(value);\n if (v.length !== 13) {\n return err(\n \"INVALID_LENGTH\",\n \"RRN must be exactly 13 digits\",\n );\n }\n if (!isdigits(v)) {\n return err(\n \"INVALID_FORMAT\",\n \"RRN must contain only digits\",\n );\n }\n if (resolveBirthDate(v) === null) {\n return err(\n \"INVALID_COMPONENT\",\n \"RRN contains an invalid date of birth\",\n );\n }\n const place = Number(v.slice(7, 9));\n if (place > 96) {\n return err(\n \"INVALID_COMPONENT\",\n \"RRN place-of-birth code must be <= 96\",\n );\n }\n if (checkDigit(v.slice(0, 12)) !== Number(v[12])) {\n return err(\n \"INVALID_CHECKSUM\",\n \"RRN check digit does not match\",\n );\n }\n return { valid: true, compact: v };\n};\n\nconst format = (value: string): string => {\n const v = compact(value);\n if (v.length === 13) {\n return `${v.slice(0, 6)}-${v.slice(6)}`;\n }\n return v;\n};\n\n/**\n * Extract birth date and gender from an RRN.\n * Returns null if the value is not valid.\n *\n * Gender digit mapping:\n * 1, 3 = male Korean; 2, 4 = female Korean\n * 5, 7 = male foreigner; 6, 8 = female foreigner\n * 0, 9 = pre-1800 (male/female by convention)\n */\nconst parse = (value: string): ParsedPersonId | null => {\n const result = validate(value);\n if (!result.valid) return null;\n\n const v = result.compact;\n const bd = resolveBirthDate(v);\n if (bd === null) return null;\n\n const genderDigit = v.charAt(6);\n const isMale = \"13579\".includes(genderDigit);\n\n return {\n birthDate: new Date(bd.year, bd.month - 1, bd.day),\n gender: isMale ? \"male\" : \"female\",\n };\n};\n\n/** Generate a random valid Korean RRN. */\nconst generate = (): string => {\n const year = randomInt(1900, 2024);\n const month = String(randomInt(1, 12)).padStart(2, \"0\");\n const day = String(randomInt(1, 28)).padStart(2, \"0\");\n const yy = String(year % 100).padStart(2, \"0\");\n const genderDigit =\n year >= 2000\n ? String(randomInt(3, 4))\n : String(randomInt(1, 2));\n const place = String(randomInt(0, 96)).padStart(2, \"0\");\n const body = `${yy}${month}${day}${genderDigit}${place}${randomDigits(3)}`;\n return `${body}${String(checkDigit(body))}`;\n};\n\n/** Korean Resident Registration Number. */\nconst rrn: CountryValidator<\"KR\", ParsedPersonId> = {\n name: \"Korean Resident Registration Number\",\n localName: \"주민등록번호\",\n abbreviation: \"RRN\",\n aliases: [\n \"RRN\",\n \"주민등록번호\",\n \"Resident Registration Number\",\n ] as const,\n candidatePattern: \"\\\\d{6}[\\\\s-]?\\\\d{7}\",\n scope: \"country\",\n country: \"KR\",\n entityType: \"person\",\n description:\n \"13-digit personal identifier issued to all\" +\n \" residents of South Korea\",\n sourceUrl:\n \"https://en.wikipedia.org/wiki/\" +\n \"Resident_registration_number\",\n lengths: [13] as const,\n examples: [\"9710139019902\", \"9501011000109\"] as const,\n compact,\n format,\n parse,\n validate,\n generate,\n};\n\nexport default rrn;\nexport { compact, format, generate, parse, validate };\n"],"mappings":"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"}
|
package/dist/kw/civil.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"civil.js","names":[],"sources":["../../src/kw/civil.ts"],"sourcesContent":["/**\n * Civil Number (الرقم المدني, Kuwait).\n *\n * 12-digit number issued by PACI (Public Authority\n * for Civil Information):\n * C = century indicator (2 = 1900s, 3 = 2000s)\n * YYMMDD = birth date\n * SSSS = serial number\n * K = check digit (mod 11 weighted sum)\n *\n * @see https://prakhar.me/articles/kuwait-civil-id-checksum/\n * @see https://kuwaitsexpat.com/kuwait-civil-id-format/\n */\n\nimport type {\n ParsedBirthDate,\n ValidateResult,\n CountryValidator,\n} from \"../types\";\n\nimport { clean } from \"#util/clean\";\nimport { isValidDate } from \"#util/date\";\nimport { randomDigits, randomInt } from \"#util/generate\";\nimport { err } from \"#util/result\";\nimport { isdigits } from \"#util/strings\";\n\nconst WEIGHTS = [2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2] as const;\n\nconst centuryMap: Record<number, number> = {\n 2: 1900,\n 3: 2000,\n};\n\nconst compact = (value: string): string =>\n clean(value, \" -\");\n\nconst calcCheckDigit = (digits: string): number => {\n let sum = 0;\n for (const [i, weight] of WEIGHTS.entries()) {\n sum += Number(digits[i]) * weight;\n }\n return (11 - (sum % 11)) % 11;\n};\n\nconst validate = (value: string): ValidateResult => {\n const v = compact(value);\n\n if (v.length !== 12) {\n return err(\n \"INVALID_LENGTH\",\n \"Kuwait civil number must be 12 digits\",\n );\n }\n\n if (!isdigits(v)) {\n return err(\n \"INVALID_FORMAT\",\n \"Kuwait civil number must contain only digits\",\n );\n }\n\n const centuryDigit = Number(v[0]);\n if (centuryDigit !== 2 && centuryDigit !== 3) {\n return err(\n \"INVALID_COMPONENT\",\n \"Century digit must be 2 or 3\",\n );\n }\n\n const yearBase = centuryMap[centuryDigit] ?? 1900;\n const yy = Number(v.slice(1, 3));\n const mm = Number(v.slice(3, 5));\n const dd = Number(v.slice(5, 7));\n const year = yearBase + yy;\n\n if (!isValidDate(year, mm, dd)) {\n return err(\n \"INVALID_COMPONENT\",\n \"Civil number contains an invalid birth date\",\n );\n }\n\n const expected = calcCheckDigit(v.slice(0, 11));\n if (expected !== Number(v[11])) {\n return err(\n \"INVALID_CHECKSUM\",\n \"Civil number check digit does not match\",\n );\n }\n\n return { valid: true, compact: v };\n};\n\nconst format = (value: string): string => {\n const v = compact(value);\n return `${v.slice(0, 1)} ${v.slice(1, 7)} ${v.slice(7, 11)} ${v.slice(11)}`;\n};\n\n/**\n * Extract birth date from a Kuwait civil number.\n * Gender is not encoded in the number.\n * Returns null if the value is not valid.\n */\nconst parse = (value: string): ParsedBirthDate | null => {\n const result = validate(value);\n if (!result.valid) return null;\n\n const v = result.compact;\n const centuryDigit = Number(v[0]);\n const yearBase = centuryMap[centuryDigit] ?? 1900;\n const yy = Number(v.slice(1, 3));\n const mm = Number(v.slice(3, 5));\n const dd = Number(v.slice(5, 7));\n const year = yearBase + yy;\n\n return {\n birthDate: new Date(year, mm - 1, dd),\n };\n};\n\n/** Generate a random valid Kuwait civil number. */\nconst generate = (): string => {\n for (;;) {\n const year = randomInt(1950, 2024);\n const month = String(randomInt(1, 12)).padStart(2, \"0\");\n const day = String(randomInt(1, 28)).padStart(2, \"0\");\n const body =\n `${year >= 2000 ? \"3\" : \"2\"}` +\n `${String(year % 100).padStart(2, \"0\")}` +\n `${month}${day}${randomDigits(4)}`;\n const check = calcCheckDigit(body);\n if (check > 9) continue;\n return `${body}${String(check)}`;\n }\n};\n\n/** Kuwait Civil Number (الرقم المدني). */\nconst civil: CountryValidator<\"KW\", ParsedBirthDate> = {\n name: \"Civil Number\",\n localName: \"الرقم المدني\",\n abbreviation: \"Civil ID\",\n aliases: [\"Civil ID\", \"الرقم المدني\"] as const,\n candidatePattern: \"\\\\d{12}\",\n scope: \"country\",\n country: \"KW\",\n entityType: \"person\",\n lengths: [12] as const,\n examples: [\"289011200032\", \"305031512348\"] as const,\n description:\n \"12-digit civil identification number issued by PACI\",\n sourceUrl:\n \"https://kuwaitsexpat.com/kuwait-civil-id-format/\",\n compact,\n format,\n parse,\n validate,\n generate,\n};\n\nexport default civil;\nexport { compact, format, generate, parse, validate };\n"],"mappings":";;;;;;AA0BA,MAAM,UAAU;CAAC;CAAG;CAAG;CAAG;CAAG;CAAG;CAAG;CAAI;CAAG;CAAG;CAAG;AAAC;AAEjD,MAAM,aAAqC;CACzC,GAAG;CACH,GAAG;AACL;AAEA,MAAM,WAAW,UACf,MAAM,OAAO,IAAI;AAEnB,MAAM,kBAAkB,WAA2B;CACjD,IAAI,MAAM;CACV,KAAK,MAAM,CAAC,GAAG,WAAW,QAAQ,QAAQ,GACxC,OAAO,OAAO,OAAO,EAAE,IAAI;CAE7B,QAAQ,KAAM,MAAM,MAAO;AAC7B;AAEA,MAAM,YAAY,UAAkC;CAClD,MAAM,IAAI,QAAQ,KAAK;CAEvB,IAAI,EAAE,WAAW,IACf,OAAO,IACL,kBACA,uCACF;CAGF,IAAI,CAAC,SAAS,CAAC,GACb,OAAO,IACL,kBACA,8CACF;CAGF,MAAM,eAAe,OAAO,EAAE,EAAE;CAChC,IAAI,iBAAiB,KAAK,iBAAiB,GACzC,OAAO,IACL,qBACA,8BACF;CAGF,MAAM,WAAW,WAAW,iBAAiB;CAC7C,MAAM,KAAK,OAAO,EAAE,MAAM,GAAG,CAAC,CAAC;CAC/B,MAAM,KAAK,OAAO,EAAE,MAAM,GAAG,CAAC,CAAC;CAC/B,MAAM,KAAK,OAAO,EAAE,MAAM,GAAG,CAAC,CAAC;CAG/B,IAAI,CAAC,YAFQ,WAAW,IAED,IAAI,EAAE,GAC3B,OAAO,IACL,qBACA,6CACF;CAIF,IADiB,eAAe,EAAE,MAAM,GAAG,EAAE,CAClC,MAAM,OAAO,EAAE,GAAG,GAC3B,OAAO,IACL,oBACA,yCACF;CAGF,OAAO;EAAE,OAAO;EAAM,SAAS;CAAE;AACnC;AAEA,MAAM,UAAU,UAA0B;CACxC,MAAM,IAAI,QAAQ,KAAK;CACvB,OAAO,GAAG,EAAE,MAAM,GAAG,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE;AAC1E;;;;;;AAOA,MAAM,SAAS,UAA0C;CACvD,MAAM,SAAS,SAAS,KAAK;CAC7B,IAAI,CAAC,OAAO,OAAO,OAAO;CAE1B,MAAM,IAAI,OAAO;CAEjB,MAAM,WAAW,WADI,OAAO,EAAE,EACS,MAAM;CAC7C,MAAM,KAAK,OAAO,EAAE,MAAM,GAAG,CAAC,CAAC;CAC/B,MAAM,KAAK,OAAO,EAAE,MAAM,GAAG,CAAC,CAAC;CAC/B,MAAM,KAAK,OAAO,EAAE,MAAM,GAAG,CAAC,CAAC;CAC/B,MAAM,OAAO,WAAW;CAExB,OAAO,EACL,WAAW,IAAI,KAAK,MAAM,KAAK,GAAG,EAAE,EACtC;AACF;;AAGA,MAAM,iBAAyB;CAC7B,SAAS;EACP,MAAM,OAAO,UAAU,MAAM,IAAI;EACjC,MAAM,QAAQ,OAAO,UAAU,GAAG,EAAE,CAAC,
|
|
1
|
+
{"version":3,"file":"civil.js","names":[],"sources":["../../src/kw/civil.ts"],"sourcesContent":["/**\n * Civil Number (الرقم المدني, Kuwait).\n *\n * 12-digit number issued by PACI (Public Authority\n * for Civil Information):\n * C = century indicator (2 = 1900s, 3 = 2000s)\n * YYMMDD = birth date\n * SSSS = serial number\n * K = check digit (mod 11 weighted sum)\n *\n * @see https://prakhar.me/articles/kuwait-civil-id-checksum/\n * @see https://kuwaitsexpat.com/kuwait-civil-id-format/\n */\n\nimport type {\n ParsedBirthDate,\n ValidateResult,\n CountryValidator,\n} from \"../types\";\n\nimport { clean } from \"#util/clean\";\nimport { isValidDate } from \"#util/date\";\nimport { randomDigits, randomInt } from \"#util/generate\";\nimport { err } from \"#util/result\";\nimport { isdigits } from \"#util/strings\";\n\nconst WEIGHTS = [2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2] as const;\n\nconst centuryMap: Record<number, number> = {\n 2: 1900,\n 3: 2000,\n};\n\nconst compact = (value: string): string =>\n clean(value, \" -\");\n\nconst calcCheckDigit = (digits: string): number => {\n let sum = 0;\n for (const [i, weight] of WEIGHTS.entries()) {\n sum += Number(digits[i]) * weight;\n }\n return (11 - (sum % 11)) % 11;\n};\n\nconst validate = (value: string): ValidateResult => {\n const v = compact(value);\n\n if (v.length !== 12) {\n return err(\n \"INVALID_LENGTH\",\n \"Kuwait civil number must be 12 digits\",\n );\n }\n\n if (!isdigits(v)) {\n return err(\n \"INVALID_FORMAT\",\n \"Kuwait civil number must contain only digits\",\n );\n }\n\n const centuryDigit = Number(v[0]);\n if (centuryDigit !== 2 && centuryDigit !== 3) {\n return err(\n \"INVALID_COMPONENT\",\n \"Century digit must be 2 or 3\",\n );\n }\n\n const yearBase = centuryMap[centuryDigit] ?? 1900;\n const yy = Number(v.slice(1, 3));\n const mm = Number(v.slice(3, 5));\n const dd = Number(v.slice(5, 7));\n const year = yearBase + yy;\n\n if (!isValidDate(year, mm, dd)) {\n return err(\n \"INVALID_COMPONENT\",\n \"Civil number contains an invalid birth date\",\n );\n }\n\n const expected = calcCheckDigit(v.slice(0, 11));\n if (expected !== Number(v[11])) {\n return err(\n \"INVALID_CHECKSUM\",\n \"Civil number check digit does not match\",\n );\n }\n\n return { valid: true, compact: v };\n};\n\nconst format = (value: string): string => {\n const v = compact(value);\n return `${v.slice(0, 1)} ${v.slice(1, 7)} ${v.slice(7, 11)} ${v.slice(11)}`;\n};\n\n/**\n * Extract birth date from a Kuwait civil number.\n * Gender is not encoded in the number.\n * Returns null if the value is not valid.\n */\nconst parse = (value: string): ParsedBirthDate | null => {\n const result = validate(value);\n if (!result.valid) return null;\n\n const v = result.compact;\n const centuryDigit = Number(v[0]);\n const yearBase = centuryMap[centuryDigit] ?? 1900;\n const yy = Number(v.slice(1, 3));\n const mm = Number(v.slice(3, 5));\n const dd = Number(v.slice(5, 7));\n const year = yearBase + yy;\n\n return {\n birthDate: new Date(year, mm - 1, dd),\n };\n};\n\n/** Generate a random valid Kuwait civil number. */\nconst generate = (): string => {\n for (;;) {\n const year = randomInt(1950, 2024);\n const month = String(randomInt(1, 12)).padStart(2, \"0\");\n const day = String(randomInt(1, 28)).padStart(2, \"0\");\n const body =\n `${year >= 2000 ? \"3\" : \"2\"}` +\n `${String(year % 100).padStart(2, \"0\")}` +\n `${month}${day}${randomDigits(4)}`;\n const check = calcCheckDigit(body);\n if (check > 9) continue;\n return `${body}${String(check)}`;\n }\n};\n\n/** Kuwait Civil Number (الرقم المدني). */\nconst civil: CountryValidator<\"KW\", ParsedBirthDate> = {\n name: \"Civil Number\",\n localName: \"الرقم المدني\",\n abbreviation: \"Civil ID\",\n aliases: [\"Civil ID\", \"الرقم المدني\"] as const,\n candidatePattern: \"\\\\d{12}\",\n scope: \"country\",\n country: \"KW\",\n entityType: \"person\",\n lengths: [12] as const,\n examples: [\"289011200032\", \"305031512348\"] as const,\n description:\n \"12-digit civil identification number issued by PACI\",\n sourceUrl:\n \"https://kuwaitsexpat.com/kuwait-civil-id-format/\",\n compact,\n format,\n parse,\n validate,\n generate,\n};\n\nexport default civil;\nexport { compact, format, generate, parse, validate };\n"],"mappings":"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"}
|
package/dist/kz/iin.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"iin.js","names":[],"sources":["../../src/kz/iin.ts"],"sourcesContent":["/**\n * IIN (Individual Identification Number,\n * Жеке сәйкестендіру нөмірі / ИИН).\n *\n * Kazakhstan individual identification number. 12 digits:\n * YYMMDD = birth date\n * C = century/gender (1-6)\n * SSSS = serial number\n * K = check digit (two-pass weighted sum mod 11)\n *\n * Odd century digits = male, even = female.\n * 1-2 = 1800s, 3-4 = 1900s, 5-6 = 2000s.\n *\n * @see https://www.oecd.org/tax/automatic-exchange/crs-implementation-and-assistance/tax-identification-numbers/Kazakhstan-TIN.pdf\n */\n\nimport type {\n ParsedPersonId,\n ValidateResult,\n CountryValidator,\n} from \"../types\";\n\nimport { weightedSum } from \"#checksums/weighted-sum\";\nimport { clean } from \"#util/clean\";\nimport { isValidDate } from \"#util/date\";\nimport { randomDigits, randomInt } from \"#util/generate\";\nimport { err } from \"#util/result\";\nimport { isdigits } from \"#util/strings\";\n\nconst WEIGHTS_1 = [\n 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,\n] as const;\nconst WEIGHTS_2 = [\n 3, 4, 5, 6, 7, 8, 9, 10, 11, 1, 2,\n] as const;\n\nconst centuryMap: Record<number, number> = {\n 1: 1800,\n 2: 1800,\n 3: 1900,\n 4: 1900,\n 5: 2000,\n 6: 2000,\n};\n\nconst compact = (value: string): string =>\n clean(value, \" -\").trim();\n\n/**\n * Two-pass check digit: weighted sum mod 11 with\n * fallback to second weight set.\n */\nconst calcCheckDigit = (digits: string): number => {\n let remainder = weightedSum(digits, WEIGHTS_1, 11);\n if (remainder === 10) {\n remainder = weightedSum(digits, WEIGHTS_2, 11);\n }\n return remainder === 10 ? 0 : remainder;\n};\n\nconst validate = (value: string): ValidateResult => {\n const v = compact(value);\n\n if (v.length !== 12) {\n return err(\n \"INVALID_LENGTH\",\n \"Kazakhstan IIN must be 12 digits\",\n );\n }\n\n if (!isdigits(v)) {\n return err(\n \"INVALID_FORMAT\",\n \"Kazakhstan IIN must contain only digits\",\n );\n }\n\n const centuryDigit = Number(v[6]);\n if (centuryDigit < 1 || centuryDigit > 6) {\n return err(\n \"INVALID_COMPONENT\",\n \"IIN century/gender digit must be 1-6\",\n );\n }\n\n const yearBase = centuryMap[centuryDigit] ?? 1900;\n const yy = Number(v.slice(0, 2));\n const mm = Number(v.slice(2, 4));\n const dd = Number(v.slice(4, 6));\n const year = yearBase + yy;\n\n if (!isValidDate(year, mm, dd)) {\n return err(\n \"INVALID_COMPONENT\",\n \"IIN contains an invalid birth date\",\n );\n }\n\n const expected = calcCheckDigit(v.slice(0, 11));\n if (expected !== Number(v[11])) {\n return err(\n \"INVALID_CHECKSUM\",\n \"IIN check digit does not match\",\n );\n }\n\n return { valid: true, compact: v };\n};\n\nconst format = (value: string): string => {\n const v = compact(value);\n return `${v.slice(0, 6)} ${v.slice(6)}`;\n};\n\n/**\n * Extract birth date and gender from a Kazakhstan IIN.\n * Returns null if the value is not valid.\n */\nconst parse = (value: string): ParsedPersonId | null => {\n const result = validate(value);\n if (!result.valid) return null;\n\n const v = result.compact;\n const centuryDigit = Number(v[6]);\n const yearBase = centuryMap[centuryDigit] ?? 1900;\n const yy = Number(v.slice(0, 2));\n const mm = Number(v.slice(2, 4));\n const dd = Number(v.slice(4, 6));\n const year = yearBase + yy;\n\n return {\n birthDate: new Date(year, mm - 1, dd),\n gender: centuryDigit % 2 === 1 ? \"male\" : \"female\",\n };\n};\n\n/** Generate a random valid Kazakhstan IIN. */\nconst generate = (): string => {\n for (;;) {\n const yy = String(randomInt(50, 99)).padStart(2, \"0\");\n const mm = String(randomInt(1, 12)).padStart(2, \"0\");\n const dd = String(randomInt(1, 28)).padStart(2, \"0\");\n const gender = String(randomInt(1, 6));\n const serial = randomDigits(4);\n const payload = yy + mm + dd + gender + serial;\n const check = calcCheckDigit(payload);\n const c = payload + String(check);\n if (validate(c).valid) return c;\n }\n};\n\n/**\n * Kazakhstan Individual Identification Number (IIN).\n *\n * Examples sourced from identique/idnumbers test suite.\n */\nconst iin: CountryValidator<\"KZ\", ParsedPersonId> = {\n name: \"Kazakhstan Individual ID\",\n localName: \"Жеке сәйкестендіру нөмірі\",\n abbreviation: \"IIN\",\n aliases: [\n \"ИИН\",\n \"IIN\",\n \"индивидуальный идентификационный номер\",\n ] as const,\n candidatePattern: \"\\\\d{12}\",\n scope: \"country\",\n country: \"KZ\",\n entityType: \"person\",\n lengths: [12] as const,\n examples: [\"880515300120\", \"950101400012\"] as const,\n compact,\n format,\n parse,\n validate,\n sourceUrl:\n \"https://www.oecd.org/tax/automatic-exchange/\" +\n \"crs-implementation-and-assistance/\" +\n \"tax-identification-numbers/Kazakhstan-TIN.pdf\",\n generate,\n};\n\nexport default iin;\nexport { compact, format, parse, validate, generate };\n"],"mappings":";;;;;;;AA6BA,MAAM,YAAY;CAChB;CAAG;CAAG;CAAG;CAAG;CAAG;CAAG;CAAG;CAAG;CAAG;CAAI;AACjC;AACA,MAAM,YAAY;CAChB;CAAG;CAAG;CAAG;CAAG;CAAG;CAAG;CAAG;CAAI;CAAI;CAAG;AAClC;AAEA,MAAM,aAAqC;CACzC,GAAG;CACH,GAAG;CACH,GAAG;CACH,GAAG;CACH,GAAG;CACH,GAAG;AACL;AAEA,MAAM,WAAW,UACf,MAAM,OAAO,IAAI,
|
|
1
|
+
{"version":3,"file":"iin.js","names":[],"sources":["../../src/kz/iin.ts"],"sourcesContent":["/**\n * IIN (Individual Identification Number,\n * Жеке сәйкестендіру нөмірі / ИИН).\n *\n * Kazakhstan individual identification number. 12 digits:\n * YYMMDD = birth date\n * C = century/gender (1-6)\n * SSSS = serial number\n * K = check digit (two-pass weighted sum mod 11)\n *\n * Odd century digits = male, even = female.\n * 1-2 = 1800s, 3-4 = 1900s, 5-6 = 2000s.\n *\n * @see https://www.oecd.org/tax/automatic-exchange/crs-implementation-and-assistance/tax-identification-numbers/Kazakhstan-TIN.pdf\n */\n\nimport type {\n ParsedPersonId,\n ValidateResult,\n CountryValidator,\n} from \"../types\";\n\nimport { weightedSum } from \"#checksums/weighted-sum\";\nimport { clean } from \"#util/clean\";\nimport { isValidDate } from \"#util/date\";\nimport { randomDigits, randomInt } from \"#util/generate\";\nimport { err } from \"#util/result\";\nimport { isdigits } from \"#util/strings\";\n\nconst WEIGHTS_1 = [\n 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,\n] as const;\nconst WEIGHTS_2 = [\n 3, 4, 5, 6, 7, 8, 9, 10, 11, 1, 2,\n] as const;\n\nconst centuryMap: Record<number, number> = {\n 1: 1800,\n 2: 1800,\n 3: 1900,\n 4: 1900,\n 5: 2000,\n 6: 2000,\n};\n\nconst compact = (value: string): string =>\n clean(value, \" -\").trim();\n\n/**\n * Two-pass check digit: weighted sum mod 11 with\n * fallback to second weight set.\n */\nconst calcCheckDigit = (digits: string): number => {\n let remainder = weightedSum(digits, WEIGHTS_1, 11);\n if (remainder === 10) {\n remainder = weightedSum(digits, WEIGHTS_2, 11);\n }\n return remainder === 10 ? 0 : remainder;\n};\n\nconst validate = (value: string): ValidateResult => {\n const v = compact(value);\n\n if (v.length !== 12) {\n return err(\n \"INVALID_LENGTH\",\n \"Kazakhstan IIN must be 12 digits\",\n );\n }\n\n if (!isdigits(v)) {\n return err(\n \"INVALID_FORMAT\",\n \"Kazakhstan IIN must contain only digits\",\n );\n }\n\n const centuryDigit = Number(v[6]);\n if (centuryDigit < 1 || centuryDigit > 6) {\n return err(\n \"INVALID_COMPONENT\",\n \"IIN century/gender digit must be 1-6\",\n );\n }\n\n const yearBase = centuryMap[centuryDigit] ?? 1900;\n const yy = Number(v.slice(0, 2));\n const mm = Number(v.slice(2, 4));\n const dd = Number(v.slice(4, 6));\n const year = yearBase + yy;\n\n if (!isValidDate(year, mm, dd)) {\n return err(\n \"INVALID_COMPONENT\",\n \"IIN contains an invalid birth date\",\n );\n }\n\n const expected = calcCheckDigit(v.slice(0, 11));\n if (expected !== Number(v[11])) {\n return err(\n \"INVALID_CHECKSUM\",\n \"IIN check digit does not match\",\n );\n }\n\n return { valid: true, compact: v };\n};\n\nconst format = (value: string): string => {\n const v = compact(value);\n return `${v.slice(0, 6)} ${v.slice(6)}`;\n};\n\n/**\n * Extract birth date and gender from a Kazakhstan IIN.\n * Returns null if the value is not valid.\n */\nconst parse = (value: string): ParsedPersonId | null => {\n const result = validate(value);\n if (!result.valid) return null;\n\n const v = result.compact;\n const centuryDigit = Number(v[6]);\n const yearBase = centuryMap[centuryDigit] ?? 1900;\n const yy = Number(v.slice(0, 2));\n const mm = Number(v.slice(2, 4));\n const dd = Number(v.slice(4, 6));\n const year = yearBase + yy;\n\n return {\n birthDate: new Date(year, mm - 1, dd),\n gender: centuryDigit % 2 === 1 ? \"male\" : \"female\",\n };\n};\n\n/** Generate a random valid Kazakhstan IIN. */\nconst generate = (): string => {\n for (;;) {\n const yy = String(randomInt(50, 99)).padStart(2, \"0\");\n const mm = String(randomInt(1, 12)).padStart(2, \"0\");\n const dd = String(randomInt(1, 28)).padStart(2, \"0\");\n const gender = String(randomInt(1, 6));\n const serial = randomDigits(4);\n const payload = yy + mm + dd + gender + serial;\n const check = calcCheckDigit(payload);\n const c = payload + String(check);\n if (validate(c).valid) return c;\n }\n};\n\n/**\n * Kazakhstan Individual Identification Number (IIN).\n *\n * Examples sourced from identique/idnumbers test suite.\n */\nconst iin: CountryValidator<\"KZ\", ParsedPersonId> = {\n name: \"Kazakhstan Individual ID\",\n localName: \"Жеке сәйкестендіру нөмірі\",\n abbreviation: \"IIN\",\n aliases: [\n \"ИИН\",\n \"IIN\",\n \"индивидуальный идентификационный номер\",\n ] as const,\n candidatePattern: \"\\\\d{12}\",\n scope: \"country\",\n country: \"KZ\",\n entityType: \"person\",\n lengths: [12] as const,\n examples: [\"880515300120\", \"950101400012\"] as const,\n compact,\n format,\n parse,\n validate,\n sourceUrl:\n \"https://www.oecd.org/tax/automatic-exchange/\" +\n \"crs-implementation-and-assistance/\" +\n \"tax-identification-numbers/Kazakhstan-TIN.pdf\",\n generate,\n};\n\nexport default iin;\nexport { compact, format, parse, validate, generate };\n"],"mappings":"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"}
|
package/dist/lei.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lei.js","names":[],"sources":["../src/lei.ts"],"sourcesContent":["/**\n * LEI (Legal Entity Identifier).\n *\n * ISO 17442. 20-character alphanumeric code\n * identifying legal entities in financial\n * transactions. Validated using ISO 7064\n * Mod 97-10 (same as IBAN).\n *\n * @see https://www.gleif.org/\n */\n\nimport type {\n ValidateResult,\n GlobalValidator,\n} from \"./types\";\n\nimport { mod97 } from \"#checksums/mod97\";\nimport { clean } from \"#util/clean\";\nimport { randomInt } from \"#util/generate\";\nimport { err } from \"#util/result\";\nimport { charValue, isalnum } from \"#util/strings\";\n\nconst compact = (value: string): string =>\n clean(value, \" -\").toUpperCase();\n\nconst validate = (value: string): ValidateResult => {\n const v = compact(value);\n if (v.length !== 20) {\n return err(\n \"INVALID_LENGTH\",\n \"LEI must be exactly 20 characters\",\n );\n }\n if (!isalnum(v)) {\n return err(\n \"INVALID_FORMAT\",\n \"LEI must contain only letters and digits\",\n );\n }\n // Characters 5-18 must be alphanumeric (entity)\n // Characters 19-20 are check digits (numeric)\n const checkPart = v.slice(18);\n if (!/^\\d{2}$/.test(checkPart)) {\n return err(\n \"INVALID_FORMAT\",\n \"LEI check digits must be numeric\",\n );\n }\n // Convert to numeric and validate mod 97\n let numeric = \"\";\n for (let i = 0; i < v.length; i++) {\n const ch = v[i];\n if (ch !== undefined) {\n numeric += String(charValue(ch));\n }\n }\n if (mod97(numeric) !== 1) {\n return err(\n \"INVALID_CHECKSUM\",\n \"LEI check digits are incorrect\",\n );\n }\n return { valid: true, compact: v };\n};\n\nconst format = (value: string): string => {\n const v = compact(value);\n return `${v.slice(0, 4)} ${v.slice(4, 8)} ${v.slice(8, 12)} ${v.slice(12, 16)} ${v.slice(16)}`;\n};\n\n/** Generate a random valid LEI. */\nconst generate = (): string => {\n for (;;) {\n let base = \"\";\n for (let i = 0; i < 18; i++)\n base += String(randomInt(0, 9));\n for (let cd = 0; cd < 100; cd++) {\n const c = base + String(cd).padStart(2, \"0\");\n if (validate(c).valid) return c;\n }\n }\n};\n\n/** Legal Entity Identifier. */\nconst lei: GlobalValidator = {\n scope: \"global\",\n name: \"Legal Entity Identifier\",\n localName: \"Legal Entity Identifier\",\n abbreviation: \"LEI\",\n aliases: [\"LEI\", \"Legal Entity Identifier\"] as const,\n candidatePattern: \"[A-Z0-9]{18}\\\\d{2}\",\n entityType: \"company\",\n sourceUrl: \"https://www.gleif.org/\",\n examples: [\"5493006MHB84DD0ZWV18\"] as const,\n compact,\n format,\n validate,\n generate,\n};\n\nexport default lei;\nexport { compact, format, validate, generate };\n"],"mappings":";;;;;;AAsBA,MAAM,WAAW,UACf,MAAM,OAAO,IAAI,
|
|
1
|
+
{"version":3,"file":"lei.js","names":[],"sources":["../src/lei.ts"],"sourcesContent":["/**\n * LEI (Legal Entity Identifier).\n *\n * ISO 17442. 20-character alphanumeric code\n * identifying legal entities in financial\n * transactions. Validated using ISO 7064\n * Mod 97-10 (same as IBAN).\n *\n * @see https://www.gleif.org/\n */\n\nimport type {\n ValidateResult,\n GlobalValidator,\n} from \"./types\";\n\nimport { mod97 } from \"#checksums/mod97\";\nimport { clean } from \"#util/clean\";\nimport { randomInt } from \"#util/generate\";\nimport { err } from \"#util/result\";\nimport { charValue, isalnum } from \"#util/strings\";\n\nconst compact = (value: string): string =>\n clean(value, \" -\").toUpperCase();\n\nconst validate = (value: string): ValidateResult => {\n const v = compact(value);\n if (v.length !== 20) {\n return err(\n \"INVALID_LENGTH\",\n \"LEI must be exactly 20 characters\",\n );\n }\n if (!isalnum(v)) {\n return err(\n \"INVALID_FORMAT\",\n \"LEI must contain only letters and digits\",\n );\n }\n // Characters 5-18 must be alphanumeric (entity)\n // Characters 19-20 are check digits (numeric)\n const checkPart = v.slice(18);\n if (!/^\\d{2}$/.test(checkPart)) {\n return err(\n \"INVALID_FORMAT\",\n \"LEI check digits must be numeric\",\n );\n }\n // Convert to numeric and validate mod 97\n let numeric = \"\";\n for (let i = 0; i < v.length; i++) {\n const ch = v[i];\n if (ch !== undefined) {\n numeric += String(charValue(ch));\n }\n }\n if (mod97(numeric) !== 1) {\n return err(\n \"INVALID_CHECKSUM\",\n \"LEI check digits are incorrect\",\n );\n }\n return { valid: true, compact: v };\n};\n\nconst format = (value: string): string => {\n const v = compact(value);\n return `${v.slice(0, 4)} ${v.slice(4, 8)} ${v.slice(8, 12)} ${v.slice(12, 16)} ${v.slice(16)}`;\n};\n\n/** Generate a random valid LEI. */\nconst generate = (): string => {\n for (;;) {\n let base = \"\";\n for (let i = 0; i < 18; i++)\n base += String(randomInt(0, 9));\n for (let cd = 0; cd < 100; cd++) {\n const c = base + String(cd).padStart(2, \"0\");\n if (validate(c).valid) return c;\n }\n }\n};\n\n/** Legal Entity Identifier. */\nconst lei: GlobalValidator = {\n scope: \"global\",\n name: \"Legal Entity Identifier\",\n localName: \"Legal Entity Identifier\",\n abbreviation: \"LEI\",\n aliases: [\"LEI\", \"Legal Entity Identifier\"] as const,\n candidatePattern: \"[A-Z0-9]{18}\\\\d{2}\",\n entityType: \"company\",\n sourceUrl: \"https://www.gleif.org/\",\n examples: [\"5493006MHB84DD0ZWV18\"] as const,\n compact,\n format,\n validate,\n generate,\n};\n\nexport default lei;\nexport { compact, format, validate, generate };\n"],"mappings":";;;;;;AAsBA,MAAM,WAAW,UACf,MAAM,OAAO,IAAI,CAAC,CAAC,YAAY;AAEjC,MAAM,YAAY,UAAkC;CAClD,MAAM,IAAI,QAAQ,KAAK;CACvB,IAAI,EAAE,WAAW,IACf,OAAO,IACL,kBACA,mCACF;CAEF,IAAI,CAAC,QAAQ,CAAC,GACZ,OAAO,IACL,kBACA,0CACF;CAIF,MAAM,YAAY,EAAE,MAAM,EAAE;CAC5B,IAAI,CAAC,UAAU,KAAK,SAAS,GAC3B,OAAO,IACL,kBACA,kCACF;CAGF,IAAI,UAAU;CACd,KAAK,IAAI,IAAI,GAAG,IAAI,EAAE,QAAQ,KAAK;EACjC,MAAM,KAAK,EAAE;EACb,IAAI,OAAO,KAAA,GACT,WAAW,OAAO,UAAU,EAAE,CAAC;CAEnC;CACA,IAAI,MAAM,OAAO,MAAM,GACrB,OAAO,IACL,oBACA,gCACF;CAEF,OAAO;EAAE,OAAO;EAAM,SAAS;CAAE;AACnC;AAEA,MAAM,UAAU,UAA0B;CACxC,MAAM,IAAI,QAAQ,KAAK;CACvB,OAAO,GAAG,EAAE,MAAM,GAAG,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,EAAE,EAAE,GAAG,EAAE,MAAM,IAAI,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE;AAC7F;;AAGA,MAAM,iBAAyB;CAC7B,SAAS;EACP,IAAI,OAAO;EACX,KAAK,IAAI,IAAI,GAAG,IAAI,IAAI,KACtB,QAAQ,OAAO,UAAU,GAAG,CAAC,CAAC;EAChC,KAAK,IAAI,KAAK,GAAG,KAAK,KAAK,MAAM;GAC/B,MAAM,IAAI,OAAO,OAAO,EAAE,CAAC,CAAC,SAAS,GAAG,GAAG;GAC3C,IAAI,SAAS,CAAC,CAAC,CAAC,OAAO,OAAO;EAChC;CACF;AACF;;AAGA,MAAM,MAAuB;CAC3B,OAAO;CACP,MAAM;CACN,WAAW;CACX,cAAc;CACd,SAAS,CAAC,OAAO,yBAAyB;CAC1C,kBAAkB;CAClB,YAAY;CACZ,WAAW;CACX,UAAU,CAAC,sBAAsB;CACjC;CACA;CACA;CACA;AACF"}
|
package/dist/li/peid.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"peid.js","names":[],"sources":["../../src/li/peid.ts"],"sourcesContent":["/** Generate a random valid Liechtenstein PEID. */\nconst generate = (): string => {\n const len = randomInt(4, 12);\n const first = String(randomInt(1, 9));\n return first + randomDigits(len - 1);\n};\n\n/**\n * PEID (Personenidentifikationsnummer).\n *\n * Liechtenstein tax code for individuals and entities.\n * Numeric code of 4 to 12 digits, with leading zeros\n * stripped during compaction.\n *\n * @see https://www.oera.li/\n */\n\nimport type {\n ValidateResult,\n CountryValidator,\n} from \"../types\";\n\nimport { clean } from \"#util/clean\";\nimport { randomDigits, randomInt } from \"#util/generate\";\nimport { err } from \"#util/result\";\nimport { isdigits } from \"#util/strings\";\n\nconst compact = (value: string): string =>\n clean(value, \" .\").replace(/^0+/, \"\");\n\nconst validate = (value: string): ValidateResult => {\n const v = compact(value);\n if (v.length < 4 || v.length > 12) {\n return err(\n \"INVALID_LENGTH\",\n \"Liechtenstein PEID must be 4 to 12 digits\",\n );\n }\n if (!isdigits(v)) {\n return err(\n \"INVALID_FORMAT\",\n \"Liechtenstein PEID must contain only digits\",\n );\n }\n return { valid: true, compact: v };\n};\n\nconst format = (value: string): string => compact(value);\n\n/** Liechtenstein Person Identification Number. */\nconst peid: CountryValidator<\"LI\"> = {\n name: \"Liechtenstein Person Identification Number\",\n localName: \"Personenidentifikationsnummer\",\n abbreviation: \"PEID\",\n aliases: [\"PEID\", \"Personenidentifikation\"] as const,\n candidatePattern: \"\\\\d{6}\",\n scope: \"country\",\n country: \"LI\",\n entityType: \"any\",\n lengths: [4, 5, 6, 7, 8, 9, 10, 11, 12] as const,\n sourceUrl: \"https://www.oera.li/\",\n examples: [\"1234567\"] as const,\n compact,\n format,\n validate,\n generate,\n};\n\nexport default peid;\nexport { compact, format, validate, generate };\n"],"mappings":";;;;;;AACA,MAAM,iBAAyB;CAC7B,MAAM,MAAM,UAAU,GAAG,EAAE;CAE3B,OADc,OAAO,UAAU,GAAG,CAAC,CACxB,IAAI,aAAa,MAAM,CAAC;AACrC;AAsBA,MAAM,WAAW,UACf,MAAM,OAAO,IAAI,
|
|
1
|
+
{"version":3,"file":"peid.js","names":[],"sources":["../../src/li/peid.ts"],"sourcesContent":["/** Generate a random valid Liechtenstein PEID. */\nconst generate = (): string => {\n const len = randomInt(4, 12);\n const first = String(randomInt(1, 9));\n return first + randomDigits(len - 1);\n};\n\n/**\n * PEID (Personenidentifikationsnummer).\n *\n * Liechtenstein tax code for individuals and entities.\n * Numeric code of 4 to 12 digits, with leading zeros\n * stripped during compaction.\n *\n * @see https://www.oera.li/\n */\n\nimport type {\n ValidateResult,\n CountryValidator,\n} from \"../types\";\n\nimport { clean } from \"#util/clean\";\nimport { randomDigits, randomInt } from \"#util/generate\";\nimport { err } from \"#util/result\";\nimport { isdigits } from \"#util/strings\";\n\nconst compact = (value: string): string =>\n clean(value, \" .\").replace(/^0+/, \"\");\n\nconst validate = (value: string): ValidateResult => {\n const v = compact(value);\n if (v.length < 4 || v.length > 12) {\n return err(\n \"INVALID_LENGTH\",\n \"Liechtenstein PEID must be 4 to 12 digits\",\n );\n }\n if (!isdigits(v)) {\n return err(\n \"INVALID_FORMAT\",\n \"Liechtenstein PEID must contain only digits\",\n );\n }\n return { valid: true, compact: v };\n};\n\nconst format = (value: string): string => compact(value);\n\n/** Liechtenstein Person Identification Number. */\nconst peid: CountryValidator<\"LI\"> = {\n name: \"Liechtenstein Person Identification Number\",\n localName: \"Personenidentifikationsnummer\",\n abbreviation: \"PEID\",\n aliases: [\"PEID\", \"Personenidentifikation\"] as const,\n candidatePattern: \"\\\\d{6}\",\n scope: \"country\",\n country: \"LI\",\n entityType: \"any\",\n lengths: [4, 5, 6, 7, 8, 9, 10, 11, 12] as const,\n sourceUrl: \"https://www.oera.li/\",\n examples: [\"1234567\"] as const,\n compact,\n format,\n validate,\n generate,\n};\n\nexport default peid;\nexport { compact, format, validate, generate };\n"],"mappings":";;;;;;AACA,MAAM,iBAAyB;CAC7B,MAAM,MAAM,UAAU,GAAG,EAAE;CAE3B,OADc,OAAO,UAAU,GAAG,CAAC,CACxB,IAAI,aAAa,MAAM,CAAC;AACrC;AAsBA,MAAM,WAAW,UACf,MAAM,OAAO,IAAI,CAAC,CAAC,QAAQ,OAAO,EAAE;AAEtC,MAAM,YAAY,UAAkC;CAClD,MAAM,IAAI,QAAQ,KAAK;CACvB,IAAI,EAAE,SAAS,KAAK,EAAE,SAAS,IAC7B,OAAO,IACL,kBACA,2CACF;CAEF,IAAI,CAAC,SAAS,CAAC,GACb,OAAO,IACL,kBACA,6CACF;CAEF,OAAO;EAAE,OAAO;EAAM,SAAS;CAAE;AACnC;AAEA,MAAM,UAAU,UAA0B,QAAQ,KAAK;;AAGvD,MAAM,OAA+B;CACnC,MAAM;CACN,WAAW;CACX,cAAc;CACd,SAAS,CAAC,QAAQ,wBAAwB;CAC1C,kBAAkB;CAClB,OAAO;CACP,SAAS;CACT,YAAY;CACZ,SAAS;EAAC;EAAG;EAAG;EAAG;EAAG;EAAG;EAAG;EAAI;EAAI;CAAE;CACtC,WAAW;CACX,UAAU,CAAC,SAAS;CACpB;CACA;CACA;CACA;AACF"}
|
package/dist/lk/nic.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nic.js","names":[],"sources":["../../src/lk/nic.ts"],"sourcesContent":["/**\n * NIC (National Identity Card, ජාතික හැඳුනුම්පත).\n *\n * Sri Lankan personal identity number. Two formats:\n * Old (pre-2016): 9 digits + V or X (10 chars)\n * New (2016+): 12 digits\n *\n * Both encode birth date (day-of-year), gender, a\n * serial number, and a check digit.\n *\n * @see https://en.wikipedia.org/wiki/National_identification_number#Sri_Lanka\n * @see https://drp.gov.lk/Templates/Artical%20-%20English%20new%20number.html\n */\n\nimport type {\n ParsedPersonId,\n ValidateResult,\n CountryValidator,\n} from \"../types\";\n\nimport { clean } from \"#util/clean\";\nimport { isValidDate } from \"#util/date\";\nimport { randomDigits, randomInt } from \"#util/generate\";\nimport { err } from \"#util/result\";\nimport { isdigits } from \"#util/strings\";\n\n/** Old format: 9 digits + V or X (case-insensitive). */\nconst OLD_RE = /^\\d{9}[VvXx]$/;\n\n/** New format: exactly 12 digits. */\nconst NEW_RE = /^\\d{12}$/;\n\nconst compact = (value: string): string =>\n clean(value, \" -\").toUpperCase();\n\n/**\n * Convert an old-format NIC to the 12-digit new\n * format for uniform validation.\n *\n * Old: YY DDD SSS C V/X (10 chars)\n * New: 19YY DDD 0SSS C (12 digits)\n */\nconst toNewFormat = (v: string): string => {\n const yy = v.slice(0, 2);\n const ddd = v.slice(2, 5);\n const sss = v.slice(5, 8);\n const check = v[8];\n return `19${yy}${ddd}0${sss}${check}`;\n};\n\n/**\n * Checksum weights for the first 11 digits.\n * Algorithm: weighted sum mod 11, then mod 10\n * if >= 10.\n */\nconst CHECK_WEIGHTS = [\n 8, 4, 3, 2, 7, 6, 5, 7, 4, 3, 2,\n] as const;\n\nconst checkDigit = (digits: string): number => {\n let sum = 0;\n for (let i = 0; i < 11; i++) {\n sum +=\n (CHECK_WEIGHTS[i] ?? 0) * Number(digits.charAt(i));\n }\n const d = 11 - (sum % 11);\n return d > 9 ? d % 10 : d;\n};\n\n/**\n * Resolve birth date and gender from the 12-digit\n * normalised form (YYYY DDD ...).\n *\n * Day-of-year encoding:\n * Male: 1–366\n * Female: 501–866 (subtract 500)\n */\nconst resolveBirthInfo = (\n v: string,\n): {\n year: number;\n month: number;\n day: number;\n gender: \"male\" | \"female\";\n} | null => {\n const year = Number(v.slice(0, 4));\n let days = Number(v.slice(4, 7));\n\n const gender: \"male\" | \"female\" =\n days > 500 ? \"female\" : \"male\";\n if (days > 500) days -= 500;\n\n if (days < 1 || days > 366) return null;\n\n // Convert day-of-year to month/day via Date\n const d = new Date(year, 0, days);\n const month = d.getMonth() + 1;\n const dayOfMonth = d.getDate();\n\n if (!isValidDate(year, month, dayOfMonth)) {\n return null;\n }\n\n // Guard against day 366 in non-leap years\n if (d.getFullYear() !== year) return null;\n\n return { year, month, day: dayOfMonth, gender };\n};\n\nconst validate = (value: string): ValidateResult => {\n const v = compact(value);\n const isOld = OLD_RE.test(v);\n const isNew = NEW_RE.test(v);\n\n if (!isOld && !isNew) {\n if (v.length !== 10 && v.length !== 12) {\n return err(\n \"INVALID_LENGTH\",\n \"NIC must be 10 (old) or 12 (new) characters\",\n );\n }\n return err(\n \"INVALID_FORMAT\",\n \"NIC must be 9 digits + V/X (old) or\" +\n \" 12 digits (new)\",\n );\n }\n\n const normalized = isOld ? toNewFormat(v) : v;\n\n if (!isdigits(normalized)) {\n return err(\n \"INVALID_FORMAT\",\n \"NIC contains invalid characters\",\n );\n }\n\n if (resolveBirthInfo(normalized) === null) {\n return err(\n \"INVALID_COMPONENT\",\n \"NIC contains an invalid date of birth\",\n );\n }\n\n const expected = checkDigit(normalized.slice(0, 11));\n if (expected !== Number(normalized[11])) {\n return err(\n \"INVALID_CHECKSUM\",\n \"NIC check digit does not match\",\n );\n }\n\n return { valid: true, compact: v };\n};\n\nconst format = (value: string): string => {\n const v = compact(value);\n // Old format: YYDDDSSSXV → no standard separator\n // New format: YYYYDDDSSSSC → no standard separator\n return v;\n};\n\n/**\n * Extract birth date and gender from an NIC.\n * Returns null if the value is not valid.\n */\nconst parse = (value: string): ParsedPersonId | null => {\n const result = validate(value);\n if (!result.valid) return null;\n\n const v = result.compact;\n const isOld = OLD_RE.test(v);\n const normalized = isOld ? toNewFormat(v) : v;\n\n const info = resolveBirthInfo(normalized);\n if (info === null) return null;\n\n return {\n birthDate: new Date(\n info.year,\n info.month - 1,\n info.day,\n ),\n gender: info.gender,\n };\n};\n\n/** Generate a random valid Sri Lankan NIC. */\nconst generate = (): string => {\n const year = randomInt(1950, 2024);\n const start = new Date(year, 0, 1);\n const end = new Date(year, 11, 31);\n const maxDay =\n Math.round(\n (end.getTime() - start.getTime()) / 86_400_000,\n ) + 1;\n const dayOfYear = randomInt(1, maxDay);\n const genderOffset = Math.random() < 0.5 ? 0 : 500;\n const ddd = String(dayOfYear + genderOffset).padStart(\n 3,\n \"0\",\n );\n const body = `${year}${ddd}0${randomDigits(3)}`;\n return `${body}${String(checkDigit(body))}`;\n};\n\n/** Sri Lankan National Identity Card. */\nconst nic: CountryValidator<\"LK\", ParsedPersonId> = {\n name: \"National Identity Card\",\n localName: \"ජාතික හැඳුනුම්පත\",\n abbreviation: \"NIC\",\n aliases: [\"NIC\", \"ජාතික හැඳුනුම්පත\"] as const,\n candidatePattern: \"\\\\d{9}[VXvx]|\\\\d{12}\",\n scope: \"country\",\n country: \"LK\",\n entityType: \"person\",\n description:\n \"Sri Lankan personal identity number issued to\" +\n \" all citizens\",\n sourceUrl:\n \"https://en.wikipedia.org/wiki/\" +\n \"National_identification_number#Sri_Lanka\",\n lengths: [10, 12] as const,\n examples: [\"197819202757\", \"862348753V\"] as const,\n compact,\n format,\n parse,\n validate,\n generate,\n};\n\nexport default nic;\nexport { compact, format, generate, parse, validate };\n"],"mappings":";;;;;;;AA2BA,MAAM,SAAS;;AAGf,MAAM,SAAS;AAEf,MAAM,WAAW,UACf,MAAM,OAAO,IAAI,
|
|
1
|
+
{"version":3,"file":"nic.js","names":[],"sources":["../../src/lk/nic.ts"],"sourcesContent":["/**\n * NIC (National Identity Card, ජාතික හැඳුනුම්පත).\n *\n * Sri Lankan personal identity number. Two formats:\n * Old (pre-2016): 9 digits + V or X (10 chars)\n * New (2016+): 12 digits\n *\n * Both encode birth date (day-of-year), gender, a\n * serial number, and a check digit.\n *\n * @see https://en.wikipedia.org/wiki/National_identification_number#Sri_Lanka\n * @see https://drp.gov.lk/Templates/Artical%20-%20English%20new%20number.html\n */\n\nimport type {\n ParsedPersonId,\n ValidateResult,\n CountryValidator,\n} from \"../types\";\n\nimport { clean } from \"#util/clean\";\nimport { isValidDate } from \"#util/date\";\nimport { randomDigits, randomInt } from \"#util/generate\";\nimport { err } from \"#util/result\";\nimport { isdigits } from \"#util/strings\";\n\n/** Old format: 9 digits + V or X (case-insensitive). */\nconst OLD_RE = /^\\d{9}[VvXx]$/;\n\n/** New format: exactly 12 digits. */\nconst NEW_RE = /^\\d{12}$/;\n\nconst compact = (value: string): string =>\n clean(value, \" -\").toUpperCase();\n\n/**\n * Convert an old-format NIC to the 12-digit new\n * format for uniform validation.\n *\n * Old: YY DDD SSS C V/X (10 chars)\n * New: 19YY DDD 0SSS C (12 digits)\n */\nconst toNewFormat = (v: string): string => {\n const yy = v.slice(0, 2);\n const ddd = v.slice(2, 5);\n const sss = v.slice(5, 8);\n const check = v[8];\n return `19${yy}${ddd}0${sss}${check}`;\n};\n\n/**\n * Checksum weights for the first 11 digits.\n * Algorithm: weighted sum mod 11, then mod 10\n * if >= 10.\n */\nconst CHECK_WEIGHTS = [\n 8, 4, 3, 2, 7, 6, 5, 7, 4, 3, 2,\n] as const;\n\nconst checkDigit = (digits: string): number => {\n let sum = 0;\n for (let i = 0; i < 11; i++) {\n sum +=\n (CHECK_WEIGHTS[i] ?? 0) * Number(digits.charAt(i));\n }\n const d = 11 - (sum % 11);\n return d > 9 ? d % 10 : d;\n};\n\n/**\n * Resolve birth date and gender from the 12-digit\n * normalised form (YYYY DDD ...).\n *\n * Day-of-year encoding:\n * Male: 1–366\n * Female: 501–866 (subtract 500)\n */\nconst resolveBirthInfo = (\n v: string,\n): {\n year: number;\n month: number;\n day: number;\n gender: \"male\" | \"female\";\n} | null => {\n const year = Number(v.slice(0, 4));\n let days = Number(v.slice(4, 7));\n\n const gender: \"male\" | \"female\" =\n days > 500 ? \"female\" : \"male\";\n if (days > 500) days -= 500;\n\n if (days < 1 || days > 366) return null;\n\n // Convert day-of-year to month/day via Date\n const d = new Date(year, 0, days);\n const month = d.getMonth() + 1;\n const dayOfMonth = d.getDate();\n\n if (!isValidDate(year, month, dayOfMonth)) {\n return null;\n }\n\n // Guard against day 366 in non-leap years\n if (d.getFullYear() !== year) return null;\n\n return { year, month, day: dayOfMonth, gender };\n};\n\nconst validate = (value: string): ValidateResult => {\n const v = compact(value);\n const isOld = OLD_RE.test(v);\n const isNew = NEW_RE.test(v);\n\n if (!isOld && !isNew) {\n if (v.length !== 10 && v.length !== 12) {\n return err(\n \"INVALID_LENGTH\",\n \"NIC must be 10 (old) or 12 (new) characters\",\n );\n }\n return err(\n \"INVALID_FORMAT\",\n \"NIC must be 9 digits + V/X (old) or\" +\n \" 12 digits (new)\",\n );\n }\n\n const normalized = isOld ? toNewFormat(v) : v;\n\n if (!isdigits(normalized)) {\n return err(\n \"INVALID_FORMAT\",\n \"NIC contains invalid characters\",\n );\n }\n\n if (resolveBirthInfo(normalized) === null) {\n return err(\n \"INVALID_COMPONENT\",\n \"NIC contains an invalid date of birth\",\n );\n }\n\n const expected = checkDigit(normalized.slice(0, 11));\n if (expected !== Number(normalized[11])) {\n return err(\n \"INVALID_CHECKSUM\",\n \"NIC check digit does not match\",\n );\n }\n\n return { valid: true, compact: v };\n};\n\nconst format = (value: string): string => {\n const v = compact(value);\n // Old format: YYDDDSSSXV → no standard separator\n // New format: YYYYDDDSSSSC → no standard separator\n return v;\n};\n\n/**\n * Extract birth date and gender from an NIC.\n * Returns null if the value is not valid.\n */\nconst parse = (value: string): ParsedPersonId | null => {\n const result = validate(value);\n if (!result.valid) return null;\n\n const v = result.compact;\n const isOld = OLD_RE.test(v);\n const normalized = isOld ? toNewFormat(v) : v;\n\n const info = resolveBirthInfo(normalized);\n if (info === null) return null;\n\n return {\n birthDate: new Date(\n info.year,\n info.month - 1,\n info.day,\n ),\n gender: info.gender,\n };\n};\n\n/** Generate a random valid Sri Lankan NIC. */\nconst generate = (): string => {\n const year = randomInt(1950, 2024);\n const start = new Date(year, 0, 1);\n const end = new Date(year, 11, 31);\n const maxDay =\n Math.round(\n (end.getTime() - start.getTime()) / 86_400_000,\n ) + 1;\n const dayOfYear = randomInt(1, maxDay);\n const genderOffset = Math.random() < 0.5 ? 0 : 500;\n const ddd = String(dayOfYear + genderOffset).padStart(\n 3,\n \"0\",\n );\n const body = `${year}${ddd}0${randomDigits(3)}`;\n return `${body}${String(checkDigit(body))}`;\n};\n\n/** Sri Lankan National Identity Card. */\nconst nic: CountryValidator<\"LK\", ParsedPersonId> = {\n name: \"National Identity Card\",\n localName: \"ජාතික හැඳුනුම්පත\",\n abbreviation: \"NIC\",\n aliases: [\"NIC\", \"ජාතික හැඳුනුම්පත\"] as const,\n candidatePattern: \"\\\\d{9}[VXvx]|\\\\d{12}\",\n scope: \"country\",\n country: \"LK\",\n entityType: \"person\",\n description:\n \"Sri Lankan personal identity number issued to\" +\n \" all citizens\",\n sourceUrl:\n \"https://en.wikipedia.org/wiki/\" +\n \"National_identification_number#Sri_Lanka\",\n lengths: [10, 12] as const,\n examples: [\"197819202757\", \"862348753V\"] as const,\n compact,\n format,\n parse,\n validate,\n generate,\n};\n\nexport default nic;\nexport { compact, format, generate, parse, validate };\n"],"mappings":"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"}
|
package/dist/lt/asmens.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"asmens.js","names":[],"sources":["../../src/lt/asmens.ts"],"sourcesContent":["/**\n * Asmens kodas (Personal Code).\n *\n * Lithuanian personal identification number. 11 digits\n * with the same structure as the Estonian Isikukood:\n * gender/century digit, YYMMDD, 3-digit serial, and\n * a two-pass weighted checksum.\n *\n * @see https://www.oecd.org/content/dam/oecd/en/topics/policy-issue-focus/aeoi/lithuania-tin.pdf\n * @see https://www.registrucentras.lt/\n */\n\nimport { twoPassCheck } from \"../ee/ik\";\nimport type {\n ValidateResult,\n CountryValidator,\n} from \"../types\";\n\nimport { clean } from \"#util/clean\";\nimport { isValidDate } from \"#util/date\";\nimport { randomInt } from \"#util/generate\";\nimport { err } from \"#util/result\";\nimport { isdigits } from \"#util/strings\";\n\nconst compact = (value: string): string =>\n clean(value, \" -\");\n\nconst validate = (value: string): ValidateResult => {\n const v = compact(value);\n if (v.length !== 11) {\n return err(\n \"INVALID_LENGTH\",\n \"Asmens kodas must be exactly 11 digits\",\n );\n }\n if (!isdigits(v)) {\n return err(\n \"INVALID_FORMAT\",\n \"Asmens kodas must contain only digits\",\n );\n }\n\n const g = Number(v[0]);\n if (g < 1 || g > 9) {\n return err(\n \"INVALID_COMPONENT\",\n \"Asmens kodas gender/century digit is invalid\",\n );\n }\n\n // Digit 9 indicates a special case where the birth\n // date is not encoded; skip date validation.\n if (g !== 9) {\n const centuryMap: Record<number, number> = {\n 1: 1800,\n 2: 1800,\n 3: 1900,\n 4: 1900,\n 5: 2000,\n 6: 2000,\n 7: 2100,\n 8: 2100,\n };\n const century = centuryMap[g] ?? 1900;\n const yy = Number(v.slice(1, 3));\n const mm = Number(v.slice(3, 5));\n const dd = Number(v.slice(5, 7));\n const year = century + yy;\n\n if (!isValidDate(year, mm, dd)) {\n return err(\n \"INVALID_COMPONENT\",\n \"Asmens kodas contains an invalid date\",\n );\n }\n }\n\n const check = twoPassCheck(v.slice(0, 10));\n if (check !== Number(v[10])) {\n return err(\n \"INVALID_CHECKSUM\",\n \"Asmens kodas check digit does not match\",\n );\n }\n\n return { valid: true, compact: v };\n};\n\nconst format = (value: string): string => compact(value);\n\n/** Generate a random valid Lithuanian personal code. */\nconst generate = (): string => {\n for (;;) {\n const g = String(randomInt(3, 6));\n const yy = String(randomInt(0, 99)).padStart(2, \"0\");\n const mm = String(randomInt(1, 12)).padStart(2, \"0\");\n const dd = String(randomInt(1, 28)).padStart(2, \"0\");\n const s = String(randomInt(0, 999)).padStart(3, \"0\");\n const payload = g + yy + mm + dd + s;\n const c = payload + String(twoPassCheck(payload));\n if (validate(c).valid) return c;\n }\n};\n\n/** Lithuanian Personal Code. */\nconst asmens: CountryValidator<\"LT\"> = {\n name: \"Lithuanian Personal ID\",\n localName: \"Asmens kodas\",\n abbreviation: \"AK\",\n aliases: [\"asmens kodas\", \"AK\"] as const,\n candidatePattern: \"[3-6]\\\\d{10}\",\n scope: \"country\",\n country: \"LT\",\n entityType: \"person\",\n sourceUrl: \"https://www.registrucentras.lt/\",\n examples: [\"33309240064\"] as const,\n compact,\n format,\n validate,\n generate,\n};\n\nexport default asmens;\nexport { compact, format, validate, generate };\n"],"mappings":";;;;;;;;;;;;;;;;;;AAwBA,MAAM,WAAW,UACf,MAAM,OAAO,IAAI;AAEnB,MAAM,YAAY,UAAkC;CAClD,MAAM,IAAI,QAAQ,KAAK;CACvB,IAAI,EAAE,WAAW,IACf,OAAO,IACL,kBACA,wCACF;CAEF,IAAI,CAAC,SAAS,CAAC,GACb,OAAO,IACL,kBACA,uCACF;CAGF,MAAM,IAAI,OAAO,EAAE,EAAE;CACrB,IAAI,IAAI,KAAK,IAAI,GACf,OAAO,IACL,qBACA,8CACF;CAKF,IAAI,MAAM,GAAG;EAWX,MAAM,UAAU;GATd,GAAG;GACH,GAAG;GACH,GAAG;GACH,GAAG;GACH,GAAG;GACH,GAAG;GACH,GAAG;GACH,GAAG;EAEoB,EAAE,MAAM;EACjC,MAAM,KAAK,OAAO,EAAE,MAAM,GAAG,CAAC,CAAC;EAC/B,MAAM,KAAK,OAAO,EAAE,MAAM,GAAG,CAAC,CAAC;EAC/B,MAAM,KAAK,OAAO,EAAE,MAAM,GAAG,CAAC,CAAC;EAG/B,IAAI,CAAC,YAFQ,UAAU,IAEA,IAAI,EAAE,GAC3B,OAAO,IACL,qBACA,uCACF;CAEJ;CAGA,IADc,aAAa,EAAE,MAAM,GAAG,EAAE,CAChC,MAAM,OAAO,EAAE,GAAG,GACxB,OAAO,IACL,oBACA,yCACF;CAGF,OAAO;EAAE,OAAO;EAAM,SAAS;CAAE;AACnC;AAEA,MAAM,UAAU,UAA0B,QAAQ,KAAK;;AAGvD,MAAM,iBAAyB;CAC7B,SAAS;EACP,MAAM,IAAI,OAAO,UAAU,GAAG,CAAC,CAAC;EAChC,MAAM,KAAK,OAAO,UAAU,GAAG,EAAE,CAAC,
|
|
1
|
+
{"version":3,"file":"asmens.js","names":[],"sources":["../../src/lt/asmens.ts"],"sourcesContent":["/**\n * Asmens kodas (Personal Code).\n *\n * Lithuanian personal identification number. 11 digits\n * with the same structure as the Estonian Isikukood:\n * gender/century digit, YYMMDD, 3-digit serial, and\n * a two-pass weighted checksum.\n *\n * @see https://www.oecd.org/content/dam/oecd/en/topics/policy-issue-focus/aeoi/lithuania-tin.pdf\n * @see https://www.registrucentras.lt/\n */\n\nimport { twoPassCheck } from \"../ee/ik\";\nimport type {\n ValidateResult,\n CountryValidator,\n} from \"../types\";\n\nimport { clean } from \"#util/clean\";\nimport { isValidDate } from \"#util/date\";\nimport { randomInt } from \"#util/generate\";\nimport { err } from \"#util/result\";\nimport { isdigits } from \"#util/strings\";\n\nconst compact = (value: string): string =>\n clean(value, \" -\");\n\nconst validate = (value: string): ValidateResult => {\n const v = compact(value);\n if (v.length !== 11) {\n return err(\n \"INVALID_LENGTH\",\n \"Asmens kodas must be exactly 11 digits\",\n );\n }\n if (!isdigits(v)) {\n return err(\n \"INVALID_FORMAT\",\n \"Asmens kodas must contain only digits\",\n );\n }\n\n const g = Number(v[0]);\n if (g < 1 || g > 9) {\n return err(\n \"INVALID_COMPONENT\",\n \"Asmens kodas gender/century digit is invalid\",\n );\n }\n\n // Digit 9 indicates a special case where the birth\n // date is not encoded; skip date validation.\n if (g !== 9) {\n const centuryMap: Record<number, number> = {\n 1: 1800,\n 2: 1800,\n 3: 1900,\n 4: 1900,\n 5: 2000,\n 6: 2000,\n 7: 2100,\n 8: 2100,\n };\n const century = centuryMap[g] ?? 1900;\n const yy = Number(v.slice(1, 3));\n const mm = Number(v.slice(3, 5));\n const dd = Number(v.slice(5, 7));\n const year = century + yy;\n\n if (!isValidDate(year, mm, dd)) {\n return err(\n \"INVALID_COMPONENT\",\n \"Asmens kodas contains an invalid date\",\n );\n }\n }\n\n const check = twoPassCheck(v.slice(0, 10));\n if (check !== Number(v[10])) {\n return err(\n \"INVALID_CHECKSUM\",\n \"Asmens kodas check digit does not match\",\n );\n }\n\n return { valid: true, compact: v };\n};\n\nconst format = (value: string): string => compact(value);\n\n/** Generate a random valid Lithuanian personal code. */\nconst generate = (): string => {\n for (;;) {\n const g = String(randomInt(3, 6));\n const yy = String(randomInt(0, 99)).padStart(2, \"0\");\n const mm = String(randomInt(1, 12)).padStart(2, \"0\");\n const dd = String(randomInt(1, 28)).padStart(2, \"0\");\n const s = String(randomInt(0, 999)).padStart(3, \"0\");\n const payload = g + yy + mm + dd + s;\n const c = payload + String(twoPassCheck(payload));\n if (validate(c).valid) return c;\n }\n};\n\n/** Lithuanian Personal Code. */\nconst asmens: CountryValidator<\"LT\"> = {\n name: \"Lithuanian Personal ID\",\n localName: \"Asmens kodas\",\n abbreviation: \"AK\",\n aliases: [\"asmens kodas\", \"AK\"] as const,\n candidatePattern: \"[3-6]\\\\d{10}\",\n scope: \"country\",\n country: \"LT\",\n entityType: \"person\",\n sourceUrl: \"https://www.registrucentras.lt/\",\n examples: [\"33309240064\"] as const,\n compact,\n format,\n validate,\n generate,\n};\n\nexport default asmens;\nexport { compact, format, validate, generate };\n"],"mappings":";;;;;;;;;;;;;;;;;;AAwBA,MAAM,WAAW,UACf,MAAM,OAAO,IAAI;AAEnB,MAAM,YAAY,UAAkC;CAClD,MAAM,IAAI,QAAQ,KAAK;CACvB,IAAI,EAAE,WAAW,IACf,OAAO,IACL,kBACA,wCACF;CAEF,IAAI,CAAC,SAAS,CAAC,GACb,OAAO,IACL,kBACA,uCACF;CAGF,MAAM,IAAI,OAAO,EAAE,EAAE;CACrB,IAAI,IAAI,KAAK,IAAI,GACf,OAAO,IACL,qBACA,8CACF;CAKF,IAAI,MAAM,GAAG;EAWX,MAAM,UAAU;GATd,GAAG;GACH,GAAG;GACH,GAAG;GACH,GAAG;GACH,GAAG;GACH,GAAG;GACH,GAAG;GACH,GAAG;EAEoB,EAAE,MAAM;EACjC,MAAM,KAAK,OAAO,EAAE,MAAM,GAAG,CAAC,CAAC;EAC/B,MAAM,KAAK,OAAO,EAAE,MAAM,GAAG,CAAC,CAAC;EAC/B,MAAM,KAAK,OAAO,EAAE,MAAM,GAAG,CAAC,CAAC;EAG/B,IAAI,CAAC,YAFQ,UAAU,IAEA,IAAI,EAAE,GAC3B,OAAO,IACL,qBACA,uCACF;CAEJ;CAGA,IADc,aAAa,EAAE,MAAM,GAAG,EAAE,CAChC,MAAM,OAAO,EAAE,GAAG,GACxB,OAAO,IACL,oBACA,yCACF;CAGF,OAAO;EAAE,OAAO;EAAM,SAAS;CAAE;AACnC;AAEA,MAAM,UAAU,UAA0B,QAAQ,KAAK;;AAGvD,MAAM,iBAAyB;CAC7B,SAAS;EACP,MAAM,IAAI,OAAO,UAAU,GAAG,CAAC,CAAC;EAChC,MAAM,KAAK,OAAO,UAAU,GAAG,EAAE,CAAC,CAAC,CAAC,SAAS,GAAG,GAAG;EACnD,MAAM,KAAK,OAAO,UAAU,GAAG,EAAE,CAAC,CAAC,CAAC,SAAS,GAAG,GAAG;EACnD,MAAM,KAAK,OAAO,UAAU,GAAG,EAAE,CAAC,CAAC,CAAC,SAAS,GAAG,GAAG;EACnD,MAAM,IAAI,OAAO,UAAU,GAAG,GAAG,CAAC,CAAC,CAAC,SAAS,GAAG,GAAG;EACnD,MAAM,UAAU,IAAI,KAAK,KAAK,KAAK;EACnC,MAAM,IAAI,UAAU,OAAO,aAAa,OAAO,CAAC;EAChD,IAAI,SAAS,CAAC,CAAC,CAAC,OAAO,OAAO;CAChC;AACF;;AAGA,MAAM,SAAiC;CACrC,MAAM;CACN,WAAW;CACX,cAAc;CACd,SAAS,CAAC,gBAAgB,IAAI;CAC9B,kBAAkB;CAClB,OAAO;CACP,SAAS;CACT,YAAY;CACZ,WAAW;CACX,UAAU,CAAC,aAAa;CACxB;CACA;CACA;CACA;AACF"}
|
package/dist/lt/vat.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vat.js","names":[],"sources":["../../src/lt/vat.ts"],"sourcesContent":["/**\n * PVM kodas (Lithuanian VAT number).\n *\n * 9 or 12 digits. 9-digit: d[7] must be 1.\n * 12-digit: d[10] must be 1. Weighted checksum\n * with fallback weights.\n *\n * @see https://www.vatify.eu/lithuania-vat-number.html\n * @see https://www.vmi.lt/\n */\n\nimport type {\n ValidateResult,\n CountryValidator,\n} from \"../types\";\n\nimport { clean } from \"#util/clean\";\nimport { randomDigits } from \"#util/generate\";\nimport { err } from \"#util/result\";\nimport { isdigits } from \"#util/strings\";\n\nconst compact = (value: string): string => {\n let v = clean(value, \" -/.\");\n if (v.startsWith(\"LT\") || v.startsWith(\"lt\")) {\n v = v.slice(2);\n }\n return v;\n};\n\nconst checksum = (v: string): boolean => {\n const n = v.length;\n let check = 0;\n for (let i = 0; i < n - 1; i++) {\n check += (1 + (i % 9)) * Number(v[i]);\n }\n check %= 11;\n if (check === 10) {\n check = 0;\n for (let i = 0; i < n - 1; i++) {\n check += (1 + ((i + 2) % 9)) * Number(v[i]);\n }\n check %= 11;\n }\n return check % 10 === Number(v[n - 1]);\n};\n\nconst validate = (value: string): ValidateResult => {\n const v = compact(value);\n if (v.length !== 9 && v.length !== 12) {\n return err(\n \"INVALID_LENGTH\",\n \"Lithuanian VAT number must be 9 or 12 digits\",\n );\n }\n if (!isdigits(v)) {\n return err(\n \"INVALID_FORMAT\",\n \"Lithuanian VAT number must contain only digits\",\n );\n }\n // Check the marker digit\n if (v.length === 9 && v[7] !== \"1\") {\n return err(\n \"INVALID_COMPONENT\",\n \"Lithuanian 9-digit VAT: digit 8 must be 1\",\n );\n }\n if (v.length === 12 && v[10] !== \"1\") {\n return err(\n \"INVALID_COMPONENT\",\n \"Lithuanian 12-digit VAT: digit 11 must be 1\",\n );\n }\n if (!checksum(v)) {\n return err(\n \"INVALID_CHECKSUM\",\n \"Lithuanian VAT number check digit mismatch\",\n );\n }\n return { valid: true, compact: v };\n};\n\nconst format = (value: string): string =>\n `LT${compact(value)}`;\n\n/** Generate a random valid Lithuanian VAT number. */\nconst generate = (): string => {\n for (;;) {\n const c = randomDigits(9);\n if (validate(c).valid) return c;\n }\n};\n\n/** Lithuanian VAT Number. */\nconst vat: CountryValidator<\"LT\"> = {\n name: \"Lithuanian VAT Number\",\n localName: \"PVM mokėtojo kodas\",\n abbreviation: \"PVM kodas\",\n aliases: [\"PVM mokėtojo kodas\", \"PVM\"] as const,\n candidatePattern: \"LT\\\\d{9,12}\",\n scope: \"country\",\n country: \"LT\",\n entityType: \"any\",\n sourceUrl: \"https://www.vmi.lt/\",\n lengths: [9, 12] as const,\n examples: [\"119511515\", \"100001919017\"] as const,\n compact,\n format,\n validate,\n generate,\n};\n\nexport default vat;\nexport { compact, format, validate, generate };\n"],"mappings":";;;;;AAqBA,MAAM,WAAW,UAA0B;CACzC,IAAI,IAAI,MAAM,OAAO,MAAM;CAC3B,IAAI,EAAE,WAAW,IAAI,KAAK,EAAE,WAAW,IAAI,GACzC,IAAI,EAAE,MAAM,CAAC;CAEf,OAAO;AACT;AAEA,MAAM,YAAY,MAAuB;CACvC,MAAM,IAAI,EAAE;CACZ,IAAI,QAAQ;CACZ,KAAK,IAAI,IAAI,GAAG,IAAI,IAAI,GAAG,KACzB,UAAU,IAAK,IAAI,KAAM,OAAO,EAAE,EAAE;CAEtC,SAAS;CACT,IAAI,UAAU,IAAI;EAChB,QAAQ;EACR,KAAK,IAAI,IAAI,GAAG,IAAI,IAAI,GAAG,KACzB,UAAU,KAAM,IAAI,KAAK,KAAM,OAAO,EAAE,EAAE;EAE5C,SAAS;CACX;CACA,OAAO,QAAQ,OAAO,OAAO,EAAE,IAAI,EAAE;AACvC;AAEA,MAAM,YAAY,UAAkC;CAClD,MAAM,IAAI,QAAQ,KAAK;CACvB,IAAI,EAAE,WAAW,KAAK,EAAE,WAAW,IACjC,OAAO,IACL,kBACA,8CACF;CAEF,IAAI,CAAC,SAAS,CAAC,GACb,OAAO,IACL,kBACA,gDACF;CAGF,IAAI,EAAE,WAAW,KAAK,EAAE,OAAO,KAC7B,OAAO,IACL,qBACA,2CACF;CAEF,IAAI,EAAE,WAAW,MAAM,EAAE,QAAQ,KAC/B,OAAO,IACL,qBACA,6CACF;CAEF,IAAI,CAAC,SAAS,CAAC,GACb,OAAO,IACL,oBACA,4CACF;CAEF,OAAO;EAAE,OAAO;EAAM,SAAS;CAAE;AACnC;AAEA,MAAM,UAAU,UACd,KAAK,QAAQ,KAAK;;AAGpB,MAAM,iBAAyB;CAC7B,SAAS;EACP,MAAM,IAAI,aAAa,CAAC;EACxB,IAAI,SAAS,CAAC,
|
|
1
|
+
{"version":3,"file":"vat.js","names":[],"sources":["../../src/lt/vat.ts"],"sourcesContent":["/**\n * PVM kodas (Lithuanian VAT number).\n *\n * 9 or 12 digits. 9-digit: d[7] must be 1.\n * 12-digit: d[10] must be 1. Weighted checksum\n * with fallback weights.\n *\n * @see https://www.vatify.eu/lithuania-vat-number.html\n * @see https://www.vmi.lt/\n */\n\nimport type {\n ValidateResult,\n CountryValidator,\n} from \"../types\";\n\nimport { clean } from \"#util/clean\";\nimport { randomDigits } from \"#util/generate\";\nimport { err } from \"#util/result\";\nimport { isdigits } from \"#util/strings\";\n\nconst compact = (value: string): string => {\n let v = clean(value, \" -/.\");\n if (v.startsWith(\"LT\") || v.startsWith(\"lt\")) {\n v = v.slice(2);\n }\n return v;\n};\n\nconst checksum = (v: string): boolean => {\n const n = v.length;\n let check = 0;\n for (let i = 0; i < n - 1; i++) {\n check += (1 + (i % 9)) * Number(v[i]);\n }\n check %= 11;\n if (check === 10) {\n check = 0;\n for (let i = 0; i < n - 1; i++) {\n check += (1 + ((i + 2) % 9)) * Number(v[i]);\n }\n check %= 11;\n }\n return check % 10 === Number(v[n - 1]);\n};\n\nconst validate = (value: string): ValidateResult => {\n const v = compact(value);\n if (v.length !== 9 && v.length !== 12) {\n return err(\n \"INVALID_LENGTH\",\n \"Lithuanian VAT number must be 9 or 12 digits\",\n );\n }\n if (!isdigits(v)) {\n return err(\n \"INVALID_FORMAT\",\n \"Lithuanian VAT number must contain only digits\",\n );\n }\n // Check the marker digit\n if (v.length === 9 && v[7] !== \"1\") {\n return err(\n \"INVALID_COMPONENT\",\n \"Lithuanian 9-digit VAT: digit 8 must be 1\",\n );\n }\n if (v.length === 12 && v[10] !== \"1\") {\n return err(\n \"INVALID_COMPONENT\",\n \"Lithuanian 12-digit VAT: digit 11 must be 1\",\n );\n }\n if (!checksum(v)) {\n return err(\n \"INVALID_CHECKSUM\",\n \"Lithuanian VAT number check digit mismatch\",\n );\n }\n return { valid: true, compact: v };\n};\n\nconst format = (value: string): string =>\n `LT${compact(value)}`;\n\n/** Generate a random valid Lithuanian VAT number. */\nconst generate = (): string => {\n for (;;) {\n const c = randomDigits(9);\n if (validate(c).valid) return c;\n }\n};\n\n/** Lithuanian VAT Number. */\nconst vat: CountryValidator<\"LT\"> = {\n name: \"Lithuanian VAT Number\",\n localName: \"PVM mokėtojo kodas\",\n abbreviation: \"PVM kodas\",\n aliases: [\"PVM mokėtojo kodas\", \"PVM\"] as const,\n candidatePattern: \"LT\\\\d{9,12}\",\n scope: \"country\",\n country: \"LT\",\n entityType: \"any\",\n sourceUrl: \"https://www.vmi.lt/\",\n lengths: [9, 12] as const,\n examples: [\"119511515\", \"100001919017\"] as const,\n compact,\n format,\n validate,\n generate,\n};\n\nexport default vat;\nexport { compact, format, validate, generate };\n"],"mappings":";;;;;AAqBA,MAAM,WAAW,UAA0B;CACzC,IAAI,IAAI,MAAM,OAAO,MAAM;CAC3B,IAAI,EAAE,WAAW,IAAI,KAAK,EAAE,WAAW,IAAI,GACzC,IAAI,EAAE,MAAM,CAAC;CAEf,OAAO;AACT;AAEA,MAAM,YAAY,MAAuB;CACvC,MAAM,IAAI,EAAE;CACZ,IAAI,QAAQ;CACZ,KAAK,IAAI,IAAI,GAAG,IAAI,IAAI,GAAG,KACzB,UAAU,IAAK,IAAI,KAAM,OAAO,EAAE,EAAE;CAEtC,SAAS;CACT,IAAI,UAAU,IAAI;EAChB,QAAQ;EACR,KAAK,IAAI,IAAI,GAAG,IAAI,IAAI,GAAG,KACzB,UAAU,KAAM,IAAI,KAAK,KAAM,OAAO,EAAE,EAAE;EAE5C,SAAS;CACX;CACA,OAAO,QAAQ,OAAO,OAAO,EAAE,IAAI,EAAE;AACvC;AAEA,MAAM,YAAY,UAAkC;CAClD,MAAM,IAAI,QAAQ,KAAK;CACvB,IAAI,EAAE,WAAW,KAAK,EAAE,WAAW,IACjC,OAAO,IACL,kBACA,8CACF;CAEF,IAAI,CAAC,SAAS,CAAC,GACb,OAAO,IACL,kBACA,gDACF;CAGF,IAAI,EAAE,WAAW,KAAK,EAAE,OAAO,KAC7B,OAAO,IACL,qBACA,2CACF;CAEF,IAAI,EAAE,WAAW,MAAM,EAAE,QAAQ,KAC/B,OAAO,IACL,qBACA,6CACF;CAEF,IAAI,CAAC,SAAS,CAAC,GACb,OAAO,IACL,oBACA,4CACF;CAEF,OAAO;EAAE,OAAO;EAAM,SAAS;CAAE;AACnC;AAEA,MAAM,UAAU,UACd,KAAK,QAAQ,KAAK;;AAGpB,MAAM,iBAAyB;CAC7B,SAAS;EACP,MAAM,IAAI,aAAa,CAAC;EACxB,IAAI,SAAS,CAAC,CAAC,CAAC,OAAO,OAAO;CAChC;AACF;;AAGA,MAAM,MAA8B;CAClC,MAAM;CACN,WAAW;CACX,cAAc;CACd,SAAS,CAAC,sBAAsB,KAAK;CACrC,kBAAkB;CAClB,OAAO;CACP,SAAS;CACT,YAAY;CACZ,WAAW;CACX,SAAS,CAAC,GAAG,EAAE;CACf,UAAU,CAAC,aAAa,cAAc;CACtC;CACA;CACA;CACA;AACF"}
|
package/dist/lu/vat.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vat.js","names":[],"sources":["../../src/lu/vat.ts"],"sourcesContent":["/**\n * TVA (Luxembourg VAT number).\n *\n * 8 digits. First 6 digits mod 89 === last 2 digits.\n *\n * @see https://www.vatify.eu/luxembourg-vat-number.html\n * @see https://www.oecd.org/content/dam/oecd/en/topics/policy-issue-focus/aeoi/luxembourg-tin.pdf\n */\n\nimport type {\n ValidateResult,\n CountryValidator,\n} from \"../types\";\n\nimport { clean } from \"#util/clean\";\nimport { randomDigits } from \"#util/generate\";\nimport { err } from \"#util/result\";\nimport { isdigits } from \"#util/strings\";\n\nconst compact = (value: string): string => {\n let v = clean(value, \" -/.\");\n if (v.startsWith(\"LU\") || v.startsWith(\"lu\")) {\n v = v.slice(2);\n }\n return v;\n};\n\nconst validate = (value: string): ValidateResult => {\n const v = compact(value);\n if (v.length !== 8) {\n return err(\n \"INVALID_LENGTH\",\n \"Luxembourg VAT number must be 8 digits\",\n );\n }\n if (!isdigits(v)) {\n return err(\n \"INVALID_FORMAT\",\n \"Luxembourg VAT number must contain only digits\",\n );\n }\n const front = Number.parseInt(v.slice(0, 6), 10);\n const check = Number.parseInt(v.slice(6, 8), 10);\n if (front % 89 !== check) {\n return err(\n \"INVALID_CHECKSUM\",\n \"Luxembourg VAT number check digit mismatch\",\n );\n }\n return { valid: true, compact: v };\n};\n\nconst format = (value: string): string =>\n `LU${compact(value)}`;\n\n/** Generate a random valid Luxembourg VAT number. */\nconst generate = (): string => {\n const body = randomDigits(6);\n return (\n body +\n String(Number.parseInt(body, 10) % 89).padStart(2, \"0\")\n );\n};\n\n/** Luxembourg VAT Number. */\nconst vat: CountryValidator<\"LU\"> = {\n name: \"Luxembourg VAT Number\",\n localName: \"Numéro de TVA\",\n abbreviation: \"TVA\",\n aliases: [\"TVA\", \"numéro d'identification TVA\"] as const,\n candidatePattern: \"LU\\\\d{8}\",\n scope: \"country\",\n country: \"LU\",\n entityType: \"company\",\n sourceUrl: \"https://pfi.public.lu/\",\n lengths: [8] as const,\n examples: [\"15027442\"] as const,\n compact,\n format,\n validate,\n generate,\n};\n\nexport default vat;\nexport { compact, format, validate, generate };\n"],"mappings":";;;;;AAmBA,MAAM,WAAW,UAA0B;CACzC,IAAI,IAAI,MAAM,OAAO,MAAM;CAC3B,IAAI,EAAE,WAAW,IAAI,KAAK,EAAE,WAAW,IAAI,GACzC,IAAI,EAAE,MAAM,CAAC;CAEf,OAAO;AACT;AAEA,MAAM,YAAY,UAAkC;CAClD,MAAM,IAAI,QAAQ,KAAK;CACvB,IAAI,EAAE,WAAW,GACf,OAAO,IACL,kBACA,wCACF;CAEF,IAAI,CAAC,SAAS,CAAC,GACb,OAAO,IACL,kBACA,gDACF;CAEF,MAAM,QAAQ,OAAO,SAAS,EAAE,MAAM,GAAG,CAAC,GAAG,EAAE;CAC/C,MAAM,QAAQ,OAAO,SAAS,EAAE,MAAM,GAAG,CAAC,GAAG,EAAE;CAC/C,IAAI,QAAQ,OAAO,OACjB,OAAO,IACL,oBACA,4CACF;CAEF,OAAO;EAAE,OAAO;EAAM,SAAS;CAAE;AACnC;AAEA,MAAM,UAAU,UACd,KAAK,QAAQ,KAAK;;AAGpB,MAAM,iBAAyB;CAC7B,MAAM,OAAO,aAAa,CAAC;CAC3B,OACE,OACA,OAAO,OAAO,SAAS,MAAM,EAAE,IAAI,EAAE,
|
|
1
|
+
{"version":3,"file":"vat.js","names":[],"sources":["../../src/lu/vat.ts"],"sourcesContent":["/**\n * TVA (Luxembourg VAT number).\n *\n * 8 digits. First 6 digits mod 89 === last 2 digits.\n *\n * @see https://www.vatify.eu/luxembourg-vat-number.html\n * @see https://www.oecd.org/content/dam/oecd/en/topics/policy-issue-focus/aeoi/luxembourg-tin.pdf\n */\n\nimport type {\n ValidateResult,\n CountryValidator,\n} from \"../types\";\n\nimport { clean } from \"#util/clean\";\nimport { randomDigits } from \"#util/generate\";\nimport { err } from \"#util/result\";\nimport { isdigits } from \"#util/strings\";\n\nconst compact = (value: string): string => {\n let v = clean(value, \" -/.\");\n if (v.startsWith(\"LU\") || v.startsWith(\"lu\")) {\n v = v.slice(2);\n }\n return v;\n};\n\nconst validate = (value: string): ValidateResult => {\n const v = compact(value);\n if (v.length !== 8) {\n return err(\n \"INVALID_LENGTH\",\n \"Luxembourg VAT number must be 8 digits\",\n );\n }\n if (!isdigits(v)) {\n return err(\n \"INVALID_FORMAT\",\n \"Luxembourg VAT number must contain only digits\",\n );\n }\n const front = Number.parseInt(v.slice(0, 6), 10);\n const check = Number.parseInt(v.slice(6, 8), 10);\n if (front % 89 !== check) {\n return err(\n \"INVALID_CHECKSUM\",\n \"Luxembourg VAT number check digit mismatch\",\n );\n }\n return { valid: true, compact: v };\n};\n\nconst format = (value: string): string =>\n `LU${compact(value)}`;\n\n/** Generate a random valid Luxembourg VAT number. */\nconst generate = (): string => {\n const body = randomDigits(6);\n return (\n body +\n String(Number.parseInt(body, 10) % 89).padStart(2, \"0\")\n );\n};\n\n/** Luxembourg VAT Number. */\nconst vat: CountryValidator<\"LU\"> = {\n name: \"Luxembourg VAT Number\",\n localName: \"Numéro de TVA\",\n abbreviation: \"TVA\",\n aliases: [\"TVA\", \"numéro d'identification TVA\"] as const,\n candidatePattern: \"LU\\\\d{8}\",\n scope: \"country\",\n country: \"LU\",\n entityType: \"company\",\n sourceUrl: \"https://pfi.public.lu/\",\n lengths: [8] as const,\n examples: [\"15027442\"] as const,\n compact,\n format,\n validate,\n generate,\n};\n\nexport default vat;\nexport { compact, format, validate, generate };\n"],"mappings":";;;;;AAmBA,MAAM,WAAW,UAA0B;CACzC,IAAI,IAAI,MAAM,OAAO,MAAM;CAC3B,IAAI,EAAE,WAAW,IAAI,KAAK,EAAE,WAAW,IAAI,GACzC,IAAI,EAAE,MAAM,CAAC;CAEf,OAAO;AACT;AAEA,MAAM,YAAY,UAAkC;CAClD,MAAM,IAAI,QAAQ,KAAK;CACvB,IAAI,EAAE,WAAW,GACf,OAAO,IACL,kBACA,wCACF;CAEF,IAAI,CAAC,SAAS,CAAC,GACb,OAAO,IACL,kBACA,gDACF;CAEF,MAAM,QAAQ,OAAO,SAAS,EAAE,MAAM,GAAG,CAAC,GAAG,EAAE;CAC/C,MAAM,QAAQ,OAAO,SAAS,EAAE,MAAM,GAAG,CAAC,GAAG,EAAE;CAC/C,IAAI,QAAQ,OAAO,OACjB,OAAO,IACL,oBACA,4CACF;CAEF,OAAO;EAAE,OAAO;EAAM,SAAS;CAAE;AACnC;AAEA,MAAM,UAAU,UACd,KAAK,QAAQ,KAAK;;AAGpB,MAAM,iBAAyB;CAC7B,MAAM,OAAO,aAAa,CAAC;CAC3B,OACE,OACA,OAAO,OAAO,SAAS,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,SAAS,GAAG,GAAG;AAE1D;;AAGA,MAAM,MAA8B;CAClC,MAAM;CACN,WAAW;CACX,cAAc;CACd,SAAS,CAAC,OAAO,6BAA6B;CAC9C,kBAAkB;CAClB,OAAO;CACP,SAAS;CACT,YAAY;CACZ,WAAW;CACX,SAAS,CAAC,CAAC;CACX,UAAU,CAAC,UAAU;CACrB;CACA;CACA;CACA;AACF"}
|
package/dist/lv/vat.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vat.js","names":[],"sources":["../../src/lv/vat.ts"],"sourcesContent":["/**\n * PVN (Latvian VAT number).\n *\n * 11 digits.\n *\n * Individuals:\n * - legacy personal codes: DDMMYY + century digit 0/1/2 + 4 digits\n * - since 1 July 2017: codes start with 3, the second digit is 2-9,\n * and the remaining digits are random\n *\n * Entities:\n * - tax administration issued: 9XXXXXXXXXX\n * - enterprise register issued: 4XXXXXXXXXX / 5XXXXXXXXXX\n *\n * Canonical sources:\n * - OECD Latvia TIN sheet for the tax-facing formats\n * - PMLP page documenting the post-2017 personal code\n *\n * These are the reasons we accept the new personal\n * code shape (`3` + second digit `2-9`) without the\n * legacy birth-date checksum, and why we reject\n * legacy personal codes with century digits outside\n * 0/1/2.\n *\n * @see https://www.oecd.org/tax/automatic-exchange/crs-implementation-and-assistance/tax-identification-numbers/Latvia-TIN.pdf\n * @see https://www.pmlp.gov.lv/en/change-personal-identity-number\n */\n\nimport type {\n ValidateResult,\n CountryValidator,\n} from \"../types\";\n\nimport { weightedSum } from \"#checksums/weighted-sum\";\nimport { clean } from \"#util/clean\";\nimport { randomDigits, randomInt } from \"#util/generate\";\nimport { err } from \"#util/result\";\nimport { isdigits } from \"#util/strings\";\n\nconst WEIGHTS = [9, 1, 4, 8, 3, 10, 2, 5, 7, 6, 1];\n\nconst compact = (value: string): string => {\n let v = clean(value, \" -/.\");\n if (v.startsWith(\"LV\") || v.startsWith(\"lv\")) {\n v = v.slice(2);\n }\n return v;\n};\n\nconst PERS_WEIGHTS = [10, 5, 8, 4, 2, 1, 6, 3, 7, 9];\n\nconst checkPersonal = (v: string): boolean => {\n // Initial sum = 1 (not 0), so can't use\n // weightedSum directly\n let sum = 1;\n for (let i = 0; i < 10; i++) {\n // SAFETY: both arrays are length 10+\n // eslint-disable-next-line no-non-null-assertion\n sum += PERS_WEIGHTS[i]! * Number(v[i]);\n }\n return (sum % 11) % 10 === Number(v[10]);\n};\n\nconst validateBirthDate = (v: string): boolean => {\n const day = Number(v.slice(0, 2));\n const month = Number(v.slice(2, 4));\n const year2 = Number(v.slice(4, 6));\n const century = Number(v[6]);\n if (century < 0 || century > 2) {\n return false;\n }\n const fullYear = 1800 + century * 100 + year2;\n const date = new Date(fullYear, month - 1, day);\n return (\n date.getFullYear() === fullYear &&\n date.getMonth() === month - 1 &&\n date.getDate() === day\n );\n};\n\nconst validate = (value: string): ValidateResult => {\n const v = compact(value);\n if (v.length !== 11) {\n return err(\n \"INVALID_LENGTH\",\n \"Latvian VAT number must be 11 digits\",\n );\n }\n if (!isdigits(v)) {\n return err(\n \"INVALID_FORMAT\",\n \"Latvian VAT number must contain only digits\",\n );\n }\n if (\n v[0] === \"3\" &&\n v[1] !== undefined &&\n v[1] >= \"2\" &&\n v[1] <= \"9\"\n ) {\n // Since 1 July 2017, new personal codes start with 3\n // and are syntactic identifiers only.\n return { valid: true, compact: v };\n }\n\n const first = v[0];\n if (first === \"4\" || first === \"5\" || first === \"9\") {\n const sum = weightedSum(v, WEIGHTS, 11);\n if (sum !== 3) {\n return err(\n \"INVALID_CHECKSUM\",\n \"Latvian VAT number check digit mismatch\",\n );\n }\n } else {\n // Old-format personal code: first 6 digits encode\n // birth date as DDMMYY, digit 7 encodes century\n if (!validateBirthDate(v)) {\n return err(\n \"INVALID_COMPONENT\",\n \"Latvian personal code has invalid birth date\",\n );\n }\n if (!checkPersonal(v)) {\n return err(\n \"INVALID_CHECKSUM\",\n \"Latvian personal code check digit mismatch\",\n );\n }\n }\n return { valid: true, compact: v };\n};\n\nconst format = (value: string): string =>\n `LV${compact(value)}`;\n\n/** Generate a random valid Latvian VAT number. */\nconst generate = (): string => {\n const entityPrefixes = [\"4\", \"5\", \"9\"] as const;\n for (;;) {\n const c =\n randomInt(0, 1) === 0\n ? `3${randomInt(2, 9)}${randomDigits(9)}`\n : `${entityPrefixes[randomInt(0, entityPrefixes.length - 1)] ?? \"4\"}${randomDigits(10)}`;\n if (validate(c).valid) return c;\n }\n};\n\n/** Latvian VAT Number. */\nconst vat: CountryValidator<\"LV\"> = {\n name: \"Latvian VAT Number\",\n localName: \"PVN reģistrācijas numurs\",\n abbreviation: \"PVN\",\n aliases: [\"PVN reģistrācijas numurs\", \"PVN\"] as const,\n candidatePattern: \"LV\\\\d{11}\",\n scope: \"country\",\n country: \"LV\",\n entityType: \"any\",\n sourceUrl:\n \"https://www.pmlp.gov.lv/en/change-personal-identity-number\",\n examples: [\"40003521600\", \"32999999999\"] as const,\n compact,\n format,\n validate,\n generate,\n};\n\nexport default vat;\nexport { compact, format, validate, generate };\n"],"mappings":";;;;;;AAuCA,MAAM,UAAU;CAAC;CAAG;CAAG;CAAG;CAAG;CAAG;CAAI;CAAG;CAAG;CAAG;CAAG;AAAC;AAEjD,MAAM,WAAW,UAA0B;CACzC,IAAI,IAAI,MAAM,OAAO,MAAM;CAC3B,IAAI,EAAE,WAAW,IAAI,KAAK,EAAE,WAAW,IAAI,GACzC,IAAI,EAAE,MAAM,CAAC;CAEf,OAAO;AACT;AAEA,MAAM,eAAe;CAAC;CAAI;CAAG;CAAG;CAAG;CAAG;CAAG;CAAG;CAAG;CAAG;AAAC;AAEnD,MAAM,iBAAiB,MAAuB;CAG5C,IAAI,MAAM;CACV,KAAK,IAAI,IAAI,GAAG,IAAI,IAAI,KAGtB,OAAO,aAAa,KAAM,OAAO,EAAE,EAAE;CAEvC,OAAQ,MAAM,KAAM,OAAO,OAAO,EAAE,GAAG;AACzC;AAEA,MAAM,qBAAqB,MAAuB;CAChD,MAAM,MAAM,OAAO,EAAE,MAAM,GAAG,CAAC,CAAC;CAChC,MAAM,QAAQ,OAAO,EAAE,MAAM,GAAG,CAAC,CAAC;CAClC,MAAM,QAAQ,OAAO,EAAE,MAAM,GAAG,CAAC,CAAC;CAClC,MAAM,UAAU,OAAO,EAAE,EAAE;CAC3B,IAAI,UAAU,KAAK,UAAU,GAC3B,OAAO;CAET,MAAM,WAAW,OAAO,UAAU,MAAM;CACxC,MAAM,OAAO,IAAI,KAAK,UAAU,QAAQ,GAAG,GAAG;CAC9C,OACE,KAAK,YAAY,MAAM,YACvB,KAAK,SAAS,MAAM,QAAQ,KAC5B,KAAK,QAAQ,MAAM;AAEvB;AAEA,MAAM,YAAY,UAAkC;CAClD,MAAM,IAAI,QAAQ,KAAK;CACvB,IAAI,EAAE,WAAW,IACf,OAAO,IACL,kBACA,sCACF;CAEF,IAAI,CAAC,SAAS,CAAC,GACb,OAAO,IACL,kBACA,6CACF;CAEF,IACE,EAAE,OAAO,OACT,EAAE,OAAO,KAAA,KACT,EAAE,MAAM,OACR,EAAE,MAAM,KAIR,OAAO;EAAE,OAAO;EAAM,SAAS;CAAE;CAGnC,MAAM,QAAQ,EAAE;CAChB,IAAI,UAAU,OAAO,UAAU,OAAO,UAAU;MAClC,YAAY,GAAG,SAAS,EAC9B,MAAM,GACV,OAAO,IACL,oBACA,yCACF;CAAA,OAEG;EAGL,IAAI,CAAC,kBAAkB,CAAC,GACtB,OAAO,IACL,qBACA,8CACF;EAEF,IAAI,CAAC,cAAc,CAAC,GAClB,OAAO,IACL,oBACA,4CACF;CAEJ;CACA,OAAO;EAAE,OAAO;EAAM,SAAS;CAAE;AACnC;AAEA,MAAM,UAAU,UACd,KAAK,QAAQ,KAAK;;AAGpB,MAAM,iBAAyB;CAC7B,MAAM,iBAAiB;EAAC;EAAK;EAAK;CAAG;CACrC,SAAS;EACP,MAAM,IACJ,UAAU,GAAG,CAAC,MAAM,IAChB,IAAI,UAAU,GAAG,CAAC,IAAI,aAAa,CAAC,MACpC,GAAG,eAAe,UAAU,GAAG,eAAe,SAAS,CAAC,MAAM,MAAM,aAAa,EAAE;EACzF,IAAI,SAAS,CAAC,
|
|
1
|
+
{"version":3,"file":"vat.js","names":[],"sources":["../../src/lv/vat.ts"],"sourcesContent":["/**\n * PVN (Latvian VAT number).\n *\n * 11 digits.\n *\n * Individuals:\n * - legacy personal codes: DDMMYY + century digit 0/1/2 + 4 digits\n * - since 1 July 2017: codes start with 3, the second digit is 2-9,\n * and the remaining digits are random\n *\n * Entities:\n * - tax administration issued: 9XXXXXXXXXX\n * - enterprise register issued: 4XXXXXXXXXX / 5XXXXXXXXXX\n *\n * Canonical sources:\n * - OECD Latvia TIN sheet for the tax-facing formats\n * - PMLP page documenting the post-2017 personal code\n *\n * These are the reasons we accept the new personal\n * code shape (`3` + second digit `2-9`) without the\n * legacy birth-date checksum, and why we reject\n * legacy personal codes with century digits outside\n * 0/1/2.\n *\n * @see https://www.oecd.org/tax/automatic-exchange/crs-implementation-and-assistance/tax-identification-numbers/Latvia-TIN.pdf\n * @see https://www.pmlp.gov.lv/en/change-personal-identity-number\n */\n\nimport type {\n ValidateResult,\n CountryValidator,\n} from \"../types\";\n\nimport { weightedSum } from \"#checksums/weighted-sum\";\nimport { clean } from \"#util/clean\";\nimport { randomDigits, randomInt } from \"#util/generate\";\nimport { err } from \"#util/result\";\nimport { isdigits } from \"#util/strings\";\n\nconst WEIGHTS = [9, 1, 4, 8, 3, 10, 2, 5, 7, 6, 1];\n\nconst compact = (value: string): string => {\n let v = clean(value, \" -/.\");\n if (v.startsWith(\"LV\") || v.startsWith(\"lv\")) {\n v = v.slice(2);\n }\n return v;\n};\n\nconst PERS_WEIGHTS = [10, 5, 8, 4, 2, 1, 6, 3, 7, 9];\n\nconst checkPersonal = (v: string): boolean => {\n // Initial sum = 1 (not 0), so can't use\n // weightedSum directly\n let sum = 1;\n for (let i = 0; i < 10; i++) {\n // SAFETY: both arrays are length 10+\n // eslint-disable-next-line no-non-null-assertion\n sum += PERS_WEIGHTS[i]! * Number(v[i]);\n }\n return (sum % 11) % 10 === Number(v[10]);\n};\n\nconst validateBirthDate = (v: string): boolean => {\n const day = Number(v.slice(0, 2));\n const month = Number(v.slice(2, 4));\n const year2 = Number(v.slice(4, 6));\n const century = Number(v[6]);\n if (century < 0 || century > 2) {\n return false;\n }\n const fullYear = 1800 + century * 100 + year2;\n const date = new Date(fullYear, month - 1, day);\n return (\n date.getFullYear() === fullYear &&\n date.getMonth() === month - 1 &&\n date.getDate() === day\n );\n};\n\nconst validate = (value: string): ValidateResult => {\n const v = compact(value);\n if (v.length !== 11) {\n return err(\n \"INVALID_LENGTH\",\n \"Latvian VAT number must be 11 digits\",\n );\n }\n if (!isdigits(v)) {\n return err(\n \"INVALID_FORMAT\",\n \"Latvian VAT number must contain only digits\",\n );\n }\n if (\n v[0] === \"3\" &&\n v[1] !== undefined &&\n v[1] >= \"2\" &&\n v[1] <= \"9\"\n ) {\n // Since 1 July 2017, new personal codes start with 3\n // and are syntactic identifiers only.\n return { valid: true, compact: v };\n }\n\n const first = v[0];\n if (first === \"4\" || first === \"5\" || first === \"9\") {\n const sum = weightedSum(v, WEIGHTS, 11);\n if (sum !== 3) {\n return err(\n \"INVALID_CHECKSUM\",\n \"Latvian VAT number check digit mismatch\",\n );\n }\n } else {\n // Old-format personal code: first 6 digits encode\n // birth date as DDMMYY, digit 7 encodes century\n if (!validateBirthDate(v)) {\n return err(\n \"INVALID_COMPONENT\",\n \"Latvian personal code has invalid birth date\",\n );\n }\n if (!checkPersonal(v)) {\n return err(\n \"INVALID_CHECKSUM\",\n \"Latvian personal code check digit mismatch\",\n );\n }\n }\n return { valid: true, compact: v };\n};\n\nconst format = (value: string): string =>\n `LV${compact(value)}`;\n\n/** Generate a random valid Latvian VAT number. */\nconst generate = (): string => {\n const entityPrefixes = [\"4\", \"5\", \"9\"] as const;\n for (;;) {\n const c =\n randomInt(0, 1) === 0\n ? `3${randomInt(2, 9)}${randomDigits(9)}`\n : `${entityPrefixes[randomInt(0, entityPrefixes.length - 1)] ?? \"4\"}${randomDigits(10)}`;\n if (validate(c).valid) return c;\n }\n};\n\n/** Latvian VAT Number. */\nconst vat: CountryValidator<\"LV\"> = {\n name: \"Latvian VAT Number\",\n localName: \"PVN reģistrācijas numurs\",\n abbreviation: \"PVN\",\n aliases: [\"PVN reģistrācijas numurs\", \"PVN\"] as const,\n candidatePattern: \"LV\\\\d{11}\",\n scope: \"country\",\n country: \"LV\",\n entityType: \"any\",\n sourceUrl:\n \"https://www.pmlp.gov.lv/en/change-personal-identity-number\",\n examples: [\"40003521600\", \"32999999999\"] as const,\n compact,\n format,\n validate,\n generate,\n};\n\nexport default vat;\nexport { compact, format, validate, generate };\n"],"mappings":";;;;;;AAuCA,MAAM,UAAU;CAAC;CAAG;CAAG;CAAG;CAAG;CAAG;CAAI;CAAG;CAAG;CAAG;CAAG;AAAC;AAEjD,MAAM,WAAW,UAA0B;CACzC,IAAI,IAAI,MAAM,OAAO,MAAM;CAC3B,IAAI,EAAE,WAAW,IAAI,KAAK,EAAE,WAAW,IAAI,GACzC,IAAI,EAAE,MAAM,CAAC;CAEf,OAAO;AACT;AAEA,MAAM,eAAe;CAAC;CAAI;CAAG;CAAG;CAAG;CAAG;CAAG;CAAG;CAAG;CAAG;AAAC;AAEnD,MAAM,iBAAiB,MAAuB;CAG5C,IAAI,MAAM;CACV,KAAK,IAAI,IAAI,GAAG,IAAI,IAAI,KAGtB,OAAO,aAAa,KAAM,OAAO,EAAE,EAAE;CAEvC,OAAQ,MAAM,KAAM,OAAO,OAAO,EAAE,GAAG;AACzC;AAEA,MAAM,qBAAqB,MAAuB;CAChD,MAAM,MAAM,OAAO,EAAE,MAAM,GAAG,CAAC,CAAC;CAChC,MAAM,QAAQ,OAAO,EAAE,MAAM,GAAG,CAAC,CAAC;CAClC,MAAM,QAAQ,OAAO,EAAE,MAAM,GAAG,CAAC,CAAC;CAClC,MAAM,UAAU,OAAO,EAAE,EAAE;CAC3B,IAAI,UAAU,KAAK,UAAU,GAC3B,OAAO;CAET,MAAM,WAAW,OAAO,UAAU,MAAM;CACxC,MAAM,OAAO,IAAI,KAAK,UAAU,QAAQ,GAAG,GAAG;CAC9C,OACE,KAAK,YAAY,MAAM,YACvB,KAAK,SAAS,MAAM,QAAQ,KAC5B,KAAK,QAAQ,MAAM;AAEvB;AAEA,MAAM,YAAY,UAAkC;CAClD,MAAM,IAAI,QAAQ,KAAK;CACvB,IAAI,EAAE,WAAW,IACf,OAAO,IACL,kBACA,sCACF;CAEF,IAAI,CAAC,SAAS,CAAC,GACb,OAAO,IACL,kBACA,6CACF;CAEF,IACE,EAAE,OAAO,OACT,EAAE,OAAO,KAAA,KACT,EAAE,MAAM,OACR,EAAE,MAAM,KAIR,OAAO;EAAE,OAAO;EAAM,SAAS;CAAE;CAGnC,MAAM,QAAQ,EAAE;CAChB,IAAI,UAAU,OAAO,UAAU,OAAO,UAAU;MAClC,YAAY,GAAG,SAAS,EAC9B,MAAM,GACV,OAAO,IACL,oBACA,yCACF;CAAA,OAEG;EAGL,IAAI,CAAC,kBAAkB,CAAC,GACtB,OAAO,IACL,qBACA,8CACF;EAEF,IAAI,CAAC,cAAc,CAAC,GAClB,OAAO,IACL,oBACA,4CACF;CAEJ;CACA,OAAO;EAAE,OAAO;EAAM,SAAS;CAAE;AACnC;AAEA,MAAM,UAAU,UACd,KAAK,QAAQ,KAAK;;AAGpB,MAAM,iBAAyB;CAC7B,MAAM,iBAAiB;EAAC;EAAK;EAAK;CAAG;CACrC,SAAS;EACP,MAAM,IACJ,UAAU,GAAG,CAAC,MAAM,IAChB,IAAI,UAAU,GAAG,CAAC,IAAI,aAAa,CAAC,MACpC,GAAG,eAAe,UAAU,GAAG,eAAe,SAAS,CAAC,MAAM,MAAM,aAAa,EAAE;EACzF,IAAI,SAAS,CAAC,CAAC,CAAC,OAAO,OAAO;CAChC;AACF;;AAGA,MAAM,MAA8B;CAClC,MAAM;CACN,WAAW;CACX,cAAc;CACd,SAAS,CAAC,4BAA4B,KAAK;CAC3C,kBAAkB;CAClB,OAAO;CACP,SAAS;CACT,YAAY;CACZ,WACE;CACF,UAAU,CAAC,eAAe,aAAa;CACvC;CACA;CACA;CACA;AACF"}
|
package/dist/ma/ice.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ice.js","names":[],"sources":["../../src/ma/ice.ts"],"sourcesContent":["/**\n * ICE (Identifiant Commun de l'Entreprise).\n *\n * Moroccan company identification number. 15 digits:\n * 9 enterprise digits + 4 establishment digits +\n * 2 check digits. Uses Mod 97-10 checksum\n * (full number mod 97 must equal 0).\n *\n * @see https://www.ice.gov.ma/\n */\n\nimport type {\n ValidateResult,\n CountryValidator,\n} from \"../types\";\n\nimport { mod97 } from \"#checksums/mod97\";\nimport { clean } from \"#util/clean\";\nimport { randomDigits } from \"#util/generate\";\nimport { err } from \"#util/result\";\nimport { isdigits } from \"#util/strings\";\n\nconst compact = (value: string): string =>\n clean(value, \" \");\n\nconst validate = (value: string): ValidateResult => {\n const v = compact(value);\n if (v.length !== 15) {\n return err(\n \"INVALID_LENGTH\",\n \"Moroccan ICE must be exactly 15 digits\",\n );\n }\n if (!isdigits(v)) {\n return err(\n \"INVALID_FORMAT\",\n \"Moroccan ICE must contain only digits\",\n );\n }\n if (mod97(v) !== 0) {\n return err(\n \"INVALID_CHECKSUM\",\n \"Moroccan ICE check digits mismatch\",\n );\n }\n return { valid: true, compact: v };\n};\n\nconst format = (value: string): string => compact(value);\n\n/** Generate a random valid Moroccan ICE. */\nconst generate = (): string => {\n for (;;) {\n const c = randomDigits(15);\n if (validate(c).valid) return c;\n }\n};\n\n/** Moroccan Company Identification Number. */\nconst ice: CountryValidator<\"MA\"> = {\n name: \"Moroccan Company Identification Number\",\n localName: \"Identifiant Commun de l'Entreprise\",\n abbreviation: \"ICE\",\n aliases: [\n \"ICE\",\n \"Identifiant Commun de l'Entreprise\",\n ] as const,\n candidatePattern: \"\\\\d{15}\",\n scope: \"country\",\n country: \"MA\",\n entityType: \"company\",\n compact,\n format,\n validate,\n description: \"Moroccan common enterprise identifier\",\n sourceUrl: \"https://www.ice.gov.ma/\",\n lengths: [15] as const,\n examples: [\"001561191000066\", \"002136093000040\"] as const,\n generate,\n};\n\nexport default ice;\nexport { compact, format, validate, generate };\n"],"mappings":";;;;;;AAsBA,MAAM,WAAW,UACf,MAAM,OAAO,GAAG;AAElB,MAAM,YAAY,UAAkC;CAClD,MAAM,IAAI,QAAQ,KAAK;CACvB,IAAI,EAAE,WAAW,IACf,OAAO,IACL,kBACA,wCACF;CAEF,IAAI,CAAC,SAAS,CAAC,GACb,OAAO,IACL,kBACA,uCACF;CAEF,IAAI,MAAM,CAAC,MAAM,GACf,OAAO,IACL,oBACA,oCACF;CAEF,OAAO;EAAE,OAAO;EAAM,SAAS;CAAE;AACnC;AAEA,MAAM,UAAU,UAA0B,QAAQ,KAAK;;AAGvD,MAAM,iBAAyB;CAC7B,SAAS;EACP,MAAM,IAAI,aAAa,EAAE;EACzB,IAAI,SAAS,CAAC,
|
|
1
|
+
{"version":3,"file":"ice.js","names":[],"sources":["../../src/ma/ice.ts"],"sourcesContent":["/**\n * ICE (Identifiant Commun de l'Entreprise).\n *\n * Moroccan company identification number. 15 digits:\n * 9 enterprise digits + 4 establishment digits +\n * 2 check digits. Uses Mod 97-10 checksum\n * (full number mod 97 must equal 0).\n *\n * @see https://www.ice.gov.ma/\n */\n\nimport type {\n ValidateResult,\n CountryValidator,\n} from \"../types\";\n\nimport { mod97 } from \"#checksums/mod97\";\nimport { clean } from \"#util/clean\";\nimport { randomDigits } from \"#util/generate\";\nimport { err } from \"#util/result\";\nimport { isdigits } from \"#util/strings\";\n\nconst compact = (value: string): string =>\n clean(value, \" \");\n\nconst validate = (value: string): ValidateResult => {\n const v = compact(value);\n if (v.length !== 15) {\n return err(\n \"INVALID_LENGTH\",\n \"Moroccan ICE must be exactly 15 digits\",\n );\n }\n if (!isdigits(v)) {\n return err(\n \"INVALID_FORMAT\",\n \"Moroccan ICE must contain only digits\",\n );\n }\n if (mod97(v) !== 0) {\n return err(\n \"INVALID_CHECKSUM\",\n \"Moroccan ICE check digits mismatch\",\n );\n }\n return { valid: true, compact: v };\n};\n\nconst format = (value: string): string => compact(value);\n\n/** Generate a random valid Moroccan ICE. */\nconst generate = (): string => {\n for (;;) {\n const c = randomDigits(15);\n if (validate(c).valid) return c;\n }\n};\n\n/** Moroccan Company Identification Number. */\nconst ice: CountryValidator<\"MA\"> = {\n name: \"Moroccan Company Identification Number\",\n localName: \"Identifiant Commun de l'Entreprise\",\n abbreviation: \"ICE\",\n aliases: [\n \"ICE\",\n \"Identifiant Commun de l'Entreprise\",\n ] as const,\n candidatePattern: \"\\\\d{15}\",\n scope: \"country\",\n country: \"MA\",\n entityType: \"company\",\n compact,\n format,\n validate,\n description: \"Moroccan common enterprise identifier\",\n sourceUrl: \"https://www.ice.gov.ma/\",\n lengths: [15] as const,\n examples: [\"001561191000066\", \"002136093000040\"] as const,\n generate,\n};\n\nexport default ice;\nexport { compact, format, validate, generate };\n"],"mappings":";;;;;;AAsBA,MAAM,WAAW,UACf,MAAM,OAAO,GAAG;AAElB,MAAM,YAAY,UAAkC;CAClD,MAAM,IAAI,QAAQ,KAAK;CACvB,IAAI,EAAE,WAAW,IACf,OAAO,IACL,kBACA,wCACF;CAEF,IAAI,CAAC,SAAS,CAAC,GACb,OAAO,IACL,kBACA,uCACF;CAEF,IAAI,MAAM,CAAC,MAAM,GACf,OAAO,IACL,oBACA,oCACF;CAEF,OAAO;EAAE,OAAO;EAAM,SAAS;CAAE;AACnC;AAEA,MAAM,UAAU,UAA0B,QAAQ,KAAK;;AAGvD,MAAM,iBAAyB;CAC7B,SAAS;EACP,MAAM,IAAI,aAAa,EAAE;EACzB,IAAI,SAAS,CAAC,CAAC,CAAC,OAAO,OAAO;CAChC;AACF;;AAGA,MAAM,MAA8B;CAClC,MAAM;CACN,WAAW;CACX,cAAc;CACd,SAAS,CACP,OACA,oCACF;CACA,kBAAkB;CAClB,OAAO;CACP,SAAS;CACT,YAAY;CACZ;CACA;CACA;CACA,aAAa;CACb,WAAW;CACX,SAAS,CAAC,EAAE;CACZ,UAAU,CAAC,mBAAmB,iBAAiB;CAC/C;AACF"}
|
package/dist/mc/tva.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tva.js","names":["frCompact","frValidate"],"sources":["../../src/mc/tva.ts"],"sourcesContent":["/**\n * n° TVA (taxe sur la valeur ajoutée, Monacan VAT).\n *\n * For VAT purposes Monaco is treated as French\n * territory. The number follows the French TVA\n * format but the SIREN part starts with \"000\"\n * (not a real SIREN). The compact form is\n * prefixed with \"FR\".\n *\n * @see https://www.economie.gouv.fr/\n */\n\nimport {\n compact as frCompact,\n validate as frValidate,\n} from \"../fr/tva\";\nimport type {\n ValidateResult,\n CountryValidator,\n} from \"../types\";\n\nimport { randomDigits } from \"#util/generate\";\nimport { err } from \"#util/result\";\n\nconst compact = (value: string): string => {\n const v = frCompact(value);\n return `FR${v}`;\n};\n\nconst validate = (value: string): ValidateResult => {\n const frResult = frValidate(value);\n if (!frResult.valid) {\n return frResult;\n }\n\n // Monaco SIREN part must start with \"000\"\n const siren = frResult.compact.slice(2);\n if (!siren.startsWith(\"000\")) {\n return err(\n \"INVALID_COMPONENT\",\n \"Monacan TVA SIREN part must start with 000\",\n );\n }\n\n return { valid: true, compact: `FR${frResult.compact}` };\n};\n\nconst format = (value: string): string => compact(value);\n\n/** Generate a random valid Monacan TVA number. */\nconst generate = (): string => {\n const siren = \"000\" + randomDigits(6);\n const prefix = String(\n (12 + 3 * (Number(siren) % 97)) % 97,\n ).padStart(2, \"0\");\n return \"FR\" + prefix + siren;\n};\n\n/** Monacan VAT Number. */\nconst tva: CountryValidator<\"MC\"> = {\n name: \"Monacan VAT Number\",\n localName: \"Numéro de TVA\",\n abbreviation: \"TVA\",\n aliases: [\"numéro de TVA\", \"TVA\"] as const,\n candidatePattern: \"FR\\\\d{11}\",\n scope: \"country\",\n country: \"MC\",\n entityType: \"company\",\n sourceUrl: \"https://www.economie.gouv.fr/\",\n examples: [\"FR53000004605\"] as const,\n compact,\n format,\n validate,\n generate,\n};\n\nexport default tva;\nexport { compact, format, validate, generate };\n"],"mappings":";;;;;;;;;;;;;;;AAwBA,MAAM,WAAW,UAA0B;CAEzC,OAAO,KADGA,UAAU,KACR;AACd;AAEA,MAAM,YAAY,UAAkC;CAClD,MAAM,WAAWC,WAAW,KAAK;CACjC,IAAI,CAAC,SAAS,OACZ,OAAO;CAKT,IAAI,CADU,SAAS,QAAQ,MAAM,CAC5B,
|
|
1
|
+
{"version":3,"file":"tva.js","names":["frCompact","frValidate"],"sources":["../../src/mc/tva.ts"],"sourcesContent":["/**\n * n° TVA (taxe sur la valeur ajoutée, Monacan VAT).\n *\n * For VAT purposes Monaco is treated as French\n * territory. The number follows the French TVA\n * format but the SIREN part starts with \"000\"\n * (not a real SIREN). The compact form is\n * prefixed with \"FR\".\n *\n * @see https://www.economie.gouv.fr/\n */\n\nimport {\n compact as frCompact,\n validate as frValidate,\n} from \"../fr/tva\";\nimport type {\n ValidateResult,\n CountryValidator,\n} from \"../types\";\n\nimport { randomDigits } from \"#util/generate\";\nimport { err } from \"#util/result\";\n\nconst compact = (value: string): string => {\n const v = frCompact(value);\n return `FR${v}`;\n};\n\nconst validate = (value: string): ValidateResult => {\n const frResult = frValidate(value);\n if (!frResult.valid) {\n return frResult;\n }\n\n // Monaco SIREN part must start with \"000\"\n const siren = frResult.compact.slice(2);\n if (!siren.startsWith(\"000\")) {\n return err(\n \"INVALID_COMPONENT\",\n \"Monacan TVA SIREN part must start with 000\",\n );\n }\n\n return { valid: true, compact: `FR${frResult.compact}` };\n};\n\nconst format = (value: string): string => compact(value);\n\n/** Generate a random valid Monacan TVA number. */\nconst generate = (): string => {\n const siren = \"000\" + randomDigits(6);\n const prefix = String(\n (12 + 3 * (Number(siren) % 97)) % 97,\n ).padStart(2, \"0\");\n return \"FR\" + prefix + siren;\n};\n\n/** Monacan VAT Number. */\nconst tva: CountryValidator<\"MC\"> = {\n name: \"Monacan VAT Number\",\n localName: \"Numéro de TVA\",\n abbreviation: \"TVA\",\n aliases: [\"numéro de TVA\", \"TVA\"] as const,\n candidatePattern: \"FR\\\\d{11}\",\n scope: \"country\",\n country: \"MC\",\n entityType: \"company\",\n sourceUrl: \"https://www.economie.gouv.fr/\",\n examples: [\"FR53000004605\"] as const,\n compact,\n format,\n validate,\n generate,\n};\n\nexport default tva;\nexport { compact, format, validate, generate };\n"],"mappings":";;;;;;;;;;;;;;;AAwBA,MAAM,WAAW,UAA0B;CAEzC,OAAO,KADGA,UAAU,KACR;AACd;AAEA,MAAM,YAAY,UAAkC;CAClD,MAAM,WAAWC,WAAW,KAAK;CACjC,IAAI,CAAC,SAAS,OACZ,OAAO;CAKT,IAAI,CADU,SAAS,QAAQ,MAAM,CAC5B,CAAC,CAAC,WAAW,KAAK,GACzB,OAAO,IACL,qBACA,4CACF;CAGF,OAAO;EAAE,OAAO;EAAM,SAAS,KAAK,SAAS;CAAU;AACzD;AAEA,MAAM,UAAU,UAA0B,QAAQ,KAAK;;AAGvD,MAAM,iBAAyB;CAC7B,MAAM,QAAQ,QAAQ,aAAa,CAAC;CAIpC,OAAO,OAHQ,QACZ,KAAK,KAAK,OAAO,KAAK,IAAI,OAAO,EACpC,CAAC,CAAC,SAAS,GAAG,GACK,IAAI;AACzB;;AAGA,MAAM,MAA8B;CAClC,MAAM;CACN,WAAW;CACX,cAAc;CACd,SAAS,CAAC,iBAAiB,KAAK;CAChC,kBAAkB;CAClB,OAAO;CACP,SAAS;CACT,YAAY;CACZ,WAAW;CACX,UAAU,CAAC,eAAe;CAC1B;CACA;CACA;CACA;AACF"}
|
package/dist/mt/vat.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vat.js","names":[],"sources":["../../src/mt/vat.ts"],"sourcesContent":["/**\n * VAT (Maltese VAT number).\n *\n * 8 digits, no leading 0.\n * Weights [3,4,6,7,8,9,10,1], sum % 37 === 0.\n *\n * @see https://www.vatify.eu/malta-vat-number.html\n * @see https://www.oecd.org/content/dam/oecd/en/topics/policy-issue-focus/aeoi/malta-tin.pdf\n */\n\nimport type {\n ValidateResult,\n CountryValidator,\n} from \"../types\";\n\nimport { weightedSum } from \"#checksums/weighted-sum\";\nimport { clean } from \"#util/clean\";\nimport { randomDigits, randomInt } from \"#util/generate\";\nimport { err } from \"#util/result\";\nimport { isdigits } from \"#util/strings\";\n\nconst WEIGHTS = [3, 4, 6, 7, 8, 9, 10, 1];\n\nconst compact = (value: string): string => {\n let v = clean(value, \" -/.\");\n if (v.startsWith(\"MT\") || v.startsWith(\"mt\")) {\n v = v.slice(2);\n }\n return v;\n};\n\nconst validate = (value: string): ValidateResult => {\n const v = compact(value);\n if (v.length !== 8) {\n return err(\n \"INVALID_LENGTH\",\n \"Maltese VAT number must be 8 digits\",\n );\n }\n if (!isdigits(v)) {\n return err(\n \"INVALID_FORMAT\",\n \"Maltese VAT number must contain only digits\",\n );\n }\n if (v[0] === \"0\") {\n return err(\n \"INVALID_COMPONENT\",\n \"Maltese VAT number cannot start with 0\",\n );\n }\n const sum = weightedSum(v, WEIGHTS, 37);\n if (sum !== 0) {\n return err(\n \"INVALID_CHECKSUM\",\n \"Maltese VAT number check digit mismatch\",\n );\n }\n return { valid: true, compact: v };\n};\n\nconst format = (value: string): string =>\n `MT${compact(value)}`;\n\n/** Generate a random valid Maltese VAT number. */\nconst generate = (): string => {\n for (;;) {\n const c = String(randomInt(1, 9)) + randomDigits(7);\n if (validate(c).valid) return c;\n }\n};\n\n/** Maltese VAT Number. */\nconst vat: CountryValidator<\"MT\"> = {\n name: \"Maltese VAT Number\",\n localName: \"VAT Registration Number\",\n abbreviation: \"VAT\",\n aliases: [\"VAT number MT\"] as const,\n candidatePattern: \"MT\\\\d{8}\",\n scope: \"country\",\n country: \"MT\",\n entityType: \"company\",\n sourceUrl: \"https://cfr.gov.mt/\",\n lengths: [8] as const,\n examples: [\"11679112\"] as const,\n compact,\n format,\n validate,\n generate,\n};\n\nexport default vat;\nexport { compact, format, validate, generate };\n"],"mappings":";;;;;;AAqBA,MAAM,UAAU;CAAC;CAAG;CAAG;CAAG;CAAG;CAAG;CAAG;CAAI;AAAC;AAExC,MAAM,WAAW,UAA0B;CACzC,IAAI,IAAI,MAAM,OAAO,MAAM;CAC3B,IAAI,EAAE,WAAW,IAAI,KAAK,EAAE,WAAW,IAAI,GACzC,IAAI,EAAE,MAAM,CAAC;CAEf,OAAO;AACT;AAEA,MAAM,YAAY,UAAkC;CAClD,MAAM,IAAI,QAAQ,KAAK;CACvB,IAAI,EAAE,WAAW,GACf,OAAO,IACL,kBACA,qCACF;CAEF,IAAI,CAAC,SAAS,CAAC,GACb,OAAO,IACL,kBACA,6CACF;CAEF,IAAI,EAAE,OAAO,KACX,OAAO,IACL,qBACA,wCACF;CAGF,IADY,YAAY,GAAG,SAAS,EAC9B,MAAM,GACV,OAAO,IACL,oBACA,yCACF;CAEF,OAAO;EAAE,OAAO;EAAM,SAAS;CAAE;AACnC;AAEA,MAAM,UAAU,UACd,KAAK,QAAQ,KAAK;;AAGpB,MAAM,iBAAyB;CAC7B,SAAS;EACP,MAAM,IAAI,OAAO,UAAU,GAAG,CAAC,CAAC,IAAI,aAAa,CAAC;EAClD,IAAI,SAAS,CAAC,
|
|
1
|
+
{"version":3,"file":"vat.js","names":[],"sources":["../../src/mt/vat.ts"],"sourcesContent":["/**\n * VAT (Maltese VAT number).\n *\n * 8 digits, no leading 0.\n * Weights [3,4,6,7,8,9,10,1], sum % 37 === 0.\n *\n * @see https://www.vatify.eu/malta-vat-number.html\n * @see https://www.oecd.org/content/dam/oecd/en/topics/policy-issue-focus/aeoi/malta-tin.pdf\n */\n\nimport type {\n ValidateResult,\n CountryValidator,\n} from \"../types\";\n\nimport { weightedSum } from \"#checksums/weighted-sum\";\nimport { clean } from \"#util/clean\";\nimport { randomDigits, randomInt } from \"#util/generate\";\nimport { err } from \"#util/result\";\nimport { isdigits } from \"#util/strings\";\n\nconst WEIGHTS = [3, 4, 6, 7, 8, 9, 10, 1];\n\nconst compact = (value: string): string => {\n let v = clean(value, \" -/.\");\n if (v.startsWith(\"MT\") || v.startsWith(\"mt\")) {\n v = v.slice(2);\n }\n return v;\n};\n\nconst validate = (value: string): ValidateResult => {\n const v = compact(value);\n if (v.length !== 8) {\n return err(\n \"INVALID_LENGTH\",\n \"Maltese VAT number must be 8 digits\",\n );\n }\n if (!isdigits(v)) {\n return err(\n \"INVALID_FORMAT\",\n \"Maltese VAT number must contain only digits\",\n );\n }\n if (v[0] === \"0\") {\n return err(\n \"INVALID_COMPONENT\",\n \"Maltese VAT number cannot start with 0\",\n );\n }\n const sum = weightedSum(v, WEIGHTS, 37);\n if (sum !== 0) {\n return err(\n \"INVALID_CHECKSUM\",\n \"Maltese VAT number check digit mismatch\",\n );\n }\n return { valid: true, compact: v };\n};\n\nconst format = (value: string): string =>\n `MT${compact(value)}`;\n\n/** Generate a random valid Maltese VAT number. */\nconst generate = (): string => {\n for (;;) {\n const c = String(randomInt(1, 9)) + randomDigits(7);\n if (validate(c).valid) return c;\n }\n};\n\n/** Maltese VAT Number. */\nconst vat: CountryValidator<\"MT\"> = {\n name: \"Maltese VAT Number\",\n localName: \"VAT Registration Number\",\n abbreviation: \"VAT\",\n aliases: [\"VAT number MT\"] as const,\n candidatePattern: \"MT\\\\d{8}\",\n scope: \"country\",\n country: \"MT\",\n entityType: \"company\",\n sourceUrl: \"https://cfr.gov.mt/\",\n lengths: [8] as const,\n examples: [\"11679112\"] as const,\n compact,\n format,\n validate,\n generate,\n};\n\nexport default vat;\nexport { compact, format, validate, generate };\n"],"mappings":";;;;;;AAqBA,MAAM,UAAU;CAAC;CAAG;CAAG;CAAG;CAAG;CAAG;CAAG;CAAI;AAAC;AAExC,MAAM,WAAW,UAA0B;CACzC,IAAI,IAAI,MAAM,OAAO,MAAM;CAC3B,IAAI,EAAE,WAAW,IAAI,KAAK,EAAE,WAAW,IAAI,GACzC,IAAI,EAAE,MAAM,CAAC;CAEf,OAAO;AACT;AAEA,MAAM,YAAY,UAAkC;CAClD,MAAM,IAAI,QAAQ,KAAK;CACvB,IAAI,EAAE,WAAW,GACf,OAAO,IACL,kBACA,qCACF;CAEF,IAAI,CAAC,SAAS,CAAC,GACb,OAAO,IACL,kBACA,6CACF;CAEF,IAAI,EAAE,OAAO,KACX,OAAO,IACL,qBACA,wCACF;CAGF,IADY,YAAY,GAAG,SAAS,EAC9B,MAAM,GACV,OAAO,IACL,oBACA,yCACF;CAEF,OAAO;EAAE,OAAO;EAAM,SAAS;CAAE;AACnC;AAEA,MAAM,UAAU,UACd,KAAK,QAAQ,KAAK;;AAGpB,MAAM,iBAAyB;CAC7B,SAAS;EACP,MAAM,IAAI,OAAO,UAAU,GAAG,CAAC,CAAC,IAAI,aAAa,CAAC;EAClD,IAAI,SAAS,CAAC,CAAC,CAAC,OAAO,OAAO;CAChC;AACF;;AAGA,MAAM,MAA8B;CAClC,MAAM;CACN,WAAW;CACX,cAAc;CACd,SAAS,CAAC,eAAe;CACzB,kBAAkB;CAClB,OAAO;CACP,SAAS;CACT,YAAY;CACZ,WAAW;CACX,SAAS,CAAC,CAAC;CACX,UAAU,CAAC,UAAU;CACrB;CACA;CACA;CACA;AACF"}
|
package/dist/mu/brn.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"brn.js","names":[],"sources":["../../src/mu/brn.ts"],"sourcesContent":["/** Generate a random valid Mauritius BRN (entity). */\nconst generate = (): string => {\n const letter = String.fromCodePoint(\n 65 + randomInt(0, 25),\n );\n const digits = randomDigits(8);\n return `${letter}${digits}`;\n};\n\n/**\n * BRN (Business Registration Number, Mauritius).\n *\n * Issued by the Corporate and Business Registration\n * Department (CBRD). Used to identify entities and\n * individuals carrying out business activities.\n *\n * Entity BRN: 1 letter prefix + 8 digits (9 chars).\n * - Letter indicates entity type (e.g. C = company,\n * F = foreign company).\n * - Digits 2-3 encode the registration year\n * (e.g. 07 = 2007).\n * - Digits 4-9 are a sequential number.\n *\n * Individual BRN: 8 digits, first digit 0-3.\n *\n * No public check digit algorithm is documented.\n *\n * @see https://companies.govmu.org/\n * @see https://www.oecd.org/content/dam/oecd/en/topics/policy-issue-focus/aeoi/mauritius-tin.pdf\n */\n\nimport type {\n ValidateResult,\n CountryValidator,\n} from \"../types\";\n\nimport { clean } from \"#util/clean\";\nimport { randomDigits, randomInt } from \"#util/generate\";\nimport { err } from \"#util/result\";\n\n/** Entity BRN: letter + 8 digits. */\nconst ENTITY_RE = /^[A-Z]\\d{8}$/;\n\n/** Individual BRN: 8 digits, starts with 0-3. */\nconst INDIVIDUAL_RE = /^[0-3]\\d{7}$/;\n\nconst compact = (value: string): string =>\n clean(value, \" -/\").toUpperCase();\n\nconst validate = (value: string): ValidateResult => {\n const v = compact(value);\n\n if (v.length !== 8 && v.length !== 9) {\n return err(\n \"INVALID_LENGTH\",\n \"Mauritius BRN must be 8 or 9 characters\",\n );\n }\n\n if (v.length === 9) {\n if (!ENTITY_RE.test(v)) {\n return err(\n \"INVALID_FORMAT\",\n \"Mauritius entity BRN must be \" +\n \"1 letter followed by 8 digits\",\n );\n }\n } else {\n if (!INDIVIDUAL_RE.test(v)) {\n return err(\n \"INVALID_FORMAT\",\n \"Mauritius individual BRN must be \" +\n \"8 digits starting with 0-3\",\n );\n }\n }\n\n return { valid: true, compact: v };\n};\n\nconst format = (value: string): string => compact(value);\n\n/** Mauritius Business Registration Number. */\nconst brn: CountryValidator<\"MU\"> = {\n name: \"Mauritius Business Registration Number\",\n localName: \"Business Registration Number\",\n abbreviation: \"BRN\",\n aliases: [\"BRN\", \"Business Registration Number\"] as const,\n candidatePattern: \"[A-Z]\\\\d{8}\",\n scope: \"country\",\n country: \"MU\",\n entityType: \"any\",\n compact,\n format,\n validate,\n description:\n \"Mauritius business registration number \" +\n \"issued by the CBRD\",\n sourceUrl: \"https://companies.govmu.org/\",\n lengths: [8, 9] as const,\n examples: [\"C07015330\", \"C16135302\"] as const,\n generate,\n};\n\nexport default brn;\nexport { compact, format, validate, generate };\n"],"mappings":";;;;;AACA,MAAM,iBAAyB;CAK7B,OAAO,GAJQ,OAAO,cACpB,KAAK,UAAU,GAAG,EAAE,CAGP,IADA,aAAa,CACJ;AAC1B;;AAkCA,MAAM,YAAY;;AAGlB,MAAM,gBAAgB;AAEtB,MAAM,WAAW,UACf,MAAM,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"brn.js","names":[],"sources":["../../src/mu/brn.ts"],"sourcesContent":["/** Generate a random valid Mauritius BRN (entity). */\nconst generate = (): string => {\n const letter = String.fromCodePoint(\n 65 + randomInt(0, 25),\n );\n const digits = randomDigits(8);\n return `${letter}${digits}`;\n};\n\n/**\n * BRN (Business Registration Number, Mauritius).\n *\n * Issued by the Corporate and Business Registration\n * Department (CBRD). Used to identify entities and\n * individuals carrying out business activities.\n *\n * Entity BRN: 1 letter prefix + 8 digits (9 chars).\n * - Letter indicates entity type (e.g. C = company,\n * F = foreign company).\n * - Digits 2-3 encode the registration year\n * (e.g. 07 = 2007).\n * - Digits 4-9 are a sequential number.\n *\n * Individual BRN: 8 digits, first digit 0-3.\n *\n * No public check digit algorithm is documented.\n *\n * @see https://companies.govmu.org/\n * @see https://www.oecd.org/content/dam/oecd/en/topics/policy-issue-focus/aeoi/mauritius-tin.pdf\n */\n\nimport type {\n ValidateResult,\n CountryValidator,\n} from \"../types\";\n\nimport { clean } from \"#util/clean\";\nimport { randomDigits, randomInt } from \"#util/generate\";\nimport { err } from \"#util/result\";\n\n/** Entity BRN: letter + 8 digits. */\nconst ENTITY_RE = /^[A-Z]\\d{8}$/;\n\n/** Individual BRN: 8 digits, starts with 0-3. */\nconst INDIVIDUAL_RE = /^[0-3]\\d{7}$/;\n\nconst compact = (value: string): string =>\n clean(value, \" -/\").toUpperCase();\n\nconst validate = (value: string): ValidateResult => {\n const v = compact(value);\n\n if (v.length !== 8 && v.length !== 9) {\n return err(\n \"INVALID_LENGTH\",\n \"Mauritius BRN must be 8 or 9 characters\",\n );\n }\n\n if (v.length === 9) {\n if (!ENTITY_RE.test(v)) {\n return err(\n \"INVALID_FORMAT\",\n \"Mauritius entity BRN must be \" +\n \"1 letter followed by 8 digits\",\n );\n }\n } else {\n if (!INDIVIDUAL_RE.test(v)) {\n return err(\n \"INVALID_FORMAT\",\n \"Mauritius individual BRN must be \" +\n \"8 digits starting with 0-3\",\n );\n }\n }\n\n return { valid: true, compact: v };\n};\n\nconst format = (value: string): string => compact(value);\n\n/** Mauritius Business Registration Number. */\nconst brn: CountryValidator<\"MU\"> = {\n name: \"Mauritius Business Registration Number\",\n localName: \"Business Registration Number\",\n abbreviation: \"BRN\",\n aliases: [\"BRN\", \"Business Registration Number\"] as const,\n candidatePattern: \"[A-Z]\\\\d{8}\",\n scope: \"country\",\n country: \"MU\",\n entityType: \"any\",\n compact,\n format,\n validate,\n description:\n \"Mauritius business registration number \" +\n \"issued by the CBRD\",\n sourceUrl: \"https://companies.govmu.org/\",\n lengths: [8, 9] as const,\n examples: [\"C07015330\", \"C16135302\"] as const,\n generate,\n};\n\nexport default brn;\nexport { compact, format, validate, generate };\n"],"mappings":";;;;;AACA,MAAM,iBAAyB;CAK7B,OAAO,GAJQ,OAAO,cACpB,KAAK,UAAU,GAAG,EAAE,CAGP,IADA,aAAa,CACJ;AAC1B;;AAkCA,MAAM,YAAY;;AAGlB,MAAM,gBAAgB;AAEtB,MAAM,WAAW,UACf,MAAM,OAAO,KAAK,CAAC,CAAC,YAAY;AAElC,MAAM,YAAY,UAAkC;CAClD,MAAM,IAAI,QAAQ,KAAK;CAEvB,IAAI,EAAE,WAAW,KAAK,EAAE,WAAW,GACjC,OAAO,IACL,kBACA,yCACF;CAGF,IAAI,EAAE,WAAW;MACX,CAAC,UAAU,KAAK,CAAC,GACnB,OAAO,IACL,kBACA,4DAEF;CAAA,OAGF,IAAI,CAAC,cAAc,KAAK,CAAC,GACvB,OAAO,IACL,kBACA,6DAEF;CAIJ,OAAO;EAAE,OAAO;EAAM,SAAS;CAAE;AACnC;AAEA,MAAM,UAAU,UAA0B,QAAQ,KAAK;;AAGvD,MAAM,MAA8B;CAClC,MAAM;CACN,WAAW;CACX,cAAc;CACd,SAAS,CAAC,OAAO,8BAA8B;CAC/C,kBAAkB;CAClB,OAAO;CACP,SAAS;CACT,YAAY;CACZ;CACA;CACA;CACA,aACE;CAEF,WAAW;CACX,SAAS,CAAC,GAAG,CAAC;CACd,UAAU,CAAC,aAAa,WAAW;CACnC;AACF"}
|
package/dist/mx/clabe.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"clabe.js","names":[],"sources":["../../src/mx/clabe.ts"],"sourcesContent":["/**\n * CLABE (Clave Bancaria Estandarizada).\n *\n * Mexican standardized bank account number used for\n * interbank electronic fund transfers. 18 digits:\n * 3 (bank code) + 3 (branch) + 11 (account number)\n * + 1 (check digit).\n *\n * The check digit is computed using a weighted sum with\n * repeating weights [3, 7, 1] modulo 10:\n * check = (10 - sum % 10) % 10.\n *\n * @see https://en.wikipedia.org/wiki/CLABE\n */\n\nimport type {\n ValidateResult,\n CountryValidator,\n} from \"../types\";\n\nimport { clean } from \"#util/clean\";\nimport { randomDigits } from \"#util/generate\";\nimport { err } from \"#util/result\";\nimport { isdigits } from \"#util/strings\";\n\nconst WEIGHTS = [3, 7, 1] as const;\n\nconst compact = (value: string): string =>\n clean(value, \" -\").trim();\n\n/**\n * Compute the CLABE check digit.\n *\n * For each of the first 17 digits, multiply by the\n * corresponding weight [3, 7, 1, 3, 7, 1, ...], take\n * the result mod 10, sum all remainders, then\n * check = (10 - sum % 10) % 10.\n */\nconst calcCheckDigit = (value: string): string => {\n let sum = 0;\n for (let i = 0; i < 17; i++) {\n const digit = Number(value[i]);\n // SAFETY: modulo guarantees valid index\n // eslint-disable-next-line no-non-null-assertion\n const weight = WEIGHTS[i % WEIGHTS.length]!;\n sum += (digit * weight) % 10;\n }\n return String((10 - (sum % 10)) % 10);\n};\n\nconst validate = (value: string): ValidateResult => {\n const v = compact(value);\n\n if (v.length !== 18) {\n return err(\"INVALID_LENGTH\", \"CLABE must be 18 digits\");\n }\n\n if (!isdigits(v)) {\n return err(\n \"INVALID_FORMAT\",\n \"CLABE must contain only digits\",\n );\n }\n\n // Verify check digit (last digit).\n const expected = calcCheckDigit(v);\n if (v[17] !== expected) {\n return err(\n \"INVALID_CHECKSUM\",\n \"CLABE check digit does not match\",\n );\n }\n\n return { valid: true, compact: v };\n};\n\nconst format = (value: string): string => {\n const v = compact(value);\n return `${v.slice(0, 3)} ${v.slice(3, 6)} ${v.slice(6, 17)} ${v.slice(17)}`;\n};\n\n/** Generate a random valid Mexican CLABE. */\nconst generate = (): string => {\n const payload = randomDigits(17);\n return payload + calcCheckDigit(payload);\n};\n\n/**\n * Mexican CLABE (standardized bank account number).\n *\n * Example sourced from Wikipedia CLABE article.\n */\nconst clabe: CountryValidator<\"MX\"> = {\n name: \"Mexican Bank Account\",\n localName: \"Clave Bancaria Estandarizada\",\n abbreviation: \"CLABE\",\n aliases: [\n \"CLABE\",\n \"Clave Bancaria Estandarizada\",\n ] as const,\n candidatePattern: \"\\\\d{18}\",\n scope: \"country\",\n country: \"MX\",\n entityType: \"any\",\n lengths: [18] as const,\n examples: [\"032180000118359719\"] as const,\n compact,\n format,\n validate,\n sourceUrl: \"https://en.wikipedia.org/wiki/CLABE\",\n generate,\n};\n\nexport default clabe;\nexport { compact, format, validate, generate };\n"],"mappings":";;;;;AAyBA,MAAM,UAAU;CAAC;CAAG;CAAG;AAAC;AAExB,MAAM,WAAW,UACf,MAAM,OAAO,IAAI,
|
|
1
|
+
{"version":3,"file":"clabe.js","names":[],"sources":["../../src/mx/clabe.ts"],"sourcesContent":["/**\n * CLABE (Clave Bancaria Estandarizada).\n *\n * Mexican standardized bank account number used for\n * interbank electronic fund transfers. 18 digits:\n * 3 (bank code) + 3 (branch) + 11 (account number)\n * + 1 (check digit).\n *\n * The check digit is computed using a weighted sum with\n * repeating weights [3, 7, 1] modulo 10:\n * check = (10 - sum % 10) % 10.\n *\n * @see https://en.wikipedia.org/wiki/CLABE\n */\n\nimport type {\n ValidateResult,\n CountryValidator,\n} from \"../types\";\n\nimport { clean } from \"#util/clean\";\nimport { randomDigits } from \"#util/generate\";\nimport { err } from \"#util/result\";\nimport { isdigits } from \"#util/strings\";\n\nconst WEIGHTS = [3, 7, 1] as const;\n\nconst compact = (value: string): string =>\n clean(value, \" -\").trim();\n\n/**\n * Compute the CLABE check digit.\n *\n * For each of the first 17 digits, multiply by the\n * corresponding weight [3, 7, 1, 3, 7, 1, ...], take\n * the result mod 10, sum all remainders, then\n * check = (10 - sum % 10) % 10.\n */\nconst calcCheckDigit = (value: string): string => {\n let sum = 0;\n for (let i = 0; i < 17; i++) {\n const digit = Number(value[i]);\n // SAFETY: modulo guarantees valid index\n // eslint-disable-next-line no-non-null-assertion\n const weight = WEIGHTS[i % WEIGHTS.length]!;\n sum += (digit * weight) % 10;\n }\n return String((10 - (sum % 10)) % 10);\n};\n\nconst validate = (value: string): ValidateResult => {\n const v = compact(value);\n\n if (v.length !== 18) {\n return err(\"INVALID_LENGTH\", \"CLABE must be 18 digits\");\n }\n\n if (!isdigits(v)) {\n return err(\n \"INVALID_FORMAT\",\n \"CLABE must contain only digits\",\n );\n }\n\n // Verify check digit (last digit).\n const expected = calcCheckDigit(v);\n if (v[17] !== expected) {\n return err(\n \"INVALID_CHECKSUM\",\n \"CLABE check digit does not match\",\n );\n }\n\n return { valid: true, compact: v };\n};\n\nconst format = (value: string): string => {\n const v = compact(value);\n return `${v.slice(0, 3)} ${v.slice(3, 6)} ${v.slice(6, 17)} ${v.slice(17)}`;\n};\n\n/** Generate a random valid Mexican CLABE. */\nconst generate = (): string => {\n const payload = randomDigits(17);\n return payload + calcCheckDigit(payload);\n};\n\n/**\n * Mexican CLABE (standardized bank account number).\n *\n * Example sourced from Wikipedia CLABE article.\n */\nconst clabe: CountryValidator<\"MX\"> = {\n name: \"Mexican Bank Account\",\n localName: \"Clave Bancaria Estandarizada\",\n abbreviation: \"CLABE\",\n aliases: [\n \"CLABE\",\n \"Clave Bancaria Estandarizada\",\n ] as const,\n candidatePattern: \"\\\\d{18}\",\n scope: \"country\",\n country: \"MX\",\n entityType: \"any\",\n lengths: [18] as const,\n examples: [\"032180000118359719\"] as const,\n compact,\n format,\n validate,\n sourceUrl: \"https://en.wikipedia.org/wiki/CLABE\",\n generate,\n};\n\nexport default clabe;\nexport { compact, format, validate, generate };\n"],"mappings":";;;;;AAyBA,MAAM,UAAU;CAAC;CAAG;CAAG;AAAC;AAExB,MAAM,WAAW,UACf,MAAM,OAAO,IAAI,CAAC,CAAC,KAAK;;;;;;;;;AAU1B,MAAM,kBAAkB,UAA0B;CAChD,IAAI,MAAM;CACV,KAAK,IAAI,IAAI,GAAG,IAAI,IAAI,KAAK;EAC3B,MAAM,QAAQ,OAAO,MAAM,EAAE;EAG7B,MAAM,SAAS,QAAQ,IAAI,QAAQ;EACnC,OAAQ,QAAQ,SAAU;CAC5B;CACA,OAAO,QAAQ,KAAM,MAAM,MAAO,EAAE;AACtC;AAEA,MAAM,YAAY,UAAkC;CAClD,MAAM,IAAI,QAAQ,KAAK;CAEvB,IAAI,EAAE,WAAW,IACf,OAAO,IAAI,kBAAkB,yBAAyB;CAGxD,IAAI,CAAC,SAAS,CAAC,GACb,OAAO,IACL,kBACA,gCACF;CAIF,MAAM,WAAW,eAAe,CAAC;CACjC,IAAI,EAAE,QAAQ,UACZ,OAAO,IACL,oBACA,kCACF;CAGF,OAAO;EAAE,OAAO;EAAM,SAAS;CAAE;AACnC;AAEA,MAAM,UAAU,UAA0B;CACxC,MAAM,IAAI,QAAQ,KAAK;CACvB,OAAO,GAAG,EAAE,MAAM,GAAG,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE;AAC1E;;AAGA,MAAM,iBAAyB;CAC7B,MAAM,UAAU,aAAa,EAAE;CAC/B,OAAO,UAAU,eAAe,OAAO;AACzC;;;;;;AAOA,MAAM,QAAgC;CACpC,MAAM;CACN,WAAW;CACX,cAAc;CACd,SAAS,CACP,SACA,8BACF;CACA,kBAAkB;CAClB,OAAO;CACP,SAAS;CACT,YAAY;CACZ,SAAS,CAAC,EAAE;CACZ,UAAU,CAAC,oBAAoB;CAC/B;CACA;CACA;CACA,WAAW;CACX;AACF"}
|
package/dist/mx/curp.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"curp.js","names":[],"sources":["../../src/mx/curp.ts"],"sourcesContent":["/**\n * CURP (Clave Única de Registro de Población).\n *\n * Mexican personal identification number. 18 characters:\n * 4 letters (name encoding) + 6 digits (YYMMDD birth\n * date) + 1 letter (gender: H=male, M=female) + 2\n * letters (state code) + 3 consonants (name\n * disambiguation) + 1 digit (century discriminator) +\n * 1 check digit.\n *\n * The check digit is computed as a weighted sum mod 10\n * using an alphabet mapping (0-9, A-N, &, O-Z).\n *\n * @see https://en.wikipedia.org/wiki/CURP\n */\n\nimport type {\n ParsedPersonId,\n ValidateResult,\n CountryValidator,\n} from \"../types\";\n\nimport { clean } from \"#util/clean\";\nimport { isValidDate } from \"#util/date\";\nimport { randomInt } from \"#util/generate\";\nimport { err } from \"#util/result\";\n\n/** Valid Mexican state codes (2-letter). */\nconst STATE_CODES = new Set([\n \"AS\",\n \"BC\",\n \"BS\",\n \"CC\",\n \"CL\",\n \"CM\",\n \"CS\",\n \"CH\",\n \"DF\",\n \"DG\",\n \"GT\",\n \"GR\",\n \"HG\",\n \"JC\",\n \"MC\",\n \"MN\",\n \"MS\",\n \"NT\",\n \"NL\",\n \"OC\",\n \"PL\",\n \"QT\",\n \"QR\",\n \"SP\",\n \"SL\",\n \"SR\",\n \"TC\",\n \"TS\",\n \"TL\",\n \"VZ\",\n \"YN\",\n \"ZS\",\n \"NE\", // Born abroad\n]);\nconst STATE_CODE_LIST = [...STATE_CODES];\nconst LETTERS = \"ABCDEFGHIJKLMNOPQRSTUVWXYZ\";\n\n/**\n * Alphabet for CURP check digit computation.\n * Matches python-stdnum: digits 0-9, then A-N,\n * ampersand, O-Z.\n */\nconst ALPHABET = \"0123456789ABCDEFGHIJKLMN&OPQRSTUVWXYZ\";\n\nconst CHAR_MAP = new Map<string, number>();\nfor (const [i, ch] of ALPHABET.split(\"\").entries()) {\n CHAR_MAP.set(ch, i);\n}\n\nconst compact = (value: string): string =>\n clean(value, \" -\").trim().toUpperCase();\n\n/**\n * Compute the CURP check digit. Sum each of the first\n * 17 characters multiplied by weight (18 - position),\n * then check = (10 - sum % 10) % 10.\n */\nconst calcCheckDigit = (value: string): string => {\n let sum = 0;\n for (let i = 0; i < 17; i++) {\n const ch = value[i];\n if (ch === undefined) continue;\n sum += (CHAR_MAP.get(ch) ?? 0) * (18 - i);\n }\n return String((10 - (sum % 10)) % 10);\n};\n\n/**\n * Resolve full year from 2-digit year and century\n * discriminator: 0-9 = 1900s, letter = 2000s.\n */\nconst resolveCurpYear = (\n yy: number,\n centuryChar: string,\n): number =>\n centuryChar >= \"0\" && centuryChar <= \"9\"\n ? 1900 + yy\n : 2000 + yy;\n\nconst CURP_RE =\n /^[A-Z]{4}\\d{6}[HM][A-Z]{2}[A-Z]{3}[0-9A-Z]\\d$/;\n\nconst validate = (value: string): ValidateResult => {\n const v = compact(value);\n\n if (v.length !== 18) {\n return err(\n \"INVALID_LENGTH\",\n \"CURP must be 18 characters\",\n );\n }\n\n if (!CURP_RE.test(v)) {\n return err(\n \"INVALID_FORMAT\",\n \"CURP has an invalid format\",\n );\n }\n\n // Validate the embedded date (YYMMDD at positions 4-9).\n const yy = Number(v.slice(4, 6));\n const mm = Number(v.slice(6, 8));\n const dd = Number(v.slice(8, 10));\n\n // SAFETY: regex validation ensures v.length === 18.\n // eslint-disable-next-line no-non-null-assertion\n const year = resolveCurpYear(yy, v[16]!);\n if (!isValidDate(year, mm, dd)) {\n return err(\n \"INVALID_COMPONENT\",\n \"CURP contains an invalid birth date\",\n );\n }\n\n // Validate state code at positions 11-12.\n const state = v.slice(11, 13);\n if (!STATE_CODES.has(state)) {\n return err(\n \"INVALID_COMPONENT\",\n \"CURP contains an invalid state code\",\n );\n }\n\n // Verify check digit (last character).\n const expected = calcCheckDigit(v);\n if (v[17] !== expected) {\n return err(\n \"INVALID_CHECKSUM\",\n \"CURP check digit does not match\",\n );\n }\n\n return { valid: true, compact: v };\n};\n\nconst format = (value: string): string => compact(value);\n\n/**\n * Extract birth date and gender from a CURP.\n * Returns null if the value is not valid.\n */\nconst parse = (value: string): ParsedPersonId | null => {\n const result = validate(value);\n if (!result.valid) return null;\n\n const v = result.compact;\n const yy = Number(v.slice(4, 6));\n const mm = Number(v.slice(6, 8));\n const dd = Number(v.slice(8, 10));\n\n // SAFETY: regex validation ensures v.length === 18.\n // eslint-disable-next-line no-non-null-assertion\n const year = resolveCurpYear(yy, v[16]!);\n\n const genderChar = v[10];\n const gender: \"male\" | \"female\" =\n genderChar === \"H\" ? \"male\" : \"female\";\n\n return {\n birthDate: new Date(year, mm - 1, dd),\n gender,\n };\n};\n\nconst randomLetter = (): string =>\n LETTERS.charAt(randomInt(0, LETTERS.length - 1));\n\n/** Generate a random valid CURP. */\nconst generate = (): string => {\n const year = randomInt(1980, 2024);\n const month = String(randomInt(1, 12)).padStart(2, \"0\");\n const day = String(randomInt(1, 28)).padStart(2, \"0\");\n const yy = String(year % 100).padStart(2, \"0\");\n const gender = Math.random() < 0.5 ? \"H\" : \"M\";\n const state =\n STATE_CODE_LIST[\n randomInt(0, STATE_CODE_LIST.length - 1)\n ] ?? \"NE\";\n const centuryChar =\n year >= 2000 ? randomLetter() : String(randomInt(0, 9));\n const body =\n `${randomLetter()}${randomLetter()}` +\n `${randomLetter()}${randomLetter()}` +\n `${yy}${month}${day}${gender}${state}` +\n `${randomLetter()}${randomLetter()}${randomLetter()}` +\n `${centuryChar}`;\n return `${body}${calcCheckDigit(body)}`;\n};\n\n/**\n * Mexican CURP (personal identification number).\n *\n * Example sourced from python-stdnum test suite.\n */\nconst curp: CountryValidator<\"MX\", ParsedPersonId> = {\n name: \"Mexican Personal ID\",\n localName: \"Clave Única de Registro de Población\",\n abbreviation: \"CURP\",\n aliases: [\n \"CURP\",\n \"Clave Única de Registro de Población\",\n ] as const,\n candidatePattern: \"[A-Z]{4}\\\\d{6}[HM][A-Z]{5}[A-Z\\\\d]\\\\d\",\n scope: \"country\",\n country: \"MX\",\n entityType: \"person\",\n lengths: [18] as const,\n examples: [\"BOXW310820HNERXN09\"] as const,\n compact,\n format,\n parse,\n validate,\n generate,\n sourceUrl: \"https://en.wikipedia.org/wiki/CURP\",\n};\n\nexport default curp;\nexport { compact, format, generate, parse, validate };\n"],"mappings":";;;;;;AA4BA,MAAM,cAAc,IAAI,IAAI;CAC1B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;AACD,MAAM,kBAAkB,CAAC,GAAG,WAAW;AACvC,MAAM,UAAU;;;;;;AAOhB,MAAM,WAAW;AAEjB,MAAM,2BAAW,IAAI,IAAoB;AACzC,KAAK,MAAM,CAAC,GAAG,OAAO,SAAS,MAAM,EAAE,
|
|
1
|
+
{"version":3,"file":"curp.js","names":[],"sources":["../../src/mx/curp.ts"],"sourcesContent":["/**\n * CURP (Clave Única de Registro de Población).\n *\n * Mexican personal identification number. 18 characters:\n * 4 letters (name encoding) + 6 digits (YYMMDD birth\n * date) + 1 letter (gender: H=male, M=female) + 2\n * letters (state code) + 3 consonants (name\n * disambiguation) + 1 digit (century discriminator) +\n * 1 check digit.\n *\n * The check digit is computed as a weighted sum mod 10\n * using an alphabet mapping (0-9, A-N, &, O-Z).\n *\n * @see https://en.wikipedia.org/wiki/CURP\n */\n\nimport type {\n ParsedPersonId,\n ValidateResult,\n CountryValidator,\n} from \"../types\";\n\nimport { clean } from \"#util/clean\";\nimport { isValidDate } from \"#util/date\";\nimport { randomInt } from \"#util/generate\";\nimport { err } from \"#util/result\";\n\n/** Valid Mexican state codes (2-letter). */\nconst STATE_CODES = new Set([\n \"AS\",\n \"BC\",\n \"BS\",\n \"CC\",\n \"CL\",\n \"CM\",\n \"CS\",\n \"CH\",\n \"DF\",\n \"DG\",\n \"GT\",\n \"GR\",\n \"HG\",\n \"JC\",\n \"MC\",\n \"MN\",\n \"MS\",\n \"NT\",\n \"NL\",\n \"OC\",\n \"PL\",\n \"QT\",\n \"QR\",\n \"SP\",\n \"SL\",\n \"SR\",\n \"TC\",\n \"TS\",\n \"TL\",\n \"VZ\",\n \"YN\",\n \"ZS\",\n \"NE\", // Born abroad\n]);\nconst STATE_CODE_LIST = [...STATE_CODES];\nconst LETTERS = \"ABCDEFGHIJKLMNOPQRSTUVWXYZ\";\n\n/**\n * Alphabet for CURP check digit computation.\n * Matches python-stdnum: digits 0-9, then A-N,\n * ampersand, O-Z.\n */\nconst ALPHABET = \"0123456789ABCDEFGHIJKLMN&OPQRSTUVWXYZ\";\n\nconst CHAR_MAP = new Map<string, number>();\nfor (const [i, ch] of ALPHABET.split(\"\").entries()) {\n CHAR_MAP.set(ch, i);\n}\n\nconst compact = (value: string): string =>\n clean(value, \" -\").trim().toUpperCase();\n\n/**\n * Compute the CURP check digit. Sum each of the first\n * 17 characters multiplied by weight (18 - position),\n * then check = (10 - sum % 10) % 10.\n */\nconst calcCheckDigit = (value: string): string => {\n let sum = 0;\n for (let i = 0; i < 17; i++) {\n const ch = value[i];\n if (ch === undefined) continue;\n sum += (CHAR_MAP.get(ch) ?? 0) * (18 - i);\n }\n return String((10 - (sum % 10)) % 10);\n};\n\n/**\n * Resolve full year from 2-digit year and century\n * discriminator: 0-9 = 1900s, letter = 2000s.\n */\nconst resolveCurpYear = (\n yy: number,\n centuryChar: string,\n): number =>\n centuryChar >= \"0\" && centuryChar <= \"9\"\n ? 1900 + yy\n : 2000 + yy;\n\nconst CURP_RE =\n /^[A-Z]{4}\\d{6}[HM][A-Z]{2}[A-Z]{3}[0-9A-Z]\\d$/;\n\nconst validate = (value: string): ValidateResult => {\n const v = compact(value);\n\n if (v.length !== 18) {\n return err(\n \"INVALID_LENGTH\",\n \"CURP must be 18 characters\",\n );\n }\n\n if (!CURP_RE.test(v)) {\n return err(\n \"INVALID_FORMAT\",\n \"CURP has an invalid format\",\n );\n }\n\n // Validate the embedded date (YYMMDD at positions 4-9).\n const yy = Number(v.slice(4, 6));\n const mm = Number(v.slice(6, 8));\n const dd = Number(v.slice(8, 10));\n\n // SAFETY: regex validation ensures v.length === 18.\n // eslint-disable-next-line no-non-null-assertion\n const year = resolveCurpYear(yy, v[16]!);\n if (!isValidDate(year, mm, dd)) {\n return err(\n \"INVALID_COMPONENT\",\n \"CURP contains an invalid birth date\",\n );\n }\n\n // Validate state code at positions 11-12.\n const state = v.slice(11, 13);\n if (!STATE_CODES.has(state)) {\n return err(\n \"INVALID_COMPONENT\",\n \"CURP contains an invalid state code\",\n );\n }\n\n // Verify check digit (last character).\n const expected = calcCheckDigit(v);\n if (v[17] !== expected) {\n return err(\n \"INVALID_CHECKSUM\",\n \"CURP check digit does not match\",\n );\n }\n\n return { valid: true, compact: v };\n};\n\nconst format = (value: string): string => compact(value);\n\n/**\n * Extract birth date and gender from a CURP.\n * Returns null if the value is not valid.\n */\nconst parse = (value: string): ParsedPersonId | null => {\n const result = validate(value);\n if (!result.valid) return null;\n\n const v = result.compact;\n const yy = Number(v.slice(4, 6));\n const mm = Number(v.slice(6, 8));\n const dd = Number(v.slice(8, 10));\n\n // SAFETY: regex validation ensures v.length === 18.\n // eslint-disable-next-line no-non-null-assertion\n const year = resolveCurpYear(yy, v[16]!);\n\n const genderChar = v[10];\n const gender: \"male\" | \"female\" =\n genderChar === \"H\" ? \"male\" : \"female\";\n\n return {\n birthDate: new Date(year, mm - 1, dd),\n gender,\n };\n};\n\nconst randomLetter = (): string =>\n LETTERS.charAt(randomInt(0, LETTERS.length - 1));\n\n/** Generate a random valid CURP. */\nconst generate = (): string => {\n const year = randomInt(1980, 2024);\n const month = String(randomInt(1, 12)).padStart(2, \"0\");\n const day = String(randomInt(1, 28)).padStart(2, \"0\");\n const yy = String(year % 100).padStart(2, \"0\");\n const gender = Math.random() < 0.5 ? \"H\" : \"M\";\n const state =\n STATE_CODE_LIST[\n randomInt(0, STATE_CODE_LIST.length - 1)\n ] ?? \"NE\";\n const centuryChar =\n year >= 2000 ? randomLetter() : String(randomInt(0, 9));\n const body =\n `${randomLetter()}${randomLetter()}` +\n `${randomLetter()}${randomLetter()}` +\n `${yy}${month}${day}${gender}${state}` +\n `${randomLetter()}${randomLetter()}${randomLetter()}` +\n `${centuryChar}`;\n return `${body}${calcCheckDigit(body)}`;\n};\n\n/**\n * Mexican CURP (personal identification number).\n *\n * Example sourced from python-stdnum test suite.\n */\nconst curp: CountryValidator<\"MX\", ParsedPersonId> = {\n name: \"Mexican Personal ID\",\n localName: \"Clave Única de Registro de Población\",\n abbreviation: \"CURP\",\n aliases: [\n \"CURP\",\n \"Clave Única de Registro de Población\",\n ] as const,\n candidatePattern: \"[A-Z]{4}\\\\d{6}[HM][A-Z]{5}[A-Z\\\\d]\\\\d\",\n scope: \"country\",\n country: \"MX\",\n entityType: \"person\",\n lengths: [18] as const,\n examples: [\"BOXW310820HNERXN09\"] as const,\n compact,\n format,\n parse,\n validate,\n generate,\n sourceUrl: \"https://en.wikipedia.org/wiki/CURP\",\n};\n\nexport default curp;\nexport { compact, format, generate, parse, validate };\n"],"mappings":"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"}
|
package/dist/mx/rfc.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rfc.js","names":[],"sources":["../../src/mx/rfc.ts"],"sourcesContent":["/**\n * RFC (Registro Federal de Contribuyentes).\n *\n * Mexican tax identification number issued by the SAT\n * (Servicio de Administración Tributaria). The number is\n * 13 characters for individuals (4 letters + 6 digits\n * YYMMDD + 3 alphanumeric) or 12 characters for\n * companies (3 letters + 6 digits YYMMDD + 3\n * alphanumeric). The last character is a check digit\n * computed with a mod 11 algorithm over a specific\n * alphabet mapping.\n *\n * Format: AAAA######XXX (persons, 13 chars)\n * AAA######XXX (companies, 12 chars)\n *\n * @see https://en.wikipedia.org/wiki/Tax_Identification_Number_(Mexico)\n */\n\nimport type {\n ValidateResult,\n CountryValidator,\n} from \"../types\";\n\nimport { clean } from \"#util/clean\";\nimport { isValidDate } from \"#util/date\";\nimport { randomInt } from \"#util/generate\";\nimport { err } from \"#util/result\";\n\n/** Alphabet for RFC check digit computation. */\nconst ALPHABET = \"0123456789ABCDEFGHIJKLMN&OPQRSTUVWXYZ Ñ\";\n\nconst CHAR_MAP = new Map<string, number>();\nfor (const [i, ch] of ALPHABET.split(\"\").entries()) {\n CHAR_MAP.set(ch, i);\n}\n\nconst compact = (value: string): string =>\n clean(value, \" -.\").trim().toUpperCase();\n\n/**\n * Compute the RFC check digit using mod 11 with\n * the SAT alphabet mapping.\n *\n * Each character is mapped to its position in the\n * ALPHABET, then multiplied by a weight that starts\n * at (len + 1) and decreases by 1. The check digit\n * is (11 - sum % 11) % 11, mapped to 0-9 or \"A\".\n */\nconst calcCheckDigit = (value: string): string => {\n // Pad 12-char company RFCs with a leading space\n // to align weights with the 13-char algorithm.\n const padded = value.length === 11 ? ` ${value}` : value;\n let sum = 0;\n for (let i = 0; i < 12; i++) {\n const ch = padded[i];\n if (ch === undefined) continue;\n const v = CHAR_MAP.get(ch) ?? 0;\n sum += v * (13 - i);\n }\n const remainder = sum % 11;\n if (remainder === 0) return \"0\";\n const digit = 11 - remainder;\n return digit === 10 ? \"A\" : String(digit);\n};\n\nconst PERSON_RE = /^[A-ZÑ&]{4}\\d{6}[A-Z\\d]{3}$/;\nconst COMPANY_RE = /^[A-ZÑ&]{3}\\d{6}[A-Z\\d]{3}$/;\nconst PREFIX_ALPHA = \"ABCDEFGHIJKLMNOPQRSTUVWXYZÑ&\";\nconst RFC_ALNUM = \"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ\";\n\nconst validate = (value: string): ValidateResult => {\n const v = compact(value);\n\n if (v.length !== 12 && v.length !== 13) {\n return err(\n \"INVALID_LENGTH\",\n \"RFC must be 12 or 13 characters\",\n );\n }\n\n const isPerson = v.length === 13;\n const re = isPerson ? PERSON_RE : COMPANY_RE;\n if (!re.test(v)) {\n return err(\n \"INVALID_FORMAT\",\n \"RFC has an invalid format\",\n );\n }\n\n // Validate the embedded date (YYMMDD).\n const dateOffset = isPerson ? 4 : 3;\n const yy = Number(v.slice(dateOffset, dateOffset + 2));\n const mm = Number(\n v.slice(dateOffset + 2, dateOffset + 4),\n );\n const dd = Number(\n v.slice(dateOffset + 4, dateOffset + 6),\n );\n\n // RFC dates use 2-digit years; use 2000 century for\n // date validation (any valid calendar date is accepted).\n const year = 2000 + yy;\n if (!isValidDate(year, mm, dd)) {\n return err(\n \"INVALID_COMPONENT\",\n \"RFC contains an invalid date\",\n );\n }\n\n // Verify check digit (last character).\n const body = v.slice(0, -1);\n const expected = calcCheckDigit(body);\n if (v.at(-1) !== expected) {\n return err(\n \"INVALID_CHECKSUM\",\n \"RFC check digit does not match\",\n );\n }\n\n return { valid: true, compact: v };\n};\n\nconst format = (value: string): string => compact(value);\n\nconst randomCharFrom = (chars: string): string =>\n chars.charAt(randomInt(0, chars.length - 1));\n\n/** Generate a random valid RFC. */\nconst generate = (): string => {\n const isPerson = Math.random() < 0.5;\n const prefixLength = isPerson ? 4 : 3;\n let prefix = \"\";\n for (let i = 0; i < prefixLength; i++) {\n prefix += randomCharFrom(PREFIX_ALPHA);\n }\n\n const year = randomInt(2000, 2099);\n const month = String(randomInt(1, 12)).padStart(2, \"0\");\n const day = String(randomInt(1, 28)).padStart(2, \"0\");\n let homoclave = \"\";\n for (let i = 0; i < 2; i++) {\n homoclave += randomCharFrom(RFC_ALNUM);\n }\n\n const body =\n `${prefix}${String(year % 100).padStart(2, \"0\")}` +\n `${month}${day}${homoclave}`;\n return `${body}${calcCheckDigit(body)}`;\n};\n\n/**\n * Mexican RFC (tax identification number).\n *\n * Examples sourced from python-stdnum test suite.\n */\nconst rfc: CountryValidator<\"MX\"> = {\n name: \"Mexican Tax ID\",\n localName: \"Registro Federal de Contribuyentes\",\n abbreviation: \"RFC\",\n aliases: [\n \"RFC\",\n \"Registro Federal de Contribuyentes\",\n ] as const,\n candidatePattern: \"[A-ZÑ&]{3,4}\\\\d{6}[A-Z\\\\d]{3}\",\n scope: \"country\",\n country: \"MX\",\n entityType: \"any\",\n lengths: [12, 13] as const,\n examples: [\"GODE561231GR8\", \"MAB9307148T4\"] as const,\n compact,\n format,\n validate,\n generate,\n sourceUrl:\n \"https://en.wikipedia.org/wiki/Tax_Identification_Number_(Mexico)\",\n};\n\nexport default rfc;\nexport { compact, format, generate, validate };\n"],"mappings":";;;;;;AA6BA,MAAM,WAAW;AAEjB,MAAM,2BAAW,IAAI,IAAoB;AACzC,KAAK,MAAM,CAAC,GAAG,OAAO,SAAS,MAAM,EAAE,
|
|
1
|
+
{"version":3,"file":"rfc.js","names":[],"sources":["../../src/mx/rfc.ts"],"sourcesContent":["/**\n * RFC (Registro Federal de Contribuyentes).\n *\n * Mexican tax identification number issued by the SAT\n * (Servicio de Administración Tributaria). The number is\n * 13 characters for individuals (4 letters + 6 digits\n * YYMMDD + 3 alphanumeric) or 12 characters for\n * companies (3 letters + 6 digits YYMMDD + 3\n * alphanumeric). The last character is a check digit\n * computed with a mod 11 algorithm over a specific\n * alphabet mapping.\n *\n * Format: AAAA######XXX (persons, 13 chars)\n * AAA######XXX (companies, 12 chars)\n *\n * @see https://en.wikipedia.org/wiki/Tax_Identification_Number_(Mexico)\n */\n\nimport type {\n ValidateResult,\n CountryValidator,\n} from \"../types\";\n\nimport { clean } from \"#util/clean\";\nimport { isValidDate } from \"#util/date\";\nimport { randomInt } from \"#util/generate\";\nimport { err } from \"#util/result\";\n\n/** Alphabet for RFC check digit computation. */\nconst ALPHABET = \"0123456789ABCDEFGHIJKLMN&OPQRSTUVWXYZ Ñ\";\n\nconst CHAR_MAP = new Map<string, number>();\nfor (const [i, ch] of ALPHABET.split(\"\").entries()) {\n CHAR_MAP.set(ch, i);\n}\n\nconst compact = (value: string): string =>\n clean(value, \" -.\").trim().toUpperCase();\n\n/**\n * Compute the RFC check digit using mod 11 with\n * the SAT alphabet mapping.\n *\n * Each character is mapped to its position in the\n * ALPHABET, then multiplied by a weight that starts\n * at (len + 1) and decreases by 1. The check digit\n * is (11 - sum % 11) % 11, mapped to 0-9 or \"A\".\n */\nconst calcCheckDigit = (value: string): string => {\n // Pad 12-char company RFCs with a leading space\n // to align weights with the 13-char algorithm.\n const padded = value.length === 11 ? ` ${value}` : value;\n let sum = 0;\n for (let i = 0; i < 12; i++) {\n const ch = padded[i];\n if (ch === undefined) continue;\n const v = CHAR_MAP.get(ch) ?? 0;\n sum += v * (13 - i);\n }\n const remainder = sum % 11;\n if (remainder === 0) return \"0\";\n const digit = 11 - remainder;\n return digit === 10 ? \"A\" : String(digit);\n};\n\nconst PERSON_RE = /^[A-ZÑ&]{4}\\d{6}[A-Z\\d]{3}$/;\nconst COMPANY_RE = /^[A-ZÑ&]{3}\\d{6}[A-Z\\d]{3}$/;\nconst PREFIX_ALPHA = \"ABCDEFGHIJKLMNOPQRSTUVWXYZÑ&\";\nconst RFC_ALNUM = \"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ\";\n\nconst validate = (value: string): ValidateResult => {\n const v = compact(value);\n\n if (v.length !== 12 && v.length !== 13) {\n return err(\n \"INVALID_LENGTH\",\n \"RFC must be 12 or 13 characters\",\n );\n }\n\n const isPerson = v.length === 13;\n const re = isPerson ? PERSON_RE : COMPANY_RE;\n if (!re.test(v)) {\n return err(\n \"INVALID_FORMAT\",\n \"RFC has an invalid format\",\n );\n }\n\n // Validate the embedded date (YYMMDD).\n const dateOffset = isPerson ? 4 : 3;\n const yy = Number(v.slice(dateOffset, dateOffset + 2));\n const mm = Number(\n v.slice(dateOffset + 2, dateOffset + 4),\n );\n const dd = Number(\n v.slice(dateOffset + 4, dateOffset + 6),\n );\n\n // RFC dates use 2-digit years; use 2000 century for\n // date validation (any valid calendar date is accepted).\n const year = 2000 + yy;\n if (!isValidDate(year, mm, dd)) {\n return err(\n \"INVALID_COMPONENT\",\n \"RFC contains an invalid date\",\n );\n }\n\n // Verify check digit (last character).\n const body = v.slice(0, -1);\n const expected = calcCheckDigit(body);\n if (v.at(-1) !== expected) {\n return err(\n \"INVALID_CHECKSUM\",\n \"RFC check digit does not match\",\n );\n }\n\n return { valid: true, compact: v };\n};\n\nconst format = (value: string): string => compact(value);\n\nconst randomCharFrom = (chars: string): string =>\n chars.charAt(randomInt(0, chars.length - 1));\n\n/** Generate a random valid RFC. */\nconst generate = (): string => {\n const isPerson = Math.random() < 0.5;\n const prefixLength = isPerson ? 4 : 3;\n let prefix = \"\";\n for (let i = 0; i < prefixLength; i++) {\n prefix += randomCharFrom(PREFIX_ALPHA);\n }\n\n const year = randomInt(2000, 2099);\n const month = String(randomInt(1, 12)).padStart(2, \"0\");\n const day = String(randomInt(1, 28)).padStart(2, \"0\");\n let homoclave = \"\";\n for (let i = 0; i < 2; i++) {\n homoclave += randomCharFrom(RFC_ALNUM);\n }\n\n const body =\n `${prefix}${String(year % 100).padStart(2, \"0\")}` +\n `${month}${day}${homoclave}`;\n return `${body}${calcCheckDigit(body)}`;\n};\n\n/**\n * Mexican RFC (tax identification number).\n *\n * Examples sourced from python-stdnum test suite.\n */\nconst rfc: CountryValidator<\"MX\"> = {\n name: \"Mexican Tax ID\",\n localName: \"Registro Federal de Contribuyentes\",\n abbreviation: \"RFC\",\n aliases: [\n \"RFC\",\n \"Registro Federal de Contribuyentes\",\n ] as const,\n candidatePattern: \"[A-ZÑ&]{3,4}\\\\d{6}[A-Z\\\\d]{3}\",\n scope: \"country\",\n country: \"MX\",\n entityType: \"any\",\n lengths: [12, 13] as const,\n examples: [\"GODE561231GR8\", \"MAB9307148T4\"] as const,\n compact,\n format,\n validate,\n generate,\n sourceUrl:\n \"https://en.wikipedia.org/wiki/Tax_Identification_Number_(Mexico)\",\n};\n\nexport default rfc;\nexport { compact, format, generate, validate };\n"],"mappings":";;;;;;AA6BA,MAAM,WAAW;AAEjB,MAAM,2BAAW,IAAI,IAAoB;AACzC,KAAK,MAAM,CAAC,GAAG,OAAO,SAAS,MAAM,EAAE,CAAC,CAAC,QAAQ,GAC/C,SAAS,IAAI,IAAI,CAAC;AAGpB,MAAM,WAAW,UACf,MAAM,OAAO,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,YAAY;;;;;;;;;;AAWzC,MAAM,kBAAkB,UAA0B;CAGhD,MAAM,SAAS,MAAM,WAAW,KAAK,IAAI,UAAU;CACnD,IAAI,MAAM;CACV,KAAK,IAAI,IAAI,GAAG,IAAI,IAAI,KAAK;EAC3B,MAAM,KAAK,OAAO;EAClB,IAAI,OAAO,KAAA,GAAW;EACtB,MAAM,IAAI,SAAS,IAAI,EAAE,KAAK;EAC9B,OAAO,KAAK,KAAK;CACnB;CACA,MAAM,YAAY,MAAM;CACxB,IAAI,cAAc,GAAG,OAAO;CAC5B,MAAM,QAAQ,KAAK;CACnB,OAAO,UAAU,KAAK,MAAM,OAAO,KAAK;AAC1C;AAEA,MAAM,YAAY;AAClB,MAAM,aAAa;AACnB,MAAM,eAAe;AACrB,MAAM,YAAY;AAElB,MAAM,YAAY,UAAkC;CAClD,MAAM,IAAI,QAAQ,KAAK;CAEvB,IAAI,EAAE,WAAW,MAAM,EAAE,WAAW,IAClC,OAAO,IACL,kBACA,iCACF;CAGF,MAAM,WAAW,EAAE,WAAW;CAE9B,IAAI,EADO,WAAW,YAAY,WAAA,CAC1B,KAAK,CAAC,GACZ,OAAO,IACL,kBACA,2BACF;CAIF,MAAM,aAAa,WAAW,IAAI;CAClC,MAAM,KAAK,OAAO,EAAE,MAAM,YAAY,aAAa,CAAC,CAAC;CACrD,MAAM,KAAK,OACT,EAAE,MAAM,aAAa,GAAG,aAAa,CAAC,CACxC;CACA,MAAM,KAAK,OACT,EAAE,MAAM,aAAa,GAAG,aAAa,CAAC,CACxC;CAKA,IAAI,CAAC,YADQ,MAAO,IACG,IAAI,EAAE,GAC3B,OAAO,IACL,qBACA,8BACF;CAKF,MAAM,WAAW,eADJ,EAAE,MAAM,GAAG,EACW,CAAC;CACpC,IAAI,EAAE,GAAG,EAAE,MAAM,UACf,OAAO,IACL,oBACA,gCACF;CAGF,OAAO;EAAE,OAAO;EAAM,SAAS;CAAE;AACnC;AAEA,MAAM,UAAU,UAA0B,QAAQ,KAAK;AAEvD,MAAM,kBAAkB,UACtB,MAAM,OAAO,UAAU,GAAG,MAAM,SAAS,CAAC,CAAC;;AAG7C,MAAM,iBAAyB;CAE7B,MAAM,eADW,KAAK,OAAO,IAAI,KACD,IAAI;CACpC,IAAI,SAAS;CACb,KAAK,IAAI,IAAI,GAAG,IAAI,cAAc,KAChC,UAAU,eAAe,YAAY;CAGvC,MAAM,OAAO,UAAU,KAAM,IAAI;CACjC,MAAM,QAAQ,OAAO,UAAU,GAAG,EAAE,CAAC,CAAC,CAAC,SAAS,GAAG,GAAG;CACtD,MAAM,MAAM,OAAO,UAAU,GAAG,EAAE,CAAC,CAAC,CAAC,SAAS,GAAG,GAAG;CACpD,IAAI,YAAY;CAChB,KAAK,IAAI,IAAI,GAAG,IAAI,GAAG,KACrB,aAAa,eAAe,SAAS;CAGvC,MAAM,OACJ,GAAG,SAAS,OAAO,OAAO,GAAG,CAAC,CAAC,SAAS,GAAG,GAAG,IAC3C,QAAQ,MAAM;CACnB,OAAO,GAAG,OAAO,eAAe,IAAI;AACtC;;;;;;AAOA,MAAM,MAA8B;CAClC,MAAM;CACN,WAAW;CACX,cAAc;CACd,SAAS,CACP,OACA,oCACF;CACA,kBAAkB;CAClB,OAAO;CACP,SAAS;CACT,YAAY;CACZ,SAAS,CAAC,IAAI,EAAE;CAChB,UAAU,CAAC,iBAAiB,cAAc;CAC1C;CACA;CACA;CACA;CACA,WACE;AACJ"}
|
package/dist/my/nric.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nric.js","names":[],"sources":["../../src/my/nric.ts"],"sourcesContent":["/**\n * NRIC (Malaysian National Registration Identity\n * Card Number, Nombor Kad Pengenalan).\n *\n * 12 digits: YYMMDD (birth date) + PB (place of\n * birth, 2 digits) + SSS (serial) + G (gender:\n * odd = male, even = female).\n *\n * No checksum; validation is structural (valid date\n * and valid place-of-birth code).\n *\n * @see https://en.wikipedia.org/wiki/Malaysian_identity_card\n */\n\nimport type {\n ParsedPersonId,\n ValidateResult,\n CountryValidator,\n} from \"../types\";\n\nimport { clean } from \"#util/clean\";\nimport { isValidDate } from \"#util/date\";\nimport { randomInt } from \"#util/generate\";\nimport { err } from \"#util/result\";\nimport { isdigits } from \"#util/strings\";\n\n/**\n * Valid place-of-birth codes (2-digit).\n * Codes 01-16 are states, 21-59 are pre-2000\n * state codes, 60-68 born outside (ASEAN),\n * 71-72/74-79 foreign nationals, 82-93 regions,\n * 98-99 legacy.\n */\nconst VALID_PB_CODES = new Set([\n \"01\",\n \"02\",\n \"03\",\n \"04\",\n \"05\",\n \"06\",\n \"07\",\n \"08\",\n \"09\",\n \"10\",\n \"11\",\n \"12\",\n \"13\",\n \"14\",\n \"15\",\n \"16\",\n \"21\",\n \"22\",\n \"23\",\n \"24\",\n \"25\",\n \"26\",\n \"27\",\n \"28\",\n \"29\",\n \"30\",\n \"31\",\n \"32\",\n \"33\",\n \"34\",\n \"35\",\n \"36\",\n \"37\",\n \"38\",\n \"39\",\n \"40\",\n \"41\",\n \"42\",\n \"43\",\n \"44\",\n \"45\",\n \"46\",\n \"47\",\n \"48\",\n \"49\",\n \"50\",\n \"51\",\n \"52\",\n \"53\",\n \"54\",\n \"55\",\n \"56\",\n \"57\",\n \"58\",\n \"59\",\n \"60\",\n \"61\",\n \"62\",\n \"63\",\n \"64\",\n \"65\",\n \"66\",\n \"67\",\n \"68\",\n \"71\",\n \"72\",\n \"74\",\n \"75\",\n \"76\",\n \"77\",\n \"78\",\n \"79\",\n \"82\",\n \"83\",\n \"84\",\n \"85\",\n \"86\",\n \"87\",\n \"88\",\n \"89\",\n \"90\",\n \"91\",\n \"92\",\n \"93\",\n \"98\",\n \"99\",\n]);\n\nconst compact = (value: string): string =>\n clean(value, \" -\");\n\n/**\n * Resolve the birth year from a 2-digit year.\n * Years after the reference year are assumed to be\n * in the previous century. Accepts an explicit\n * reference year to avoid multiple clock reads.\n */\nconst resolveYear = (\n yy: number,\n referenceYear: number,\n): number => {\n const century = Math.floor(referenceYear / 100);\n const year = century * 100 + yy;\n return year > referenceYear ? year - 100 : year;\n};\n\nconst validate = (value: string): ValidateResult => {\n const v = compact(value);\n if (v.length !== 12) {\n return err(\n \"INVALID_LENGTH\",\n \"NRIC must be exactly 12 digits\",\n );\n }\n if (!isdigits(v)) {\n return err(\n \"INVALID_FORMAT\",\n \"NRIC must contain only digits\",\n );\n }\n\n // Validate birth date (YYMMDD)\n const yy = Number(v.slice(0, 2));\n const mm = Number(v.slice(2, 4));\n const dd = Number(v.slice(4, 6));\n\n const now = new Date().getFullYear();\n const year = resolveYear(yy, now);\n\n if (!isValidDate(year, mm, dd)) {\n return err(\n \"INVALID_COMPONENT\",\n \"NRIC contains an invalid birth date\",\n );\n }\n\n // Validate place-of-birth code\n const pb = v.slice(6, 8);\n if (!VALID_PB_CODES.has(pb)) {\n return err(\n \"INVALID_COMPONENT\",\n \"Invalid place-of-birth code: \" + pb,\n );\n }\n\n return { valid: true, compact: v };\n};\n\nconst format = (value: string): string => {\n const v = compact(value);\n return (\n v.slice(0, 6) + \"-\" + v.slice(6, 8) + \"-\" + v.slice(8)\n );\n};\n\n/**\n * Extract birth date and gender from an NRIC.\n * Returns null if the value is not valid.\n */\nconst parse = (value: string): ParsedPersonId | null => {\n const result = validate(value);\n if (!result.valid) return null;\n\n const v = result.compact;\n const yy = Number(v.slice(0, 2));\n const mm = Number(v.slice(2, 4));\n const dd = Number(v.slice(4, 6));\n const now = new Date().getFullYear();\n const year = resolveYear(yy, now);\n\n // Last digit: odd = male, even = female\n const lastDigit = Number(v[11]);\n const gender = lastDigit % 2 === 1 ? \"male\" : \"female\";\n\n return {\n birthDate: new Date(year, mm - 1, dd),\n gender,\n };\n};\n\nconst PB_CODES = [...VALID_PB_CODES];\n\n/** Generate a random valid Malaysian NRIC. */\nconst generate = (): string => {\n const year = randomInt(1950, 2024);\n const month = String(randomInt(1, 12)).padStart(2, \"0\");\n const day = String(randomInt(1, 28)).padStart(2, \"0\");\n const pb =\n PB_CODES[randomInt(0, PB_CODES.length - 1)] ?? \"01\";\n const serial = String(randomInt(0, 999)).padStart(3, \"0\");\n const isMale = Math.random() < 0.5;\n const lastDigit = isMale\n ? String(randomInt(0, 4) * 2 + 1)\n : String(randomInt(0, 4) * 2);\n\n return (\n `${String(year % 100).padStart(2, \"0\")}` +\n `${month}${day}${pb}${serial}${lastDigit}`\n );\n};\n\n/** Malaysian National Registration Identity Card. */\nconst nric: CountryValidator<\"MY\", ParsedPersonId> = {\n name: \"Malaysian National Registration Identity Card Number\",\n localName: \"Nombor Kad Pengenalan\",\n abbreviation: \"NRIC\",\n aliases: [\"NRIC\", \"MyKad\", \"kad pengenalan\"] as const,\n candidatePattern: \"\\\\d{6}-?\\\\d{2}-?\\\\d{4}\",\n scope: \"country\",\n country: \"MY\",\n entityType: \"person\",\n description:\n \"12-digit identity card number encoding birth date, place of birth, and gender\",\n sourceUrl:\n \"https://en.wikipedia.org/wiki/Malaysian_identity_card\",\n lengths: [12] as const,\n examples: [\"770305021234\", \"880715141234\"] as const,\n compact,\n format,\n parse,\n validate,\n generate,\n};\n\nexport default nric;\nexport { compact, format, generate, parse, validate };\n"],"mappings":";;;;;;;;;;;;;AAiCA,MAAM,iBAAiB,IAAI,IAAI;CAC7B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;AAED,MAAM,WAAW,UACf,MAAM,OAAO,IAAI;;;;;;;AAQnB,MAAM,eACJ,IACA,kBACW;CAEX,MAAM,OADU,KAAK,MAAM,gBAAgB,GACxB,IAAI,MAAM;CAC7B,OAAO,OAAO,gBAAgB,OAAO,MAAM;AAC7C;AAEA,MAAM,YAAY,UAAkC;CAClD,MAAM,IAAI,QAAQ,KAAK;CACvB,IAAI,EAAE,WAAW,IACf,OAAO,IACL,kBACA,gCACF;CAEF,IAAI,CAAC,SAAS,CAAC,GACb,OAAO,IACL,kBACA,+BACF;CAIF,MAAM,KAAK,OAAO,EAAE,MAAM,GAAG,CAAC,CAAC;CAC/B,MAAM,KAAK,OAAO,EAAE,MAAM,GAAG,CAAC,CAAC;CAC/B,MAAM,KAAK,OAAO,EAAE,MAAM,GAAG,CAAC,CAAC;CAK/B,IAAI,CAAC,YAFQ,YAAY,qBADb,IAAI,KAAK,
|
|
1
|
+
{"version":3,"file":"nric.js","names":[],"sources":["../../src/my/nric.ts"],"sourcesContent":["/**\n * NRIC (Malaysian National Registration Identity\n * Card Number, Nombor Kad Pengenalan).\n *\n * 12 digits: YYMMDD (birth date) + PB (place of\n * birth, 2 digits) + SSS (serial) + G (gender:\n * odd = male, even = female).\n *\n * No checksum; validation is structural (valid date\n * and valid place-of-birth code).\n *\n * @see https://en.wikipedia.org/wiki/Malaysian_identity_card\n */\n\nimport type {\n ParsedPersonId,\n ValidateResult,\n CountryValidator,\n} from \"../types\";\n\nimport { clean } from \"#util/clean\";\nimport { isValidDate } from \"#util/date\";\nimport { randomInt } from \"#util/generate\";\nimport { err } from \"#util/result\";\nimport { isdigits } from \"#util/strings\";\n\n/**\n * Valid place-of-birth codes (2-digit).\n * Codes 01-16 are states, 21-59 are pre-2000\n * state codes, 60-68 born outside (ASEAN),\n * 71-72/74-79 foreign nationals, 82-93 regions,\n * 98-99 legacy.\n */\nconst VALID_PB_CODES = new Set([\n \"01\",\n \"02\",\n \"03\",\n \"04\",\n \"05\",\n \"06\",\n \"07\",\n \"08\",\n \"09\",\n \"10\",\n \"11\",\n \"12\",\n \"13\",\n \"14\",\n \"15\",\n \"16\",\n \"21\",\n \"22\",\n \"23\",\n \"24\",\n \"25\",\n \"26\",\n \"27\",\n \"28\",\n \"29\",\n \"30\",\n \"31\",\n \"32\",\n \"33\",\n \"34\",\n \"35\",\n \"36\",\n \"37\",\n \"38\",\n \"39\",\n \"40\",\n \"41\",\n \"42\",\n \"43\",\n \"44\",\n \"45\",\n \"46\",\n \"47\",\n \"48\",\n \"49\",\n \"50\",\n \"51\",\n \"52\",\n \"53\",\n \"54\",\n \"55\",\n \"56\",\n \"57\",\n \"58\",\n \"59\",\n \"60\",\n \"61\",\n \"62\",\n \"63\",\n \"64\",\n \"65\",\n \"66\",\n \"67\",\n \"68\",\n \"71\",\n \"72\",\n \"74\",\n \"75\",\n \"76\",\n \"77\",\n \"78\",\n \"79\",\n \"82\",\n \"83\",\n \"84\",\n \"85\",\n \"86\",\n \"87\",\n \"88\",\n \"89\",\n \"90\",\n \"91\",\n \"92\",\n \"93\",\n \"98\",\n \"99\",\n]);\n\nconst compact = (value: string): string =>\n clean(value, \" -\");\n\n/**\n * Resolve the birth year from a 2-digit year.\n * Years after the reference year are assumed to be\n * in the previous century. Accepts an explicit\n * reference year to avoid multiple clock reads.\n */\nconst resolveYear = (\n yy: number,\n referenceYear: number,\n): number => {\n const century = Math.floor(referenceYear / 100);\n const year = century * 100 + yy;\n return year > referenceYear ? year - 100 : year;\n};\n\nconst validate = (value: string): ValidateResult => {\n const v = compact(value);\n if (v.length !== 12) {\n return err(\n \"INVALID_LENGTH\",\n \"NRIC must be exactly 12 digits\",\n );\n }\n if (!isdigits(v)) {\n return err(\n \"INVALID_FORMAT\",\n \"NRIC must contain only digits\",\n );\n }\n\n // Validate birth date (YYMMDD)\n const yy = Number(v.slice(0, 2));\n const mm = Number(v.slice(2, 4));\n const dd = Number(v.slice(4, 6));\n\n const now = new Date().getFullYear();\n const year = resolveYear(yy, now);\n\n if (!isValidDate(year, mm, dd)) {\n return err(\n \"INVALID_COMPONENT\",\n \"NRIC contains an invalid birth date\",\n );\n }\n\n // Validate place-of-birth code\n const pb = v.slice(6, 8);\n if (!VALID_PB_CODES.has(pb)) {\n return err(\n \"INVALID_COMPONENT\",\n \"Invalid place-of-birth code: \" + pb,\n );\n }\n\n return { valid: true, compact: v };\n};\n\nconst format = (value: string): string => {\n const v = compact(value);\n return (\n v.slice(0, 6) + \"-\" + v.slice(6, 8) + \"-\" + v.slice(8)\n );\n};\n\n/**\n * Extract birth date and gender from an NRIC.\n * Returns null if the value is not valid.\n */\nconst parse = (value: string): ParsedPersonId | null => {\n const result = validate(value);\n if (!result.valid) return null;\n\n const v = result.compact;\n const yy = Number(v.slice(0, 2));\n const mm = Number(v.slice(2, 4));\n const dd = Number(v.slice(4, 6));\n const now = new Date().getFullYear();\n const year = resolveYear(yy, now);\n\n // Last digit: odd = male, even = female\n const lastDigit = Number(v[11]);\n const gender = lastDigit % 2 === 1 ? \"male\" : \"female\";\n\n return {\n birthDate: new Date(year, mm - 1, dd),\n gender,\n };\n};\n\nconst PB_CODES = [...VALID_PB_CODES];\n\n/** Generate a random valid Malaysian NRIC. */\nconst generate = (): string => {\n const year = randomInt(1950, 2024);\n const month = String(randomInt(1, 12)).padStart(2, \"0\");\n const day = String(randomInt(1, 28)).padStart(2, \"0\");\n const pb =\n PB_CODES[randomInt(0, PB_CODES.length - 1)] ?? \"01\";\n const serial = String(randomInt(0, 999)).padStart(3, \"0\");\n const isMale = Math.random() < 0.5;\n const lastDigit = isMale\n ? String(randomInt(0, 4) * 2 + 1)\n : String(randomInt(0, 4) * 2);\n\n return (\n `${String(year % 100).padStart(2, \"0\")}` +\n `${month}${day}${pb}${serial}${lastDigit}`\n );\n};\n\n/** Malaysian National Registration Identity Card. */\nconst nric: CountryValidator<\"MY\", ParsedPersonId> = {\n name: \"Malaysian National Registration Identity Card Number\",\n localName: \"Nombor Kad Pengenalan\",\n abbreviation: \"NRIC\",\n aliases: [\"NRIC\", \"MyKad\", \"kad pengenalan\"] as const,\n candidatePattern: \"\\\\d{6}-?\\\\d{2}-?\\\\d{4}\",\n scope: \"country\",\n country: \"MY\",\n entityType: \"person\",\n description:\n \"12-digit identity card number encoding birth date, place of birth, and gender\",\n sourceUrl:\n \"https://en.wikipedia.org/wiki/Malaysian_identity_card\",\n lengths: [12] as const,\n examples: [\"770305021234\", \"880715141234\"] as const,\n compact,\n format,\n parse,\n validate,\n generate,\n};\n\nexport default nric;\nexport { compact, format, generate, parse, validate };\n"],"mappings":";;;;;;;;;;;;;AAiCA,MAAM,iBAAiB,IAAI,IAAI;CAC7B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;AAED,MAAM,WAAW,UACf,MAAM,OAAO,IAAI;;;;;;;AAQnB,MAAM,eACJ,IACA,kBACW;CAEX,MAAM,OADU,KAAK,MAAM,gBAAgB,GACxB,IAAI,MAAM;CAC7B,OAAO,OAAO,gBAAgB,OAAO,MAAM;AAC7C;AAEA,MAAM,YAAY,UAAkC;CAClD,MAAM,IAAI,QAAQ,KAAK;CACvB,IAAI,EAAE,WAAW,IACf,OAAO,IACL,kBACA,gCACF;CAEF,IAAI,CAAC,SAAS,CAAC,GACb,OAAO,IACL,kBACA,+BACF;CAIF,MAAM,KAAK,OAAO,EAAE,MAAM,GAAG,CAAC,CAAC;CAC/B,MAAM,KAAK,OAAO,EAAE,MAAM,GAAG,CAAC,CAAC;CAC/B,MAAM,KAAK,OAAO,EAAE,MAAM,GAAG,CAAC,CAAC;CAK/B,IAAI,CAAC,YAFQ,YAAY,qBADb,IAAI,KAAK,EAAA,CAAE,YACQ,CAEX,GAAG,IAAI,EAAE,GAC3B,OAAO,IACL,qBACA,qCACF;CAIF,MAAM,KAAK,EAAE,MAAM,GAAG,CAAC;CACvB,IAAI,CAAC,eAAe,IAAI,EAAE,GACxB,OAAO,IACL,qBACA,kCAAkC,EACpC;CAGF,OAAO;EAAE,OAAO;EAAM,SAAS;CAAE;AACnC;AAEA,MAAM,UAAU,UAA0B;CACxC,MAAM,IAAI,QAAQ,KAAK;CACvB,OACE,EAAE,MAAM,GAAG,CAAC,IAAI,MAAM,EAAE,MAAM,GAAG,CAAC,IAAI,MAAM,EAAE,MAAM,CAAC;AAEzD;;;;;AAMA,MAAM,SAAS,UAAyC;CACtD,MAAM,SAAS,SAAS,KAAK;CAC7B,IAAI,CAAC,OAAO,OAAO,OAAO;CAE1B,MAAM,IAAI,OAAO;CACjB,MAAM,KAAK,OAAO,EAAE,MAAM,GAAG,CAAC,CAAC;CAC/B,MAAM,KAAK,OAAO,EAAE,MAAM,GAAG,CAAC,CAAC;CAC/B,MAAM,KAAK,OAAO,EAAE,MAAM,GAAG,CAAC,CAAC;CAE/B,MAAM,OAAO,YAAY,qBADb,IAAI,KAAK,EAAA,CAAE,YACQ,CAAC;CAIhC,MAAM,SADY,OAAO,EAAE,GACJ,IAAI,MAAM,IAAI,SAAS;CAE9C,OAAO;EACL,WAAW,IAAI,KAAK,MAAM,KAAK,GAAG,EAAE;EACpC;CACF;AACF;AAEA,MAAM,WAAW,CAAC,GAAG,cAAc;;AAGnC,MAAM,iBAAyB;CAC7B,MAAM,OAAO,UAAU,MAAM,IAAI;CACjC,MAAM,QAAQ,OAAO,UAAU,GAAG,EAAE,CAAC,CAAC,CAAC,SAAS,GAAG,GAAG;CACtD,MAAM,MAAM,OAAO,UAAU,GAAG,EAAE,CAAC,CAAC,CAAC,SAAS,GAAG,GAAG;CACpD,MAAM,KACJ,SAAS,UAAU,GAAG,SAAS,SAAS,CAAC,MAAM;CACjD,MAAM,SAAS,OAAO,UAAU,GAAG,GAAG,CAAC,CAAC,CAAC,SAAS,GAAG,GAAG;CAExD,MAAM,YADS,KAAK,OAAO,IAAI,KAE3B,OAAO,UAAU,GAAG,CAAC,IAAI,IAAI,CAAC,IAC9B,OAAO,UAAU,GAAG,CAAC,IAAI,CAAC;CAE9B,OACE,GAAG,OAAO,OAAO,GAAG,CAAC,CAAC,SAAS,GAAG,GAAG,IAClC,QAAQ,MAAM,KAAK,SAAS;AAEnC;;AAGA,MAAM,OAA+C;CACnD,MAAM;CACN,WAAW;CACX,cAAc;CACd,SAAS;EAAC;EAAQ;EAAS;CAAgB;CAC3C,kBAAkB;CAClB,OAAO;CACP,SAAS;CACT,YAAY;CACZ,aACE;CACF,WACE;CACF,SAAS,CAAC,EAAE;CACZ,UAAU,CAAC,gBAAgB,cAAc;CACzC;CACA;CACA;CACA;CACA;AACF"}
|