@stll/stdnum 2.0.1 → 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 +11 -9
- 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/cz/rc.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rc.js","names":[],"sources":["../../src/cz/rc.ts"],"sourcesContent":["/**\n * RČ (Rodné číslo).\n *\n * Czech and Slovak national identifier (birth\n * number). 9 or 10 digits encoding date of birth\n * and gender. 10-digit numbers (post-1954) must\n * be divisible by 11.\n *\n * Women have 50 added to the month. For modern\n * 10-digit numbers, an additional +20 offset may\n * be used when the daily serial range is exhausted\n * (+70 for women).\n *\n * Canonical source:\n * - Czech Ministry of the Interior guidance on rodne\n * cislo, with the modern overflow-month rules\n *\n * We use this to keep the 9-digit historical form\n * valid while rejecting modern +20/+70 month offsets\n * on 9-digit numbers. Those overflow offsets are a\n * modern rule for 10-digit values.\n *\n * @see https://www.mvcr.cz/mvcren/docDetail.aspx?docid=21975362&doctype=ART\n * @see Law 133/2000 Sb.\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\nconst compact = (value: string): string =>\n clean(value, \" /\");\n\nconst decodeMonth = (\n rawMonth: number,\n year: number,\n length: number,\n): number | null => {\n const candidates =\n length === 10 && year >= 2004\n ? [0, 50, 20, 70]\n : [0, 50];\n for (const offset of candidates) {\n const month = rawMonth - offset;\n if (month >= 1 && month <= 12) {\n return month;\n }\n }\n return null;\n};\n\nconst validate = (value: string): ValidateResult => {\n const v = compact(value);\n if (v.length !== 9 && v.length !== 10) {\n return err(\n \"INVALID_LENGTH\",\n \"Birth number must be 9 or 10 digits\",\n );\n }\n if (!isdigits(v)) {\n return err(\n \"INVALID_FORMAT\",\n \"Birth number must contain only digits\",\n );\n }\n\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 // Resolve full year\n let year = yy + 1900;\n if (v.length === 9) {\n // 9-digit: pre-1954 only\n if (year >= 1980) year -= 100;\n if (year > 1953) {\n return err(\n \"INVALID_COMPONENT\",\n \"9-digit birth numbers are pre-1954 only\",\n );\n }\n } else {\n // 10-digit: if year < 1954, it's 2000s\n if (year < 1954) year += 100;\n }\n\n const month = decodeMonth(mm, year, v.length);\n if (month === null) {\n return err(\n \"INVALID_COMPONENT\",\n \"Birth number contains an invalid month\",\n );\n }\n\n if (dd < 1 || dd > 31) {\n return err(\n \"INVALID_COMPONENT\",\n \"Birth number contains an invalid day\",\n );\n }\n\n // Validate the actual date\n if (!isValidDate(year, month, dd)) {\n return err(\n \"INVALID_COMPONENT\",\n \"Birth number contains an invalid date\",\n );\n }\n\n // 10-digit: check digit is first 9 digits\n // mod 11 mod 10\n if (v.length === 10) {\n const front = Number(v.slice(0, 9));\n const check = (front % 11) % 10;\n if (String(check) !== v[9]) {\n return err(\n \"INVALID_CHECKSUM\",\n \"Birth number is not divisible by 11\",\n );\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 birth number.\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\n const gender = mm >= 50 ? \"female\" : \"male\";\n let year = yy + 1900;\n if (v.length === 10 && year < 1954) year += 100;\n if (v.length === 9 && year >= 1980) year -= 100;\n\n const month = decodeMonth(mm, year, v.length);\n if (month === null) {\n return null;\n }\n\n return {\n birthDate: new Date(year, month - 1, dd),\n gender,\n };\n};\n\n/**\n * Generate a random valid 10-digit birth number.\n * Uses a random date between 1954 and 2024.\n */\nconst generate = (): string => {\n const year = randomInt(1954, 2024);\n const month = randomInt(1, 12);\n const day = randomInt(1, 28);\n const isFemale = Math.random() < 0.5;\n const mm = isFemale ? month + 50 : month;\n\n const yy = String(year % 100).padStart(2, \"0\");\n const mmStr = String(mm).padStart(2, \"0\");\n const dd = String(day).padStart(2, \"0\");\n\n const datePart = `${yy}${mmStr}${dd}`;\n for (let seq = 0; seq < 1000; seq++) {\n const seqStr = String(seq).padStart(3, \"0\");\n const front = `${datePart}${seqStr}`;\n const num = Number(front);\n const check = (num % 11) % 10;\n const result = `${front}${String(check)}`;\n if (validate(result).valid) {\n return result;\n }\n }\n\n throw new Error(\"Failed to generate valid birth number\");\n};\n\n/** Czech/Slovak Birth Number. */\nconst rc: CountryValidator<\"CZ\", ParsedPersonId> = {\n name: \"Czech Birth Number\",\n localName: \"Rodné číslo\",\n abbreviation: \"RČ\",\n aliases: [\"rodné číslo\", \"RČ\", \"birth number\"] as const,\n candidatePattern: \"\\\\d{6}/\\\\d{3,4}\",\n scope: \"country\",\n country: \"CZ\",\n entityType: \"person\",\n description:\n \"Czech/Slovak national identifier encoding birth date and gender\",\n sourceUrl:\n \"https://www.mvcr.cz/mvcren/docDetail.aspx?docid=21975362&doctype=ART\",\n lengths: [9, 10] as const,\n examples: [\"7103192745\"] as const,\n compact,\n format,\n parse,\n validate,\n generate,\n};\n\nexport default rc;\nexport { compact, format, generate, parse, validate };\n"],"mappings":";;;;;;AAsCA,MAAM,WAAW,UACf,MAAM,OAAO,IAAI;AAEnB,MAAM,eACJ,UACA,MACA,WACkB;CAClB,MAAM,aACJ,WAAW,MAAM,QAAQ,OACrB;EAAC;EAAG;EAAI;EAAI;CAAE,IACd,CAAC,GAAG,EAAE;CACZ,KAAK,MAAM,UAAU,YAAY;EAC/B,MAAM,QAAQ,WAAW;EACzB,IAAI,SAAS,KAAK,SAAS,IACzB,OAAO;CAEX;CACA,OAAO;AACT;AAEA,MAAM,YAAY,UAAkC;CAClD,MAAM,IAAI,QAAQ,KAAK;CACvB,IAAI,EAAE,WAAW,KAAK,EAAE,WAAW,IACjC,OAAO,IACL,kBACA,qCACF;CAEF,IAAI,CAAC,SAAS,CAAC,GACb,OAAO,IACL,kBACA,uCACF;CAGF,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,OAAO,KAAK;CAChB,IAAI,EAAE,WAAW,GAAG;EAElB,IAAI,QAAQ,MAAM,QAAQ;EAC1B,IAAI,OAAO,MACT,OAAO,IACL,qBACA,yCACF;CAEJ,OAEE,IAAI,OAAO,MAAM,QAAQ;CAG3B,MAAM,QAAQ,YAAY,IAAI,MAAM,EAAE,MAAM;CAC5C,IAAI,UAAU,MACZ,OAAO,IACL,qBACA,wCACF;CAGF,IAAI,KAAK,KAAK,KAAK,IACjB,OAAO,IACL,qBACA,sCACF;CAIF,IAAI,CAAC,YAAY,MAAM,OAAO,EAAE,GAC9B,OAAO,IACL,qBACA,uCACF;CAKF,IAAI,EAAE,WAAW,IAAI;EAEnB,MAAM,QADQ,OAAO,EAAE,MAAM,GAAG,CAAC,CACd,IAAI,KAAM;EAC7B,IAAI,OAAO,KAAK,MAAM,EAAE,IACtB,OAAO,IACL,oBACA,qCACF;CAEJ;CAEA,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,CAAC;AACtC;;;;;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,SAAS,MAAM,KAAK,WAAW;CACrC,IAAI,OAAO,KAAK;CAChB,IAAI,EAAE,WAAW,MAAM,OAAO,MAAM,QAAQ;CAC5C,IAAI,EAAE,WAAW,KAAK,QAAQ,MAAM,QAAQ;CAE5C,MAAM,QAAQ,YAAY,IAAI,MAAM,EAAE,MAAM;CAC5C,IAAI,UAAU,MACZ,OAAO;CAGT,OAAO;EACL,WAAW,IAAI,KAAK,MAAM,QAAQ,GAAG,EAAE;EACvC;CACF;AACF;;;;;AAMA,MAAM,iBAAyB;CAC7B,MAAM,OAAO,UAAU,MAAM,IAAI;CACjC,MAAM,QAAQ,UAAU,GAAG,EAAE;CAC7B,MAAM,MAAM,UAAU,GAAG,EAAE;CAE3B,MAAM,KADW,KAAK,OAAO,IAAI,KACX,QAAQ,KAAK;CAMnC,MAAM,WAAW,GAJN,OAAO,OAAO,GAAG,
|
|
1
|
+
{"version":3,"file":"rc.js","names":[],"sources":["../../src/cz/rc.ts"],"sourcesContent":["/**\n * RČ (Rodné číslo).\n *\n * Czech and Slovak national identifier (birth\n * number). 9 or 10 digits encoding date of birth\n * and gender. 10-digit numbers (post-1954) must\n * be divisible by 11.\n *\n * Women have 50 added to the month. For modern\n * 10-digit numbers, an additional +20 offset may\n * be used when the daily serial range is exhausted\n * (+70 for women).\n *\n * Canonical source:\n * - Czech Ministry of the Interior guidance on rodne\n * cislo, with the modern overflow-month rules\n *\n * We use this to keep the 9-digit historical form\n * valid while rejecting modern +20/+70 month offsets\n * on 9-digit numbers. Those overflow offsets are a\n * modern rule for 10-digit values.\n *\n * @see https://www.mvcr.cz/mvcren/docDetail.aspx?docid=21975362&doctype=ART\n * @see Law 133/2000 Sb.\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\nconst compact = (value: string): string =>\n clean(value, \" /\");\n\nconst decodeMonth = (\n rawMonth: number,\n year: number,\n length: number,\n): number | null => {\n const candidates =\n length === 10 && year >= 2004\n ? [0, 50, 20, 70]\n : [0, 50];\n for (const offset of candidates) {\n const month = rawMonth - offset;\n if (month >= 1 && month <= 12) {\n return month;\n }\n }\n return null;\n};\n\nconst validate = (value: string): ValidateResult => {\n const v = compact(value);\n if (v.length !== 9 && v.length !== 10) {\n return err(\n \"INVALID_LENGTH\",\n \"Birth number must be 9 or 10 digits\",\n );\n }\n if (!isdigits(v)) {\n return err(\n \"INVALID_FORMAT\",\n \"Birth number must contain only digits\",\n );\n }\n\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 // Resolve full year\n let year = yy + 1900;\n if (v.length === 9) {\n // 9-digit: pre-1954 only\n if (year >= 1980) year -= 100;\n if (year > 1953) {\n return err(\n \"INVALID_COMPONENT\",\n \"9-digit birth numbers are pre-1954 only\",\n );\n }\n } else {\n // 10-digit: if year < 1954, it's 2000s\n if (year < 1954) year += 100;\n }\n\n const month = decodeMonth(mm, year, v.length);\n if (month === null) {\n return err(\n \"INVALID_COMPONENT\",\n \"Birth number contains an invalid month\",\n );\n }\n\n if (dd < 1 || dd > 31) {\n return err(\n \"INVALID_COMPONENT\",\n \"Birth number contains an invalid day\",\n );\n }\n\n // Validate the actual date\n if (!isValidDate(year, month, dd)) {\n return err(\n \"INVALID_COMPONENT\",\n \"Birth number contains an invalid date\",\n );\n }\n\n // 10-digit: check digit is first 9 digits\n // mod 11 mod 10\n if (v.length === 10) {\n const front = Number(v.slice(0, 9));\n const check = (front % 11) % 10;\n if (String(check) !== v[9]) {\n return err(\n \"INVALID_CHECKSUM\",\n \"Birth number is not divisible by 11\",\n );\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 birth number.\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\n const gender = mm >= 50 ? \"female\" : \"male\";\n let year = yy + 1900;\n if (v.length === 10 && year < 1954) year += 100;\n if (v.length === 9 && year >= 1980) year -= 100;\n\n const month = decodeMonth(mm, year, v.length);\n if (month === null) {\n return null;\n }\n\n return {\n birthDate: new Date(year, month - 1, dd),\n gender,\n };\n};\n\n/**\n * Generate a random valid 10-digit birth number.\n * Uses a random date between 1954 and 2024.\n */\nconst generate = (): string => {\n const year = randomInt(1954, 2024);\n const month = randomInt(1, 12);\n const day = randomInt(1, 28);\n const isFemale = Math.random() < 0.5;\n const mm = isFemale ? month + 50 : month;\n\n const yy = String(year % 100).padStart(2, \"0\");\n const mmStr = String(mm).padStart(2, \"0\");\n const dd = String(day).padStart(2, \"0\");\n\n const datePart = `${yy}${mmStr}${dd}`;\n for (let seq = 0; seq < 1000; seq++) {\n const seqStr = String(seq).padStart(3, \"0\");\n const front = `${datePart}${seqStr}`;\n const num = Number(front);\n const check = (num % 11) % 10;\n const result = `${front}${String(check)}`;\n if (validate(result).valid) {\n return result;\n }\n }\n\n throw new Error(\"Failed to generate valid birth number\");\n};\n\n/** Czech/Slovak Birth Number. */\nconst rc: CountryValidator<\"CZ\", ParsedPersonId> = {\n name: \"Czech Birth Number\",\n localName: \"Rodné číslo\",\n abbreviation: \"RČ\",\n aliases: [\"rodné číslo\", \"RČ\", \"birth number\"] as const,\n candidatePattern: \"\\\\d{6}/\\\\d{3,4}\",\n scope: \"country\",\n country: \"CZ\",\n entityType: \"person\",\n description:\n \"Czech/Slovak national identifier encoding birth date and gender\",\n sourceUrl:\n \"https://www.mvcr.cz/mvcren/docDetail.aspx?docid=21975362&doctype=ART\",\n lengths: [9, 10] as const,\n examples: [\"7103192745\"] as const,\n compact,\n format,\n parse,\n validate,\n generate,\n};\n\nexport default rc;\nexport { compact, format, generate, parse, validate };\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handelsreg.js","names":[],"sources":["../../src/de/handelsreg.ts"],"sourcesContent":["const GENERATE_TYPES = [\n \"HRA\",\n \"HRB\",\n \"GNR\",\n \"PR\",\n \"VR\",\n] as const;\n\n/** Generate a random valid Handelsregisternummer. */\nconst generate = (): string => {\n const type = randomPick(GENERATE_TYPES);\n const number = randomDigits(5);\n return `${type} ${number}`;\n};\n\n/**\n * Handelsregisternummer (German Company Register Number).\n *\n * German commercial register number assigned by the\n * local court (Amtsgericht). Format: register type\n * (HRA, HRB, GnR, PR, VR) + number.\n *\n * HRA = Handelsregister Abteilung A (partnerships,\n * sole proprietors). HRB = Abteilung B (corporations).\n * GnR = Genossenschaftsregister.\n * PR = Partnerschaftsregister.\n * VR = Vereinsregister.\n *\n * No check digit; validation is structural only.\n *\n * @see https://de.wikipedia.org/wiki/Handelsregister_(Deutschland)\n */\n\nimport type {\n ValidateResult,\n CountryValidator,\n} from \"../types\";\n\nimport { clean } from \"#util/clean\";\nimport { randomDigits, randomPick } from \"#util/generate\";\nimport { err } from \"#util/result\";\n\nconst REGISTER_TYPES = new Set([\n \"HRA\",\n \"HRB\",\n \"GNR\",\n \"PR\",\n \"VR\",\n]);\n\n/**\n * Strip separators and normalize to canonical form.\n * Accepts \"HRB 12345\", \"HRB12345\", \"hrb 12345\".\n */\nconst compact = (value: string): string => {\n const v = clean(value, \" -.\").trim().toUpperCase();\n const match = v.match(/^(HRA|HRB|GNR|PR|VR)(\\d{1,7})$/);\n if (!match) return v;\n return `${match[1]} ${match[2]}`;\n};\n\nconst validate = (value: string): ValidateResult => {\n const v = clean(value, \" -.\").trim().toUpperCase();\n\n const match = v.match(/^([A-Z]{2,3})(\\d{1,7})$/);\n if (!match) {\n return err(\n \"INVALID_FORMAT\",\n \"Handelsregisternummer must be a register type \" +\n \"(HRA, HRB, GnR, PR, VR) followed by \" +\n \"1-7 digits\",\n );\n }\n\n // SAFETY: both capture groups are required, so a\n // successful match always populates [1] and [2].\n // eslint-disable-next-line no-non-null-assertion\n const type = match[1]!;\n // eslint-disable-next-line no-non-null-assertion\n const number = match[2]!;\n\n if (!REGISTER_TYPES.has(type)) {\n return err(\n \"INVALID_COMPONENT\",\n `Unrecognised register type \"${type}\"`,\n );\n }\n\n return {\n valid: true,\n compact: `${type} ${number}`,\n };\n};\n\nconst format = (value: string): string => compact(value);\n\n/** German Company Register Number. */\nconst handelsreg: CountryValidator<\"DE\"> = {\n name: \"German Company Register Number\",\n localName: \"Handelsregisternummer\",\n abbreviation: \"HReg\",\n aliases: [\n \"Handelsregisternummer\",\n \"Handelsregister\",\n \"HRB\",\n \"HRA\",\n ] as const,\n candidatePattern: \"(?:HRA|HRB|GnR|PR|VR)\\\\s*\\\\d{1,7}\",\n scope: \"country\",\n country: \"DE\",\n entityType: \"company\",\n sourceUrl:\n \"https://de.wikipedia.org/wiki/Handelsregister_(Deutschland)\",\n examples: [\"HRB 12345\"] as const,\n compact,\n format,\n validate,\n generate,\n};\n\nexport default handelsreg;\nexport { compact, format, validate, generate };\n"],"mappings":";;;;AAAA,MAAM,iBAAiB;CACrB;CACA;CACA;CACA;CACA;AACF;;AAGA,MAAM,iBAAyB;CAG7B,OAAO,GAFM,WAAW,cAEX,EAAE,GADA,aAAa,CACL;AACzB;AA6BA,MAAM,iBAAiB,IAAI,IAAI;CAC7B;CACA;CACA;CACA;CACA;AACF,CAAC;;;;;AAMD,MAAM,WAAW,UAA0B;CACzC,MAAM,IAAI,MAAM,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"handelsreg.js","names":[],"sources":["../../src/de/handelsreg.ts"],"sourcesContent":["const GENERATE_TYPES = [\n \"HRA\",\n \"HRB\",\n \"GNR\",\n \"PR\",\n \"VR\",\n] as const;\n\n/** Generate a random valid Handelsregisternummer. */\nconst generate = (): string => {\n const type = randomPick(GENERATE_TYPES);\n const number = randomDigits(5);\n return `${type} ${number}`;\n};\n\n/**\n * Handelsregisternummer (German Company Register Number).\n *\n * German commercial register number assigned by the\n * local court (Amtsgericht). Format: register type\n * (HRA, HRB, GnR, PR, VR) + number.\n *\n * HRA = Handelsregister Abteilung A (partnerships,\n * sole proprietors). HRB = Abteilung B (corporations).\n * GnR = Genossenschaftsregister.\n * PR = Partnerschaftsregister.\n * VR = Vereinsregister.\n *\n * No check digit; validation is structural only.\n *\n * @see https://de.wikipedia.org/wiki/Handelsregister_(Deutschland)\n */\n\nimport type {\n ValidateResult,\n CountryValidator,\n} from \"../types\";\n\nimport { clean } from \"#util/clean\";\nimport { randomDigits, randomPick } from \"#util/generate\";\nimport { err } from \"#util/result\";\n\nconst REGISTER_TYPES = new Set([\n \"HRA\",\n \"HRB\",\n \"GNR\",\n \"PR\",\n \"VR\",\n]);\n\n/**\n * Strip separators and normalize to canonical form.\n * Accepts \"HRB 12345\", \"HRB12345\", \"hrb 12345\".\n */\nconst compact = (value: string): string => {\n const v = clean(value, \" -.\").trim().toUpperCase();\n const match = v.match(/^(HRA|HRB|GNR|PR|VR)(\\d{1,7})$/);\n if (!match) return v;\n return `${match[1]} ${match[2]}`;\n};\n\nconst validate = (value: string): ValidateResult => {\n const v = clean(value, \" -.\").trim().toUpperCase();\n\n const match = v.match(/^([A-Z]{2,3})(\\d{1,7})$/);\n if (!match) {\n return err(\n \"INVALID_FORMAT\",\n \"Handelsregisternummer must be a register type \" +\n \"(HRA, HRB, GnR, PR, VR) followed by \" +\n \"1-7 digits\",\n );\n }\n\n // SAFETY: both capture groups are required, so a\n // successful match always populates [1] and [2].\n // eslint-disable-next-line no-non-null-assertion\n const type = match[1]!;\n // eslint-disable-next-line no-non-null-assertion\n const number = match[2]!;\n\n if (!REGISTER_TYPES.has(type)) {\n return err(\n \"INVALID_COMPONENT\",\n `Unrecognised register type \"${type}\"`,\n );\n }\n\n return {\n valid: true,\n compact: `${type} ${number}`,\n };\n};\n\nconst format = (value: string): string => compact(value);\n\n/** German Company Register Number. */\nconst handelsreg: CountryValidator<\"DE\"> = {\n name: \"German Company Register Number\",\n localName: \"Handelsregisternummer\",\n abbreviation: \"HReg\",\n aliases: [\n \"Handelsregisternummer\",\n \"Handelsregister\",\n \"HRB\",\n \"HRA\",\n ] as const,\n candidatePattern: \"(?:HRA|HRB|GnR|PR|VR)\\\\s*\\\\d{1,7}\",\n scope: \"country\",\n country: \"DE\",\n entityType: \"company\",\n sourceUrl:\n \"https://de.wikipedia.org/wiki/Handelsregister_(Deutschland)\",\n examples: [\"HRB 12345\"] as const,\n compact,\n format,\n validate,\n generate,\n};\n\nexport default handelsreg;\nexport { compact, format, validate, generate };\n"],"mappings":";;;;AAAA,MAAM,iBAAiB;CACrB;CACA;CACA;CACA;CACA;AACF;;AAGA,MAAM,iBAAyB;CAG7B,OAAO,GAFM,WAAW,cAEX,EAAE,GADA,aAAa,CACL;AACzB;AA6BA,MAAM,iBAAiB,IAAI,IAAI;CAC7B;CACA;CACA;CACA;CACA;AACF,CAAC;;;;;AAMD,MAAM,WAAW,UAA0B;CACzC,MAAM,IAAI,MAAM,OAAO,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,YAAY;CACjD,MAAM,QAAQ,EAAE,MAAM,gCAAgC;CACtD,IAAI,CAAC,OAAO,OAAO;CACnB,OAAO,GAAG,MAAM,GAAG,GAAG,MAAM;AAC9B;AAEA,MAAM,YAAY,UAAkC;CAGlD,MAAM,QAFI,MAAM,OAAO,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,YAEvB,CAAC,CAAC,MAAM,yBAAyB;CAC/C,IAAI,CAAC,OACH,OAAO,IACL,kBACA,8FAGF;CAMF,MAAM,OAAO,MAAM;CAEnB,MAAM,SAAS,MAAM;CAErB,IAAI,CAAC,eAAe,IAAI,IAAI,GAC1B,OAAO,IACL,qBACA,+BAA+B,KAAK,EACtC;CAGF,OAAO;EACL,OAAO;EACP,SAAS,GAAG,KAAK,GAAG;CACtB;AACF;AAEA,MAAM,UAAU,UAA0B,QAAQ,KAAK;;AAGvD,MAAM,aAAqC;CACzC,MAAM;CACN,WAAW;CACX,cAAc;CACd,SAAS;EACP;EACA;EACA;EACA;CACF;CACA,kBAAkB;CAClB,OAAO;CACP,SAAS;CACT,YAAY;CACZ,WACE;CACF,UAAU,CAAC,WAAW;CACtB;CACA;CACA;CACA;AACF"}
|
package/dist/de/idnr.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"idnr.js","names":[],"sources":["../../src/de/idnr.ts"],"sourcesContent":["/**\n * IdNr (Steuerliche Identifikationsnummer).\n *\n * German personal tax identification number.\n * 11 digits; the last digit is a check digit\n * (ISO 7064 Mod 11,10). Exactly one digit\n * appears twice or three times; the rest are\n * unique. The first digit cannot be 0.\n *\n * @see https://www.bzst.de/DE/Privatpersonen/SteuerlicheIdentifikationsnummer/steuerlicheidentifikationsnummer_node.html\n */\n\nimport type {\n ValidateResult,\n CountryValidator,\n} from \"../types\";\n\nimport {\n mod1110validate,\n mod1110checkDigit,\n} from \"#checksums/mod1110\";\nimport { clean } from \"#util/clean\";\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\n/**\n * Validate digit distribution: in the first 10\n * digits, exactly one digit appears 2 or 3\n * times; all others appear exactly once.\n */\nconst validDistribution = (value: string): boolean => {\n const counts = Array.from<number>({ length: 10 }).fill(0);\n for (let i = 0; i < 10; i++) {\n const idx = Number(value.charAt(i));\n const prev = counts[idx];\n if (prev !== undefined) counts[idx] = prev + 1;\n }\n let doubles = 0;\n let triples = 0;\n for (const c of counts) {\n if (c === 2) doubles++;\n if (c === 3) triples++;\n if (c > 3) return false;\n }\n // Exactly one digit repeated (2 or 3 times)\n return (\n (doubles === 1 && triples === 0) ||\n (doubles === 0 && triples === 1)\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 \"German IdNr must be 11 digits\",\n );\n }\n if (!isdigits(v)) {\n return err(\n \"INVALID_FORMAT\",\n \"German IdNr must contain only digits\",\n );\n }\n if (v[0] === \"0\") {\n return err(\n \"INVALID_COMPONENT\",\n \"German IdNr cannot start with 0\",\n );\n }\n if (!validDistribution(v)) {\n return err(\n \"INVALID_COMPONENT\",\n \"German IdNr digit distribution invalid\",\n );\n }\n if (!mod1110validate(v)) {\n return err(\n \"INVALID_CHECKSUM\",\n \"German IdNr check digit mismatch\",\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, 2)} ${v.slice(2, 5)} ${v.slice(5, 8)} ${v.slice(8)}`;\n};\n\n/** Generate a random valid German IdNr. */\nconst generate = (): string => {\n for (;;) {\n const repeated = randomInt(0, 9);\n const times = Math.random() < 0.8 ? 2 : 3;\n const pool: number[] = [];\n for (let d = 0; d < 10; d++) {\n const count = d === repeated ? times : 1;\n for (let t = 0; t < count; t++) pool.push(d);\n }\n while (pool.length < 10) pool.push(randomInt(0, 9));\n for (let i = pool.length - 1; i > 0; i--) {\n const j = randomInt(0, i);\n // SAFETY: i and j are valid indices of `pool`\n // (i > 0, j ∈ [0, i]).\n // eslint-disable-next-line no-non-null-assertion\n [pool[i], pool[j]] = [pool[j]!, pool[i]!];\n }\n if (pool[0] === 0) continue;\n const payload = pool.slice(0, 10).join(\"\");\n if (!validDistribution(payload)) continue;\n const check = mod1110checkDigit(payload);\n const c = payload + String(check);\n if (validate(c).valid) return c;\n }\n};\n\n/** German Personal Tax ID. */\nconst idnr: CountryValidator<\"DE\"> = {\n name: \"German Tax ID\",\n localName: \"Steuerliche Identifikationsnummer\",\n abbreviation: \"IdNr\",\n aliases: [\n \"Steuerliche Identifikationsnummer\",\n \"IdNr\",\n \"Steuer-ID\",\n ] as const,\n candidatePattern: \"\\\\d{2}\\\\s?\\\\d{3}\\\\s?\\\\d{3}\\\\s?\\\\d{3}\",\n scope: \"country\",\n country: \"DE\",\n entityType: \"person\",\n sourceUrl:\n \"https://www.bzst.de/DE/Privatpersonen/SteuerlicheIdentifikationsnummer/steuerlicheidentifikationsnummer_node.html\",\n examples: [\"36574261809\"] as const,\n compact,\n format,\n validate,\n generate,\n};\n\nexport default idnr;\nexport { compact, format, validate, generate };\n"],"mappings":";;;;;;AA0BA,MAAM,WAAW,UACf,MAAM,OAAO,KAAK;;;;;;AAOpB,MAAM,qBAAqB,UAA2B;CACpD,MAAM,SAAS,MAAM,KAAa,EAAE,QAAQ,GAAG,CAAC,
|
|
1
|
+
{"version":3,"file":"idnr.js","names":[],"sources":["../../src/de/idnr.ts"],"sourcesContent":["/**\n * IdNr (Steuerliche Identifikationsnummer).\n *\n * German personal tax identification number.\n * 11 digits; the last digit is a check digit\n * (ISO 7064 Mod 11,10). Exactly one digit\n * appears twice or three times; the rest are\n * unique. The first digit cannot be 0.\n *\n * @see https://www.bzst.de/DE/Privatpersonen/SteuerlicheIdentifikationsnummer/steuerlicheidentifikationsnummer_node.html\n */\n\nimport type {\n ValidateResult,\n CountryValidator,\n} from \"../types\";\n\nimport {\n mod1110validate,\n mod1110checkDigit,\n} from \"#checksums/mod1110\";\nimport { clean } from \"#util/clean\";\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\n/**\n * Validate digit distribution: in the first 10\n * digits, exactly one digit appears 2 or 3\n * times; all others appear exactly once.\n */\nconst validDistribution = (value: string): boolean => {\n const counts = Array.from<number>({ length: 10 }).fill(0);\n for (let i = 0; i < 10; i++) {\n const idx = Number(value.charAt(i));\n const prev = counts[idx];\n if (prev !== undefined) counts[idx] = prev + 1;\n }\n let doubles = 0;\n let triples = 0;\n for (const c of counts) {\n if (c === 2) doubles++;\n if (c === 3) triples++;\n if (c > 3) return false;\n }\n // Exactly one digit repeated (2 or 3 times)\n return (\n (doubles === 1 && triples === 0) ||\n (doubles === 0 && triples === 1)\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 \"German IdNr must be 11 digits\",\n );\n }\n if (!isdigits(v)) {\n return err(\n \"INVALID_FORMAT\",\n \"German IdNr must contain only digits\",\n );\n }\n if (v[0] === \"0\") {\n return err(\n \"INVALID_COMPONENT\",\n \"German IdNr cannot start with 0\",\n );\n }\n if (!validDistribution(v)) {\n return err(\n \"INVALID_COMPONENT\",\n \"German IdNr digit distribution invalid\",\n );\n }\n if (!mod1110validate(v)) {\n return err(\n \"INVALID_CHECKSUM\",\n \"German IdNr check digit mismatch\",\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, 2)} ${v.slice(2, 5)} ${v.slice(5, 8)} ${v.slice(8)}`;\n};\n\n/** Generate a random valid German IdNr. */\nconst generate = (): string => {\n for (;;) {\n const repeated = randomInt(0, 9);\n const times = Math.random() < 0.8 ? 2 : 3;\n const pool: number[] = [];\n for (let d = 0; d < 10; d++) {\n const count = d === repeated ? times : 1;\n for (let t = 0; t < count; t++) pool.push(d);\n }\n while (pool.length < 10) pool.push(randomInt(0, 9));\n for (let i = pool.length - 1; i > 0; i--) {\n const j = randomInt(0, i);\n // SAFETY: i and j are valid indices of `pool`\n // (i > 0, j ∈ [0, i]).\n // eslint-disable-next-line no-non-null-assertion\n [pool[i], pool[j]] = [pool[j]!, pool[i]!];\n }\n if (pool[0] === 0) continue;\n const payload = pool.slice(0, 10).join(\"\");\n if (!validDistribution(payload)) continue;\n const check = mod1110checkDigit(payload);\n const c = payload + String(check);\n if (validate(c).valid) return c;\n }\n};\n\n/** German Personal Tax ID. */\nconst idnr: CountryValidator<\"DE\"> = {\n name: \"German Tax ID\",\n localName: \"Steuerliche Identifikationsnummer\",\n abbreviation: \"IdNr\",\n aliases: [\n \"Steuerliche Identifikationsnummer\",\n \"IdNr\",\n \"Steuer-ID\",\n ] as const,\n candidatePattern: \"\\\\d{2}\\\\s?\\\\d{3}\\\\s?\\\\d{3}\\\\s?\\\\d{3}\",\n scope: \"country\",\n country: \"DE\",\n entityType: \"person\",\n sourceUrl:\n \"https://www.bzst.de/DE/Privatpersonen/SteuerlicheIdentifikationsnummer/steuerlicheidentifikationsnummer_node.html\",\n examples: [\"36574261809\"] as const,\n compact,\n format,\n validate,\n generate,\n};\n\nexport default idnr;\nexport { compact, format, validate, generate };\n"],"mappings":";;;;;;AA0BA,MAAM,WAAW,UACf,MAAM,OAAO,KAAK;;;;;;AAOpB,MAAM,qBAAqB,UAA2B;CACpD,MAAM,SAAS,MAAM,KAAa,EAAE,QAAQ,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC;CACxD,KAAK,IAAI,IAAI,GAAG,IAAI,IAAI,KAAK;EAC3B,MAAM,MAAM,OAAO,MAAM,OAAO,CAAC,CAAC;EAClC,MAAM,OAAO,OAAO;EACpB,IAAI,SAAS,KAAA,GAAW,OAAO,OAAO,OAAO;CAC/C;CACA,IAAI,UAAU;CACd,IAAI,UAAU;CACd,KAAK,MAAM,KAAK,QAAQ;EACtB,IAAI,MAAM,GAAG;EACb,IAAI,MAAM,GAAG;EACb,IAAI,IAAI,GAAG,OAAO;CACpB;CAEA,OACG,YAAY,KAAK,YAAY,KAC7B,YAAY,KAAK,YAAY;AAElC;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,sCACF;CAEF,IAAI,EAAE,OAAO,KACX,OAAO,IACL,qBACA,iCACF;CAEF,IAAI,CAAC,kBAAkB,CAAC,GACtB,OAAO,IACL,qBACA,wCACF;CAEF,IAAI,CAAC,gBAAgB,CAAC,GACpB,OAAO,IACL,oBACA,kCACF;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,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC;AACxE;;AAGA,MAAM,iBAAyB;CAC7B,SAAS;EACP,MAAM,WAAW,UAAU,GAAG,CAAC;EAC/B,MAAM,QAAQ,KAAK,OAAO,IAAI,KAAM,IAAI;EACxC,MAAM,OAAiB,CAAC;EACxB,KAAK,IAAI,IAAI,GAAG,IAAI,IAAI,KAAK;GAC3B,MAAM,QAAQ,MAAM,WAAW,QAAQ;GACvC,KAAK,IAAI,IAAI,GAAG,IAAI,OAAO,KAAK,KAAK,KAAK,CAAC;EAC7C;EACA,OAAO,KAAK,SAAS,IAAI,KAAK,KAAK,UAAU,GAAG,CAAC,CAAC;EAClD,KAAK,IAAI,IAAI,KAAK,SAAS,GAAG,IAAI,GAAG,KAAK;GACxC,MAAM,IAAI,UAAU,GAAG,CAAC;GAIxB,CAAC,KAAK,IAAI,KAAK,MAAM,CAAC,KAAK,IAAK,KAAK,EAAG;EAC1C;EACA,IAAI,KAAK,OAAO,GAAG;EACnB,MAAM,UAAU,KAAK,MAAM,GAAG,EAAE,CAAC,CAAC,KAAK,EAAE;EACzC,IAAI,CAAC,kBAAkB,OAAO,GAAG;EACjC,MAAM,QAAQ,kBAAkB,OAAO;EACvC,MAAM,IAAI,UAAU,OAAO,KAAK;EAChC,IAAI,SAAS,CAAC,CAAC,CAAC,OAAO,OAAO;CAChC;AACF;;AAGA,MAAM,OAA+B;CACnC,MAAM;CACN,WAAW;CACX,cAAc;CACd,SAAS;EACP;EACA;EACA;CACF;CACA,kBAAkB;CAClB,OAAO;CACP,SAAS;CACT,YAAY;CACZ,WACE;CACF,UAAU,CAAC,aAAa;CACxB;CACA;CACA;CACA;AACF"}
|
package/dist/de/stnr.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stnr.js","names":[],"sources":["../../src/de/stnr.ts"],"sourcesContent":["/**\n * Generate a random valid Steuernummer.\n * Uses Berlin regional format (FFBBBUUUUP, 10 digits).\n */\nconst generate = (): string => {\n const ff = randomDigits(2);\n const bbb = randomDigits(3);\n const uuuu = randomDigits(4);\n const p = randomDigits(1);\n return `${ff}${bbb}${uuuu}${p}`;\n};\n\n/**\n * StNr (Steuernummer).\n *\n * German tax number assigned by the local tax office.\n * Regional format: 10 or 11 digits. Unified federal\n * format: 13 digits (prefixed with a 2–4 digit BUFA\n * code). Structure varies by federal state.\n *\n * The number encodes: BUFA (Bundesfinanzamtsnummer),\n * district code, serial number, and check digit. No\n * algorithmic check digit validation is standardised\n * across all states; validation is format-only.\n *\n * @see https://de.wikipedia.org/wiki/Steuernummer\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\n/**\n * Format patterns per federal state.\n * [0] = regional format, [1] = unified (federal) format.\n *\n * Legend: F = finance office, B = district,\n * U = serial, P = check digit,\n * leading digits are literal.\n */\nconst STATES = {\n \"Baden-Württemberg\": [\"FFBBBUUUUP\", \"28FF0BBBUUUUP\"],\n Bayern: [\"FFFBBBUUUUP\", \"9FFF0BBBUUUUP\"],\n Berlin: [\"FFBBBUUUUP\", \"11FF0BBBUUUUP\"],\n Brandenburg: [\"0FFBBBUUUUP\", \"30FF0BBBUUUUP\"],\n Bremen: [\"FFBBBUUUUP\", \"24FF0BBBUUUUP\"],\n Hamburg: [\"FFBBBUUUUP\", \"22FF0BBBUUUUP\"],\n Hessen: [\"0FFBBBUUUUP\", \"26FF0BBBUUUUP\"],\n \"Mecklenburg-Vorpommern\": [\n \"0FFBBBUUUUP\",\n \"40FF0BBBUUUUP\",\n ],\n Niedersachsen: [\"FFBBBUUUUP\", \"23FF0BBBUUUUP\"],\n \"Nordrhein-Westfalen\": [\"FFFBBBBUUUP\", \"5FFF0BBBBUUUP\"],\n \"Rheinland-Pfalz\": [\"FFBBBUUUUP\", \"27FF0BBBUUUUP\"],\n Saarland: [\"0FFBBBUUUUP\", \"10FF0BBBUUUUP\"],\n Sachsen: [\"2FFBBBUUUUP\", \"32FF0BBBUUUUP\"],\n \"Sachsen-Anhalt\": [\"1FFBBBUUUUP\", \"31FF0BBBUUUUP\"],\n \"Schleswig-Holstein\": [\"FFBBBUUUUP\", \"21FF0BBBUUUUP\"],\n Thüringen: [\"1FFBBBUUUUP\", \"41FF0BBBUUUUP\"],\n} as const;\n\ntype StateName = keyof typeof STATES;\n\n/**\n * Convert a format pattern to a regex.\n * Literal digits match themselves; F/B/U match any\n * digit; P (check digit) matches any digit.\n */\nconst patternToRegex = (pattern: string): RegExp => {\n let re = \"^\";\n for (const ch of pattern) {\n if (ch >= \"0\" && ch <= \"9\") {\n re += ch;\n } else {\n re += \"\\\\d\";\n }\n }\n re += \"$\";\n return new RegExp(re);\n};\n\n/** Pre-compiled regexes for each state + format. */\nconst COMPILED: Array<{\n state: StateName;\n regional: RegExp;\n regionalFmt: string;\n federal: RegExp;\n federalFmt: string;\n}> = (\n Object.entries(STATES) as Array<\n [StateName, readonly [string, string]]\n >\n).map(([state, [regionalFmt, federalFmt]]) => ({\n state,\n regional: patternToRegex(regionalFmt),\n regionalFmt,\n federal: patternToRegex(federalFmt),\n federalFmt,\n}));\n\nconst compact = (value: string): string =>\n clean(value, \" -/.\");\n\nconst matchesAnyPattern = (v: string): boolean => {\n for (const { regional, federal } of COMPILED) {\n if (regional.test(v) || federal.test(v)) return true;\n }\n return false;\n};\n\nconst validate = (value: string): ValidateResult => {\n const v = compact(value);\n\n if (!isdigits(v)) {\n return err(\n \"INVALID_FORMAT\",\n \"German Steuernummer must contain only digits\",\n );\n }\n\n if (\n v.length !== 10 &&\n v.length !== 11 &&\n v.length !== 13\n ) {\n return err(\n \"INVALID_LENGTH\",\n \"German Steuernummer must be 10, 11, or 13 digits\",\n );\n }\n\n if (!matchesAnyPattern(v)) {\n return err(\n \"INVALID_COMPONENT\",\n \"German Steuernummer does not match any \" +\n \"known state format\",\n );\n }\n\n return { valid: true, compact: v };\n};\n\n/**\n * Format with slashes at segment boundaries.\n * Uses the first matching state pattern to determine\n * segment positions.\n */\nconst format = (value: string): string => {\n const v = compact(value);\n\n for (const entry of COMPILED) {\n const pattern =\n v.length === 13 ? entry.federal : entry.regional;\n if (!pattern.test(v)) continue;\n\n const fmt =\n v.length === 13\n ? entry.federalFmt\n : entry.regionalFmt;\n\n // Group by consecutive runs of the same letter\n // category: literal digits, F, B, U, P\n const segments: string[] = [];\n let segStart = 0;\n // SAFETY: fmt is a non-empty literal from COMPILED.\n // eslint-disable-next-line no-non-null-assertion\n let prevCat = charCat(fmt[0]!);\n\n for (let i = 1; i < fmt.length; i++) {\n // SAFETY: i < fmt.length.\n // eslint-disable-next-line no-non-null-assertion\n const cat = charCat(fmt[i]!);\n if (cat !== prevCat) {\n segments.push(v.slice(segStart, i));\n segStart = i;\n prevCat = cat;\n }\n }\n segments.push(v.slice(segStart));\n\n return segments.join(\"/\");\n }\n\n return v;\n};\n\nconst charCat = (ch: string): string => {\n if (ch >= \"0\" && ch <= \"9\") return \"L\"; // literal\n return ch; // F, B, U, P\n};\n\n/** German Tax Number (Steuernummer). */\nconst stnr: CountryValidator<\"DE\"> = {\n name: \"German Tax Number\",\n localName: \"Steuernummer\",\n abbreviation: \"StNr\",\n aliases: [\"Steuernummer\", \"St.Nr.\"] as const,\n candidatePattern: \"\\\\d{2,4}/\\\\d{3,4}/\\\\d{4,5}\",\n scope: \"country\",\n country: \"DE\",\n entityType: \"any\",\n description:\n \"German tax number assigned by the local \" +\n \"tax office, varies by federal state\",\n sourceUrl: \"https://de.wikipedia.org/wiki/Steuernummer\",\n lengths: [10, 11, 13] as const,\n examples: [\"2181508150\", \"18181508155\"] as const,\n compact,\n format,\n validate,\n generate,\n};\n\nexport default stnr;\nexport { compact, format, validate, generate };\n"],"mappings":";;;;;;;;;AAIA,MAAM,iBAAyB;CAK7B,OAAO,GAJI,aAAa,CAIb,IAHC,aAAa,CAGR,IAFJ,aAAa,CAEF,IADd,aAAa,CACK;AAC9B;;;;;;;;;AAoCA,MAAM,SAAS;CACb,qBAAqB,CAAC,cAAc,eAAe;CACnD,QAAQ,CAAC,eAAe,eAAe;CACvC,QAAQ,CAAC,cAAc,eAAe;CACtC,aAAa,CAAC,eAAe,eAAe;CAC5C,QAAQ,CAAC,cAAc,eAAe;CACtC,SAAS,CAAC,cAAc,eAAe;CACvC,QAAQ,CAAC,eAAe,eAAe;CACvC,0BAA0B,CACxB,eACA,eACF;CACA,eAAe,CAAC,cAAc,eAAe;CAC7C,uBAAuB,CAAC,eAAe,eAAe;CACtD,mBAAmB,CAAC,cAAc,eAAe;CACjD,UAAU,CAAC,eAAe,eAAe;CACzC,SAAS,CAAC,eAAe,eAAe;CACxC,kBAAkB,CAAC,eAAe,eAAe;CACjD,sBAAsB,CAAC,cAAc,eAAe;CACpD,WAAW,CAAC,eAAe,eAAe;AAC5C;;;;;;AASA,MAAM,kBAAkB,YAA4B;CAClD,IAAI,KAAK;CACT,KAAK,MAAM,MAAM,SACf,IAAI,MAAM,OAAO,MAAM,KACrB,MAAM;MAEN,MAAM;CAGV,MAAM;CACN,OAAO,IAAI,OAAO,EAAE;AACtB;;AAGA,MAAM,WAOJ,OAAO,QAAQ,MAAM,
|
|
1
|
+
{"version":3,"file":"stnr.js","names":[],"sources":["../../src/de/stnr.ts"],"sourcesContent":["/**\n * Generate a random valid Steuernummer.\n * Uses Berlin regional format (FFBBBUUUUP, 10 digits).\n */\nconst generate = (): string => {\n const ff = randomDigits(2);\n const bbb = randomDigits(3);\n const uuuu = randomDigits(4);\n const p = randomDigits(1);\n return `${ff}${bbb}${uuuu}${p}`;\n};\n\n/**\n * StNr (Steuernummer).\n *\n * German tax number assigned by the local tax office.\n * Regional format: 10 or 11 digits. Unified federal\n * format: 13 digits (prefixed with a 2–4 digit BUFA\n * code). Structure varies by federal state.\n *\n * The number encodes: BUFA (Bundesfinanzamtsnummer),\n * district code, serial number, and check digit. No\n * algorithmic check digit validation is standardised\n * across all states; validation is format-only.\n *\n * @see https://de.wikipedia.org/wiki/Steuernummer\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\n/**\n * Format patterns per federal state.\n * [0] = regional format, [1] = unified (federal) format.\n *\n * Legend: F = finance office, B = district,\n * U = serial, P = check digit,\n * leading digits are literal.\n */\nconst STATES = {\n \"Baden-Württemberg\": [\"FFBBBUUUUP\", \"28FF0BBBUUUUP\"],\n Bayern: [\"FFFBBBUUUUP\", \"9FFF0BBBUUUUP\"],\n Berlin: [\"FFBBBUUUUP\", \"11FF0BBBUUUUP\"],\n Brandenburg: [\"0FFBBBUUUUP\", \"30FF0BBBUUUUP\"],\n Bremen: [\"FFBBBUUUUP\", \"24FF0BBBUUUUP\"],\n Hamburg: [\"FFBBBUUUUP\", \"22FF0BBBUUUUP\"],\n Hessen: [\"0FFBBBUUUUP\", \"26FF0BBBUUUUP\"],\n \"Mecklenburg-Vorpommern\": [\n \"0FFBBBUUUUP\",\n \"40FF0BBBUUUUP\",\n ],\n Niedersachsen: [\"FFBBBUUUUP\", \"23FF0BBBUUUUP\"],\n \"Nordrhein-Westfalen\": [\"FFFBBBBUUUP\", \"5FFF0BBBBUUUP\"],\n \"Rheinland-Pfalz\": [\"FFBBBUUUUP\", \"27FF0BBBUUUUP\"],\n Saarland: [\"0FFBBBUUUUP\", \"10FF0BBBUUUUP\"],\n Sachsen: [\"2FFBBBUUUUP\", \"32FF0BBBUUUUP\"],\n \"Sachsen-Anhalt\": [\"1FFBBBUUUUP\", \"31FF0BBBUUUUP\"],\n \"Schleswig-Holstein\": [\"FFBBBUUUUP\", \"21FF0BBBUUUUP\"],\n Thüringen: [\"1FFBBBUUUUP\", \"41FF0BBBUUUUP\"],\n} as const;\n\ntype StateName = keyof typeof STATES;\n\n/**\n * Convert a format pattern to a regex.\n * Literal digits match themselves; F/B/U match any\n * digit; P (check digit) matches any digit.\n */\nconst patternToRegex = (pattern: string): RegExp => {\n let re = \"^\";\n for (const ch of pattern) {\n if (ch >= \"0\" && ch <= \"9\") {\n re += ch;\n } else {\n re += \"\\\\d\";\n }\n }\n re += \"$\";\n return new RegExp(re);\n};\n\n/** Pre-compiled regexes for each state + format. */\nconst COMPILED: Array<{\n state: StateName;\n regional: RegExp;\n regionalFmt: string;\n federal: RegExp;\n federalFmt: string;\n}> = (\n Object.entries(STATES) as Array<\n [StateName, readonly [string, string]]\n >\n).map(([state, [regionalFmt, federalFmt]]) => ({\n state,\n regional: patternToRegex(regionalFmt),\n regionalFmt,\n federal: patternToRegex(federalFmt),\n federalFmt,\n}));\n\nconst compact = (value: string): string =>\n clean(value, \" -/.\");\n\nconst matchesAnyPattern = (v: string): boolean => {\n for (const { regional, federal } of COMPILED) {\n if (regional.test(v) || federal.test(v)) return true;\n }\n return false;\n};\n\nconst validate = (value: string): ValidateResult => {\n const v = compact(value);\n\n if (!isdigits(v)) {\n return err(\n \"INVALID_FORMAT\",\n \"German Steuernummer must contain only digits\",\n );\n }\n\n if (\n v.length !== 10 &&\n v.length !== 11 &&\n v.length !== 13\n ) {\n return err(\n \"INVALID_LENGTH\",\n \"German Steuernummer must be 10, 11, or 13 digits\",\n );\n }\n\n if (!matchesAnyPattern(v)) {\n return err(\n \"INVALID_COMPONENT\",\n \"German Steuernummer does not match any \" +\n \"known state format\",\n );\n }\n\n return { valid: true, compact: v };\n};\n\n/**\n * Format with slashes at segment boundaries.\n * Uses the first matching state pattern to determine\n * segment positions.\n */\nconst format = (value: string): string => {\n const v = compact(value);\n\n for (const entry of COMPILED) {\n const pattern =\n v.length === 13 ? entry.federal : entry.regional;\n if (!pattern.test(v)) continue;\n\n const fmt =\n v.length === 13\n ? entry.federalFmt\n : entry.regionalFmt;\n\n // Group by consecutive runs of the same letter\n // category: literal digits, F, B, U, P\n const segments: string[] = [];\n let segStart = 0;\n // SAFETY: fmt is a non-empty literal from COMPILED.\n // eslint-disable-next-line no-non-null-assertion\n let prevCat = charCat(fmt[0]!);\n\n for (let i = 1; i < fmt.length; i++) {\n // SAFETY: i < fmt.length.\n // eslint-disable-next-line no-non-null-assertion\n const cat = charCat(fmt[i]!);\n if (cat !== prevCat) {\n segments.push(v.slice(segStart, i));\n segStart = i;\n prevCat = cat;\n }\n }\n segments.push(v.slice(segStart));\n\n return segments.join(\"/\");\n }\n\n return v;\n};\n\nconst charCat = (ch: string): string => {\n if (ch >= \"0\" && ch <= \"9\") return \"L\"; // literal\n return ch; // F, B, U, P\n};\n\n/** German Tax Number (Steuernummer). */\nconst stnr: CountryValidator<\"DE\"> = {\n name: \"German Tax Number\",\n localName: \"Steuernummer\",\n abbreviation: \"StNr\",\n aliases: [\"Steuernummer\", \"St.Nr.\"] as const,\n candidatePattern: \"\\\\d{2,4}/\\\\d{3,4}/\\\\d{4,5}\",\n scope: \"country\",\n country: \"DE\",\n entityType: \"any\",\n description:\n \"German tax number assigned by the local \" +\n \"tax office, varies by federal state\",\n sourceUrl: \"https://de.wikipedia.org/wiki/Steuernummer\",\n lengths: [10, 11, 13] as const,\n examples: [\"2181508150\", \"18181508155\"] as const,\n compact,\n format,\n validate,\n generate,\n};\n\nexport default stnr;\nexport { compact, format, validate, generate };\n"],"mappings":"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"}
|
package/dist/de/svnr.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"svnr.js","names":[],"sources":["../../src/de/svnr.ts"],"sourcesContent":["/**\n * SVNR (Sozialversicherungsnummer).\n *\n * German social insurance number. 12 characters:\n * area number (2 digits) + date of birth (DDMMYY)\n * + initial letter of birth name (1 letter) +\n * serial number (2 digits) + check digit (1 digit).\n *\n * @see https://de.wikipedia.org/wiki/Sozialversicherungsnummer\n */\n\nimport type {\n ValidateResult,\n CountryValidator,\n} from \"../types\";\n\nimport { clean } from \"#util/clean\";\nimport {\n isValidDate,\n resolveTwoDigitYear,\n} from \"#util/date\";\nimport { randomInt } from \"#util/generate\";\nimport { err } from \"#util/result\";\n\nconst letterValue = (ch: string): number => {\n const code = ch.charCodeAt(0);\n if (code >= 65 && code <= 90) return code - 64;\n return -1;\n};\n\nconst WEIGHTS = [\n 2, 1, 2, 5, 7, 1, 2, 1, 2, 1, 2, 1,\n] as const;\n\nconst compact = (value: string): string =>\n clean(value, \" -/\").toUpperCase();\n\nconst computeCheck = (v: string): number => {\n const digits: number[] = [];\n\n for (let i = 0; i < 8; i++) {\n digits.push(Number(v[i]));\n }\n\n // SAFETY: caller guarantees v.length === 12.\n // eslint-disable-next-line no-non-null-assertion\n const lv = letterValue(v[8]!);\n digits.push(Math.floor(lv / 10));\n digits.push(lv % 10);\n\n digits.push(Number(v[9]));\n digits.push(Number(v[10]));\n\n let sum = 0;\n for (const [i, weight] of WEIGHTS.entries()) {\n // SAFETY: WEIGHTS.length === 12 and we just pushed\n // 12 entries onto `digits`.\n // eslint-disable-next-line no-non-null-assertion\n const product = digits[i]! * weight;\n if (product >= 10) {\n sum += Math.floor(product / 10) + (product % 10);\n } else {\n sum += product;\n }\n }\n\n return sum % 10;\n};\n\nconst validate = (value: string): ValidateResult => {\n const v = compact(value);\n if (v.length !== 12) {\n return err(\n \"INVALID_LENGTH\",\n \"German SVNR must be 12 characters\",\n );\n }\n\n if (!/^\\d{8}[A-Z]\\d{3}$/.test(v)) {\n return err(\n \"INVALID_FORMAT\",\n \"German SVNR must be 8 digits, 1 letter, \" +\n \"3 digits\",\n );\n }\n\n const dd = Number(v.slice(2, 4));\n const mm = Number(v.slice(4, 6));\n const yy = Number(v.slice(6, 8));\n\n const year = resolveTwoDigitYear(yy);\n\n if (!isValidDate(year, mm, dd)) {\n return err(\n \"INVALID_COMPONENT\",\n \"German SVNR contains an invalid birth date\",\n );\n }\n\n const expected = computeCheck(v);\n const actual = Number(v[11]);\n\n if (expected !== actual) {\n return err(\n \"INVALID_CHECKSUM\",\n \"German SVNR check digit mismatch\",\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, 2)} ${v.slice(2, 8)}` +\n ` ${v.slice(8, 9)} ${v.slice(9, 11)}` +\n ` ${v.slice(11)}`\n );\n};\n\n/** Generate a random valid German SVNR. */\nconst generate = (): string => {\n for (;;) {\n const area = String(randomInt(1, 99)).padStart(2, \"0\");\n const dd = String(randomInt(1, 28)).padStart(2, \"0\");\n const mm = String(randomInt(1, 12)).padStart(2, \"0\");\n const yy = String(randomInt(40, 99)).padStart(2, \"0\");\n const letter = String.fromCharCode(\n 65 + randomInt(0, 25),\n );\n const serial = String(randomInt(0, 49)).padStart(\n 2,\n \"0\",\n );\n const partial = area + dd + mm + yy + letter + serial;\n const c = partial + String(computeCheck(partial));\n if (validate(c).valid) return c;\n }\n};\n\n/** German Social Insurance Number. */\nconst svnr: CountryValidator<\"DE\"> = {\n name: \"German Social Insurance Number\",\n localName: \"Sozialversicherungsnummer\",\n abbreviation: \"SVNR\",\n aliases: [\n \"Sozialversicherungsnummer\",\n \"SVNR\",\n \"Versicherungsnummer\",\n ] as const,\n candidatePattern: \"\\\\d{2}\\\\s?\\\\d{6}\\\\s?[A-Z]\\\\s?\\\\d{3}\",\n scope: \"country\",\n country: \"DE\",\n entityType: \"person\",\n description:\n \"German social insurance number encoding \" +\n \"birth date and name initial\",\n sourceUrl:\n \"https://de.wikipedia.org/wiki/\" +\n \"Sozialversicherungsnummer\",\n lengths: [12] as const,\n examples: [\"12010188M011\"] as const,\n compact,\n format,\n validate,\n generate,\n};\n\nexport default svnr;\nexport { compact, format, validate, generate };\n"],"mappings":";;;;;AAwBA,MAAM,eAAe,OAAuB;CAC1C,MAAM,OAAO,GAAG,WAAW,CAAC;CAC5B,IAAI,QAAQ,MAAM,QAAQ,IAAI,OAAO,OAAO;CAC5C,OAAO;AACT;AAEA,MAAM,UAAU;CACd;CAAG;CAAG;CAAG;CAAG;CAAG;CAAG;CAAG;CAAG;CAAG;CAAG;CAAG;AACnC;AAEA,MAAM,WAAW,UACf,MAAM,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"svnr.js","names":[],"sources":["../../src/de/svnr.ts"],"sourcesContent":["/**\n * SVNR (Sozialversicherungsnummer).\n *\n * German social insurance number. 12 characters:\n * area number (2 digits) + date of birth (DDMMYY)\n * + initial letter of birth name (1 letter) +\n * serial number (2 digits) + check digit (1 digit).\n *\n * @see https://de.wikipedia.org/wiki/Sozialversicherungsnummer\n */\n\nimport type {\n ValidateResult,\n CountryValidator,\n} from \"../types\";\n\nimport { clean } from \"#util/clean\";\nimport {\n isValidDate,\n resolveTwoDigitYear,\n} from \"#util/date\";\nimport { randomInt } from \"#util/generate\";\nimport { err } from \"#util/result\";\n\nconst letterValue = (ch: string): number => {\n const code = ch.charCodeAt(0);\n if (code >= 65 && code <= 90) return code - 64;\n return -1;\n};\n\nconst WEIGHTS = [\n 2, 1, 2, 5, 7, 1, 2, 1, 2, 1, 2, 1,\n] as const;\n\nconst compact = (value: string): string =>\n clean(value, \" -/\").toUpperCase();\n\nconst computeCheck = (v: string): number => {\n const digits: number[] = [];\n\n for (let i = 0; i < 8; i++) {\n digits.push(Number(v[i]));\n }\n\n // SAFETY: caller guarantees v.length === 12.\n // eslint-disable-next-line no-non-null-assertion\n const lv = letterValue(v[8]!);\n digits.push(Math.floor(lv / 10));\n digits.push(lv % 10);\n\n digits.push(Number(v[9]));\n digits.push(Number(v[10]));\n\n let sum = 0;\n for (const [i, weight] of WEIGHTS.entries()) {\n // SAFETY: WEIGHTS.length === 12 and we just pushed\n // 12 entries onto `digits`.\n // eslint-disable-next-line no-non-null-assertion\n const product = digits[i]! * weight;\n if (product >= 10) {\n sum += Math.floor(product / 10) + (product % 10);\n } else {\n sum += product;\n }\n }\n\n return sum % 10;\n};\n\nconst validate = (value: string): ValidateResult => {\n const v = compact(value);\n if (v.length !== 12) {\n return err(\n \"INVALID_LENGTH\",\n \"German SVNR must be 12 characters\",\n );\n }\n\n if (!/^\\d{8}[A-Z]\\d{3}$/.test(v)) {\n return err(\n \"INVALID_FORMAT\",\n \"German SVNR must be 8 digits, 1 letter, \" +\n \"3 digits\",\n );\n }\n\n const dd = Number(v.slice(2, 4));\n const mm = Number(v.slice(4, 6));\n const yy = Number(v.slice(6, 8));\n\n const year = resolveTwoDigitYear(yy);\n\n if (!isValidDate(year, mm, dd)) {\n return err(\n \"INVALID_COMPONENT\",\n \"German SVNR contains an invalid birth date\",\n );\n }\n\n const expected = computeCheck(v);\n const actual = Number(v[11]);\n\n if (expected !== actual) {\n return err(\n \"INVALID_CHECKSUM\",\n \"German SVNR check digit mismatch\",\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, 2)} ${v.slice(2, 8)}` +\n ` ${v.slice(8, 9)} ${v.slice(9, 11)}` +\n ` ${v.slice(11)}`\n );\n};\n\n/** Generate a random valid German SVNR. */\nconst generate = (): string => {\n for (;;) {\n const area = String(randomInt(1, 99)).padStart(2, \"0\");\n const dd = String(randomInt(1, 28)).padStart(2, \"0\");\n const mm = String(randomInt(1, 12)).padStart(2, \"0\");\n const yy = String(randomInt(40, 99)).padStart(2, \"0\");\n const letter = String.fromCharCode(\n 65 + randomInt(0, 25),\n );\n const serial = String(randomInt(0, 49)).padStart(\n 2,\n \"0\",\n );\n const partial = area + dd + mm + yy + letter + serial;\n const c = partial + String(computeCheck(partial));\n if (validate(c).valid) return c;\n }\n};\n\n/** German Social Insurance Number. */\nconst svnr: CountryValidator<\"DE\"> = {\n name: \"German Social Insurance Number\",\n localName: \"Sozialversicherungsnummer\",\n abbreviation: \"SVNR\",\n aliases: [\n \"Sozialversicherungsnummer\",\n \"SVNR\",\n \"Versicherungsnummer\",\n ] as const,\n candidatePattern: \"\\\\d{2}\\\\s?\\\\d{6}\\\\s?[A-Z]\\\\s?\\\\d{3}\",\n scope: \"country\",\n country: \"DE\",\n entityType: \"person\",\n description:\n \"German social insurance number encoding \" +\n \"birth date and name initial\",\n sourceUrl:\n \"https://de.wikipedia.org/wiki/\" +\n \"Sozialversicherungsnummer\",\n lengths: [12] as const,\n examples: [\"12010188M011\"] as const,\n compact,\n format,\n validate,\n generate,\n};\n\nexport default svnr;\nexport { compact, format, validate, generate };\n"],"mappings":"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"}
|
package/dist/dk/cpr.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cpr.js","names":[],"sources":["../../src/dk/cpr.ts"],"sourcesContent":["/**\n * CPR (Det Centrale Personregister).\n *\n * Danish personal identification number. 10 digits\n * in format DDMMYY-SSSS. Checksum enforcement was\n * dropped in 2007; only date validation is performed.\n *\n * @see https://cpr.dk/\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 getCentury = (yy: number, s7: number): number => {\n // 7th digit (first of serial) determines century\n if (s7 >= 0 && s7 <= 3) return 1900;\n if (s7 === 4 || s7 === 9) {\n return yy <= 36 ? 2000 : 1900;\n }\n // s7 5..8\n return yy <= 57 ? 2000 : 1800;\n};\n\nconst validate = (value: string): ValidateResult => {\n const v = compact(value);\n if (v.length !== 10) {\n return err(\n \"INVALID_LENGTH\",\n \"CPR must be exactly 10 digits\",\n );\n }\n if (!isdigits(v)) {\n return err(\n \"INVALID_FORMAT\",\n \"CPR must contain only digits\",\n );\n }\n\n const dd = Number(v.slice(0, 2));\n const mm = Number(v.slice(2, 4));\n const yy = Number(v.slice(4, 6));\n const s7 = Number(v[6]);\n\n const century = getCentury(yy, s7);\n const year = century + yy;\n\n if (!isValidDate(year, mm, dd)) {\n return err(\n \"INVALID_COMPONENT\",\n \"CPR contains an invalid date\",\n );\n }\n\n // Reject future birth dates\n if (new Date(year, mm - 1, dd) > new Date()) {\n return err(\n \"INVALID_COMPONENT\",\n \"CPR birth date is in the future\",\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 CPR number.\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 dd = Number(v.slice(0, 2));\n const mm = Number(v.slice(2, 4));\n const yy = Number(v.slice(4, 6));\n const s7 = Number(v[6]);\n const lastDigit = Number(v[9]);\n\n const century = getCentury(yy, s7);\n const year = century + yy;\n\n return {\n birthDate: new Date(year, mm - 1, dd),\n gender: lastDigit % 2 === 0 ? \"female\" : \"male\",\n };\n};\n\n/** Generate a random valid Danish CPR. */\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 serialPrefix = String(randomInt(0, 3));\n return `${day}${month}${yy}${serialPrefix}${randomDigits(3)}`;\n};\n\n/** Danish Personal Identification Number. */\nconst cpr: CountryValidator<\"DK\", ParsedPersonId> = {\n name: \"Danish Personal ID\",\n localName: \"Det Centrale Personregister\",\n abbreviation: \"CPR\",\n aliases: [\"CPR-nummer\", \"personnummer\", \"CPR\"] as const,\n candidatePattern: \"\\\\d{6}-?\\\\d{4}\",\n scope: \"country\",\n country: \"DK\",\n entityType: \"person\",\n sourceUrl: \"https://cpr.dk/\",\n lengths: [10] as const,\n examples: [\"2110625629\"] as const,\n compact,\n format,\n parse,\n validate,\n generate,\n};\n\nexport default cpr;\nexport { compact, format, generate, parse, validate };\n"],"mappings":";;;;;;AAsBA,MAAM,WAAW,UACf,MAAM,OAAO,IAAI;AAEnB,MAAM,cAAc,IAAY,OAAuB;CAErD,IAAI,MAAM,KAAK,MAAM,GAAG,OAAO;CAC/B,IAAI,OAAO,KAAK,OAAO,GACrB,OAAO,MAAM,KAAK,MAAO;CAG3B,OAAO,MAAM,KAAK,MAAO;AAC3B;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;CAGF,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;CAI/B,MAAM,OADU,WAAW,IAFhB,OAAO,EAAE,EAEY,CACb,IAAI;CAEvB,IAAI,CAAC,YAAY,MAAM,IAAI,EAAE,GAC3B,OAAO,IACL,qBACA,8BACF;CAIF,IAAI,IAAI,KAAK,MAAM,KAAK,GAAG,EAAE,oBAAI,IAAI,KAAK,GACxC,OAAO,IACL,qBACA,iCACF;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,CAAC;AACtC;;;;;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;CAC/B,MAAM,KAAK,OAAO,EAAE,EAAE;CACtB,MAAM,YAAY,OAAO,EAAE,EAAE;CAG7B,MAAM,OADU,WAAW,IAAI,EACZ,IAAI;CAEvB,OAAO;EACL,WAAW,IAAI,KAAK,MAAM,KAAK,GAAG,EAAE;EACpC,QAAQ,YAAY,MAAM,IAAI,WAAW;CAC3C;AACF;;AAGA,MAAM,iBAAyB;CAC7B,MAAM,OAAO,UAAU,MAAM,IAAI;CACjC,MAAM,QAAQ,OAAO,UAAU,GAAG,EAAE,CAAC,
|
|
1
|
+
{"version":3,"file":"cpr.js","names":[],"sources":["../../src/dk/cpr.ts"],"sourcesContent":["/**\n * CPR (Det Centrale Personregister).\n *\n * Danish personal identification number. 10 digits\n * in format DDMMYY-SSSS. Checksum enforcement was\n * dropped in 2007; only date validation is performed.\n *\n * @see https://cpr.dk/\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 getCentury = (yy: number, s7: number): number => {\n // 7th digit (first of serial) determines century\n if (s7 >= 0 && s7 <= 3) return 1900;\n if (s7 === 4 || s7 === 9) {\n return yy <= 36 ? 2000 : 1900;\n }\n // s7 5..8\n return yy <= 57 ? 2000 : 1800;\n};\n\nconst validate = (value: string): ValidateResult => {\n const v = compact(value);\n if (v.length !== 10) {\n return err(\n \"INVALID_LENGTH\",\n \"CPR must be exactly 10 digits\",\n );\n }\n if (!isdigits(v)) {\n return err(\n \"INVALID_FORMAT\",\n \"CPR must contain only digits\",\n );\n }\n\n const dd = Number(v.slice(0, 2));\n const mm = Number(v.slice(2, 4));\n const yy = Number(v.slice(4, 6));\n const s7 = Number(v[6]);\n\n const century = getCentury(yy, s7);\n const year = century + yy;\n\n if (!isValidDate(year, mm, dd)) {\n return err(\n \"INVALID_COMPONENT\",\n \"CPR contains an invalid date\",\n );\n }\n\n // Reject future birth dates\n if (new Date(year, mm - 1, dd) > new Date()) {\n return err(\n \"INVALID_COMPONENT\",\n \"CPR birth date is in the future\",\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 CPR number.\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 dd = Number(v.slice(0, 2));\n const mm = Number(v.slice(2, 4));\n const yy = Number(v.slice(4, 6));\n const s7 = Number(v[6]);\n const lastDigit = Number(v[9]);\n\n const century = getCentury(yy, s7);\n const year = century + yy;\n\n return {\n birthDate: new Date(year, mm - 1, dd),\n gender: lastDigit % 2 === 0 ? \"female\" : \"male\",\n };\n};\n\n/** Generate a random valid Danish CPR. */\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 serialPrefix = String(randomInt(0, 3));\n return `${day}${month}${yy}${serialPrefix}${randomDigits(3)}`;\n};\n\n/** Danish Personal Identification Number. */\nconst cpr: CountryValidator<\"DK\", ParsedPersonId> = {\n name: \"Danish Personal ID\",\n localName: \"Det Centrale Personregister\",\n abbreviation: \"CPR\",\n aliases: [\"CPR-nummer\", \"personnummer\", \"CPR\"] as const,\n candidatePattern: \"\\\\d{6}-?\\\\d{4}\",\n scope: \"country\",\n country: \"DK\",\n entityType: \"person\",\n sourceUrl: \"https://cpr.dk/\",\n lengths: [10] as const,\n examples: [\"2110625629\"] as const,\n compact,\n format,\n parse,\n validate,\n generate,\n};\n\nexport default cpr;\nexport { compact, format, generate, parse, validate };\n"],"mappings":";;;;;;AAsBA,MAAM,WAAW,UACf,MAAM,OAAO,IAAI;AAEnB,MAAM,cAAc,IAAY,OAAuB;CAErD,IAAI,MAAM,KAAK,MAAM,GAAG,OAAO;CAC/B,IAAI,OAAO,KAAK,OAAO,GACrB,OAAO,MAAM,KAAK,MAAO;CAG3B,OAAO,MAAM,KAAK,MAAO;AAC3B;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;CAGF,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;CAI/B,MAAM,OADU,WAAW,IAFhB,OAAO,EAAE,EAEY,CACb,IAAI;CAEvB,IAAI,CAAC,YAAY,MAAM,IAAI,EAAE,GAC3B,OAAO,IACL,qBACA,8BACF;CAIF,IAAI,IAAI,KAAK,MAAM,KAAK,GAAG,EAAE,oBAAI,IAAI,KAAK,GACxC,OAAO,IACL,qBACA,iCACF;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,CAAC;AACtC;;;;;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;CAC/B,MAAM,KAAK,OAAO,EAAE,EAAE;CACtB,MAAM,YAAY,OAAO,EAAE,EAAE;CAG7B,MAAM,OADU,WAAW,IAAI,EACZ,IAAI;CAEvB,OAAO;EACL,WAAW,IAAI,KAAK,MAAM,KAAK,GAAG,EAAE;EACpC,QAAQ,YAAY,MAAM,IAAI,WAAW;CAC3C;AACF;;AAGA,MAAM,iBAAyB;CAC7B,MAAM,OAAO,UAAU,MAAM,IAAI;CACjC,MAAM,QAAQ,OAAO,UAAU,GAAG,EAAE,CAAC,CAAC,CAAC,SAAS,GAAG,GAAG;CAItD,OAAO,GAHK,OAAO,UAAU,GAAG,EAAE,CAAC,CAAC,CAAC,SAAS,GAAG,GAGrC,IAAI,QAFL,OAAO,OAAO,GAAG,CAAC,CAAC,SAAS,GAAG,GAEjB,IADJ,OAAO,UAAU,GAAG,CAAC,CACF,IAAI,aAAa,CAAC;AAC5D;;AAGA,MAAM,MAA8C;CAClD,MAAM;CACN,WAAW;CACX,cAAc;CACd,SAAS;EAAC;EAAc;EAAgB;CAAK;CAC7C,kBAAkB;CAClB,OAAO;CACP,SAAS;CACT,YAAY;CACZ,WAAW;CACX,SAAS,CAAC,EAAE;CACZ,UAAU,CAAC,YAAY;CACvB;CACA;CACA;CACA;CACA;AACF"}
|
package/dist/dk/cvr.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cvr.js","names":[],"sources":["../../src/dk/cvr.ts"],"sourcesContent":["/**\n * CVR (Central Business Register number).\n *\n * 8 digits, no leading 0.\n * Weights [2,7,6,5,4,3,2,1], sum % 11 === 0.\n *\n * Same algorithm as the Danish VAT number, but this\n * is the company register identifier.\n *\n * @see https://erhvervsstyrelsen.dk/\n *\n * Denmark dropped mod-11 for CPR in 2007; unconfirmed\n * for CVR.\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 = [2, 7, 6, 5, 4, 3, 2, 1];\n\nconst compact = (value: string): string =>\n clean(value, \" -/.\");\n\nconst validate = (value: string): ValidateResult => {\n const v = compact(value);\n if (v.length !== 8) {\n return err(\n \"INVALID_LENGTH\",\n \"Danish CVR must be 8 digits\",\n );\n }\n if (!isdigits(v)) {\n return err(\n \"INVALID_FORMAT\",\n \"Danish CVR must contain only digits\",\n );\n }\n if (v[0] === \"0\") {\n return err(\n \"INVALID_COMPONENT\",\n \"Danish CVR cannot start with 0\",\n );\n }\n const sum = weightedSum(v, WEIGHTS, 11);\n if (sum !== 0) {\n return err(\n \"INVALID_CHECKSUM\",\n \"Danish CVR check digit mismatch\",\n );\n }\n return { valid: true, compact: v };\n};\n\nconst format = (value: string): string => compact(value);\n\n/** Generate a random valid Danish CVR. */\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/** Danish Central Business Register Number. */\nconst cvr: CountryValidator<\"DK\"> = {\n name: \"Danish Business Register Number\",\n localName: \"CVR-nummer\",\n abbreviation: \"CVR\",\n aliases: [\"CVR-nummer\", \"CVR\"] as const,\n candidatePattern: \"\\\\d{8}\",\n scope: \"country\",\n country: \"DK\",\n entityType: \"company\",\n sourceUrl: \"https://erhvervsstyrelsen.dk/\",\n examples: [\"13585628\"] as const,\n compact,\n format,\n validate,\n generate,\n};\n\nexport default cvr;\nexport { compact, format, validate, generate };\n"],"mappings":";;;;;;AA0BA,MAAM,UAAU;CAAC;CAAG;CAAG;CAAG;CAAG;CAAG;CAAG;CAAG;AAAC;AAEvC,MAAM,WAAW,UACf,MAAM,OAAO,MAAM;AAErB,MAAM,YAAY,UAAkC;CAClD,MAAM,IAAI,QAAQ,KAAK;CACvB,IAAI,EAAE,WAAW,GACf,OAAO,IACL,kBACA,6BACF;CAEF,IAAI,CAAC,SAAS,CAAC,GACb,OAAO,IACL,kBACA,qCACF;CAEF,IAAI,EAAE,OAAO,KACX,OAAO,IACL,qBACA,gCACF;CAGF,IADY,YAAY,GAAG,SAAS,EAC9B,MAAM,GACV,OAAO,IACL,oBACA,iCACF;CAEF,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,IAAI,aAAa,CAAC;EAClD,IAAI,SAAS,CAAC,
|
|
1
|
+
{"version":3,"file":"cvr.js","names":[],"sources":["../../src/dk/cvr.ts"],"sourcesContent":["/**\n * CVR (Central Business Register number).\n *\n * 8 digits, no leading 0.\n * Weights [2,7,6,5,4,3,2,1], sum % 11 === 0.\n *\n * Same algorithm as the Danish VAT number, but this\n * is the company register identifier.\n *\n * @see https://erhvervsstyrelsen.dk/\n *\n * Denmark dropped mod-11 for CPR in 2007; unconfirmed\n * for CVR.\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 = [2, 7, 6, 5, 4, 3, 2, 1];\n\nconst compact = (value: string): string =>\n clean(value, \" -/.\");\n\nconst validate = (value: string): ValidateResult => {\n const v = compact(value);\n if (v.length !== 8) {\n return err(\n \"INVALID_LENGTH\",\n \"Danish CVR must be 8 digits\",\n );\n }\n if (!isdigits(v)) {\n return err(\n \"INVALID_FORMAT\",\n \"Danish CVR must contain only digits\",\n );\n }\n if (v[0] === \"0\") {\n return err(\n \"INVALID_COMPONENT\",\n \"Danish CVR cannot start with 0\",\n );\n }\n const sum = weightedSum(v, WEIGHTS, 11);\n if (sum !== 0) {\n return err(\n \"INVALID_CHECKSUM\",\n \"Danish CVR check digit mismatch\",\n );\n }\n return { valid: true, compact: v };\n};\n\nconst format = (value: string): string => compact(value);\n\n/** Generate a random valid Danish CVR. */\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/** Danish Central Business Register Number. */\nconst cvr: CountryValidator<\"DK\"> = {\n name: \"Danish Business Register Number\",\n localName: \"CVR-nummer\",\n abbreviation: \"CVR\",\n aliases: [\"CVR-nummer\", \"CVR\"] as const,\n candidatePattern: \"\\\\d{8}\",\n scope: \"country\",\n country: \"DK\",\n entityType: \"company\",\n sourceUrl: \"https://erhvervsstyrelsen.dk/\",\n examples: [\"13585628\"] as const,\n compact,\n format,\n validate,\n generate,\n};\n\nexport default cvr;\nexport { compact, format, validate, generate };\n"],"mappings":";;;;;;AA0BA,MAAM,UAAU;CAAC;CAAG;CAAG;CAAG;CAAG;CAAG;CAAG;CAAG;AAAC;AAEvC,MAAM,WAAW,UACf,MAAM,OAAO,MAAM;AAErB,MAAM,YAAY,UAAkC;CAClD,MAAM,IAAI,QAAQ,KAAK;CACvB,IAAI,EAAE,WAAW,GACf,OAAO,IACL,kBACA,6BACF;CAEF,IAAI,CAAC,SAAS,CAAC,GACb,OAAO,IACL,kBACA,qCACF;CAEF,IAAI,EAAE,OAAO,KACX,OAAO,IACL,qBACA,gCACF;CAGF,IADY,YAAY,GAAG,SAAS,EAC9B,MAAM,GACV,OAAO,IACL,oBACA,iCACF;CAEF,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,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,cAAc,KAAK;CAC7B,kBAAkB;CAClB,OAAO;CACP,SAAS;CACT,YAAY;CACZ,WAAW;CACX,UAAU,CAAC,UAAU;CACrB;CACA;CACA;CACA;AACF"}
|
package/dist/dk/vat.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vat.js","names":[],"sources":["../../src/dk/vat.ts"],"sourcesContent":["/**\n * CVR (Danish VAT / business registration number).\n *\n * 8 digits, no leading 0.\n * Weights [2,7,6,5,4,3,2,1], sum % 11 === 0.\n *\n * @see https://www.vatify.eu/denmark-vat-number.html\n * @see https://erhvervsstyrelsen.dk/det-centrale-virksomhedsregister-cvr\n *\n * Denmark dropped mod-11 for CPR in 2007; unconfirmed\n * for CVR.\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 = [2, 7, 6, 5, 4, 3, 2, 1];\n\nconst compact = (value: string): string => {\n let v = clean(value, \" -/.\");\n if (v.startsWith(\"DK\") || v.startsWith(\"dk\")) {\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 \"Danish VAT number must be 8 digits\",\n );\n }\n if (!isdigits(v)) {\n return err(\n \"INVALID_FORMAT\",\n \"Danish VAT number must contain only digits\",\n );\n }\n if (v[0] === \"0\") {\n return err(\n \"INVALID_COMPONENT\",\n \"Danish VAT number cannot start with 0\",\n );\n }\n const sum = weightedSum(v, WEIGHTS, 11);\n if (sum !== 0) {\n return err(\n \"INVALID_CHECKSUM\",\n \"Danish VAT number check digit mismatch\",\n );\n }\n return { valid: true, compact: v };\n};\n\nconst format = (value: string): string =>\n `DK${compact(value)}`;\n\n/** Generate a random valid Danish 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/** Danish VAT Number. */\nconst vat: CountryValidator<\"DK\"> = {\n name: \"Danish VAT Number\",\n localName: \"Momsregistreringsnummer\",\n abbreviation: \"CVR\",\n aliases: [\"momsnummer\", \"SE-nummer\"] as const,\n candidatePattern: \"DK\\\\d{8}\",\n scope: \"country\",\n country: \"DK\",\n entityType: \"company\",\n sourceUrl: \"https://erhvervsstyrelsen.dk/\",\n lengths: [8] as const,\n examples: [\"13585628\"] as const,\n compact,\n format,\n validate,\n generate,\n};\n\nexport default vat;\nexport { compact, format, validate, generate };\n"],"mappings":";;;;;;AAwBA,MAAM,UAAU;CAAC;CAAG;CAAG;CAAG;CAAG;CAAG;CAAG;CAAG;AAAC;AAEvC,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,oCACF;CAEF,IAAI,CAAC,SAAS,CAAC,GACb,OAAO,IACL,kBACA,4CACF;CAEF,IAAI,EAAE,OAAO,KACX,OAAO,IACL,qBACA,uCACF;CAGF,IADY,YAAY,GAAG,SAAS,EAC9B,MAAM,GACV,OAAO,IACL,oBACA,wCACF;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/dk/vat.ts"],"sourcesContent":["/**\n * CVR (Danish VAT / business registration number).\n *\n * 8 digits, no leading 0.\n * Weights [2,7,6,5,4,3,2,1], sum % 11 === 0.\n *\n * @see https://www.vatify.eu/denmark-vat-number.html\n * @see https://erhvervsstyrelsen.dk/det-centrale-virksomhedsregister-cvr\n *\n * Denmark dropped mod-11 for CPR in 2007; unconfirmed\n * for CVR.\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 = [2, 7, 6, 5, 4, 3, 2, 1];\n\nconst compact = (value: string): string => {\n let v = clean(value, \" -/.\");\n if (v.startsWith(\"DK\") || v.startsWith(\"dk\")) {\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 \"Danish VAT number must be 8 digits\",\n );\n }\n if (!isdigits(v)) {\n return err(\n \"INVALID_FORMAT\",\n \"Danish VAT number must contain only digits\",\n );\n }\n if (v[0] === \"0\") {\n return err(\n \"INVALID_COMPONENT\",\n \"Danish VAT number cannot start with 0\",\n );\n }\n const sum = weightedSum(v, WEIGHTS, 11);\n if (sum !== 0) {\n return err(\n \"INVALID_CHECKSUM\",\n \"Danish VAT number check digit mismatch\",\n );\n }\n return { valid: true, compact: v };\n};\n\nconst format = (value: string): string =>\n `DK${compact(value)}`;\n\n/** Generate a random valid Danish 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/** Danish VAT Number. */\nconst vat: CountryValidator<\"DK\"> = {\n name: \"Danish VAT Number\",\n localName: \"Momsregistreringsnummer\",\n abbreviation: \"CVR\",\n aliases: [\"momsnummer\", \"SE-nummer\"] as const,\n candidatePattern: \"DK\\\\d{8}\",\n scope: \"country\",\n country: \"DK\",\n entityType: \"company\",\n sourceUrl: \"https://erhvervsstyrelsen.dk/\",\n lengths: [8] as const,\n examples: [\"13585628\"] as const,\n compact,\n format,\n validate,\n generate,\n};\n\nexport default vat;\nexport { compact, format, validate, generate };\n"],"mappings":";;;;;;AAwBA,MAAM,UAAU;CAAC;CAAG;CAAG;CAAG;CAAG;CAAG;CAAG;CAAG;AAAC;AAEvC,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,oCACF;CAEF,IAAI,CAAC,SAAS,CAAC,GACb,OAAO,IACL,kBACA,4CACF;CAEF,IAAI,EAAE,OAAO,KACX,OAAO,IACL,qBACA,uCACF;CAGF,IADY,YAAY,GAAG,SAAS,EAC9B,MAAM,GACV,OAAO,IACL,oBACA,wCACF;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,cAAc,WAAW;CACnC,kBAAkB;CAClB,OAAO;CACP,SAAS;CACT,YAAY;CACZ,WAAW;CACX,SAAS,CAAC,CAAC;CACX,UAAU,CAAC,UAAU;CACrB;CACA;CACA;CACA;AACF"}
|
package/dist/do/rnc.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rnc.js","names":[],"sources":["../../src/do/rnc.ts"],"sourcesContent":["/**\n * RNC (Registro Nacional del Contribuyente) /\n * Cédula de Identidad.\n *\n * Dominican Republic tax identification number.\n * Companies use a 9-digit RNC with a check digit\n * computed via weighted sum mod 11. Individuals\n * use an 11-digit Cédula validated with the Luhn\n * algorithm.\n *\n * Format: ######### (9 digits, company RNC)\n * ########### (11 digits, individual Cédula)\n *\n * @see https://dgii.gov.do/\n */\n\nimport type {\n ValidateResult,\n CountryValidator,\n} from \"../types\";\n\nimport { luhnValidate } from \"#checksums/luhn\";\nimport { clean } from \"#util/clean\";\nimport { randomDigits } from \"#util/generate\";\nimport { err } from \"#util/result\";\nimport { isdigits } from \"#util/strings\";\n\n/**\n * Weights for the 9-digit RNC check digit.\n * Applied to positions 0-7; check digit is at\n * position 8.\n */\nconst RNC_WEIGHTS = [7, 9, 8, 6, 5, 4, 3, 2] as const;\n\nconst compact = (value: string): string =>\n clean(value, \" -.\").trim();\n\n/**\n * RNC check digit: weighted sum mod 11, then\n * ((10 - remainder) % 9) + 1.\n */\nconst calcRncCheckDigit = (value: string): string => {\n let sum = 0;\n for (const [i, weight] of RNC_WEIGHTS.entries()) {\n sum += Number(value[i]) * weight;\n }\n const remainder = sum % 11;\n return String(((10 - remainder) % 9) + 1);\n};\n\nconst validate = (value: string): ValidateResult => {\n const v = compact(value);\n\n if (v.length !== 9 && v.length !== 11) {\n return err(\n \"INVALID_LENGTH\",\n \"Must be 9 digits (RNC) or 11 digits (Cédula)\",\n );\n }\n\n if (!isdigits(v)) {\n return err(\n \"INVALID_FORMAT\",\n \"Must contain only digits\",\n );\n }\n\n if (v.length === 9) {\n const expected = calcRncCheckDigit(v);\n if (v.at(-1) !== expected) {\n return err(\n \"INVALID_CHECKSUM\",\n \"RNC check digit does not match\",\n );\n }\n } else {\n // 11-digit Cédula uses Luhn checksum\n if (!luhnValidate(v)) {\n return err(\n \"INVALID_CHECKSUM\",\n \"Cédula Luhn checksum does not match\",\n );\n }\n }\n\n return { valid: true, compact: v };\n};\n\nconst format = (value: string): string => {\n const v = compact(value);\n if (v.length === 9) {\n return `${v[0]}-${v.slice(1, 3)}-${v.slice(3, 8)}-${v.slice(8)}`;\n }\n return `${v.slice(0, 3)}-${v.slice(3, 10)}-${v.slice(10)}`;\n};\n\n/** Generate a random valid 9-digit RNC. */\nconst generate = (): string => {\n const payload = randomDigits(8);\n return payload + calcRncCheckDigit(payload);\n};\n\n/**\n * Dominican Republic RNC / Cédula.\n *\n * Examples sourced from python-stdnum test suite.\n */\nconst rnc: CountryValidator<\"DO\"> = {\n name: \"Dominican Republic Tax ID\",\n localName: \"Registro Nacional del Contribuyente\",\n abbreviation: \"RNC\",\n aliases: [\n \"RNC\",\n \"Registro Nacional del Contribuyente\",\n ] as const,\n candidatePattern: \"\\\\d{9}\",\n scope: \"country\",\n country: \"DO\",\n entityType: \"any\",\n lengths: [9, 11] as const,\n examples: [\"131098193\", \"00113918205\"] as const,\n compact,\n format,\n validate,\n sourceUrl: \"https://dgii.gov.do/\",\n generate,\n};\n\nexport default rnc;\nexport { compact, format, validate, generate };\n"],"mappings":";;;;;;;;;;;AAgCA,MAAM,cAAc;CAAC;CAAG;CAAG;CAAG;CAAG;CAAG;CAAG;CAAG;AAAC;AAE3C,MAAM,WAAW,UACf,MAAM,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"rnc.js","names":[],"sources":["../../src/do/rnc.ts"],"sourcesContent":["/**\n * RNC (Registro Nacional del Contribuyente) /\n * Cédula de Identidad.\n *\n * Dominican Republic tax identification number.\n * Companies use a 9-digit RNC with a check digit\n * computed via weighted sum mod 11. Individuals\n * use an 11-digit Cédula validated with the Luhn\n * algorithm.\n *\n * Format: ######### (9 digits, company RNC)\n * ########### (11 digits, individual Cédula)\n *\n * @see https://dgii.gov.do/\n */\n\nimport type {\n ValidateResult,\n CountryValidator,\n} from \"../types\";\n\nimport { luhnValidate } from \"#checksums/luhn\";\nimport { clean } from \"#util/clean\";\nimport { randomDigits } from \"#util/generate\";\nimport { err } from \"#util/result\";\nimport { isdigits } from \"#util/strings\";\n\n/**\n * Weights for the 9-digit RNC check digit.\n * Applied to positions 0-7; check digit is at\n * position 8.\n */\nconst RNC_WEIGHTS = [7, 9, 8, 6, 5, 4, 3, 2] as const;\n\nconst compact = (value: string): string =>\n clean(value, \" -.\").trim();\n\n/**\n * RNC check digit: weighted sum mod 11, then\n * ((10 - remainder) % 9) + 1.\n */\nconst calcRncCheckDigit = (value: string): string => {\n let sum = 0;\n for (const [i, weight] of RNC_WEIGHTS.entries()) {\n sum += Number(value[i]) * weight;\n }\n const remainder = sum % 11;\n return String(((10 - remainder) % 9) + 1);\n};\n\nconst validate = (value: string): ValidateResult => {\n const v = compact(value);\n\n if (v.length !== 9 && v.length !== 11) {\n return err(\n \"INVALID_LENGTH\",\n \"Must be 9 digits (RNC) or 11 digits (Cédula)\",\n );\n }\n\n if (!isdigits(v)) {\n return err(\n \"INVALID_FORMAT\",\n \"Must contain only digits\",\n );\n }\n\n if (v.length === 9) {\n const expected = calcRncCheckDigit(v);\n if (v.at(-1) !== expected) {\n return err(\n \"INVALID_CHECKSUM\",\n \"RNC check digit does not match\",\n );\n }\n } else {\n // 11-digit Cédula uses Luhn checksum\n if (!luhnValidate(v)) {\n return err(\n \"INVALID_CHECKSUM\",\n \"Cédula Luhn checksum does not match\",\n );\n }\n }\n\n return { valid: true, compact: v };\n};\n\nconst format = (value: string): string => {\n const v = compact(value);\n if (v.length === 9) {\n return `${v[0]}-${v.slice(1, 3)}-${v.slice(3, 8)}-${v.slice(8)}`;\n }\n return `${v.slice(0, 3)}-${v.slice(3, 10)}-${v.slice(10)}`;\n};\n\n/** Generate a random valid 9-digit RNC. */\nconst generate = (): string => {\n const payload = randomDigits(8);\n return payload + calcRncCheckDigit(payload);\n};\n\n/**\n * Dominican Republic RNC / Cédula.\n *\n * Examples sourced from python-stdnum test suite.\n */\nconst rnc: CountryValidator<\"DO\"> = {\n name: \"Dominican Republic Tax ID\",\n localName: \"Registro Nacional del Contribuyente\",\n abbreviation: \"RNC\",\n aliases: [\n \"RNC\",\n \"Registro Nacional del Contribuyente\",\n ] as const,\n candidatePattern: \"\\\\d{9}\",\n scope: \"country\",\n country: \"DO\",\n entityType: \"any\",\n lengths: [9, 11] as const,\n examples: [\"131098193\", \"00113918205\"] as const,\n compact,\n format,\n validate,\n sourceUrl: \"https://dgii.gov.do/\",\n generate,\n};\n\nexport default rnc;\nexport { compact, format, validate, generate };\n"],"mappings":";;;;;;;;;;;AAgCA,MAAM,cAAc;CAAC;CAAG;CAAG;CAAG;CAAG;CAAG;CAAG;CAAG;AAAC;AAE3C,MAAM,WAAW,UACf,MAAM,OAAO,KAAK,CAAC,CAAC,KAAK;;;;;AAM3B,MAAM,qBAAqB,UAA0B;CACnD,IAAI,MAAM;CACV,KAAK,MAAM,CAAC,GAAG,WAAW,YAAY,QAAQ,GAC5C,OAAO,OAAO,MAAM,EAAE,IAAI;CAE5B,MAAM,YAAY,MAAM;CACxB,OAAO,QAAS,KAAK,aAAa,IAAK,CAAC;AAC1C;AAEA,MAAM,YAAY,UAAkC;CAClD,MAAM,IAAI,QAAQ,KAAK;CAEvB,IAAI,EAAE,WAAW,KAAK,EAAE,WAAW,IACjC,OAAO,IACL,kBACA,8CACF;CAGF,IAAI,CAAC,SAAS,CAAC,GACb,OAAO,IACL,kBACA,0BACF;CAGF,IAAI,EAAE,WAAW,GAAG;EAClB,MAAM,WAAW,kBAAkB,CAAC;EACpC,IAAI,EAAE,GAAG,EAAE,MAAM,UACf,OAAO,IACL,oBACA,gCACF;CAEJ,OAEE,IAAI,CAAC,aAAa,CAAC,GACjB,OAAO,IACL,oBACA,qCACF;CAIJ,OAAO;EAAE,OAAO;EAAM,SAAS;CAAE;AACnC;AAEA,MAAM,UAAU,UAA0B;CACxC,MAAM,IAAI,QAAQ,KAAK;CACvB,IAAI,EAAE,WAAW,GACf,OAAO,GAAG,EAAE,GAAG,GAAG,EAAE,MAAM,GAAG,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC;CAE/D,OAAO,GAAG,EAAE,MAAM,GAAG,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE;AACzD;;AAGA,MAAM,iBAAyB;CAC7B,MAAM,UAAU,aAAa,CAAC;CAC9B,OAAO,UAAU,kBAAkB,OAAO;AAC5C;;;;;;AAOA,MAAM,MAA8B;CAClC,MAAM;CACN,WAAW;CACX,cAAc;CACd,SAAS,CACP,OACA,qCACF;CACA,kBAAkB;CAClB,OAAO;CACP,SAAS;CACT,YAAY;CACZ,SAAS,CAAC,GAAG,EAAE;CACf,UAAU,CAAC,aAAa,aAAa;CACrC;CACA;CACA;CACA,WAAW;CACX;AACF"}
|
package/dist/ec/ruc.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ruc.js","names":[],"sources":["../../src/ec/ruc.ts"],"sourcesContent":["/**\n * RUC (Registro Único de Contribuyentes).\n *\n * Ecuadorian tax identification number. 13 digits:\n * - Digits 1-2: province code (01-24, 30, or 50)\n * - Digit 3: type indicator\n * - 0-5: natural person (CI + establishment)\n * - 6: public entity (or natural person fallback)\n * - 9: juridical entity (public checksum first,\n * then juridical fallback)\n *\n * Natural persons use a Luhn-like mod-10 checksum on\n * the first 10 digits. Public entities use a mod-11\n * checksum on the first 9 digits. Private companies\n * use a mod-11 checksum on the first 10 digits.\n *\n * @see https://www.sri.gob.ec/\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, \" -\");\n\n/** CI mod-10 checksum: weights [2,1,2,1,...], fold\n * products > 9 by subtracting 9, sum mod 10 == 0. */\nconst ciChecksum = (digits: string): number => {\n let sum = 0;\n for (let i = 0; i < digits.length; i++) {\n const weight = i % 2 === 0 ? 2 : 1;\n let product = Number(digits[i]) * weight;\n if (product > 9) product -= 9;\n sum += product;\n }\n return sum % 10;\n};\n\n/** Weighted mod-11 checksum. */\nconst mod11Checksum = (\n digits: string,\n weights: readonly number[],\n): number => {\n let sum = 0;\n for (const [i, weight] of weights.entries()) {\n sum += Number(digits[i]) * weight;\n }\n return sum % 11;\n};\n\nconst PUBLIC_WEIGHTS = [3, 2, 7, 6, 5, 4, 3, 2, 1];\nconst JURIDICAL_WEIGHTS = [4, 3, 2, 7, 6, 5, 4, 3, 2, 1];\n\nconst isValidProvince = (v: string): boolean => {\n const province = v.slice(0, 2);\n return (\n (province >= \"01\" && province <= \"24\") ||\n province === \"30\" ||\n province === \"50\"\n );\n};\n\nconst validateNatural = (\n v: string,\n): ValidateResult | null => {\n if (v.slice(-3) === \"000\") return null;\n if (ciChecksum(v.slice(0, 10)) !== 0) return null;\n return { valid: true, compact: v };\n};\n\nconst validatePublic = (\n v: string,\n): ValidateResult | null => {\n if (v.slice(-4) === \"0000\") return null;\n if (mod11Checksum(v, PUBLIC_WEIGHTS) !== 0) return null;\n return { valid: true, compact: v };\n};\n\nconst validateJuridical = (\n v: string,\n): ValidateResult | null => {\n if (v.slice(-3) === \"000\") return null;\n if (mod11Checksum(v, JURIDICAL_WEIGHTS) !== 0) {\n return null;\n }\n return { valid: true, compact: v };\n};\n\nconst validate = (value: string): ValidateResult => {\n const v = compact(value);\n if (v.length !== 13) {\n return err(\n \"INVALID_LENGTH\",\n \"RUC must be exactly 13 digits\",\n );\n }\n if (!isdigits(v)) {\n return err(\n \"INVALID_FORMAT\",\n \"RUC must contain only digits\",\n );\n }\n if (!isValidProvince(v)) {\n return err(\n \"INVALID_COMPONENT\",\n \"RUC province code is invalid\",\n );\n }\n\n const typeDigit = Number(v[2]);\n\n if (typeDigit <= 5) {\n // Natural person\n const result = validateNatural(v);\n if (result) return result;\n return err(\n \"INVALID_CHECKSUM\",\n \"RUC check digit does not match\",\n );\n }\n\n if (typeDigit === 6) {\n // Public entity; fall back to natural person\n const pub = validatePublic(v);\n if (pub) return pub;\n const nat = validateNatural(v);\n if (nat) return nat;\n return err(\n \"INVALID_CHECKSUM\",\n \"RUC check digit does not match\",\n );\n }\n\n if (typeDigit === 9) {\n // Juridical RUC; try public checksum first, fall\n // back to juridical (matches python-stdnum).\n const pub = validatePublic(v);\n if (pub) return pub;\n const jur = validateJuridical(v);\n if (jur) return jur;\n return err(\n \"INVALID_CHECKSUM\",\n \"RUC check digit does not match\",\n );\n }\n\n return err(\n \"INVALID_COMPONENT\",\n \"RUC type digit is invalid\",\n );\n};\n\nconst format = (value: string): string => compact(value);\n\n/** Generate a random valid Ecuadorian RUC. */\nconst generate = (): string => {\n for (;;) {\n const province = String(randomInt(1, 24)).padStart(\n 2,\n \"0\",\n );\n const c = province + randomDigits(11);\n if (validate(c).valid) return c;\n }\n};\n\n/** Ecuadorian tax identification number. */\nconst ruc: CountryValidator<\"EC\"> = {\n name: \"Registro Único de Contribuyentes\",\n localName: \"Registro Único de Contribuyentes\",\n abbreviation: \"RUC\",\n aliases: [\n \"RUC\",\n \"Registro Único de Contribuyentes\",\n ] as const,\n candidatePattern: \"\\\\d{13}\",\n scope: \"country\",\n country: \"EC\",\n entityType: \"any\",\n description:\n \"13-digit tax identification number issued by the SRI\",\n sourceUrl: \"https://www.sri.gob.ec/\",\n lengths: [13] as const,\n examples: [\"1792060346001\", \"1790011674001\"] as const,\n compact,\n format,\n validate,\n generate,\n};\n\nexport default ruc;\nexport { compact, format, validate, generate };\n"],"mappings":";;;;;AA6BA,MAAM,WAAW,UACf,MAAM,OAAO,IAAI;;;AAInB,MAAM,cAAc,WAA2B;CAC7C,IAAI,MAAM;CACV,KAAK,IAAI,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;EACtC,MAAM,SAAS,IAAI,MAAM,IAAI,IAAI;EACjC,IAAI,UAAU,OAAO,OAAO,EAAE,IAAI;EAClC,IAAI,UAAU,GAAG,WAAW;EAC5B,OAAO;CACT;CACA,OAAO,MAAM;AACf;;AAGA,MAAM,iBACJ,QACA,YACW;CACX,IAAI,MAAM;CACV,KAAK,MAAM,CAAC,GAAG,WAAW,QAAQ,QAAQ,GACxC,OAAO,OAAO,OAAO,EAAE,IAAI;CAE7B,OAAO,MAAM;AACf;AAEA,MAAM,iBAAiB;CAAC;CAAG;CAAG;CAAG;CAAG;CAAG;CAAG;CAAG;CAAG;AAAC;AACjD,MAAM,oBAAoB;CAAC;CAAG;CAAG;CAAG;CAAG;CAAG;CAAG;CAAG;CAAG;CAAG;AAAC;AAEvD,MAAM,mBAAmB,MAAuB;CAC9C,MAAM,WAAW,EAAE,MAAM,GAAG,CAAC;CAC7B,OACG,YAAY,QAAQ,YAAY,QACjC,aAAa,QACb,aAAa;AAEjB;AAEA,MAAM,mBACJ,MAC0B;CAC1B,IAAI,EAAE,MAAM,EAAE,MAAM,OAAO,OAAO;CAClC,IAAI,WAAW,EAAE,MAAM,GAAG,EAAE,CAAC,MAAM,GAAG,OAAO;CAC7C,OAAO;EAAE,OAAO;EAAM,SAAS;CAAE;AACnC;AAEA,MAAM,kBACJ,MAC0B;CAC1B,IAAI,EAAE,MAAM,EAAE,MAAM,QAAQ,OAAO;CACnC,IAAI,cAAc,GAAG,cAAc,MAAM,GAAG,OAAO;CACnD,OAAO;EAAE,OAAO;EAAM,SAAS;CAAE;AACnC;AAEA,MAAM,qBACJ,MAC0B;CAC1B,IAAI,EAAE,MAAM,EAAE,MAAM,OAAO,OAAO;CAClC,IAAI,cAAc,GAAG,iBAAiB,MAAM,GAC1C,OAAO;CAET,OAAO;EAAE,OAAO;EAAM,SAAS;CAAE;AACnC;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,CAAC,gBAAgB,CAAC,GACpB,OAAO,IACL,qBACA,8BACF;CAGF,MAAM,YAAY,OAAO,EAAE,EAAE;CAE7B,IAAI,aAAa,GAAG;EAElB,MAAM,SAAS,gBAAgB,CAAC;EAChC,IAAI,QAAQ,OAAO;EACnB,OAAO,IACL,oBACA,gCACF;CACF;CAEA,IAAI,cAAc,GAAG;EAEnB,MAAM,MAAM,eAAe,CAAC;EAC5B,IAAI,KAAK,OAAO;EAChB,MAAM,MAAM,gBAAgB,CAAC;EAC7B,IAAI,KAAK,OAAO;EAChB,OAAO,IACL,oBACA,gCACF;CACF;CAEA,IAAI,cAAc,GAAG;EAGnB,MAAM,MAAM,eAAe,CAAC;EAC5B,IAAI,KAAK,OAAO;EAChB,MAAM,MAAM,kBAAkB,CAAC;EAC/B,IAAI,KAAK,OAAO;EAChB,OAAO,IACL,oBACA,gCACF;CACF;CAEA,OAAO,IACL,qBACA,2BACF;AACF;AAEA,MAAM,UAAU,UAA0B,QAAQ,KAAK;;AAGvD,MAAM,iBAAyB;CAC7B,SAAS;EAKP,MAAM,IAJW,OAAO,UAAU,GAAG,EAAE,CAAC,
|
|
1
|
+
{"version":3,"file":"ruc.js","names":[],"sources":["../../src/ec/ruc.ts"],"sourcesContent":["/**\n * RUC (Registro Único de Contribuyentes).\n *\n * Ecuadorian tax identification number. 13 digits:\n * - Digits 1-2: province code (01-24, 30, or 50)\n * - Digit 3: type indicator\n * - 0-5: natural person (CI + establishment)\n * - 6: public entity (or natural person fallback)\n * - 9: juridical entity (public checksum first,\n * then juridical fallback)\n *\n * Natural persons use a Luhn-like mod-10 checksum on\n * the first 10 digits. Public entities use a mod-11\n * checksum on the first 9 digits. Private companies\n * use a mod-11 checksum on the first 10 digits.\n *\n * @see https://www.sri.gob.ec/\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, \" -\");\n\n/** CI mod-10 checksum: weights [2,1,2,1,...], fold\n * products > 9 by subtracting 9, sum mod 10 == 0. */\nconst ciChecksum = (digits: string): number => {\n let sum = 0;\n for (let i = 0; i < digits.length; i++) {\n const weight = i % 2 === 0 ? 2 : 1;\n let product = Number(digits[i]) * weight;\n if (product > 9) product -= 9;\n sum += product;\n }\n return sum % 10;\n};\n\n/** Weighted mod-11 checksum. */\nconst mod11Checksum = (\n digits: string,\n weights: readonly number[],\n): number => {\n let sum = 0;\n for (const [i, weight] of weights.entries()) {\n sum += Number(digits[i]) * weight;\n }\n return sum % 11;\n};\n\nconst PUBLIC_WEIGHTS = [3, 2, 7, 6, 5, 4, 3, 2, 1];\nconst JURIDICAL_WEIGHTS = [4, 3, 2, 7, 6, 5, 4, 3, 2, 1];\n\nconst isValidProvince = (v: string): boolean => {\n const province = v.slice(0, 2);\n return (\n (province >= \"01\" && province <= \"24\") ||\n province === \"30\" ||\n province === \"50\"\n );\n};\n\nconst validateNatural = (\n v: string,\n): ValidateResult | null => {\n if (v.slice(-3) === \"000\") return null;\n if (ciChecksum(v.slice(0, 10)) !== 0) return null;\n return { valid: true, compact: v };\n};\n\nconst validatePublic = (\n v: string,\n): ValidateResult | null => {\n if (v.slice(-4) === \"0000\") return null;\n if (mod11Checksum(v, PUBLIC_WEIGHTS) !== 0) return null;\n return { valid: true, compact: v };\n};\n\nconst validateJuridical = (\n v: string,\n): ValidateResult | null => {\n if (v.slice(-3) === \"000\") return null;\n if (mod11Checksum(v, JURIDICAL_WEIGHTS) !== 0) {\n return null;\n }\n return { valid: true, compact: v };\n};\n\nconst validate = (value: string): ValidateResult => {\n const v = compact(value);\n if (v.length !== 13) {\n return err(\n \"INVALID_LENGTH\",\n \"RUC must be exactly 13 digits\",\n );\n }\n if (!isdigits(v)) {\n return err(\n \"INVALID_FORMAT\",\n \"RUC must contain only digits\",\n );\n }\n if (!isValidProvince(v)) {\n return err(\n \"INVALID_COMPONENT\",\n \"RUC province code is invalid\",\n );\n }\n\n const typeDigit = Number(v[2]);\n\n if (typeDigit <= 5) {\n // Natural person\n const result = validateNatural(v);\n if (result) return result;\n return err(\n \"INVALID_CHECKSUM\",\n \"RUC check digit does not match\",\n );\n }\n\n if (typeDigit === 6) {\n // Public entity; fall back to natural person\n const pub = validatePublic(v);\n if (pub) return pub;\n const nat = validateNatural(v);\n if (nat) return nat;\n return err(\n \"INVALID_CHECKSUM\",\n \"RUC check digit does not match\",\n );\n }\n\n if (typeDigit === 9) {\n // Juridical RUC; try public checksum first, fall\n // back to juridical (matches python-stdnum).\n const pub = validatePublic(v);\n if (pub) return pub;\n const jur = validateJuridical(v);\n if (jur) return jur;\n return err(\n \"INVALID_CHECKSUM\",\n \"RUC check digit does not match\",\n );\n }\n\n return err(\n \"INVALID_COMPONENT\",\n \"RUC type digit is invalid\",\n );\n};\n\nconst format = (value: string): string => compact(value);\n\n/** Generate a random valid Ecuadorian RUC. */\nconst generate = (): string => {\n for (;;) {\n const province = String(randomInt(1, 24)).padStart(\n 2,\n \"0\",\n );\n const c = province + randomDigits(11);\n if (validate(c).valid) return c;\n }\n};\n\n/** Ecuadorian tax identification number. */\nconst ruc: CountryValidator<\"EC\"> = {\n name: \"Registro Único de Contribuyentes\",\n localName: \"Registro Único de Contribuyentes\",\n abbreviation: \"RUC\",\n aliases: [\n \"RUC\",\n \"Registro Único de Contribuyentes\",\n ] as const,\n candidatePattern: \"\\\\d{13}\",\n scope: \"country\",\n country: \"EC\",\n entityType: \"any\",\n description:\n \"13-digit tax identification number issued by the SRI\",\n sourceUrl: \"https://www.sri.gob.ec/\",\n lengths: [13] as const,\n examples: [\"1792060346001\", \"1790011674001\"] as const,\n compact,\n format,\n validate,\n generate,\n};\n\nexport default ruc;\nexport { compact, format, validate, generate };\n"],"mappings":"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"}
|
package/dist/ee/ik.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ik.js","names":[],"sources":["../../src/ee/ik.ts"],"sourcesContent":["/**\n * Isikukood (IK).\n *\n * Estonian personal identification code. 11 digits:\n * gender/century digit, YYMMDD, 3-digit serial, and\n * a check digit computed with two-pass weighted sums.\n *\n * Canonical sources:\n * - Estonian legislation for the personal code format\n * - OECD Estonia TIN sheet summarising the same\n * first-digit ranges for cross-border tax use\n *\n * These sources are why we restrict the first digit\n * to 1-6 rather than accepting speculative 7/8 forms.\n *\n * @see https://www.riigiteataja.ee/en/eli/512012015003/consolide\n * @see https://www.oecd.org/tax/automatic-exchange/crs-implementation-and-assistance/tax-identification-numbers/Estonia-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 { randomInt } from \"#util/generate\";\nimport { err } from \"#util/result\";\nimport { isdigits } from \"#util/strings\";\n\nconst WEIGHTS_1 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 1] as const;\nconst WEIGHTS_2 = [3, 4, 5, 6, 7, 8, 9, 1, 2, 3] 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, \" -\");\n\n/**\n * Compute the two-pass check digit used by both\n * Estonian IK and Lithuanian Asmens kodas.\n */\nexport const twoPassCheck = (digits: string): number => {\n let remainder = weightedSum(digits, WEIGHTS_1, 11);\n if (remainder !== 10) return remainder;\n remainder = weightedSum(digits, WEIGHTS_2, 11);\n return remainder === 10 ? 0 : remainder;\n};\n\nconst validate = (value: string): ValidateResult => {\n const v = compact(value);\n if (v.length !== 11) {\n return err(\n \"INVALID_LENGTH\",\n \"Isikukood must be exactly 11 digits\",\n );\n }\n if (!isdigits(v)) {\n return err(\n \"INVALID_FORMAT\",\n \"Isikukood must contain only digits\",\n );\n }\n\n const g = Number(v[0]);\n // Official Estonian structure uses 1-6:\n // 1-2 = 1800s, 3-4 = 1900s, 5-6 = 2000s.\n if (g < 1 || g > 6) {\n return err(\n \"INVALID_COMPONENT\",\n \"Isikukood gender/century digit is invalid\",\n );\n }\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 \"Isikukood contains an invalid date\",\n );\n }\n\n const check = twoPassCheck(v.slice(0, 10));\n if (check !== Number(v[10])) {\n return err(\n \"INVALID_CHECKSUM\",\n \"Isikukood 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 an Isikukood.\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 g = Number(v[0]);\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 return {\n birthDate: new Date(year, mm - 1, dd),\n gender: g % 2 === 1 ? \"male\" : \"female\",\n };\n};\n\n/** Generate a random valid Estonian IK. */\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/** Estonian Personal Identification Code. */\nconst ik: CountryValidator<\"EE\", ParsedPersonId> = {\n name: \"Estonian Personal ID\",\n localName: \"Isikukood\",\n abbreviation: \"IK\",\n aliases: [\"isikukood\", \"IK\"] as const,\n candidatePattern: \"[1-6]\\\\d{10}\",\n scope: \"country\",\n country: \"EE\",\n entityType: \"person\",\n sourceUrl:\n \"https://www.riigiteataja.ee/en/eli/512012015003/consolide\",\n examples: [\"36805280109\"] as const,\n compact,\n format,\n parse,\n validate,\n generate,\n};\n\nexport default ik;\nexport { compact, format, parse, validate, generate };\n"],"mappings":";;;;;;;AAgCA,MAAM,YAAY;CAAC;CAAG;CAAG;CAAG;CAAG;CAAG;CAAG;CAAG;CAAG;CAAG;AAAC;AAC/C,MAAM,YAAY;CAAC;CAAG;CAAG;CAAG;CAAG;CAAG;CAAG;CAAG;CAAG;CAAG;AAAC;AAE/C,MAAM,aAAqC;CACzC,GAAG;CACH,GAAG;CACH,GAAG;CACH,GAAG;CACH,GAAG;CACH,GAAG;AACL;AAEA,MAAM,WAAW,UACf,MAAM,OAAO,IAAI;;;;;AAMnB,MAAa,gBAAgB,WAA2B;CACtD,IAAI,YAAY,YAAY,QAAQ,WAAW,EAAE;CACjD,IAAI,cAAc,IAAI,OAAO;CAC7B,YAAY,YAAY,QAAQ,WAAW,EAAE;CAC7C,OAAO,cAAc,KAAK,IAAI;AAChC;AAEA,MAAM,YAAY,UAAkC;CAClD,MAAM,IAAI,QAAQ,KAAK;CACvB,IAAI,EAAE,WAAW,IACf,OAAO,IACL,kBACA,qCACF;CAEF,IAAI,CAAC,SAAS,CAAC,GACb,OAAO,IACL,kBACA,oCACF;CAGF,MAAM,IAAI,OAAO,EAAE,EAAE;CAGrB,IAAI,IAAI,KAAK,IAAI,GACf,OAAO,IACL,qBACA,2CACF;CAGF,MAAM,UAAU,WAAW,MAAM;CACjC,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,UAAU,IAEA,IAAI,EAAE,GAC3B,OAAO,IACL,qBACA,oCACF;CAIF,IADc,aAAa,EAAE,MAAM,GAAG,EAAE,CAChC,MAAM,OAAO,EAAE,GAAG,GACxB,OAAO,IACL,oBACA,sCACF;CAGF,OAAO;EAAE,OAAO;EAAM,SAAS;CAAE;AACnC;AAEA,MAAM,UAAU,UAA0B,QAAQ,KAAK;;;;;AAMvD,MAAM,SAAS,UAAyC;CACtD,MAAM,SAAS,SAAS,KAAK;CAC7B,IAAI,CAAC,OAAO,OAAO,OAAO;CAE1B,MAAM,IAAI,OAAO;CACjB,MAAM,IAAI,OAAO,EAAE,EAAE;CACrB,MAAM,UAAU,WAAW,MAAM;CACjC,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,UAAU;CAEvB,OAAO;EACL,WAAW,IAAI,KAAK,MAAM,KAAK,GAAG,EAAE;EACpC,QAAQ,IAAI,MAAM,IAAI,SAAS;CACjC;AACF;;AAGA,MAAM,iBAAyB;CAC7B,SAAS;EACP,MAAM,IAAI,OAAO,UAAU,GAAG,CAAC,CAAC;EAChC,MAAM,KAAK,OAAO,UAAU,GAAG,EAAE,CAAC,
|
|
1
|
+
{"version":3,"file":"ik.js","names":[],"sources":["../../src/ee/ik.ts"],"sourcesContent":["/**\n * Isikukood (IK).\n *\n * Estonian personal identification code. 11 digits:\n * gender/century digit, YYMMDD, 3-digit serial, and\n * a check digit computed with two-pass weighted sums.\n *\n * Canonical sources:\n * - Estonian legislation for the personal code format\n * - OECD Estonia TIN sheet summarising the same\n * first-digit ranges for cross-border tax use\n *\n * These sources are why we restrict the first digit\n * to 1-6 rather than accepting speculative 7/8 forms.\n *\n * @see https://www.riigiteataja.ee/en/eli/512012015003/consolide\n * @see https://www.oecd.org/tax/automatic-exchange/crs-implementation-and-assistance/tax-identification-numbers/Estonia-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 { randomInt } from \"#util/generate\";\nimport { err } from \"#util/result\";\nimport { isdigits } from \"#util/strings\";\n\nconst WEIGHTS_1 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 1] as const;\nconst WEIGHTS_2 = [3, 4, 5, 6, 7, 8, 9, 1, 2, 3] 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, \" -\");\n\n/**\n * Compute the two-pass check digit used by both\n * Estonian IK and Lithuanian Asmens kodas.\n */\nexport const twoPassCheck = (digits: string): number => {\n let remainder = weightedSum(digits, WEIGHTS_1, 11);\n if (remainder !== 10) return remainder;\n remainder = weightedSum(digits, WEIGHTS_2, 11);\n return remainder === 10 ? 0 : remainder;\n};\n\nconst validate = (value: string): ValidateResult => {\n const v = compact(value);\n if (v.length !== 11) {\n return err(\n \"INVALID_LENGTH\",\n \"Isikukood must be exactly 11 digits\",\n );\n }\n if (!isdigits(v)) {\n return err(\n \"INVALID_FORMAT\",\n \"Isikukood must contain only digits\",\n );\n }\n\n const g = Number(v[0]);\n // Official Estonian structure uses 1-6:\n // 1-2 = 1800s, 3-4 = 1900s, 5-6 = 2000s.\n if (g < 1 || g > 6) {\n return err(\n \"INVALID_COMPONENT\",\n \"Isikukood gender/century digit is invalid\",\n );\n }\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 \"Isikukood contains an invalid date\",\n );\n }\n\n const check = twoPassCheck(v.slice(0, 10));\n if (check !== Number(v[10])) {\n return err(\n \"INVALID_CHECKSUM\",\n \"Isikukood 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 an Isikukood.\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 g = Number(v[0]);\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 return {\n birthDate: new Date(year, mm - 1, dd),\n gender: g % 2 === 1 ? \"male\" : \"female\",\n };\n};\n\n/** Generate a random valid Estonian IK. */\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/** Estonian Personal Identification Code. */\nconst ik: CountryValidator<\"EE\", ParsedPersonId> = {\n name: \"Estonian Personal ID\",\n localName: \"Isikukood\",\n abbreviation: \"IK\",\n aliases: [\"isikukood\", \"IK\"] as const,\n candidatePattern: \"[1-6]\\\\d{10}\",\n scope: \"country\",\n country: \"EE\",\n entityType: \"person\",\n sourceUrl:\n \"https://www.riigiteataja.ee/en/eli/512012015003/consolide\",\n examples: [\"36805280109\"] as const,\n compact,\n format,\n parse,\n validate,\n generate,\n};\n\nexport default ik;\nexport { compact, format, parse, validate, generate };\n"],"mappings":";;;;;;;AAgCA,MAAM,YAAY;CAAC;CAAG;CAAG;CAAG;CAAG;CAAG;CAAG;CAAG;CAAG;CAAG;AAAC;AAC/C,MAAM,YAAY;CAAC;CAAG;CAAG;CAAG;CAAG;CAAG;CAAG;CAAG;CAAG;CAAG;AAAC;AAE/C,MAAM,aAAqC;CACzC,GAAG;CACH,GAAG;CACH,GAAG;CACH,GAAG;CACH,GAAG;CACH,GAAG;AACL;AAEA,MAAM,WAAW,UACf,MAAM,OAAO,IAAI;;;;;AAMnB,MAAa,gBAAgB,WAA2B;CACtD,IAAI,YAAY,YAAY,QAAQ,WAAW,EAAE;CACjD,IAAI,cAAc,IAAI,OAAO;CAC7B,YAAY,YAAY,QAAQ,WAAW,EAAE;CAC7C,OAAO,cAAc,KAAK,IAAI;AAChC;AAEA,MAAM,YAAY,UAAkC;CAClD,MAAM,IAAI,QAAQ,KAAK;CACvB,IAAI,EAAE,WAAW,IACf,OAAO,IACL,kBACA,qCACF;CAEF,IAAI,CAAC,SAAS,CAAC,GACb,OAAO,IACL,kBACA,oCACF;CAGF,MAAM,IAAI,OAAO,EAAE,EAAE;CAGrB,IAAI,IAAI,KAAK,IAAI,GACf,OAAO,IACL,qBACA,2CACF;CAGF,MAAM,UAAU,WAAW,MAAM;CACjC,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,UAAU,IAEA,IAAI,EAAE,GAC3B,OAAO,IACL,qBACA,oCACF;CAIF,IADc,aAAa,EAAE,MAAM,GAAG,EAAE,CAChC,MAAM,OAAO,EAAE,GAAG,GACxB,OAAO,IACL,oBACA,sCACF;CAGF,OAAO;EAAE,OAAO;EAAM,SAAS;CAAE;AACnC;AAEA,MAAM,UAAU,UAA0B,QAAQ,KAAK;;;;;AAMvD,MAAM,SAAS,UAAyC;CACtD,MAAM,SAAS,SAAS,KAAK;CAC7B,IAAI,CAAC,OAAO,OAAO,OAAO;CAE1B,MAAM,IAAI,OAAO;CACjB,MAAM,IAAI,OAAO,EAAE,EAAE;CACrB,MAAM,UAAU,WAAW,MAAM;CACjC,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,UAAU;CAEvB,OAAO;EACL,WAAW,IAAI,KAAK,MAAM,KAAK,GAAG,EAAE;EACpC,QAAQ,IAAI,MAAM,IAAI,SAAS;CACjC;AACF;;AAGA,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,KAA6C;CACjD,MAAM;CACN,WAAW;CACX,cAAc;CACd,SAAS,CAAC,aAAa,IAAI;CAC3B,kBAAkB;CAClB,OAAO;CACP,SAAS;CACT,YAAY;CACZ,WACE;CACF,UAAU,CAAC,aAAa;CACxB;CACA;CACA;CACA;CACA;AACF"}
|
package/dist/ee/vat.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vat.js","names":[],"sources":["../../src/ee/vat.ts"],"sourcesContent":["/**\n * KMKR (Estonian VAT number).\n *\n * 9 digits. Weights [3,7,1,3,7,1,3,7,1],\n * sum % 10 === 0.\n *\n * @see https://www.vatify.eu/estonia-vat-number.html\n * @see https://www.emta.ee/en\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 } from \"#util/generate\";\nimport { err } from \"#util/result\";\nimport { isdigits } from \"#util/strings\";\n\nconst WEIGHTS = [3, 7, 1, 3, 7, 1, 3, 7, 1];\n\nconst compact = (value: string): string => {\n let v = clean(value, \" -/.\");\n if (v.startsWith(\"EE\") || v.startsWith(\"ee\")) {\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 !== 9) {\n return err(\n \"INVALID_LENGTH\",\n \"Estonian VAT number must be 9 digits\",\n );\n }\n if (!isdigits(v)) {\n return err(\n \"INVALID_FORMAT\",\n \"Estonian VAT number must contain only digits\",\n );\n }\n const sum = weightedSum(v, WEIGHTS, 10);\n if (sum !== 0) {\n return err(\n \"INVALID_CHECKSUM\",\n \"Estonian VAT number check digit mismatch\",\n );\n }\n return { valid: true, compact: v };\n};\n\nconst format = (value: string): string =>\n `EE${compact(value)}`;\n\n/** Generate a random valid Estonian VAT number. */\nconst generate = (): string => {\n for (;;) {\n const c = randomDigits(9);\n if (validate(c).valid) return c;\n }\n};\n\n/** Estonian VAT Number. */\nconst vat: CountryValidator<\"EE\"> = {\n name: \"Estonian VAT Number\",\n localName: \"Käibemaksukohustuslase number\",\n abbreviation: \"KMKR\",\n aliases: [\n \"käibemaksukohustuslase number\",\n \"KMKR\",\n ] as const,\n candidatePattern: \"EE\\\\d{9}\",\n scope: \"country\",\n country: \"EE\",\n entityType: \"company\",\n sourceUrl: \"https://www.emta.ee/en\",\n examples: [\"100931558\"] 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;CAAG;CAAG;AAAC;AAE1C,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,sCACF;CAEF,IAAI,CAAC,SAAS,CAAC,GACb,OAAO,IACL,kBACA,8CACF;CAGF,IADY,YAAY,GAAG,SAAS,EAC9B,MAAM,GACV,OAAO,IACL,oBACA,0CACF;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/ee/vat.ts"],"sourcesContent":["/**\n * KMKR (Estonian VAT number).\n *\n * 9 digits. Weights [3,7,1,3,7,1,3,7,1],\n * sum % 10 === 0.\n *\n * @see https://www.vatify.eu/estonia-vat-number.html\n * @see https://www.emta.ee/en\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 } from \"#util/generate\";\nimport { err } from \"#util/result\";\nimport { isdigits } from \"#util/strings\";\n\nconst WEIGHTS = [3, 7, 1, 3, 7, 1, 3, 7, 1];\n\nconst compact = (value: string): string => {\n let v = clean(value, \" -/.\");\n if (v.startsWith(\"EE\") || v.startsWith(\"ee\")) {\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 !== 9) {\n return err(\n \"INVALID_LENGTH\",\n \"Estonian VAT number must be 9 digits\",\n );\n }\n if (!isdigits(v)) {\n return err(\n \"INVALID_FORMAT\",\n \"Estonian VAT number must contain only digits\",\n );\n }\n const sum = weightedSum(v, WEIGHTS, 10);\n if (sum !== 0) {\n return err(\n \"INVALID_CHECKSUM\",\n \"Estonian VAT number check digit mismatch\",\n );\n }\n return { valid: true, compact: v };\n};\n\nconst format = (value: string): string =>\n `EE${compact(value)}`;\n\n/** Generate a random valid Estonian VAT number. */\nconst generate = (): string => {\n for (;;) {\n const c = randomDigits(9);\n if (validate(c).valid) return c;\n }\n};\n\n/** Estonian VAT Number. */\nconst vat: CountryValidator<\"EE\"> = {\n name: \"Estonian VAT Number\",\n localName: \"Käibemaksukohustuslase number\",\n abbreviation: \"KMKR\",\n aliases: [\n \"käibemaksukohustuslase number\",\n \"KMKR\",\n ] as const,\n candidatePattern: \"EE\\\\d{9}\",\n scope: \"country\",\n country: \"EE\",\n entityType: \"company\",\n sourceUrl: \"https://www.emta.ee/en\",\n examples: [\"100931558\"] 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;CAAG;CAAG;AAAC;AAE1C,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,sCACF;CAEF,IAAI,CAAC,SAAS,CAAC,GACb,OAAO,IACL,kBACA,8CACF;CAGF,IADY,YAAY,GAAG,SAAS,EAC9B,MAAM,GACV,OAAO,IACL,oBACA,0CACF;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,CACP,iCACA,MACF;CACA,kBAAkB;CAClB,OAAO;CACP,SAAS;CACT,YAAY;CACZ,WAAW;CACX,UAAU,CAAC,WAAW;CACtB;CACA;CACA;CACA;AACF"}
|
package/dist/eg/tn.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tn.js","names":[],"sources":["../../src/eg/tn.ts"],"sourcesContent":["/** Generate a random valid Egyptian TN. */\nconst generate = (): string => randomDigits(9);\n\n/**\n * TN (Tax Registration Number, الرقم الضريبي).\n *\n * Egyptian tax number. 9 digits, no checksum.\n * Displayed as XXX-XXX-XXX. Accepts both\n * Arabic-Indic and Extended Arabic-Indic digits.\n *\n * @see https://www.eta.gov.eg/\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\n/**\n * Arabic-Indic and Extended Arabic-Indic digit map.\n * Converts Arabic numeral characters to ASCII digits.\n */\nconst ARABIC_DIGITS: Record<string, string> = {\n \"\\u0660\": \"0\", // ٠\n \"\\u0661\": \"1\", // ١\n \"\\u0662\": \"2\", // ٢\n \"\\u0663\": \"3\", // ٣\n \"\\u0664\": \"4\", // ٤\n \"\\u0665\": \"5\", // ٥\n \"\\u0666\": \"6\", // ٦\n \"\\u0667\": \"7\", // ٧\n \"\\u0668\": \"8\", // ٨\n \"\\u0669\": \"9\", // ٩\n \"\\u06F0\": \"0\", // ۰\n \"\\u06F1\": \"1\", // ۱\n \"\\u06F2\": \"2\", // ۲\n \"\\u06F3\": \"3\", // ۳\n \"\\u06F4\": \"4\", // ۴\n \"\\u06F5\": \"5\", // ۵\n \"\\u06F6\": \"6\", // ۶\n \"\\u06F7\": \"7\", // ۷\n \"\\u06F8\": \"8\", // ۸\n \"\\u06F9\": \"9\", // ۹\n};\n\nconst ARABIC_REGEX = new RegExp(\n `[${Object.keys(ARABIC_DIGITS).join(\"\")}]`,\n \"g\",\n);\n\nconst compact = (value: string): string => {\n const cleaned = clean(value, \" -/\");\n return cleaned.replace(\n ARABIC_REGEX,\n (ch) => ARABIC_DIGITS[ch] ?? ch,\n );\n};\n\nconst validate = (value: string): ValidateResult => {\n const v = compact(value);\n if (v.length !== 9) {\n return err(\n \"INVALID_LENGTH\",\n \"Egyptian TN must be exactly 9 digits\",\n );\n }\n if (!isdigits(v)) {\n return err(\n \"INVALID_FORMAT\",\n \"Egyptian TN must contain only digits\",\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)}`;\n};\n\n/** Egyptian Tax Registration Number. */\nconst tn: CountryValidator<\"EG\"> = {\n name: \"Egyptian Tax Registration Number\",\n localName: \"الرقم الضريبي\",\n abbreviation: \"TN\",\n aliases: [\"الرقم الضريبي\", \"tax number\"] as const,\n candidatePattern: \"\\\\d{9}\",\n scope: \"country\",\n country: \"EG\",\n entityType: \"any\",\n compact,\n format,\n validate,\n description: \"Egyptian tax identification number\",\n sourceUrl: \"https://www.eta.gov.eg/\",\n lengths: [9] as const,\n examples: [\"100531385\", \"331105268\"] as const,\n generate,\n};\n\nexport default tn;\nexport { compact, format, validate, generate };\n"],"mappings":";;;;;;AACA,MAAM,iBAAyB,aAAa,CAAC;;;;;AA0B7C,MAAM,gBAAwC;CAC5C,KAAU;CACV,KAAU;CACV,KAAU;CACV,KAAU;CACV,KAAU;CACV,KAAU;CACV,KAAU;CACV,KAAU;CACV,KAAU;CACV,KAAU;CACV,KAAU;CACV,KAAU;CACV,KAAU;CACV,KAAU;CACV,KAAU;CACV,KAAU;CACV,KAAU;CACV,KAAU;CACV,KAAU;CACV,KAAU;AACZ;AAEA,MAAM,eAAe,IAAI,OACvB,IAAI,OAAO,KAAK,aAAa,
|
|
1
|
+
{"version":3,"file":"tn.js","names":[],"sources":["../../src/eg/tn.ts"],"sourcesContent":["/** Generate a random valid Egyptian TN. */\nconst generate = (): string => randomDigits(9);\n\n/**\n * TN (Tax Registration Number, الرقم الضريبي).\n *\n * Egyptian tax number. 9 digits, no checksum.\n * Displayed as XXX-XXX-XXX. Accepts both\n * Arabic-Indic and Extended Arabic-Indic digits.\n *\n * @see https://www.eta.gov.eg/\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\n/**\n * Arabic-Indic and Extended Arabic-Indic digit map.\n * Converts Arabic numeral characters to ASCII digits.\n */\nconst ARABIC_DIGITS: Record<string, string> = {\n \"\\u0660\": \"0\", // ٠\n \"\\u0661\": \"1\", // ١\n \"\\u0662\": \"2\", // ٢\n \"\\u0663\": \"3\", // ٣\n \"\\u0664\": \"4\", // ٤\n \"\\u0665\": \"5\", // ٥\n \"\\u0666\": \"6\", // ٦\n \"\\u0667\": \"7\", // ٧\n \"\\u0668\": \"8\", // ٨\n \"\\u0669\": \"9\", // ٩\n \"\\u06F0\": \"0\", // ۰\n \"\\u06F1\": \"1\", // ۱\n \"\\u06F2\": \"2\", // ۲\n \"\\u06F3\": \"3\", // ۳\n \"\\u06F4\": \"4\", // ۴\n \"\\u06F5\": \"5\", // ۵\n \"\\u06F6\": \"6\", // ۶\n \"\\u06F7\": \"7\", // ۷\n \"\\u06F8\": \"8\", // ۸\n \"\\u06F9\": \"9\", // ۹\n};\n\nconst ARABIC_REGEX = new RegExp(\n `[${Object.keys(ARABIC_DIGITS).join(\"\")}]`,\n \"g\",\n);\n\nconst compact = (value: string): string => {\n const cleaned = clean(value, \" -/\");\n return cleaned.replace(\n ARABIC_REGEX,\n (ch) => ARABIC_DIGITS[ch] ?? ch,\n );\n};\n\nconst validate = (value: string): ValidateResult => {\n const v = compact(value);\n if (v.length !== 9) {\n return err(\n \"INVALID_LENGTH\",\n \"Egyptian TN must be exactly 9 digits\",\n );\n }\n if (!isdigits(v)) {\n return err(\n \"INVALID_FORMAT\",\n \"Egyptian TN must contain only digits\",\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)}`;\n};\n\n/** Egyptian Tax Registration Number. */\nconst tn: CountryValidator<\"EG\"> = {\n name: \"Egyptian Tax Registration Number\",\n localName: \"الرقم الضريبي\",\n abbreviation: \"TN\",\n aliases: [\"الرقم الضريبي\", \"tax number\"] as const,\n candidatePattern: \"\\\\d{9}\",\n scope: \"country\",\n country: \"EG\",\n entityType: \"any\",\n compact,\n format,\n validate,\n description: \"Egyptian tax identification number\",\n sourceUrl: \"https://www.eta.gov.eg/\",\n lengths: [9] as const,\n examples: [\"100531385\", \"331105268\"] as const,\n generate,\n};\n\nexport default tn;\nexport { compact, format, validate, generate };\n"],"mappings":";;;;;;AACA,MAAM,iBAAyB,aAAa,CAAC;;;;;AA0B7C,MAAM,gBAAwC;CAC5C,KAAU;CACV,KAAU;CACV,KAAU;CACV,KAAU;CACV,KAAU;CACV,KAAU;CACV,KAAU;CACV,KAAU;CACV,KAAU;CACV,KAAU;CACV,KAAU;CACV,KAAU;CACV,KAAU;CACV,KAAU;CACV,KAAU;CACV,KAAU;CACV,KAAU;CACV,KAAU;CACV,KAAU;CACV,KAAU;AACZ;AAEA,MAAM,eAAe,IAAI,OACvB,IAAI,OAAO,KAAK,aAAa,CAAC,CAAC,KAAK,EAAE,EAAE,IACxC,GACF;AAEA,MAAM,WAAW,UAA0B;CAEzC,OADgB,MAAM,OAAO,KAChB,CAAC,CAAC,QACb,eACC,OAAO,cAAc,OAAO,EAC/B;AACF;AAEA,MAAM,YAAY,UAAkC;CAClD,MAAM,IAAI,QAAQ,KAAK;CACvB,IAAI,EAAE,WAAW,GACf,OAAO,IACL,kBACA,sCACF;CAEF,IAAI,CAAC,SAAS,CAAC,GACb,OAAO,IACL,kBACA,sCACF;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,CAAC;AACvD;;AAGA,MAAM,KAA6B;CACjC,MAAM;CACN,WAAW;CACX,cAAc;CACd,SAAS,CAAC,iBAAiB,YAAY;CACvC,kBAAkB;CAClB,OAAO;CACP,SAAS;CACT,YAAY;CACZ;CACA;CACA;CACA,aAAa;CACb,WAAW;CACX,SAAS,CAAC,CAAC;CACX,UAAU,CAAC,aAAa,WAAW;CACnC;AACF"}
|
package/dist/es/dni.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dni.js","names":[],"sources":["../../src/es/dni.ts"],"sourcesContent":["/**\n * DNI (Documento Nacional de Identidad).\n *\n * Spanish national identity document number.\n * 8 digits followed by a check letter determined\n * by the remainder of dividing the number by 23.\n *\n * @see https://www.interior.gob.es/opencms/es/servicios-al-ciudadano/tramites-y-gestiones/dni/\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 CHECK_LETTERS = \"TRWAGMYFPDXBNJZSQVHLCKE\";\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 !== 9) {\n return err(\n \"INVALID_LENGTH\",\n \"DNI must be 8 digits and 1 letter\",\n );\n }\n\n const digits = v.slice(0, 8);\n const letter = v[8];\n\n if (!isdigits(digits)) {\n return err(\n \"INVALID_FORMAT\",\n \"DNI must start with 8 digits\",\n );\n }\n\n const expected = CHECK_LETTERS[Number(digits) % 23];\n if (letter !== expected) {\n return err(\n \"INVALID_CHECKSUM\",\n \"DNI check letter 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 Spanish DNI. */\nconst generate = (): string => {\n const num = randomDigits(8);\n return num + CHECK_LETTERS[Number(num) % 23];\n};\n\n/** Spanish National Identity Document. */\nconst dni: CountryValidator<\"ES\"> = {\n name: \"Spanish National ID\",\n localName: \"Documento Nacional de Identidad\",\n abbreviation: \"DNI\",\n aliases: [\n \"D.N.I.\",\n \"DNI\",\n \"documento nacional de identidad\",\n ] as const,\n candidatePattern: \"\\\\d{1,2}\\\\.?\\\\d{3}\\\\.?\\\\d{3}-?[A-Z]\",\n scope: \"country\",\n country: \"ES\",\n entityType: \"person\",\n sourceUrl:\n \"https://www.interior.gob.es/opencms/es/servicios-al-ciudadano/tramites-y-gestiones/dni/\",\n examples: [\"54362315K\"] as const,\n compact,\n format,\n validate,\n generate,\n};\n\nexport default dni;\nexport {\n CHECK_LETTERS,\n compact,\n format,\n validate,\n generate,\n};\n"],"mappings":";;;;;AAoBA,MAAM,gBAAgB;AAEtB,MAAM,WAAW,UACf,MAAM,OAAO,IAAI,
|
|
1
|
+
{"version":3,"file":"dni.js","names":[],"sources":["../../src/es/dni.ts"],"sourcesContent":["/**\n * DNI (Documento Nacional de Identidad).\n *\n * Spanish national identity document number.\n * 8 digits followed by a check letter determined\n * by the remainder of dividing the number by 23.\n *\n * @see https://www.interior.gob.es/opencms/es/servicios-al-ciudadano/tramites-y-gestiones/dni/\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 CHECK_LETTERS = \"TRWAGMYFPDXBNJZSQVHLCKE\";\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 !== 9) {\n return err(\n \"INVALID_LENGTH\",\n \"DNI must be 8 digits and 1 letter\",\n );\n }\n\n const digits = v.slice(0, 8);\n const letter = v[8];\n\n if (!isdigits(digits)) {\n return err(\n \"INVALID_FORMAT\",\n \"DNI must start with 8 digits\",\n );\n }\n\n const expected = CHECK_LETTERS[Number(digits) % 23];\n if (letter !== expected) {\n return err(\n \"INVALID_CHECKSUM\",\n \"DNI check letter 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 Spanish DNI. */\nconst generate = (): string => {\n const num = randomDigits(8);\n return num + CHECK_LETTERS[Number(num) % 23];\n};\n\n/** Spanish National Identity Document. */\nconst dni: CountryValidator<\"ES\"> = {\n name: \"Spanish National ID\",\n localName: \"Documento Nacional de Identidad\",\n abbreviation: \"DNI\",\n aliases: [\n \"D.N.I.\",\n \"DNI\",\n \"documento nacional de identidad\",\n ] as const,\n candidatePattern: \"\\\\d{1,2}\\\\.?\\\\d{3}\\\\.?\\\\d{3}-?[A-Z]\",\n scope: \"country\",\n country: \"ES\",\n entityType: \"person\",\n sourceUrl:\n \"https://www.interior.gob.es/opencms/es/servicios-al-ciudadano/tramites-y-gestiones/dni/\",\n examples: [\"54362315K\"] as const,\n compact,\n format,\n validate,\n generate,\n};\n\nexport default dni;\nexport {\n CHECK_LETTERS,\n compact,\n format,\n validate,\n generate,\n};\n"],"mappings":";;;;;AAoBA,MAAM,gBAAgB;AAEtB,MAAM,WAAW,UACf,MAAM,OAAO,IAAI,CAAC,CAAC,YAAY;AAEjC,MAAM,YAAY,UAAkC;CAClD,MAAM,IAAI,QAAQ,KAAK;CACvB,IAAI,EAAE,WAAW,GACf,OAAO,IACL,kBACA,mCACF;CAGF,MAAM,SAAS,EAAE,MAAM,GAAG,CAAC;CAC3B,MAAM,SAAS,EAAE;CAEjB,IAAI,CAAC,SAAS,MAAM,GAClB,OAAO,IACL,kBACA,8BACF;CAIF,IAAI,WAAA,0BAD2B,OAAO,MAAM,IAAI,KAE9C,OAAO,IACL,oBACA,iCACF;CAGF,OAAO;EAAE,OAAO;EAAM,SAAS;CAAE;AACnC;AAEA,MAAM,UAAU,UAA0B,QAAQ,KAAK;;AAGvD,MAAM,iBAAyB;CAC7B,MAAM,MAAM,aAAa,CAAC;CAC1B,OAAO,MAAM,cAAc,OAAO,GAAG,IAAI;AAC3C;;AAGA,MAAM,MAA8B;CAClC,MAAM;CACN,WAAW;CACX,cAAc;CACd,SAAS;EACP;EACA;EACA;CACF;CACA,kBAAkB;CAClB,OAAO;CACP,SAAS;CACT,YAAY;CACZ,WACE;CACF,UAAU,CAAC,WAAW;CACtB;CACA;CACA;CACA;AACF"}
|
package/dist/es/nie.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nie.js","names":[],"sources":["../../src/es/nie.ts"],"sourcesContent":["/**\n * NIE (Número de Identidad de Extranjero).\n *\n * Spanish foreigner identification number. Starts\n * with X, Y, or Z, followed by 7 digits and a check\n * letter computed using the DNI algorithm.\n *\n * @see https://www.interior.gob.es/opencms/es/servicios-al-ciudadano/tramites-y-gestiones/nie/\n */\n\nimport type {\n ValidateResult,\n CountryValidator,\n} from \"../types\";\nimport { CHECK_LETTERS } from \"./dni\";\n\nimport { clean } from \"#util/clean\";\nimport { randomDigits, randomPick } from \"#util/generate\";\nimport { err } from \"#util/result\";\nimport { isdigits } from \"#util/strings\";\n\nconst PREFIX_MAP: Record<string, string> = {\n X: \"0\",\n Y: \"1\",\n Z: \"2\",\n};\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 !== 9) {\n return err(\n \"INVALID_LENGTH\",\n \"NIE must be 9 characters\",\n );\n }\n\n const prefix = v.charAt(0);\n const replacement = PREFIX_MAP[prefix];\n if (replacement === undefined) {\n return err(\n \"INVALID_FORMAT\",\n \"NIE must start with X, Y, or Z\",\n );\n }\n\n const digits = v.slice(1, 8);\n if (!isdigits(digits)) {\n return err(\n \"INVALID_FORMAT\",\n \"NIE must have 7 digits after prefix\",\n );\n }\n\n const letter = v.charAt(8);\n const num = Number(replacement + digits);\n const expected = CHECK_LETTERS.charAt(num % 23);\n if (letter !== expected) {\n return err(\n \"INVALID_CHECKSUM\",\n \"NIE check letter does not match\",\n );\n }\n\n return { valid: true, compact: v };\n};\n\nconst format = (value: string): string => compact(value);\n\nconst NIE_PREFIXES = [\"X\", \"Y\", \"Z\"] as const;\nconst NIE_PREFIX_VALUES: Record<\n (typeof NIE_PREFIXES)[number],\n number\n> = { X: 0, Y: 1, Z: 2 };\n\n/** Generate a random valid Spanish NIE. */\nconst generate = (): string => {\n const prefix = randomPick(NIE_PREFIXES);\n const digits = randomDigits(7);\n const num =\n NIE_PREFIX_VALUES[prefix] * 10000000 + Number(digits);\n return prefix + digits + CHECK_LETTERS.charAt(num % 23);\n};\n\n/** Spanish Foreigner Identification Number. */\nconst nie: CountryValidator<\"ES\"> = {\n name: \"Spanish Foreigner ID\",\n localName: \"Número de Identidad de Extranjero\",\n abbreviation: \"NIE\",\n aliases: [\n \"N.I.E.\",\n \"NIE\",\n \"número de identidad de extranjero\",\n ] as const,\n candidatePattern: \"[XYZ]-?\\\\d{7}-?[A-Z]\",\n scope: \"country\",\n country: \"ES\",\n entityType: \"person\",\n sourceUrl:\n \"https://www.interior.gob.es/opencms/es/servicios-al-ciudadano/tramites-y-gestiones/nie/\",\n examples: [\"X5253868R\"] as const,\n compact,\n format,\n validate,\n generate,\n};\n\nexport default nie;\nexport { compact, format, validate, generate };\n"],"mappings":";;;;;;AAqBA,MAAM,aAAqC;CACzC,GAAG;CACH,GAAG;CACH,GAAG;AACL;AAEA,MAAM,WAAW,UACf,MAAM,OAAO,IAAI,
|
|
1
|
+
{"version":3,"file":"nie.js","names":[],"sources":["../../src/es/nie.ts"],"sourcesContent":["/**\n * NIE (Número de Identidad de Extranjero).\n *\n * Spanish foreigner identification number. Starts\n * with X, Y, or Z, followed by 7 digits and a check\n * letter computed using the DNI algorithm.\n *\n * @see https://www.interior.gob.es/opencms/es/servicios-al-ciudadano/tramites-y-gestiones/nie/\n */\n\nimport type {\n ValidateResult,\n CountryValidator,\n} from \"../types\";\nimport { CHECK_LETTERS } from \"./dni\";\n\nimport { clean } from \"#util/clean\";\nimport { randomDigits, randomPick } from \"#util/generate\";\nimport { err } from \"#util/result\";\nimport { isdigits } from \"#util/strings\";\n\nconst PREFIX_MAP: Record<string, string> = {\n X: \"0\",\n Y: \"1\",\n Z: \"2\",\n};\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 !== 9) {\n return err(\n \"INVALID_LENGTH\",\n \"NIE must be 9 characters\",\n );\n }\n\n const prefix = v.charAt(0);\n const replacement = PREFIX_MAP[prefix];\n if (replacement === undefined) {\n return err(\n \"INVALID_FORMAT\",\n \"NIE must start with X, Y, or Z\",\n );\n }\n\n const digits = v.slice(1, 8);\n if (!isdigits(digits)) {\n return err(\n \"INVALID_FORMAT\",\n \"NIE must have 7 digits after prefix\",\n );\n }\n\n const letter = v.charAt(8);\n const num = Number(replacement + digits);\n const expected = CHECK_LETTERS.charAt(num % 23);\n if (letter !== expected) {\n return err(\n \"INVALID_CHECKSUM\",\n \"NIE check letter does not match\",\n );\n }\n\n return { valid: true, compact: v };\n};\n\nconst format = (value: string): string => compact(value);\n\nconst NIE_PREFIXES = [\"X\", \"Y\", \"Z\"] as const;\nconst NIE_PREFIX_VALUES: Record<\n (typeof NIE_PREFIXES)[number],\n number\n> = { X: 0, Y: 1, Z: 2 };\n\n/** Generate a random valid Spanish NIE. */\nconst generate = (): string => {\n const prefix = randomPick(NIE_PREFIXES);\n const digits = randomDigits(7);\n const num =\n NIE_PREFIX_VALUES[prefix] * 10000000 + Number(digits);\n return prefix + digits + CHECK_LETTERS.charAt(num % 23);\n};\n\n/** Spanish Foreigner Identification Number. */\nconst nie: CountryValidator<\"ES\"> = {\n name: \"Spanish Foreigner ID\",\n localName: \"Número de Identidad de Extranjero\",\n abbreviation: \"NIE\",\n aliases: [\n \"N.I.E.\",\n \"NIE\",\n \"número de identidad de extranjero\",\n ] as const,\n candidatePattern: \"[XYZ]-?\\\\d{7}-?[A-Z]\",\n scope: \"country\",\n country: \"ES\",\n entityType: \"person\",\n sourceUrl:\n \"https://www.interior.gob.es/opencms/es/servicios-al-ciudadano/tramites-y-gestiones/nie/\",\n examples: [\"X5253868R\"] as const,\n compact,\n format,\n validate,\n generate,\n};\n\nexport default nie;\nexport { compact, format, validate, generate };\n"],"mappings":";;;;;;AAqBA,MAAM,aAAqC;CACzC,GAAG;CACH,GAAG;CACH,GAAG;AACL;AAEA,MAAM,WAAW,UACf,MAAM,OAAO,IAAI,CAAC,CAAC,YAAY;AAEjC,MAAM,YAAY,UAAkC;CAClD,MAAM,IAAI,QAAQ,KAAK;CACvB,IAAI,EAAE,WAAW,GACf,OAAO,IACL,kBACA,0BACF;CAIF,MAAM,cAAc,WADL,EAAE,OAAO,CACY;CACpC,IAAI,gBAAgB,KAAA,GAClB,OAAO,IACL,kBACA,gCACF;CAGF,MAAM,SAAS,EAAE,MAAM,GAAG,CAAC;CAC3B,IAAI,CAAC,SAAS,MAAM,GAClB,OAAO,IACL,kBACA,qCACF;CAGF,MAAM,SAAS,EAAE,OAAO,CAAC;CACzB,MAAM,MAAM,OAAO,cAAc,MAAM;CAEvC,IAAI,WAAA,0BAD2B,OAAO,MAAM,EACtB,GACpB,OAAO,IACL,oBACA,iCACF;CAGF,OAAO;EAAE,OAAO;EAAM,SAAS;CAAE;AACnC;AAEA,MAAM,UAAU,UAA0B,QAAQ,KAAK;AAEvD,MAAM,eAAe;CAAC;CAAK;CAAK;AAAG;AACnC,MAAM,oBAGF;CAAE,GAAG;CAAG,GAAG;CAAG,GAAG;AAAE;;AAGvB,MAAM,iBAAyB;CAC7B,MAAM,SAAS,WAAW,YAAY;CACtC,MAAM,SAAS,aAAa,CAAC;CAC7B,MAAM,MACJ,kBAAkB,UAAU,MAAW,OAAO,MAAM;CACtD,OAAO,SAAS,SAAS,cAAc,OAAO,MAAM,EAAE;AACxD;;AAGA,MAAM,MAA8B;CAClC,MAAM;CACN,WAAW;CACX,cAAc;CACd,SAAS;EACP;EACA;EACA;CACF;CACA,kBAAkB;CAClB,OAAO;CACP,SAAS;CACT,YAAY;CACZ,WACE;CACF,UAAU,CAAC,WAAW;CACtB;CACA;CACA;CACA;AACF"}
|
package/dist/es/nss.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nss.js","names":[],"sources":["../../src/es/nss.ts"],"sourcesContent":["/**\n * NSS (Número de la Seguridad Social).\n *\n * Spanish Social Security Number. 12 digits:\n * 2-digit province code (01–52) + 8-digit affiliate\n * number + 2-digit check digits.\n *\n * Check digits are computed as:\n * if affiliate < 10_000_000:\n * (affiliate + province × 10_000_000) mod 97\n * else:\n * parseInt(province + affiliate) mod 97\n *\n * @see https://www.seg-social.es/\n * @see https://intervia.com/doc/validar-numeros-de-la-seguridad-social/\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 clean(value, \" -/.\");\n\nconst validate = (value: string): ValidateResult => {\n const v = compact(value);\n if (v.length !== 12) {\n return err(\n \"INVALID_LENGTH\",\n \"Spanish NSS must be 12 digits\",\n );\n }\n if (!isdigits(v)) {\n return err(\n \"INVALID_FORMAT\",\n \"Spanish NSS must contain only digits\",\n );\n }\n\n const province = Number(v.slice(0, 2));\n if (province < 1 || province > 52) {\n return err(\n \"INVALID_COMPONENT\",\n \"Spanish NSS province code must be 01–52\",\n );\n }\n\n const affiliate = Number(v.slice(2, 10));\n const check = Number(v.slice(10, 12));\n\n let base: number;\n if (affiliate < 10_000_000) {\n base = affiliate + province * 10_000_000;\n } else {\n // Concatenate province and affiliate as a number\n base = Number(`${province}${v.slice(2, 10)}`);\n }\n\n const expected = base % 97;\n\n if (expected !== check) {\n return err(\n \"INVALID_CHECKSUM\",\n \"Spanish NSS check digits do 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, 2)}/${v.slice(2, 10)}/${v.slice(10)}`;\n};\n\n/** Generate a random valid Spanish NSS. */\nconst generate = (): string => {\n for (;;) {\n const c = randomDigits(12);\n if (validate(c).valid) return c;\n }\n};\n\n/** Spanish Social Security Number. */\nconst nss: CountryValidator<\"ES\"> = {\n name: \"Spanish Social Security Number\",\n localName: \"Número de la Seguridad Social\",\n abbreviation: \"NSS\",\n aliases: [\"NSS\", \"número de seguridad social\"] as const,\n candidatePattern: \"\\\\d{12}\",\n scope: \"country\",\n country: \"ES\",\n entityType: \"person\",\n description:\n \"Spanish Social Security affiliation number \" +\n \"with mod-97 check digits\",\n sourceUrl: \"https://www.seg-social.es/\",\n lengths: [12] as const,\n examples: [\"281234567840\"] as const,\n compact,\n format,\n validate,\n generate,\n};\n\nexport default nss;\nexport { compact, format, validate, generate };\n"],"mappings":";;;;;AA2BA,MAAM,WAAW,UACf,MAAM,OAAO,MAAM;AAErB,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,sCACF;CAGF,MAAM,WAAW,OAAO,EAAE,MAAM,GAAG,CAAC,CAAC;CACrC,IAAI,WAAW,KAAK,WAAW,IAC7B,OAAO,IACL,qBACA,yCACF;CAGF,MAAM,YAAY,OAAO,EAAE,MAAM,GAAG,EAAE,CAAC;CACvC,MAAM,QAAQ,OAAO,EAAE,MAAM,IAAI,EAAE,CAAC;CAEpC,IAAI;CACJ,IAAI,YAAY,KACd,OAAO,YAAY,WAAW;MAG9B,OAAO,OAAO,GAAG,WAAW,EAAE,MAAM,GAAG,EAAE,GAAG;CAK9C,IAFiB,OAAO,OAEP,OACf,OAAO,IACL,oBACA,uCACF;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,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE;AACzD;;AAGA,MAAM,iBAAyB;CAC7B,SAAS;EACP,MAAM,IAAI,aAAa,EAAE;EACzB,IAAI,SAAS,CAAC,
|
|
1
|
+
{"version":3,"file":"nss.js","names":[],"sources":["../../src/es/nss.ts"],"sourcesContent":["/**\n * NSS (Número de la Seguridad Social).\n *\n * Spanish Social Security Number. 12 digits:\n * 2-digit province code (01–52) + 8-digit affiliate\n * number + 2-digit check digits.\n *\n * Check digits are computed as:\n * if affiliate < 10_000_000:\n * (affiliate + province × 10_000_000) mod 97\n * else:\n * parseInt(province + affiliate) mod 97\n *\n * @see https://www.seg-social.es/\n * @see https://intervia.com/doc/validar-numeros-de-la-seguridad-social/\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 clean(value, \" -/.\");\n\nconst validate = (value: string): ValidateResult => {\n const v = compact(value);\n if (v.length !== 12) {\n return err(\n \"INVALID_LENGTH\",\n \"Spanish NSS must be 12 digits\",\n );\n }\n if (!isdigits(v)) {\n return err(\n \"INVALID_FORMAT\",\n \"Spanish NSS must contain only digits\",\n );\n }\n\n const province = Number(v.slice(0, 2));\n if (province < 1 || province > 52) {\n return err(\n \"INVALID_COMPONENT\",\n \"Spanish NSS province code must be 01–52\",\n );\n }\n\n const affiliate = Number(v.slice(2, 10));\n const check = Number(v.slice(10, 12));\n\n let base: number;\n if (affiliate < 10_000_000) {\n base = affiliate + province * 10_000_000;\n } else {\n // Concatenate province and affiliate as a number\n base = Number(`${province}${v.slice(2, 10)}`);\n }\n\n const expected = base % 97;\n\n if (expected !== check) {\n return err(\n \"INVALID_CHECKSUM\",\n \"Spanish NSS check digits do 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, 2)}/${v.slice(2, 10)}/${v.slice(10)}`;\n};\n\n/** Generate a random valid Spanish NSS. */\nconst generate = (): string => {\n for (;;) {\n const c = randomDigits(12);\n if (validate(c).valid) return c;\n }\n};\n\n/** Spanish Social Security Number. */\nconst nss: CountryValidator<\"ES\"> = {\n name: \"Spanish Social Security Number\",\n localName: \"Número de la Seguridad Social\",\n abbreviation: \"NSS\",\n aliases: [\"NSS\", \"número de seguridad social\"] as const,\n candidatePattern: \"\\\\d{12}\",\n scope: \"country\",\n country: \"ES\",\n entityType: \"person\",\n description:\n \"Spanish Social Security affiliation number \" +\n \"with mod-97 check digits\",\n sourceUrl: \"https://www.seg-social.es/\",\n lengths: [12] as const,\n examples: [\"281234567840\"] as const,\n compact,\n format,\n validate,\n generate,\n};\n\nexport default nss;\nexport { compact, format, validate, generate };\n"],"mappings":";;;;;AA2BA,MAAM,WAAW,UACf,MAAM,OAAO,MAAM;AAErB,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,sCACF;CAGF,MAAM,WAAW,OAAO,EAAE,MAAM,GAAG,CAAC,CAAC;CACrC,IAAI,WAAW,KAAK,WAAW,IAC7B,OAAO,IACL,qBACA,yCACF;CAGF,MAAM,YAAY,OAAO,EAAE,MAAM,GAAG,EAAE,CAAC;CACvC,MAAM,QAAQ,OAAO,EAAE,MAAM,IAAI,EAAE,CAAC;CAEpC,IAAI;CACJ,IAAI,YAAY,KACd,OAAO,YAAY,WAAW;MAG9B,OAAO,OAAO,GAAG,WAAW,EAAE,MAAM,GAAG,EAAE,GAAG;CAK9C,IAFiB,OAAO,OAEP,OACf,OAAO,IACL,oBACA,uCACF;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,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE;AACzD;;AAGA,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,CAAC,OAAO,4BAA4B;CAC7C,kBAAkB;CAClB,OAAO;CACP,SAAS;CACT,YAAY;CACZ,aACE;CAEF,WAAW;CACX,SAAS,CAAC,EAAE;CACZ,UAAU,CAAC,cAAc;CACzB;CACA;CACA;CACA;AACF"}
|
package/dist/eu/vat.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vat.js","names":["atUid","beVat","bgVat","cyVat","czDic","deVat","dkVat","eeVat","grVat","esVat","fiVat","frTva","hrVat","huVat","ieVat","itIva","ltVat","luVat","lvVat","mtVat","nlVat","plNip","ptVat","roVat","seVat","siVat","skDic","gbVat"],"sources":["../../src/eu/vat.ts"],"sourcesContent":["/**\n * EU VAT (European Union VAT Number).\n *\n * Dispatcher that routes to per-country VAT\n * validators. Extracts the 2-letter country prefix\n * and delegates to the matching country module.\n *\n * Special cases:\n * - EL → Greece (gr.vat)\n * - XI → Northern Ireland (gb.vat)\n *\n * @see https://ec.europa.eu/taxation_customs/vies/\n */\n\nimport atUid from \"../at/uid\";\nimport beVat from \"../be/vat\";\nimport bgVat from \"../bg/vat\";\nimport cyVat from \"../cy/vat\";\nimport czDic from \"../cz/dic\";\nimport deVat from \"../de/vat\";\nimport dkVat from \"../dk/vat\";\nimport eeVat from \"../ee/vat\";\nimport esVat from \"../es/vat\";\nimport fiVat from \"../fi/vat\";\nimport frTva from \"../fr/tva\";\nimport gbVat from \"../gb/vat\";\nimport grVat from \"../gr/vat\";\nimport hrVat from \"../hr/vat\";\nimport huVat from \"../hu/vat\";\nimport ieVat from \"../ie/vat\";\nimport itIva from \"../it/iva\";\nimport ltVat from \"../lt/vat\";\nimport luVat from \"../lu/vat\";\nimport lvVat from \"../lv/vat\";\nimport mtVat from \"../mt/vat\";\nimport nlVat from \"../nl/vat\";\nimport plNip from \"../pl/nip\";\nimport ptVat from \"../pt/vat\";\nimport roVat from \"../ro/vat\";\nimport seVat from \"../se/vat\";\nimport siVat from \"../si/vat\";\nimport skDic from \"../sk/dic\";\nimport type {\n GlobalValidator,\n ValidateResult,\n} from \"../types\";\n\nimport { clean } from \"#util/clean\";\nimport { randomInt } from \"#util/generate\";\nimport { err } from \"#util/result\";\n\ntype CountryValidator = {\n compact: (value: string) => string;\n validate: (value: string) => ValidateResult;\n format: (value: string) => string;\n};\n\n/**\n * Map of EU VAT country prefixes to their\n * country-specific validators.\n */\nconst VALIDATORS: Record<string, CountryValidator> = {\n AT: atUid,\n BE: beVat,\n BG: bgVat,\n CY: cyVat,\n CZ: czDic,\n DE: deVat,\n DK: dkVat,\n EE: eeVat,\n EL: grVat, // Greece uses EL prefix in EU VAT\n ES: esVat,\n FI: fiVat,\n FR: frTva,\n GR: grVat,\n HR: hrVat,\n HU: huVat,\n IE: ieVat,\n IT: itIva,\n LT: ltVat,\n LU: luVat,\n LV: lvVat,\n MT: mtVat,\n NL: nlVat,\n PL: plNip,\n PT: ptVat,\n RO: roVat,\n SE: seVat,\n SI: siVat,\n SK: skDic,\n XI: gbVat, // Northern Ireland\n};\n\n/**\n * Extract the 2-letter country prefix and the\n * remaining number from an EU VAT string.\n */\nconst splitPrefix = (\n value: string,\n): { cc: string; rest: string } | undefined => {\n const v = clean(value, \" -\").toUpperCase();\n if (v.length < 3) return undefined;\n const cc = v.slice(0, 2);\n if (!/^[A-Z]{2}$/.test(cc)) return undefined;\n return { cc, rest: v.slice(2) };\n};\n\nconst compact = (value: string): string => {\n const parts = splitPrefix(value);\n if (parts === undefined) {\n return clean(value, \" -\").toUpperCase();\n }\n const validator = VALIDATORS[parts.cc];\n if (validator === undefined) {\n return clean(value, \" -\").toUpperCase();\n }\n return `${parts.cc}${validator.compact(parts.rest)}`;\n};\n\nconst validate = (value: string): ValidateResult => {\n const parts = splitPrefix(value);\n if (parts === undefined) {\n return err(\n \"INVALID_FORMAT\",\n \"EU VAT must start with a 2-letter country code\",\n );\n }\n const validator = VALIDATORS[parts.cc];\n if (validator === undefined) {\n return err(\n \"INVALID_COMPONENT\",\n `Unsupported EU VAT country: ${parts.cc}`,\n );\n }\n const result = validator.validate(parts.rest);\n if (!result.valid) return result;\n return {\n valid: true,\n compact: `${parts.cc}${result.compact}`,\n };\n};\n\n/**\n * Format returns the compact EU VAT form:\n * `CC` + country-compacted number. Equivalent to\n * compact() because country format() methods\n * prepend their own prefix (e.g., AT UID format\n * returns \"ATU...\"), which would cause double-\n * prefixing if used here.\n */\nconst format = (value: string): string => {\n const parts = splitPrefix(value);\n if (parts === undefined) return value;\n const validator = VALIDATORS[parts.cc];\n if (validator === undefined) return value;\n return `${parts.cc}${validator.compact(parts.rest)}`;\n};\n\nconst GENERATORS = [\n [\"AT\", atUid],\n [\"BE\", beVat],\n [\"BG\", bgVat],\n [\"CY\", cyVat],\n [\"CZ\", czDic],\n [\"DE\", deVat],\n [\"DK\", dkVat],\n [\"EE\", eeVat],\n [\"ES\", esVat],\n [\"FI\", fiVat],\n [\"FR\", frTva],\n [\"GR\", grVat],\n [\"HR\", hrVat],\n [\"HU\", huVat],\n [\"IE\", ieVat],\n [\"IT\", itIva],\n [\"LT\", ltVat],\n [\"LU\", luVat],\n [\"LV\", lvVat],\n [\"MT\", mtVat],\n [\"NL\", nlVat],\n [\"PL\", plNip],\n [\"PT\", ptVat],\n [\"RO\", roVat],\n [\"SE\", seVat],\n [\"SI\", siVat],\n [\"SK\", skDic],\n] as const;\n\n/** Generate a random EU VAT number from a member validator. */\nconst generate = (): string => {\n for (;;) {\n const candidate =\n GENERATORS[randomInt(0, GENERATORS.length - 1)];\n if (!candidate) continue;\n\n const [prefix, validator] = candidate;\n if (validator.generate === undefined) continue;\n return `${prefix}${validator.generate()}`;\n }\n};\n\n/** European Union VAT Number. */\nconst euVat: GlobalValidator = {\n scope: \"global\",\n name: \"EU VAT Number\",\n localName: \"EU VAT Number\",\n abbreviation: \"EU VAT\",\n aliases: [\"EU VAT\", \"VAT identification number\"] as const,\n candidatePattern: \"[A-Z]{2}\\\\d{8,12}\",\n entityType: \"company\",\n sourceUrl: \"https://ec.europa.eu/taxation_customs/vies/\",\n examples: [\"ATU13585627\", \"DE136695976\"] as const,\n compact,\n format,\n validate,\n generate,\n};\n\nexport default euVat;\nexport { compact, format, generate, validate };\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6DA,MAAM,aAA+C;CACnD,IAAIA;CACJ,IAAIC;CACJ,IAAIC;CACJ,IAAIC;CACJ,IAAIC;CACJ,IAAIC;CACJ,IAAIC;CACJ,IAAIC;CACJ,IAAIC;CACJ,IAAIC;CACJ,IAAIC;CACJ,IAAIC;CACJ,IAAIH;CACJ,IAAII;CACJ,IAAIC;CACJ,IAAIC;CACJ,IAAIC;CACJ,IAAIC;CACJ,IAAIC;CACJ,IAAIC;CACJ,IAAIC;CACJ,IAAIC;CACJ,IAAIC;CACJ,IAAIC;CACJ,IAAIC;CACJ,IAAIC;CACJ,IAAIC;CACJ,IAAIC;CACJ,IAAIC;AACN;;;;;AAMA,MAAM,eACJ,UAC6C;CAC7C,MAAM,IAAI,MAAM,OAAO,IAAI,
|
|
1
|
+
{"version":3,"file":"vat.js","names":["atUid","beVat","bgVat","cyVat","czDic","deVat","dkVat","eeVat","grVat","esVat","fiVat","frTva","hrVat","huVat","ieVat","itIva","ltVat","luVat","lvVat","mtVat","nlVat","plNip","ptVat","roVat","seVat","siVat","skDic","gbVat"],"sources":["../../src/eu/vat.ts"],"sourcesContent":["/**\n * EU VAT (European Union VAT Number).\n *\n * Dispatcher that routes to per-country VAT\n * validators. Extracts the 2-letter country prefix\n * and delegates to the matching country module.\n *\n * Special cases:\n * - EL → Greece (gr.vat)\n * - XI → Northern Ireland (gb.vat)\n *\n * @see https://ec.europa.eu/taxation_customs/vies/\n */\n\nimport atUid from \"../at/uid\";\nimport beVat from \"../be/vat\";\nimport bgVat from \"../bg/vat\";\nimport cyVat from \"../cy/vat\";\nimport czDic from \"../cz/dic\";\nimport deVat from \"../de/vat\";\nimport dkVat from \"../dk/vat\";\nimport eeVat from \"../ee/vat\";\nimport esVat from \"../es/vat\";\nimport fiVat from \"../fi/vat\";\nimport frTva from \"../fr/tva\";\nimport gbVat from \"../gb/vat\";\nimport grVat from \"../gr/vat\";\nimport hrVat from \"../hr/vat\";\nimport huVat from \"../hu/vat\";\nimport ieVat from \"../ie/vat\";\nimport itIva from \"../it/iva\";\nimport ltVat from \"../lt/vat\";\nimport luVat from \"../lu/vat\";\nimport lvVat from \"../lv/vat\";\nimport mtVat from \"../mt/vat\";\nimport nlVat from \"../nl/vat\";\nimport plNip from \"../pl/nip\";\nimport ptVat from \"../pt/vat\";\nimport roVat from \"../ro/vat\";\nimport seVat from \"../se/vat\";\nimport siVat from \"../si/vat\";\nimport skDic from \"../sk/dic\";\nimport type {\n GlobalValidator,\n ValidateResult,\n} from \"../types\";\n\nimport { clean } from \"#util/clean\";\nimport { randomInt } from \"#util/generate\";\nimport { err } from \"#util/result\";\n\ntype CountryValidator = {\n compact: (value: string) => string;\n validate: (value: string) => ValidateResult;\n format: (value: string) => string;\n};\n\n/**\n * Map of EU VAT country prefixes to their\n * country-specific validators.\n */\nconst VALIDATORS: Record<string, CountryValidator> = {\n AT: atUid,\n BE: beVat,\n BG: bgVat,\n CY: cyVat,\n CZ: czDic,\n DE: deVat,\n DK: dkVat,\n EE: eeVat,\n EL: grVat, // Greece uses EL prefix in EU VAT\n ES: esVat,\n FI: fiVat,\n FR: frTva,\n GR: grVat,\n HR: hrVat,\n HU: huVat,\n IE: ieVat,\n IT: itIva,\n LT: ltVat,\n LU: luVat,\n LV: lvVat,\n MT: mtVat,\n NL: nlVat,\n PL: plNip,\n PT: ptVat,\n RO: roVat,\n SE: seVat,\n SI: siVat,\n SK: skDic,\n XI: gbVat, // Northern Ireland\n};\n\n/**\n * Extract the 2-letter country prefix and the\n * remaining number from an EU VAT string.\n */\nconst splitPrefix = (\n value: string,\n): { cc: string; rest: string } | undefined => {\n const v = clean(value, \" -\").toUpperCase();\n if (v.length < 3) return undefined;\n const cc = v.slice(0, 2);\n if (!/^[A-Z]{2}$/.test(cc)) return undefined;\n return { cc, rest: v.slice(2) };\n};\n\nconst compact = (value: string): string => {\n const parts = splitPrefix(value);\n if (parts === undefined) {\n return clean(value, \" -\").toUpperCase();\n }\n const validator = VALIDATORS[parts.cc];\n if (validator === undefined) {\n return clean(value, \" -\").toUpperCase();\n }\n return `${parts.cc}${validator.compact(parts.rest)}`;\n};\n\nconst validate = (value: string): ValidateResult => {\n const parts = splitPrefix(value);\n if (parts === undefined) {\n return err(\n \"INVALID_FORMAT\",\n \"EU VAT must start with a 2-letter country code\",\n );\n }\n const validator = VALIDATORS[parts.cc];\n if (validator === undefined) {\n return err(\n \"INVALID_COMPONENT\",\n `Unsupported EU VAT country: ${parts.cc}`,\n );\n }\n const result = validator.validate(parts.rest);\n if (!result.valid) return result;\n return {\n valid: true,\n compact: `${parts.cc}${result.compact}`,\n };\n};\n\n/**\n * Format returns the compact EU VAT form:\n * `CC` + country-compacted number. Equivalent to\n * compact() because country format() methods\n * prepend their own prefix (e.g., AT UID format\n * returns \"ATU...\"), which would cause double-\n * prefixing if used here.\n */\nconst format = (value: string): string => {\n const parts = splitPrefix(value);\n if (parts === undefined) return value;\n const validator = VALIDATORS[parts.cc];\n if (validator === undefined) return value;\n return `${parts.cc}${validator.compact(parts.rest)}`;\n};\n\nconst GENERATORS = [\n [\"AT\", atUid],\n [\"BE\", beVat],\n [\"BG\", bgVat],\n [\"CY\", cyVat],\n [\"CZ\", czDic],\n [\"DE\", deVat],\n [\"DK\", dkVat],\n [\"EE\", eeVat],\n [\"ES\", esVat],\n [\"FI\", fiVat],\n [\"FR\", frTva],\n [\"GR\", grVat],\n [\"HR\", hrVat],\n [\"HU\", huVat],\n [\"IE\", ieVat],\n [\"IT\", itIva],\n [\"LT\", ltVat],\n [\"LU\", luVat],\n [\"LV\", lvVat],\n [\"MT\", mtVat],\n [\"NL\", nlVat],\n [\"PL\", plNip],\n [\"PT\", ptVat],\n [\"RO\", roVat],\n [\"SE\", seVat],\n [\"SI\", siVat],\n [\"SK\", skDic],\n] as const;\n\n/** Generate a random EU VAT number from a member validator. */\nconst generate = (): string => {\n for (;;) {\n const candidate =\n GENERATORS[randomInt(0, GENERATORS.length - 1)];\n if (!candidate) continue;\n\n const [prefix, validator] = candidate;\n if (validator.generate === undefined) continue;\n return `${prefix}${validator.generate()}`;\n }\n};\n\n/** European Union VAT Number. */\nconst euVat: GlobalValidator = {\n scope: \"global\",\n name: \"EU VAT Number\",\n localName: \"EU VAT Number\",\n abbreviation: \"EU VAT\",\n aliases: [\"EU VAT\", \"VAT identification number\"] as const,\n candidatePattern: \"[A-Z]{2}\\\\d{8,12}\",\n entityType: \"company\",\n sourceUrl: \"https://ec.europa.eu/taxation_customs/vies/\",\n examples: [\"ATU13585627\", \"DE136695976\"] as const,\n compact,\n format,\n validate,\n generate,\n};\n\nexport default euVat;\nexport { compact, format, generate, validate };\n"],"mappings":"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"}
|
package/dist/fi/hetu.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hetu.js","names":[],"sources":["../../src/fi/hetu.ts"],"sourcesContent":["/**\n * HETU (Henkilötunnus).\n *\n * Finnish personal identity code. 11 characters in\n * format DDMMYY+CCC+X where the separator indicates\n * the century and X is a check character.\n *\n * @see https://dvv.fi/en/personal-identity-code\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\nconst CHECK_CHARS = \"0123456789ABCDEFHJKLMNPRSTUVWXY\";\n\nconst SEPARATORS_1800 = new Set([\"+\"]);\nconst SEPARATORS_1900 = new Set([\n \"-\",\n \"Y\",\n \"X\",\n \"W\",\n \"V\",\n \"U\",\n]);\nconst SEPARATORS_2000 = new Set([\n \"A\",\n \"B\",\n \"C\",\n \"D\",\n \"E\",\n \"F\",\n]);\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 !== 11) {\n return err(\n \"INVALID_LENGTH\",\n \"HETU must be exactly 11 characters\",\n );\n }\n\n const dd = v.slice(0, 2);\n const mm = v.slice(2, 4);\n const yy = v.slice(4, 6);\n const separator = v.charAt(6);\n const serial = v.slice(7, 10);\n const checkChar = v.charAt(10);\n\n if (!isdigits(dd) || !isdigits(mm) || !isdigits(yy)) {\n return err(\n \"INVALID_FORMAT\",\n \"HETU date part must be digits\",\n );\n }\n if (!isdigits(serial)) {\n return err(\n \"INVALID_FORMAT\",\n \"HETU serial must be digits\",\n );\n }\n\n let century: number;\n if (SEPARATORS_1800.has(separator)) {\n century = 1800;\n } else if (SEPARATORS_1900.has(separator)) {\n century = 1900;\n } else if (SEPARATORS_2000.has(separator)) {\n century = 2000;\n } else {\n return err(\n \"INVALID_COMPONENT\",\n \"HETU separator is invalid\",\n );\n }\n\n const year = century + Number(yy);\n const month = Number(mm);\n const day = Number(dd);\n\n if (!isValidDate(year, month, day)) {\n return err(\n \"INVALID_COMPONENT\",\n \"HETU contains an invalid date\",\n );\n }\n\n // Individual number must be >= 002\n const serialNum = Number(serial);\n if (serialNum < 2) {\n return err(\n \"INVALID_COMPONENT\",\n \"HETU individual number must be >= 002\",\n );\n }\n // Temporary range 900-999 is not valid\n if (serialNum >= 900) {\n return err(\n \"INVALID_COMPONENT\",\n \"HETU temporary individual range 900-999\",\n );\n }\n\n const checkNum = Number(`${dd}${mm}${yy}${serial}`);\n const expected = CHECK_CHARS.charAt(checkNum % 31);\n if (checkChar !== expected) {\n return err(\n \"INVALID_CHECKSUM\",\n \"HETU check character 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 HETU.\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 dd = Number(v.slice(0, 2));\n const mm = Number(v.slice(2, 4));\n const yy = Number(v.slice(4, 6));\n const separator = v.charAt(6);\n const serial = Number(v.slice(7, 10));\n\n let century: number;\n if (SEPARATORS_1800.has(separator)) {\n century = 1800;\n } else if (SEPARATORS_1900.has(separator)) {\n century = 1900;\n } else {\n century = 2000;\n }\n\n const year = century + yy;\n\n return {\n birthDate: new Date(year, mm - 1, dd),\n gender: serial % 2 === 0 ? \"female\" : \"male\",\n };\n};\n\n/** Generate a random valid HETU. */\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 separator = year >= 2000 ? \"A\" : \"-\";\n const serial = String(randomInt(2, 899)).padStart(3, \"0\");\n const checkNum = Number(`${day}${month}${yy}${serial}`);\n const checkChar = CHECK_CHARS.charAt(checkNum % 31);\n return `${day}${month}${yy}${separator}${serial}${checkChar}`;\n};\n\n/** Finnish Personal Identity Code. */\nconst hetu: CountryValidator<\"FI\", ParsedPersonId> = {\n name: \"Finnish Personal ID\",\n localName: \"Henkilötunnus\",\n abbreviation: \"HETU\",\n aliases: [\n \"henkilötunnus\",\n \"HETU\",\n \"sosiaaliturvatunnus\",\n ] as const,\n candidatePattern: \"\\\\d{6}[-+A]\\\\d{3}[\\\\dA-Z]\",\n scope: \"country\",\n country: \"FI\",\n entityType: \"person\",\n sourceUrl: \"https://dvv.fi/en/personal-identity-code\",\n lengths: [11] as const,\n examples: [\"131052-308T\"] as const,\n compact,\n format,\n parse,\n validate,\n generate,\n};\n\nexport default hetu;\nexport { compact, format, generate, parse, validate };\n"],"mappings":";;;;;;AAsBA,MAAM,cAAc;AAEpB,MAAM,kBAAkB,IAAI,IAAI,CAAC,GAAG,CAAC;AACrC,MAAM,kBAAkB,IAAI,IAAI;CAC9B;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;AACD,MAAM,kBAAkB,IAAI,IAAI;CAC9B;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;AAED,MAAM,WAAW,UACf,MAAM,OAAO,GAAG,
|
|
1
|
+
{"version":3,"file":"hetu.js","names":[],"sources":["../../src/fi/hetu.ts"],"sourcesContent":["/**\n * HETU (Henkilötunnus).\n *\n * Finnish personal identity code. 11 characters in\n * format DDMMYY+CCC+X where the separator indicates\n * the century and X is a check character.\n *\n * @see https://dvv.fi/en/personal-identity-code\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\nconst CHECK_CHARS = \"0123456789ABCDEFHJKLMNPRSTUVWXY\";\n\nconst SEPARATORS_1800 = new Set([\"+\"]);\nconst SEPARATORS_1900 = new Set([\n \"-\",\n \"Y\",\n \"X\",\n \"W\",\n \"V\",\n \"U\",\n]);\nconst SEPARATORS_2000 = new Set([\n \"A\",\n \"B\",\n \"C\",\n \"D\",\n \"E\",\n \"F\",\n]);\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 !== 11) {\n return err(\n \"INVALID_LENGTH\",\n \"HETU must be exactly 11 characters\",\n );\n }\n\n const dd = v.slice(0, 2);\n const mm = v.slice(2, 4);\n const yy = v.slice(4, 6);\n const separator = v.charAt(6);\n const serial = v.slice(7, 10);\n const checkChar = v.charAt(10);\n\n if (!isdigits(dd) || !isdigits(mm) || !isdigits(yy)) {\n return err(\n \"INVALID_FORMAT\",\n \"HETU date part must be digits\",\n );\n }\n if (!isdigits(serial)) {\n return err(\n \"INVALID_FORMAT\",\n \"HETU serial must be digits\",\n );\n }\n\n let century: number;\n if (SEPARATORS_1800.has(separator)) {\n century = 1800;\n } else if (SEPARATORS_1900.has(separator)) {\n century = 1900;\n } else if (SEPARATORS_2000.has(separator)) {\n century = 2000;\n } else {\n return err(\n \"INVALID_COMPONENT\",\n \"HETU separator is invalid\",\n );\n }\n\n const year = century + Number(yy);\n const month = Number(mm);\n const day = Number(dd);\n\n if (!isValidDate(year, month, day)) {\n return err(\n \"INVALID_COMPONENT\",\n \"HETU contains an invalid date\",\n );\n }\n\n // Individual number must be >= 002\n const serialNum = Number(serial);\n if (serialNum < 2) {\n return err(\n \"INVALID_COMPONENT\",\n \"HETU individual number must be >= 002\",\n );\n }\n // Temporary range 900-999 is not valid\n if (serialNum >= 900) {\n return err(\n \"INVALID_COMPONENT\",\n \"HETU temporary individual range 900-999\",\n );\n }\n\n const checkNum = Number(`${dd}${mm}${yy}${serial}`);\n const expected = CHECK_CHARS.charAt(checkNum % 31);\n if (checkChar !== expected) {\n return err(\n \"INVALID_CHECKSUM\",\n \"HETU check character 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 HETU.\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 dd = Number(v.slice(0, 2));\n const mm = Number(v.slice(2, 4));\n const yy = Number(v.slice(4, 6));\n const separator = v.charAt(6);\n const serial = Number(v.slice(7, 10));\n\n let century: number;\n if (SEPARATORS_1800.has(separator)) {\n century = 1800;\n } else if (SEPARATORS_1900.has(separator)) {\n century = 1900;\n } else {\n century = 2000;\n }\n\n const year = century + yy;\n\n return {\n birthDate: new Date(year, mm - 1, dd),\n gender: serial % 2 === 0 ? \"female\" : \"male\",\n };\n};\n\n/** Generate a random valid HETU. */\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 separator = year >= 2000 ? \"A\" : \"-\";\n const serial = String(randomInt(2, 899)).padStart(3, \"0\");\n const checkNum = Number(`${day}${month}${yy}${serial}`);\n const checkChar = CHECK_CHARS.charAt(checkNum % 31);\n return `${day}${month}${yy}${separator}${serial}${checkChar}`;\n};\n\n/** Finnish Personal Identity Code. */\nconst hetu: CountryValidator<\"FI\", ParsedPersonId> = {\n name: \"Finnish Personal ID\",\n localName: \"Henkilötunnus\",\n abbreviation: \"HETU\",\n aliases: [\n \"henkilötunnus\",\n \"HETU\",\n \"sosiaaliturvatunnus\",\n ] as const,\n candidatePattern: \"\\\\d{6}[-+A]\\\\d{3}[\\\\dA-Z]\",\n scope: \"country\",\n country: \"FI\",\n entityType: \"person\",\n sourceUrl: \"https://dvv.fi/en/personal-identity-code\",\n lengths: [11] as const,\n examples: [\"131052-308T\"] as const,\n compact,\n format,\n parse,\n validate,\n generate,\n};\n\nexport default hetu;\nexport { compact, format, generate, parse, validate };\n"],"mappings":"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"}
|